fix(spec): grade action.onSuccess.navigate/openIn and translation.flows.screens live - #20328
Conversation
…ws.screens live objectui reads all three at the .objectui-sha pin f8a9d0fb: ActionRunner's handlePostExecution -> readOnSuccessNavigation -> navigateOnSuccess for the onSuccess pair, and FlowRunner's localizeScreen for flows.<flow>.screens. Each row now cites its mounted reader by file and symbol, names the producer leg, and records why it was planned and what flipped it. translation.flows.label and the flows container's authorWarn are unchanged. state-counts.md regenerated. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bf38320cf50ec95bd37c10e0250af8bb2a6d01cc && git checkout bf38320cf50ec95bd37c10e0250af8bb2a6d01cc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dfd8e398aacfa74b8f401a9186814fec093cf010 080df02a51b2ef5c3d2b91f78672d9f915dde622 && git checkout -B drift-repro dfd8e398aacfa74b8f401a9186814fec093cf010 && git merge --no-ff 080df02a51b2ef5c3d2b91f78672d9f915dde622
node scripts/docs-audit/affected-docs.mjs --json dfd8e398aacfa74b8f401a9186814fec093cf010
|
…derstated-planned-rows
…ade false Text only: the flows container's authorHint now says FlowRunner reads `screens` and only the flow `label` is unread (authorWarn kept); the liveness README translation cell, the onSuccess JSDoc in action.zod.ts, the flows JSDoc in translation.zod.ts and the translateFlow docblock in i18n-resolver.ts name the landed readers. No schema, describe or accept-set change. Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Local-runs: ① Derived judgmentsReviewed at head 1. Each flipped row names a real, MOUNTED reader at the pin Mount chain, closed by hand at the pin, once for both
Per-read control ( 2. The Structural walk of 3.
Structural walk base → head:
4. The three
Prose, checked at the pin: the 5. The lint and CLI flow-liveness pins do not move; Static half: the warn set is derived from the shipped ledgers alone ( 6. Changeset / semver — RIGHT. See ②. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Blocking items: none. |
…bjectstack-ai#20383) Fixes objectstack-ai#20339 Clause-②: no ## What this changes Comment text only, in `packages/cli`. Four sentences there said nothing reads the `flows` translation group, and two named an objectui runner's landing as the flip trigger. Since PR objectstack-ai#20328, the shipped liveness ledger's `flows` row (`packages/spec/liveness/translation.json`) says otherwise: `children.screens` is `live`, because the console's screen-flow runner reads it, and only `children.label` is still read by nothing (objectstack-ai#20318). The container keeps `planned` + `authorWarn: true`, and that warn bit is group-level. So the gating these comments describe is unchanged and still correct. Only the reason they gave was stale. | where | before | after | |---|---|---| | `packages/cli/src/utils/i18n-extract.ts`, the `authorWarnedTranslationGroups` docblock (site 2) | "no shipped runner reads the group, so a translated wizard string really is stored and never shown" | "Only part of the group is read: the console's screen-flow runner reads `screens`, but the flow's own `label` is read by nothing yet (objectstack-ai#20318), so a translated flow label really is stored and never shown. The warn is group-level, so it still covers the whole group." | | the same docblock, its "Shape" paragraph | "the day the objectui screen-flow runner lands and the row flips to `live`" | "the day the row flips to `live` (dropping its `authorWarn`; for `flows` that waits on objectstack-ai#20318)" | | `packages/cli/test/i18n-flow-liveness-gate.test.ts`, the header's ledger sentence (site 3) | "no shipped runner reads the group" | "only part of the group is read: the console's screen-flow runner reads `screens`, but the flow's own `label` is read by nothing yet (objectstack-ai#20318)" | | the same header, its warn-side paragraph (site 3) | "Nothing reads the group, so a translated wizard string really is stored and never shown" | "The flow's own `label` is read by nothing yet (objectstack-ai#20318), so a translated flow label really is stored and never shown [...] The warn is group-level, so it still covers the whole group." | | the same file, the comment inside the cell `never lets both rules speak about the same keys` | "nothing reads the group, so this copy is stored and never shown" | "the flow's own `label` is read by nothing yet, so its translated copy is stored and never shown" | | `packages/cli/test/i18n-flow-screen-coverage.test.ts`, the header's flip-trigger sentence (patch round 1) | "it turns itself back on the day an objectui screen-flow runner lands and the row flips" | "it turns itself back on the day the row flips to `live` (dropping its `authorWarn`; for `flows` that waits on objectstack-ai#20318)" | The "Shape" sentence is changed because it sits in the same docblock and named the wrong trigger. The screen-flow runner has already shipped (objectui at the `.objectui-sha` pin `f8a9d0fb`), and the row did not flip. The ledger's `screens` note says the container bit drops when objectstack-ai#20318 lands. The in-cell comment is changed because the claim's file surface names every `packages/cli` sentence that gives this reason. Only its comment lines move, and the cell's code is unchanged (proof below). The header of `i18n-flow-screen-coverage.test.ts` named the same wrong trigger under the same rule, so patch round 1 gives it the Shape clause's wording. `packages/cli` now states one trigger. The file is the one the gate header points readers to, and the claim names it. ## Evidence - **Premise.** Measured on base `5a6267f48`, and unchanged through the merge of `862b6ce86`. The `flows` row is `status: planned` and `authorWarn: true`; `children.label` is `planned`; `children.screens` is `live` (`verifiedAt: 2026-09-27`). Its `authorHint` reads: "Only part of this group is read. The console's screen-flow runner reads `screens` [...] The flow's own `label` is read by nothing yet, so a translated flow label is stored and never shown". The new sentences follow that row. - **Comment-only proof.** Instrument: the TypeScript parser, printing both files with `removeComments: true`. The print at base and the print at HEAD are identical: 48261 of 48261 characters for `i18n-extract.ts`, 5544 of 5544 for the gate test, and 18534 of 18534 for `i18n-flow-screen-coverage.test.ts` (patch round 1; measured against both `bd7b473e2` and merge base `c577e6663`). Control legs: the same instrument detects a one-identifier rename of the exported function, and a one-identifier rename of the screen-coverage file's mocked `authorWarnedProperties`. No assertion, fixture or behaviour moves. - **Published surface.** After `pnpm --filter @objectstack/cli build`, both `dist/utils/i18n-extract.d.ts` and `dist/utils/i18n-extract.js` carry the new sentence (1 hit each) and not the old one (0 hits each). Positive control: the `authorWarnedTranslationGroups` export line gets 1 hit in each. `files: ["dist"]` ships both files, so this PR carries an `@objectstack/cli` `patch` changeset. ## Local verification (HEAD `bd7b473e2`; patch round 1 at `dbff132d7`) - **Patch round 1**, at `dbff132d7`, after merging `origin/main` `c577e6663`: - `pnpm lint` exit 0; - `node scripts/check-issue-citations.mjs --base origin/main` exit 0: 2 added citations judged, both live; - `vitest run --project unit --maxWorkers=2` over `i18n-flow-screen-coverage.test.ts` and `i18n-flow-liveness-gate.test.ts`: 2 files and 29 tests passed; - the 63 derived gates, re-run on this head: 63 run, 0 NOT-MEASURED, 0 UNRUN. The bullets below are the first round, at `bd7b473e2`. - `pnpm --workspace-concurrency=2 --filter '@objectstack/cli^...' build` exit 0. `pnpm --filter @objectstack/cli build` exit 0. - Targeted unit run (`vitest run --project unit --maxWorkers=2`) over the 18 test files that import `i18n-extract`: 18 files and 250 tests passed. The gate file on its own, verbose: 8 of 8 cells passed. - The full `unit` layer of `@objectstack/cli`: 231 files and 3309 tests passed. The `integration` layer is left to CI, because no spawn entry or integration-tier file is touched. - `pnpm --filter @objectstack/cli run typecheck` exit 0 (`tsc --noEmit`, then `check:test-typecheck` OK). - `pnpm --filter @objectstack/spec run check:liveness` exit 0. - `pnpm lint` (the full `eslint . --no-inline-config`) exit 0, no findings. - `node scripts/check-issue-citations.mjs --base origin/main` exit 0. It judged the 2 added citations (objectstack-ai#20318, objectstack-ai#20339), and both are live issue numbers. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 63 commands, and all 63 ended at exit 0. The `--ran` reconciliation found 63 run, 0 NOT-MEASURED and 0 UNRUN. Three first attempts measured nothing and were re-run green after their prerequisites were built: - `check:dual-build-cjs-loads` (exit 3, PREREQUISITE NOT MET): 104 entry points load. - `check:i18n-coverage` (exit 3, PREREQUISITE NOT MET): 13 configs, none new. - `check:type-check-debt` (hit a 300 s per-command cap): 4 entries, none above its record. - The roster gates whose roster directory holds one of these paths all exit 0: `check-changeset-fixed`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`. So does `check:nul-bytes`. ## Acceptance notes - **Site 1 is not in this PR.** Site 1 is `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`, the `why` of the `onSuccess` omit entry. Triage routed it to the spec lane. It is still stale on `origin/main` `c577e6663`. objectstack-ai#20362 was a spec-lane PR that touched that file after triage, and it did not carry the fix. - **Other copies of the same stale wording, not edited here:** - `packages/cli/CHANGELOG.md`, the same entry in `packages/lint/CHANGELOG.md`, and `packages/spec/CHANGELOG.md`. These are release-owned, and each sentence was true when it was released. - The `flows` bullet in `content/docs/ui/translations.mdx`. This customer-facing page still says "no shipped screen-flow runner reads the group yet, so a wizard still renders the strings authored on the flow". It is outside `packages/cli`, so it goes to the seat. - **One sentence left as it is.** The test header's "(a) it self-activates when the row flips to `live` with the objectui runner" stays byte-identical, as the dispatch directs. It holds under objectstack-ai#20318's ENFORCE proposal, whose reader surface is objectui `FlowRunner.tsx` plus the launcher. objectstack-ai#20318 still waits for the maintainer's ENFORCE-or-RETIRE decision. --- _Generated by [Claude Code](https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20296
Clause-②: no
Three liveness rows were graded
planned, but objectui reads all three at the.objectui-shapin this repo builds against (f8a9d0fb). This PR grades themlive. Each row cites its mounted reader by file and symbol, with no line numbers, and names its producer leg. Each row's note says why it wasplannedand what flipped it.Following the seat ruling on the card (comment 5860372033, answer B), the PR also corrects every sentence these flips made false. The
flowscontainer keeps itsauthorWarn, and itsauthorHintnow tells the truth. Four prose sites are restated. ⛔ No schema, parse,.describe()or accept-set change. The threepackages/spec/src/**edits are comments only, and a comment-stripped transpile proves it (below).Files:
packages/spec/liveness/action.json: the twoonSuccessrows.packages/spec/liveness/translation.json: theflows.screensrow, plus theflowscontainer'sauthorHintand a dated sentence appended to itsnote.packages/spec/liveness/README.md: the translation Notes cell.packages/spec/liveness/state-counts.md: regenerated.packages/spec/src/ui/action.zod.ts,packages/spec/src/system/translation.zod.tsandpackages/spec/src/system/i18n-resolver.ts..changeset/20296-understated-planned-rows.md.Row → reader, at objectui
@f8a9d0fb256b4c9eaction.onSuccess.navigatepackages/core/src/actions/ActionRunner.ts·handlePostExecution→readOnSuccessNavigation→navigateOnSuccess;packages/app-shell/src/hooks/useConsoleActionRuntime.tsx·useConsoleActionRuntime(itsnavigateHandler)block.navigate, interpolated with${param.*}/${ctx.*}/${result.*}, with the scheme guarded, then handed to the host'snavigationHandler(a react-routernavigatein the console)onSuccess: schema.onSuccessinActionButtonRenderer,ActionIconRenderer,ActionMenuRendererandActionGroupRenderer, andDeclaredActionButton's spread of the declared actionaction.onSuccess.openInActionRunner.ts·navigateOnSuccess;useConsoleActionRuntime(navigateHandler)block.openIn === 'newTab'becomesnewTab, with no?? 'self'of its ownActionSchemamaterializes.default('self'); the same renderer forwardtranslation.flows.screenspackages/spec/src/system/i18n-resolver.ts·resolveFlowScreenTitle; objectuipackages/app-shell/src/views/FlowRunner.tsx·localizeScreen,activeFlowsBundleandFlowRunnerresolveFlowScreenTitle, and each field'slabel/placeholderfrombundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fieldsoverFLOW_SCREEN_FIELD_COPY_KEYSpackages/runtime/src/app-plugin.ts·loadTranslationsandpackages/runtime/src/domains/i18n.ts·handleI18nRequestserve the bundle whole. objectui:apps/console/src/loadLanguage.ts·loadLanguage→packages/i18n/src/utils/spec-translations.ts·transformSpecTranslations(forwardsflowsverbatim underapp) →packages/i18n/src/provider.tsx·I18nProvider(addResourceBundleintotranslation)The per-read control. Each cited string was counted with
git show REF:PATH | grep -cFat the pin and at objectui main. All 15 reads gave the same count at both:readOnSuccessNavigation(action.onSuccess)1/1,private navigateOnSuccess(1/1,block.navigate2/2,block.openIn === 'newTab'1/1,onSuccess: schema.onSuccess1/1, consoleServerAction'sreadOnSuccessNavigation(action.onSuccess)1/1,resolveFlowScreenTitle(bundle, flowName, screen1/1,?.screens?.[screen.nodeId]?.fields1/1,function activeFlowsBundle1/1,FlowRunnerinuseConsoleActionRuntime.tsx1/1,useConsoleActionRuntime({ dataSource })inConsoleShell.tsx1/1,ConsoleShellinapps/console/src/App.tsx1/1,'flows',inspec-translations.ts1/1,transformSpecTranslations(translations)1/1, andr.setNavigationHandler(onNavigate)1/1. The ledger cites the historical form, "the.objectui-shapin f8a9d0fb".Mount chains, closed by hand at the pin
A reader inside a component that nothing mounts is not evidence. So each chain was walked down from the console root.
onSuccesspair.apps/console/src/App.tsxrendersConsoleShell.ConsoleShellcallsuseConsoleActionRuntime({ dataSource })and mountsActionProviderwithonNavigate={navigateHandler}andhandlers=api,flowandscript.@object-ui/react'sActionContextbuilds theActionRunnerand callssetNavigationHandler(onNavigate).action:button,action:icon,action:menuoraction:group; ObjectView's list toolbar rendersaction:bar. It can also be rendered byDeclaredActionsBar. Each of these callsexecutewithonSuccessstill on the def.ActionRunner.executedispatches to the registeredapiorscripthandler, then tohandlePostExecution. That call follows both the custom-handler path and the built-in path. It then runsreadOnSuccessNavigationandnavigateOnSuccess.scriptaction,consoleServerAction.tssees the declared block and only closes the pre-opened tab, so one success makes one navigation. The spec already refusesonSuccesstogether withopensInNewTab: true.flows.screens.FlowRunneris mounted in three places: inuseConsoleActionRuntime's dialogs (rendered byConsoleShelland by the runtimes that ObjectView and DeclaredActionsBar run), inRecordDetailView, and on the console routedeveloper/flow-runs(FlowRunsPage).type: 'flow'action's run pauses at a screen node, the host sets{ flowName, runId, screen }.FlowRunnercomputesshown = localizeScreen(...), drawsshown.title, and handsshowntoScreenView.I18nProvider loadLanguage={loadLanguage}inapps/console/src/main.tsx.AppPlugin.loadTranslationshands each bundle's locale data to the i18n service whole. The file adapter deep-merges it with no group filter. Both translations routes answergetTranslations(locale)whole.The
flowscontainer:authorWarnkept,authorHintmade true (seat ruling B)None of the three rows carries an
authorWarn. The one involved sits on theflowscontainer. That container also coversflows.label, which nothing reads yet (#20318). The bit has two readers:@objectstack/lint's warn map;authorWarnedTranslationGroupsinpackages/cli/src/utils/i18n-extract.ts, which holds back the wholeflows.*demand while the bit is set.So the bit stays. When #20318 lands the label reader, the group becomes wholly live, the bit drops, and the CLI demand comes back by design. Only the hint text changes.
The new hint carries no tracker number, because it is text an author reads. #20318 is cited in the container
noteinstead, in a dated sentence appended to it. Measured with the built lint at the head: azh-CNbundle authoringflows.lead_conversion.screens.s1.titlestill draws exactly one finding. Its rule isliveness-planned-property, its message is unchanged ("setsflowsbut this translation property is planned — …"), and itshintis the new text above.authorWarnedProperties('translation')is still["flows"]. An action authoringonSuccessdraws 0 findings, before and after.Prose restated (comments and ledger text only)
packages/spec/liveness/README.md, the translation Notes cell. The seeding sentence now reads "flowswas the one that was not, and wasplannedat seeding". A dated liveness ledger: action.onSuccess.navigate / openIn and translation.flows.screens are graded planned, but objectui already reads all three at the pinned sha #20296 entry is appended:flows.screensisliveand read by FlowRunner;flows.labelstaysplanned(i18n: the flow launcher and runner header readtranslation.flows.<flow>.label(1 key) #20318), and so does the container'sauthorWarn, whose hint names the read and unread halves.packages/spec/src/ui/action.zod.ts, theonSuccessJSDoc. "The console consumer is not wired yet … atplannedstrength" becomes a paragraph that namesActionRunner(handlePostExecution→readOnSuccessNavigation→navigateOnSuccess) at the.objectui-shapinf8a9d0fb. It describes the interpolation scopes, the router handoff andopenIn: 'newTab', and says the ledger rows arelive.packages/spec/src/system/translation.zod.ts, theflowsJSDoc. "no shipped runner reads it yet" becomes:FlowRunnerreadsscreens(each screen'stitle, and each field'slabelandplaceholder); the flow's ownlabelis read by nothing yet;screensisliveandlabelstaysplanned.packages/spec/src/system/i18n-resolver.ts, thetranslateFlowdocblock. "while its liveness rows areplanned" becomes: the i18n: nothing readsTranslationData.flows— a screen flow still renders its authored English in every locale (the runner half #7646 deferred) #11287 runner card took the client side, andFlowRunneroverlaysflows.FLOW.screens. This function stays unregistered because the server-side route is not the one taken.flows.FLOW.labelhas no reader yet.translation.json, theflows.screensrow's closing sentence now matches the ruling (the bit drops when i18n: the flow launcher and runner header readtranslation.flows.<flow>.label(1 key) #20318 lands).Comment-only, proven. Each of the three
srcfiles was transpiled withtypescript6.0.3transpileModule(removeComments: true, ES2022 / ESNext) at the merge base4e8d48d9and at the head080df02a. At4e8d48d9these three files equalorigin/main10ea9eb2(git diff --quietexit 0). The output hashes (sha256, first 16 hex characters):ui/action.zod.tsb2e1bac7…→507f9472…(differ)458e751a64bf8ce4458e751a64bf8ce4IDENTICAL'self'→'selg'givesb56bf8b4…, which the instrument seessystem/translation.zod.ts9a0c42ff…→fdf2c86e…(differ)a9fe3aa64c3a71e2a9fe3aa64c3a71e2IDENTICALconstgives6844d95c…, which it seessystem/i18n-resolver.ts237136a3…→0b3352a5…(differ)5680cc3e05cebe9a5680cc3e05cebe9aIDENTICALconstgives3029a546…, which it seesPins, and whether they moved
They did not move. Readings at
080df02a:pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts src/component-field-specs-liveness.test.ts: exit 0,Tests 91 passed (91), the same count as round one. This includesauthorWarnedProperties('translation').has('flows')=== true.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/i18n-flow-liveness-gate.test.ts test/i18n-flow-screen-coverage.test.ts: exit 0,Tests 29 passed (29), the same count. This includes the real-ledger equalityauthorWarnedTranslationGroups()==['flows']and the gatedflows.*demand.Verification (every reading at HEAD
080df02a, after mergingorigin/main10ea9eb2)The merge ran through
bash scripts/pm/os-regen-merge.shas merge commit4e8d48d9.state-counts.mdwas kept on the branch side and then re-verified by the gate. The spec, lint, platform-objects and objectql closures were rebuilt withpnpm exec turbo run build --filter=@objectstack/lint --filter=@objectstack/platform-objects --filter=@objectstack/objectql --concurrency=2under the verify lock (15 tasks successful).check:liveness: exit 0.action 49 classified (live 46, dead 3),translation 24 classified (live 23, planned 1), and✓ packages/spec/liveness/state-counts.md is current. Evidence paths: 617 local, all resolved; 223 foreign. Symbol anchors: 764 of 764. Container coverage: 109 / 564. At base4e0f72e8it read action 44 live / 2 planned and translation 22 / 2.check:objectui-pin-citations: exit 0,47 asserting objectui pin citation(s) match .objectui-sha (f8a9d0fb0), 53 historical. Round one read 50 historical; the three new ones are the.objectui-shapinf8a9d0fbsentences above, in the historical form. Sources: 1653.--project local: exit 0,Test Files 553 passed (553),Tests 16326 passed | 1 todo.--project repo: exit 0,Test Files 33 passed (33),Tests 604 passed (604). The repo project includes the docblock pins that readaction.zod.tsas text, such asaction-requires-confirmation-docblock.pin.test.ts.pnpm --filter @objectstack/spec run typecheck: exit 0.@objectstack/platform-objectssrc/apps/translations/action-body-panel-echo-decisions.test.ts: exit 0, 22 passed.@objectstack/sdui-parsersrc/__tests__/dashboard-widget-options.test.ts: exit 0, 21 passed.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived from the 8 actual changed paths at080df02a, gave 83 commands, identical to the derivation atf6e3558d. 81 exited 0. Reconciled:✓ dispatch-gates --ran: 83 derived famil(ies) accounted for — 81 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).check:generated,check:api-surface,check:docs,check:authorable-surfaceandcheck-spec-docblock-symbol-anchorseach exited 0.check-changeset-fixed,check:meta-url-spelling,check:spec-changes,check:authz-resolver,check:error-code-casingandcheck:filter-alias-parity): each exit 0.pnpm check:dual-build-cjs-loads. Exit 3,PREREQUISITE NOT MET. It needs every publishable package built. This is a declared narrowing; CI builds the workspace and runs it.pnpm check:i18n-walk-parity. Exit 3,PREREQUISITE NOT MET. It needs@objectstack/clibuilt, a closure of about 45 packages. It is derived here only becausei18n-resolver.tschanged, and that change is comment-only: the stripped transpile above is identical. This is a declared narrowing; CI builds and runs it.files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'], and 3 of the 8 changed paths are in it: the threesrcfiles.pnpm exec eslint --no-inline-config --format jsonover all 8 paths gives exit 0, 8 files, 0 errors and 5 warnings. The 3srcfiles have 0 errors and 0 warnings. Each of the 5 warnings isFile ignored because no matching configuration was supplied, on the JSON, markdown and changeset paths. Invariance: the config enables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on any untouched file.Acceptance notes
packages/spec/src/system/metadata-form-zod-reconciliation.test.ts, theactiononSuccessomit entry. Itswhyreads "declared, not enforced yet — both of its children (navigate,openIn) carry the liveness verdictplanned: no console consumer reads the block yet. No offer until it is enforced; whether to offer it then is a ruling for the enforcement, not for this gate". The key is now enforced. So this is no longer only text: whether the Studio action form should offeronSuccessis the ruling the entry defers.packages/cli/src/utils/i18n-extract.tsauthorWarnedTranslationGroupsdocblock ("no shipped runner reads the group, so a translated wizard string really is stored and never shown"), and the matching header prose inpackages/cli/test/i18n-flow-liveness-gate.test.ts. The gating these describe is unchanged. Only the reason given ("no shipped runner reads the group") is now half-true.256b4c9eis this container's objectuiorigin/mainref (a read-only checkout, not re-fetched). That checkout is shallow, so pin → main ancestry is not asserted.Changeset
.changeset/20296-understated-planned-rows.md,@objectstack/spec: patch, updated for the hint and prose corrections.liveness/andsrc/**/*.zod.tsare inpackages/spec/package.jsonfiles[], and@objectstack/lintreads the shipped ledgers at runtime, so this diff publishes. That is why this PR carries noskip-changeset.Deviations from the claim's file surface
authorHint, the README and the three comment sites. Two edits go beyond it, and both are text insidetranslation.json, which the claim already names:flowscontainernote, recording the flip and why the bit stays;flows.screensrow's own closing sentence, which named the pre-ruling state.undrilled-containers.baseline.jsonis untouched, because the gate named no container row.Generated by Claude Code