Repository navigation
feat(platform): migrate onto the @objectstack/* 17.4.0 line - #1814
Merged
Merged
Conversation
Bump all 21 @objectstack/* pins 17.3.0 -> 17.4.0, exact, and take the three contract changes the line carries. 1. dashboard.refreshInterval -> refreshIntervalSeconds across all five dashboards, applied from what `os migrate meta` prescribed. The old key is a ZodNever tombstone, so the rename is the bump's precondition rather than a step that can follow it. 2. A static readonly field is now stripped from a non-system caller's INSERT payload, not only from an update. crm_campaign_member.added_date is that shape, so the enrollment INSERT moves into two dedicated runAs:'system' sub-flows reached through subflow nodes. The campaign_enrollment SCREEN flow stays runAs:'user' per AGENTS.md house rule 9 -- elevating it would also lift row-level security off its two bulk reads over a private-sharing object. 3. driver-memory refuses any call handed a tenant scope. Three test harnesses passed an incidental tenantId into single-tenant fixtures that measure nothing about tenancy; they no longer ask an unisolating driver to isolate. Declared versions follow: objectstack.manifest.json specVersion + engines.protocol, objectstack.config.ts manifest.engines.protocol, docs/STATUS.md and the three whats-new locales. Test rows that pinned the retired platform semantic are retired rather than re-aimed at the new one -- restating a platform write rule locally is what AGENTS.md scope rule 3 forbids. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
os-steve
marked this pull request as ready for review
September 9, 2026 12:28
This was referenced Sep 9, 2026
Merged
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1807
Description
Migrate this repo onto the
@objectstack/*17.4.0 line: the whole-line pin bump, thedashboard.refreshIntervalrename, and the contract changes the bump surfaces.pnpm verifyis green end to end andpnpm lint --jsonreportserrors: 0.⭐ Three load-bearing claims in the card turned out to be wrong. They are reported here rather than quietly matched — the readonly framing in particular, which the card had already corrected once in the wrong direction.
Type of Change
Related Issues
Card #1807. The
--strictflip on #1581 is unblocked by this, and is not part of this PR. #1216'scomponent-props-unknown-keywarning is left standing — it is not this card's. Draft PR #1806 is untouched.Changes Made
1. The pin bump — 21 packages, exact
20 in
dependenciesplus@objectstack/formulaindevDependencies, all17.3.0→17.4.0, no carets.Lockfile movement, measured with a version-normalized diff (
sed 's/17.3.0/17.X.0/'againstsed 's/17.4.0/17.X.0/', so only non-version changes survive):packages:entries@objectstack/*+create-objectstack)croner: 10.0.1under@objectstack/trigger-schedule@17.4.0, a version already in the treebetter-authresolves at exact1.7.2. Confirmed at the source as well as in the lock:@objectstack/plugin-authdeclares"better-auth": "^1.7.2"at 17.3.0 and"better-auth": "1.7.2"at 17.4.0 (read off the registry metadata for both versions).2. The rename — what
os migrate metaactually emittedos migrate meta --from 17— the command the tombstone names and the card prescribes — emits nothing for this surface. Verbatim:The conversion is registered with
toMajor: 18, and--todefaults to this runtime's major (17), so the chain 17→17 is empty. The command that lists it isos migrate meta --from 17 --to 18:os migrate meta --from 17to list the mechanical edits for existing sources; apply them by hand."--outwrites a migrated stack snapshot (JSON), not TypeScript. So the edits here are the codemod's prescription applied, not a hand-derived rename: the machine-readableapplied[](--json) named the five paths and the conversion id, and the emitted snapshot was used as the oracle for the result —refreshIntervalSecondsin the platform's snapshotsales_activity_dashboardactivity.dashboard.tscrm_overview_dashboardcrm.dashboard.tsexecutive_dashboardexecutive.dashboard.tssales_dashboardsales.dashboard.tsservice_dashboardservice.dashboard.tsrefreshInterval= 0 occurrences undersrc/,test/,content/,docs/after the change.3. Declared versions
objectstack.manifest.jsonspecVersion+engines.protocol,objectstack.config.tsmanifest.engines.protocol,docs/STATUS.md, and all threecontent/docs/whats-new.*locales.content/docs/releases/untouched.4. ⭐ The readonly leg — the card's correction was itself wrong, in the other direction
The card withdrew its earlier framing ("17.4.0 strips readonly on INSERT too") and replaced it with "the change is
preserveAuditbecoming UPDATE-only, ⛔ NOT a blanket new INSERT strip". The withdrawn framing was the correct one.@objectstack/objectql@17.4.0's own changelog entryb398ad2:and, in the same entry:
So
preserveAudit's semantics did not change — what is new is the diagnostic that says so aloud, which is the string the card's re-derivation read. The shipped author-time rule agrees;pnpm validateat 17.4.0 printed, before the fix:The remedy the card names is unaffected by the delta, and is what shipped. Two further deltas in how it had to be applied:
runAsis not a node-level key.CreateRecordConfigSchemais astrictObjectdeclaring exactlyobjectName,fields,outputVariable;runAslives on the flow. The card's "the twoadded_datecreate_record nodes takerunAs: 'system'" is not authorable as written, and the platform diagnostic says "declare the flowrunAs:'system'".⛔ But this flow may not be elevated.
campaign_enrollmentis a screen flow, and AGENTS.md house rule 9 (maintainer ruling, precedent #1434) is explicit:That rule is not a style preference here. Elevating the parent would lift row-level security off
query_leadsandquery_contacts— andcrm_leadissharingModel: 'private', so a rep clicking Enroll Members would have enrolled the whole organisation's lead pool instead of the leads they can see. A product change, not a migration.So the shape is the one
case_escalation_stampalready uses forescalate_case(src/flows/case-escalation-stamp.flow.ts, same ruling): the screen flow keeps the marketer's identity for the campaign read, the eligibility queries, the opt-out honour and the per-member dedupe, and hands only the insert to a dedicated callee. New filesrc/flows/campaign-member-enroll.flow.tsshipscampaign_lead_member_enrollandcampaign_contact_member_enroll—autolaunched,runAs: 'system', onecreate_recordeach, no branching. Attribution is unaffected: elevation is not anonymity (resolveRunDataContextreturns{ isSystem: true, actor, ...userId }), so each membership row still belongs to the person who enrolled it.⛔ The field was not dropped, and no
beforeInserthook was used to dodge the diagnostic.5. ⭐ A THIRD contract change the card does not mention
@objectstack/driver-memory@17.4.0refuses any call the engine hands a tenant scope (MemoryMultiTenantUnsupportedError, objectstack#16589) — it has no row-level tenant isolation, so it refuses rather than answering unisolated. Three test harnesses went red on the bare bump for this reason and for no other:test/flow-filter-today-token.test.tstest/forecast-current-quarter-view.test.tstest/escalation-task-subject.test.tsEach passed an incidental
tenantId: 'org_1'into a single-tenant fixture measuring something else entirely (a{TODAY()}token, quarter boundaries, a 255-character cap). ThetenantIdis gone from those fivecreateContextcalls. ⛔tenancy: { enabled: false }was not used — the refusal names that as the wrong answer for data that really is per-organization.6. The
test/**files touched, and whyThe card lifts #1581's
test/**ban "for exactly the files the migration forces". The enumeration in the card is short by the ones it had not measured; the principle is what was applied, and every file below is red on the bare bump or on a change this card forces:docs-analytics-vocabulary.test.tsd.refreshInterval→d.refreshIntervalSeconds(named by the card)readonly-write-semantics.test.tsdescribe('the strip is an UPDATE-path rule — insert is deliberately exempt')retired, with a tombstone. ⛔ Not rewritten to assert the new contractaudit-stamp-readonly.test.tsguest-submission-sanitisation.test.tsis_escalatedhalf of a negative control retired;internal_notes/resolutionstill carry the guaranteecase-guest-branch-leftovers.test.tsescalation_reason(deliberately not readonly) still carries the guaranteeflow-filter-today-token.test.tstenantIddroppedforecast-current-quarter-view.test.tstenantIddroppedescalation-task-subject.test.tstenantIddroppedflow-campaign-enrollment.test.tssubflownode resolves by nameflow-variable-conditions.test.tsautomation-docs-coverage.test.ts⛔ No test was rewritten to mirror the new platform rule. Under AGENTS.md scope rule 3 those rows belong upstream; they are retired here with a note handing them to epic step 5c (objectstack#15953).
7. Docs the two new flows force
Two new flows move the registered count 27 → 29:
README.md(three sites),docs/STATUS.md's validate transcript, and a row plus the header count oncontent/docs/administration/automation.{mdx,zh-Hans.mdx,zh-Hant.mdx}. The Chinese labels follow the caller's own 营销活动/行銷活動 + 登记/登記 vocabulary and take 子流程 in the type column, ascase_escalation_stampdoes.Testing
pnpm verifygreen end to end —validate·typecheck·lint·lint:i18n-gate·hygiene·hygiene:tokens·build·testpnpm test— 165 test files passed, 3454 passed, 1 skipped, 0 failedpnpm lint --json—errors: 0·warnings: 13·suggestions: 12,passed: true.changeset/objectstack-17-4-0.md)Full lint breakdown by rule id:
field-no-consumers@objectstack/lint@17.4.0; per-field disposition belowcomponent-props-unknown-keyapproval-approvers-may-resolve-emptyobject/missing-name-fieldrelationship/line-item-should-be-master-detailrelationship/line-items-inline-editrollup/missing-summaryToken ratchet after the change: business semantics ~85,267 (ceiling ~100,000), interaction layer ~37,846 (ceiling ~40,000), authored total ~137,758 (ceiling ~140,000). All three clean; no ceiling moved.
Additional Notes
The 12
field-no-consumerswarnings — per-field disposition⛔ Nothing suppressed, whitelisted or re-severitied. These are reported findings, per the card: "if a field is genuinely wanted-but-unconsumed, say so per field with the reason."
One reading applies to all twelve and should be settled before any of them is acted on. Every one carries a
group, every group is a declaredfieldGroup, and each of these objects renders through the synthesized detail/form layout rather than an authored*.page.ts— so all twelve are visible and editable in the product today. The rule's own wording is about naming: "no view column, form section, page binding, flow node, dataset, widget, formula, validation, hook or action names it." A synthesized layout names nothing. So "no consumer" here means "not named in metadata", not "unreachable by a user" — which is why none of these is a straightforward removal.crm_account.logobrandinggroup besidebrand_color. No list column shows it andaccount_detail.page.tsdoes not bind it — the gap is a page gap, not a dead field. Candidate consumer: the account detail header.crm_article_feedback.commentdescriptionstates the consumer that does not exist yet: "Optional note explaining the verdict — read by the article's author." Nothing surfaces it to the author. Either build that surface or the column is collecting text nobody reads.crm_campaign.descriptionbasicgroup; seven seed campaigns fill it. Not on any view column and no campaign detail page exists.crm_campaign_member.added_datereadonlyin #1667 precisely because it is an audit stamp. Recommendation: give it a view column or a detail binding oncrm_campaign_member, which makes the stamp legible and settles the rule in the same move. ⛔ Removing it would contradict this card.crm_contact.mailing_street·mailing_city·mailing_state·mailing_postal_code·mailing_countrymailing_addressfield group on the synthesized contact layout (defaultExpanded: false);contact.view.tshas zeromailing_columns. Note the precedent one comment below them incontact.object.ts:birthdatewas deliberately not declared on the same "declared but inert" reasoning — with the difference that a mailing address has a stated importer that fills it.crm_contract.descriptioncrm_contractalso declaresspecial_terms, which IS consumed — so the two markdown bodies are not redundant by accident, and which one a reader is meant to use is a product question.crm_forecast.seed_keyhidden: true,readonly: true, and itsdescriptionsays "Demo-fixture identity. Written only by the seed loader; empty on every real snapshot." Its whole job is to be a seeder-only upsert identity. A consumer would defeat it. Recommendation: leave it and take the warning, or raise thehidden: true+ seeder-identity shape upstream as a case the rule should recognise.crm_quote_line_item.line_numberreadonlyordinal in thebasicgroup; 19 seed rows carry it. Nothing orders or displays by it — the line-item related list does not use it as a sort or a column, which is the obvious consumer and is probably the actual defect here.Recommended follow-ups, none of which belongs in a migration PR: one card for the display gaps (#1, #3, #4, #10, #12 — all "give it the view column or detail binding it should already have"), one for #2 (build the author-facing surface its
descriptionpromises, or retire the column), one product question for #5–9 (does the contact layout want the mailing group on a view, given the importer fills it), and one upstream question for #11.Assumptions and judgement calls, stated
test/**permission was read as its principle, not its enumeration — "exactly the files the migration forces". Five files beyond the card's list are red on the bump and are edited; none is edited for any other reason. Table in section 6.runAs) and its flow-level form is what rule 9 forbids by name. The sub-flow split is rule 9's own prescribed shape and has an in-repo precedent. If the PM prefers the one-line flow-level elevation, this is the decision to reverse — it costs the RLS widening described in section 4 and redsaudit-stamp-readonly.test.ts's "the enrollment SCREEN flow is still NOT elevated" row.os migrate meta's prescription was applied, not re-derived. The command cannot rewrite.tssources and its own tombstone says to apply the listed edits by hand; the snapshot it emits was used as the oracle for the result.field-no-consumerswarnings are reported, not fixed. Every one needs a product decision (which view column, which detail binding) or a 57-site carrier removal, and ci(deps): bump softprops/action-gh-release from 1 to 2 #4 is in direct tension with this card's own item 4.src/recording "measured on 17.3.0" were left alone. They are dated records of when a measurement was taken, not claims about the current pin; rewriting them would falsify the record.docs/STATUS.md:41carries a sentence — "The README states the same 27" — inside a paragraph about the 31 Actions figure. It reads as pre-existing drift from when actions were 27, unrelated to the flow count this PR moved. Left untouched; noted so it is not read as collateral.🤖 Generated with Claude Code
https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
Generated by Claude Code