Repository navigation
test(spec): record why eleven top-level keys are never offered by a metadata form - #20362
Merged
Merged
Conversation
…etadata form Three ruled root reason classes in the metadata-form reconciliation ledger: authored through its own editor (object.actions, object.listViews, page.slots, object.external), code-declared platform configuration (object.tenancy, object.systemFields, app.contextSelectors, action.resultDialog) and no registered widget spells this union (object.stageField, field.inlineEdit, field.relatedList). Each class holds exactly the keys the ruling put in it, and the own-editor and union classes are also read off the key's own node. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 28, 2026
This was referenced Sep 28, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…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>
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.
Part of #19332
Clause-②: no
What this changes
One file changes:
packages/spec/src/system/metadata-form-zod-reconciliation.test.ts, +314 / -1. It adds 11 rootomitrows to the reconciliationLEDGER, under three new reason classes, and onedescribeblock of four admission tests for those classes. No schema, form,describe(), liveness row or generated artifact changes.This is the ledger flight of ruling record
5861442317(letter A on groups G3, G4 and G5; #19332). The G1 and G2 form-row flights are still to come, which is why this PR saysPart of.It does not wire the top-level
zodOnlydirection. That is #19333 item 2, which comes after every flight. It also leaves outaction.aria, because #20323 is re-grading its liveness verdict.whyopens with it)object.actions(theactiontype),object.listViews(theviewtype, per the #19330 ruling),page.slots(the page designer),object.external(the import flow)object.tenancy,object.systemFields,app.contextSelectors,action.resultDialogobject.stageField,field.inlineEdit,field.relatedListFormSelectOptionSchemarefuses as an optionvalueEach row's
whyis one sentence. It names the editor, the writers or the union arms, and it cites the ruling record, with the tracker number beside it as a link.The admission tests (file lines 1251–1454)
These five root reasons were already in the file: platform-written, deprecated alias, not enforced yet, overlay, renderer-owned vocabulary. Each of them is read off the key itself. The three new ones were ruled: for each key, the maintainer chose between a form row and a recorded reason. So the first admission is the ruling's own key list, transcribed as
RULED_ROOT_REASONS.Test 1: each class holds exactly the keys the ruling put in it. A row that gives a ruled reason for any other key fails, and so does a ruled key that has lost its row. A key that a form offers later already has to drop its row, through the existing resolve test. The same edit then takes it out of the list.
Where this package can read the class off the key's own node, the row must pass that reading too:
editorTypeOf(type, key). This returns the registered type whose own node the key stores, judged by node identity. It is never judged by name or by overlapping key sets.object.actionsreads asaction, because its element isgetMetadataTypeSchema('action').object.listViewsreads asview, because its value is the node theviewcontainer declares underlistViews. The existingobject.fieldssubset row reads asfield, and it serves as the lit control. For the dark controls,object.tenancyandobject.accessread as nothing.page.slotsandobject.externalalso read as nothing, and that is a measurement, not an assumption: the page designer lives in objectui, and the import flow lives in the datasource service. So those two rows rest on the ruling alone.unspellableArmValuesOf(type, key). The node must be a union, and it must have an arm whose valueFormSelectOptionSchema.safeParserefuses. That refusal is read from the schema, not from a list kept in the test. The lit controls arestageField, which returns[false], andinlineEdit, which returns[true, false]. The dark controls areobject.requiredPermissions, a union with no such arm, which returns[], andobject.tenancy, which is not a union. The half that says no widget renders the whole union is objectui's registry, and it rests on the ruling (the objectui readings are below).describe()text does not say "platform only", and their writers are platform objects declared in other packages. A spec-package test could not read those writers without becoming a cross-package test. So this class is admitted by the ruling's key list alone, and each row names the writers I measured.One dark control is deliberate:
object.systemFields. Itsfalsearm passes the union reading ([false]), but the ruling put it in platform configuration. The test pins that fact, so the reading alone never decides the class.Mechanism assumptions (PM), measured
nameis offered by 17 of 17 forms and declared by 17 of 17 schemas. DARK, a fabricated key, is offered by 0 and declared by 0.c74de10a9(PM's826f3279cis an ancestor), the object-rooted residue is 39, the ruling's 39. None were added, removed or retired, and all 11 keys of this flight are in it. The liveness rows arelive, or drilled with every childlive.view42). Root rows: 15 → 26.object.fields.unique(type: 'boolean'),field.unique(derived),action.visibleandaction.disabled(textarea), anddashboard.globalFilters.defaultValue. So that fact cannot be the class's reading.5861442317.What the rows rest on, measured
Framework readings are at
154da111e. objectui readings are at the.objectui-shapinf8a9d0fb0596, from a shallow clone. HotCRM readings are at2f7b2326.app-shell/.../inspectors/PageBlockInspector.tsx:387writesslots: { ...slotsObj, [slotName]: nextArr }.POST /datasources/:name/external/tables/:remote/draftand/import(packages/rest/src/external-datasource-routes.ts:31-32).external: { remoteSchema?, remoteName }(packages/services/service-datasource/src/external-datasource-service.ts:486,:931-932).ImportObjectDialog.tsxandos datasource introspect.listViewsin 13 files, and every one is a*.view.ts. The examples write them in 9 files, all*.view.ts.fieldGroupshits 18 files. The code writers are:tenancy:sys_api_key,sys_sso_provider;systemFields:sys_metadata_activation({ tenant: false });contextSelectors:studio.app.ts;resultDialog: actions onsys_user(4),sys_two_factor(2),sys_oauth_application(2),sys_sso_provider(1).FormSelectOptionSchemarefusestrueandfalseas an optionvalue, and acceptsprimary,gridandform(measured bysafeParse).WIDGETS(widgets.tsx:2909) registers 20 widgets, and none of them is a union selector.resolveUnionBranchfalls back to the first branch in create mode (SchemaForm.tsx:238-250).Ablation
Every leg ran from committed state (
154da111e, blobd2fe718f77e5) throughscripts/ablation-replace.mjsin WRAP mode, under the verify lock. In every leg, the anchor hit once, the mutation was verified on disk (anchor count and blob change), and the gate plus a probe regenerated from the mutated file ran. The restore was proven each time: blob equal to HEAD, andgit diff HEADempty. The subject is imported fromsrcby relative path, so there is nodist/between the mutation and the run.object.externalrow (own editor)object.externalaction.resultDialogrow (platform config)action.resultDialogfield.relatedListrow (union)field.relatedListkey: 'actions'→'access'object.actionsin,object.accessout)object.access: the ruling names no editor for this key)key: 'tenancy'→'access'object.tenancyin,object.accessout)key: 'inlineEdit'→'readonlyWhen'field.inlineEditin,field.readonlyWhenout)field.readonlyWhen: an option can spell every arm)Every other test stayed green on every leg (57 of 58, or 56 of 58). The resolve test stayed green on the swaps, because the swapped-in keys are authorable and unoffered.
Verification (head
154da111e)VERDICT command-exit 0. This ran at8f31b4a5d; the gate file's blob there,d2fe718f77e5, is byte-identical at154da111e, and the ablation legs re-ran the same pair at154da111ebefore each mutation's verdict.@objectstack/specbuild: exit 0. The tree stayed clean: noauthorable-surface.base.jsonor other artifact movement.pnpm --filter @objectstack/spec typecheck(tsc, scripts-typecheck and test-typecheck): exit 0.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 554 files, 16431 passed, 1 todo, exit 0. The gate file is in thelocalproject; it is not invitest.repo-tests.json.pnpm --filter @objectstack/spec test:repo(--project repo, run under the lock as the seat asked): 33 files, 604 passed,VERDICT command-exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 78 commands. Each was run with its exit code captured after a redirect, never through a pipe. 74 exited 0.PREREQUISITE NOT METon other packages'dist/:check:doc-formula-expressions,check:dual-build-cjs-loads,check:lean-entry-closureandcheck:type-check-debt. The test(spec): record why fourteen top-level keys are never offered by a metadata form #20064 and test(spec): record why field.format is never offered by a metadata form #20322 precedents hit the same four families.--ranreports78 derived famil(ies) accounted for — 74 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3), exit 0.eslint --no-inline-config --format jsonon the one changed file reports 1 file, 0 errors and 0 warnings.--print-configresolves a config for the file, so the file is inside the linted population.parserOptions.projectand noprojectService, and it enables 4 rules, none of them type-aware. So this edit cannot move any other file's verdict.Changeset
None is owed. The change is test-only and publishes nothing.
npm pack --dry-runof@objectstack/speclists 2027 files, and none is a*.test.ts. The changed path is absent from the list. The positive controls,src/ui/view.zod.tsandliveness/object.json, are present. The no-release route is theskip-changesetlabel, as on #20064.Acceptance notes
object.external.introspectedAtclaims a writer that does not write it. Its describe says "Set byos datasource introspect", but that command's draft path writes onlyremoteSchemaandremoteName(external-datasource-service.ts:486,:931-932). This is a source reading only; I did not run it. Carrier: none. The own-editor reading deliberately does not lean on that describe.field.uniqueandobject.fields.uniqueoffer only the boolean arm:object.form.ts:130hastype: 'boolean', andfield.form.ts:26is derived, so the first arm is the boolean. That makes'global'/'organization'(ADR-0120) unreachable from those Studio rows. It is the same shape the ruling refused as option C for G5. I read it from source and did not browser-run it. Carrier: the G2 flight that curatesindexes.unique, or whoever builds a union-aware control.action.ariais still in the 28-key residue. [finding]action.ariais gradedlivein the liveness ledger, but no action surface reads it — and the Studio action editor still renders anariacontrol #20323 carries it.Generated by Claude Code