Skip to content

Commit 0cb72cf

Browse files
fix(spec): os migrate meta guidance states the contract-first rule in words and drops a sweep batch ordinal (stage 10, the last) (#20642)
Fixes #20233 Stage 10, the last: the two dangling contributor-guide rule references and one sweep batch ordinal left in the author-shown text of the ADR-0087 semantic entries. Clause-②: no `os migrate meta` prints each semantic entry's `surface`, `replacement`, `reason` and `acceptanceCriteria` to the author. Stage 9 left every entry at 0 tracker ids in those fields. Three sentences still sent the reader to something they would have to look up: - `ui-list-view-grouping-field-padded-refused` and `ui-list-view-groupbyfield-padded-refused` said a trimming schema is "the consumer-tolerance direction `AGENTS.md #0.1` refuses". That number is rule 0.1 of the renderer repository's contributor guide, "Fix the metadata, not the renderer (contract-first)", which the ruling behind these entries cited as its governing text: fix the producer, do not teach the renderer to tolerate. It names nothing in this repository's AGENTS.md, and an author's project has neither guide. Both `reason` fields now state the rule itself: fix the metadata, not the renderer; off-spec metadata is refused where it is authored, never coerced into working. - `ui-notification-action-embed-config-retired` named "its ui/ batch 14" of the v17 unknown-key strictness sweep. The same sentence already says what that batch measured (each ui/ file checked for an authoring door first; these two shapes three ways on 2026-08-03) and what it decided (not to close them with `.strict()`, and to leave the disposition to ADR-0049's enforce-or-remove). So the ordinal only pointed at a row of the campaign's plan. It is dropped, and the later "That batch" now reads "The sweep". Stage 6 dropped the same campaign's other batch labels the same way. - **Text only:** a base-vs-head AST comparison over the 3 entry files and `registry.ts` finds 0 non-prose changes. 6 `reason` fields changed (3 per copy), with no `id`, `surface`, `replacement`, `acceptanceCriteria`, `from` / `to`, matcher, import, comment or token-skeleton change. Its self-test detects an id, comment, surface, skeleton and import mutation, and stays dark on a prose-only one. - **Census (stage 9's AST instrument, whole tree, 318 entries):** base `a918fe7fd0` has 0 prose sites, 0 surface sites and 10 short numbers. Head has 0 / 0 / 8. The 8 left are references to this repository's Prime Directives 2, 10 and 12, which stay. Before it was trusted, the instrument reproduced all 7 recorded readings from stages 5 to 9. Comment lines with a tracker id: 126 in `semantic/` and 838 across `entries/`, the same at base and head. - **Generated:** `registry.ts`, `spec-changes.json` and `docs/protocol-upgrade-guide.md`, by their generators. A second fresh regeneration leaves the tree clean. The two protocol-18 entries are not in the two projections, which carry released majors only. `patch` changeset. ## Verification (head `fa949a84f2`) - **Build:** `turbo run build --concurrency=2 --filter=@objectstack/cli^...`: 58 of 58 tasks succeeded, and the tree was clean afterwards. - **Tests (one lock turn at this head):** - spec `--project local`: 575 of 575 files passed (16915 tests passed, 1 todo). - spec `--project repo`: 43 of 43 files passed (761 tests). - CLI `--project integration`, three files: the guidance pin `test/migrate-meta-engine-guidance.test.ts`, `test/migrate-meta-default-range.test.ts`, and `src/commands/migrate/meta.stored-flow-resolution.integration.test.ts`. All 3 files passed (13 tests passed, 1 skipped by the default-range file's own `skipIf`). - spec typecheck: exit 0, and the test-layer debt held (53 files, 251 errors). - The guidance pin, run again alone with the verbose reporter: all 3 of its tests passed. - **Gates:** `dispatch-gates.mjs --commands` derives 83 families, and all 83 ran after the last commit. 82 exit 0. Among them: `check:doc-authoring` (16803 customer-facing strings clean), `check:generated` (all 15 artifacts up to date), `check:migration-registry`, `check:spec-changes`, `check:upgrade-guide`, `check:issue-citations`, `check:nul-bytes`, `check:api-surface` (unchanged), `check:authorable-surface` and `check:adr-0087-registration` (no declared-breaking changeset). - `check:dual-build-cjs-loads` is NOT MEASURED. Its prerequisite is not met: 9 packages outside the CLI closure have no `dist/`. Build Core runs it. - `--ran` reconciliation: 83 accounted for, 82 run, 1 NOT MEASURED, 0 unrun. - **Lint:** eslint `--no-inline-config --format json` over the 4 changed `.ts` files: 4 files read, with 0 errors and 0 warnings. The population comes from `eslint.config.mjs`. The config enables no type-aware rules, so this diff cannot change the verdict for any file it does not touch. - **Mergeability:** a driver-free `merge-tree` onto `origin/main` `3f45b6cc13` is clean. The one commit on main since the base touches none of these paths. The dev report on #20233 has the sources each sentence was read from, the census readings and the AST-comparison record. ## Acceptance notes - This is the last stage, so the PR says `Fixes #20233`. After it, the card's population is 0: no tracker id and no dangling rule number in any semantic entry's printed fields. - `AGENTS.md #0.1` also appears outside this card's positions: in code comments (two docblocks in `ui/view.zod.ts`, one in `ui/component.zod.ts`, and two comments in `ui/view.test.ts`), and in four released `packages/spec/CHANGELOG.md` entries. Comments are form C, under #20234. The changelog is release-owned and never edited in a code PR. None of these is printed by `os migrate meta`. - The pin's detector reads `#` plus four or five digits. A batch ordinal with no `#`, like the one removed here, is held by review, not by the pin. - Two more `batch` tokens appear in printed prose, and both are file names, not plan ordinals. `authoring-schemas-strict-unknown-keys` lists the changeset file names its batches shipped under. `ui-widget-i18n-family-retired` names a test file. Both are outside this claim and untouched. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 89801cd commit 0cb72cf

7 files changed

Lines changed: 42 additions & 11 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the two padded list-view field-name entries states the contract-first rule in words, and the notification/embed retirement drops a sweep batch ordinal
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries are printed by `os migrate meta` as the header, `why:` and
10+
`verify:` lines of a manual change. Two of them —
11+
`ui-list-view-grouping-field-padded-refused` and `ui-list-view-groupbyfield-padded-refused` —
12+
explained why a padded field name is refused rather than trimmed by pointing at a rule number
13+
in a contributor guide, a number that names nothing in this repository's guide. Their `why:`
14+
text now states the rule itself: fix the metadata, not the renderer — off-spec metadata is
15+
refused where it is authored, never coerced into working.
16+
17+
`ui-notification-action-embed-config-retired` named the batch of the v17 unknown-key
18+
strictness sweep that measured the two retired shapes by its ordinal. The sentence already
19+
says what that batch measured and decided, so the ordinal is dropped.
20+
21+
Text only: no entry id, `surface`, `from` / `to`, conversion or matching logic changes, and the
22+
chain rewrites exactly what it rewrote before. The generated migration registry,
23+
`spec-changes.json` and the protocol upgrade guide carry the same text.

‎docs/protocol-upgrade-guide.md‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎packages/spec/spec-changes.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@
854854
"replacement": "(removed — there is no replacement shape, because there was never a key to write either into. Delete the import and the value. Notification presentation is still described by the surviving `NotificationType` / `NotificationSeverity` / `NotificationPosition` vocabulary; public access to a form is granted by the LIVE `FormView.sharing` block (`SharingConfig`), which is untouched. Notification action buttons as metadata, and iframe embedding, return via the enforce route of ADR-0049 through a new ADR — carrier key and renderer first, vocabulary second)",
855855
"migrationId": "ui-notification-action-embed-config-retired",
856856
"toMajor": 17,
857-
"rationale": "Both shapes were published `@objectstack/spec/ui` vocabulary with NO AUTHORING DOOR. The v17 unknown-key strictness sweep (its ui/ batch 14), which measured each ui/ file for an authoring door before closing any shape, measured them three ways on 2026-08-03 and this retirement re-ran all three against `origin/main` before removing anything, each with a positive control that passed in the same run: (1) CARRIER — no schema in `packages/spec/src` declared a key of either type (`ui/notification.zod`'s only non-test importer was the barrel; `ui/sharing.zod`'s were the barrel and `ui/view.zod.ts`, which names its SIBLING `SharingConfigSchema`), measured by resolving specifiers rather than substring-matching, because the repo holds two `sharing.zod` modules and a substring test miscredits `stack.zod.ts` to the UI one; (2) REACHABILITY — a BFS from the 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema`, over `build-schemas.ts`'s own walk including its derived-clone bridge, never reached either, while `Page` / `Action` / `DashboardWidget` / `Webhook` and `SharingConfig` itself all resolved `root-graph` in the same run and an injected synthetic carrier flipped both; (3) PARSE — zero `.parse()` in objectstack, cloud or objectui outside their own unit tests. So nobody could author one and nothing ever validated one: the shape of the unwired plugin sandboxing config removed before it, an exported schema with no consumer read as a capability, and the ADR-0033 trap where an AI author takes `EmbedConfigSchema` in the published bundle as proof the platform serves iframes. Neither is stored metadata and neither has a carrier, so no `sys_metadata` row can hold one and there is no source for the D2 chain to rewrite; this entry is the D3 record. That batch deliberately did NOT close them with `.strict()` — strictness is a property of a PARSE, and closing a shape nothing parses buys only \"a precisely-validated dead slot, the more convincing lie\" (the lesson of the datasource capability flags, whose `readOnly` was precisely validated and read by nothing) — and left the disposition to ADR-0049's enforce-or-remove, which came back REMOVE on 2026-08-04: a dead surface with no authoring door retires implementation-first, as three same-shape rulings that week had already decided. Each was orphaned by an earlier retirement one level up: `NotificationAction` lost its wrappers when the dual-source cleanup removed the `./ui` copies of `NotificationSchema` / `NotificationConfigSchema` (the same names declared differently on other entry points) — that retirement's published \"zero consumers\" evidence was later falsified for objectui, which re-exported both names, and is corrected on `ui/notification.zod`'s tombstone; the removal itself stands — and `EmbedConfig` lost its key at 17.0.0 when the 2026-06 liveness audit retired `App.embed` (no iframe route ever read it) — that key still stands as a `retiredKey()` tombstone in `app.zod.ts`, so an author who wrote the KEY already meets a prescription; this removes the value shape that outlived it. ⚠️ The retirement is per SCHEMA, not per file: `ui/sharing.zod` KEEPS `SharingConfigSchema`, a live door carried by `FormViewSchema.sharing` and read by `rest-server.ts` to mount the anonymous form routes, and `ui/notification.zod` keeps its three presentation enums. objectui consumed `NotificationActionSchema.shape.variant` as a VOCABULARY (never a parse) to pin its own hand-written `NotificationActionButton` interface — which is exactly why \"has a consumer\" never meant \"has an authoring door\" here; that pin is adapted objectui-side when it refreshes this dependency. ADR-0049."
857+
"rationale": "Both shapes were published `@objectstack/spec/ui` vocabulary with NO AUTHORING DOOR. The v17 unknown-key strictness sweep, which measured each ui/ file for an authoring door before closing any shape, measured them three ways on 2026-08-03 and this retirement re-ran all three against `origin/main` before removing anything, each with a positive control that passed in the same run: (1) CARRIER — no schema in `packages/spec/src` declared a key of either type (`ui/notification.zod`'s only non-test importer was the barrel; `ui/sharing.zod`'s were the barrel and `ui/view.zod.ts`, which names its SIBLING `SharingConfigSchema`), measured by resolving specifiers rather than substring-matching, because the repo holds two `sharing.zod` modules and a substring test miscredits `stack.zod.ts` to the UI one; (2) REACHABILITY — a BFS from the 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema`, over `build-schemas.ts`'s own walk including its derived-clone bridge, never reached either, while `Page` / `Action` / `DashboardWidget` / `Webhook` and `SharingConfig` itself all resolved `root-graph` in the same run and an injected synthetic carrier flipped both; (3) PARSE — zero `.parse()` in objectstack, cloud or objectui outside their own unit tests. So nobody could author one and nothing ever validated one: the shape of the unwired plugin sandboxing config removed before it, an exported schema with no consumer read as a capability, and the ADR-0033 trap where an AI author takes `EmbedConfigSchema` in the published bundle as proof the platform serves iframes. Neither is stored metadata and neither has a carrier, so no `sys_metadata` row can hold one and there is no source for the D2 chain to rewrite; this entry is the D3 record. The sweep deliberately did NOT close them with `.strict()` — strictness is a property of a PARSE, and closing a shape nothing parses buys only \"a precisely-validated dead slot, the more convincing lie\" (the lesson of the datasource capability flags, whose `readOnly` was precisely validated and read by nothing) — and left the disposition to ADR-0049's enforce-or-remove, which came back REMOVE on 2026-08-04: a dead surface with no authoring door retires implementation-first, as three same-shape rulings that week had already decided. Each was orphaned by an earlier retirement one level up: `NotificationAction` lost its wrappers when the dual-source cleanup removed the `./ui` copies of `NotificationSchema` / `NotificationConfigSchema` (the same names declared differently on other entry points) — that retirement's published \"zero consumers\" evidence was later falsified for objectui, which re-exported both names, and is corrected on `ui/notification.zod`'s tombstone; the removal itself stands — and `EmbedConfig` lost its key at 17.0.0 when the 2026-06 liveness audit retired `App.embed` (no iframe route ever read it) — that key still stands as a `retiredKey()` tombstone in `app.zod.ts`, so an author who wrote the KEY already meets a prescription; this removes the value shape that outlived it. ⚠️ The retirement is per SCHEMA, not per file: `ui/sharing.zod` KEEPS `SharingConfigSchema`, a live door carried by `FormViewSchema.sharing` and read by `rest-server.ts` to mount the anonymous form routes, and `ui/notification.zod` keeps its three presentation enums. objectui consumed `NotificationActionSchema.shape.variant` as a VOCABULARY (never a parse) to pin its own hand-written `NotificationActionButton` interface — which is exactly why \"has a consumer\" never meant \"has an authoring door\" here; that pin is adapted objectui-side when it refreshes this dependency. ADR-0049."
858858
},
859859
{
860860
"surface": "ui.widgetManifest / ui.widgetLifecycle / ui.widgetEvent / ui.widgetProperty / ui.widgetSource / ui.i18nObject / ui.pluralRule / ui.numberFormat / ui.dateFormat / ui.localeConfig (the widget-registration vocabulary of ui/widget.zod.ts, and the five doorless shapes of ui/i18n.zod.ts — 10 defs, 26 exported names)",
@@ -1746,7 +1746,7 @@
17461746
"replacement": "(removed — there is no replacement shape, because there was never a key to write either into. Delete the import and the value. Notification presentation is still described by the surviving `NotificationType` / `NotificationSeverity` / `NotificationPosition` vocabulary; public access to a form is granted by the LIVE `FormView.sharing` block (`SharingConfig`), which is untouched. Notification action buttons as metadata, and iframe embedding, return via the enforce route of ADR-0049 through a new ADR — carrier key and renderer first, vocabulary second)",
17471747
"migrationId": "ui-notification-action-embed-config-retired",
17481748
"toMajor": 17,
1749-
"rationale": "Both shapes were published `@objectstack/spec/ui` vocabulary with NO AUTHORING DOOR. The v17 unknown-key strictness sweep (its ui/ batch 14), which measured each ui/ file for an authoring door before closing any shape, measured them three ways on 2026-08-03 and this retirement re-ran all three against `origin/main` before removing anything, each with a positive control that passed in the same run: (1) CARRIER — no schema in `packages/spec/src` declared a key of either type (`ui/notification.zod`'s only non-test importer was the barrel; `ui/sharing.zod`'s were the barrel and `ui/view.zod.ts`, which names its SIBLING `SharingConfigSchema`), measured by resolving specifiers rather than substring-matching, because the repo holds two `sharing.zod` modules and a substring test miscredits `stack.zod.ts` to the UI one; (2) REACHABILITY — a BFS from the 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema`, over `build-schemas.ts`'s own walk including its derived-clone bridge, never reached either, while `Page` / `Action` / `DashboardWidget` / `Webhook` and `SharingConfig` itself all resolved `root-graph` in the same run and an injected synthetic carrier flipped both; (3) PARSE — zero `.parse()` in objectstack, cloud or objectui outside their own unit tests. So nobody could author one and nothing ever validated one: the shape of the unwired plugin sandboxing config removed before it, an exported schema with no consumer read as a capability, and the ADR-0033 trap where an AI author takes `EmbedConfigSchema` in the published bundle as proof the platform serves iframes. Neither is stored metadata and neither has a carrier, so no `sys_metadata` row can hold one and there is no source for the D2 chain to rewrite; this entry is the D3 record. That batch deliberately did NOT close them with `.strict()` — strictness is a property of a PARSE, and closing a shape nothing parses buys only \"a precisely-validated dead slot, the more convincing lie\" (the lesson of the datasource capability flags, whose `readOnly` was precisely validated and read by nothing) — and left the disposition to ADR-0049's enforce-or-remove, which came back REMOVE on 2026-08-04: a dead surface with no authoring door retires implementation-first, as three same-shape rulings that week had already decided. Each was orphaned by an earlier retirement one level up: `NotificationAction` lost its wrappers when the dual-source cleanup removed the `./ui` copies of `NotificationSchema` / `NotificationConfigSchema` (the same names declared differently on other entry points) — that retirement's published \"zero consumers\" evidence was later falsified for objectui, which re-exported both names, and is corrected on `ui/notification.zod`'s tombstone; the removal itself stands — and `EmbedConfig` lost its key at 17.0.0 when the 2026-06 liveness audit retired `App.embed` (no iframe route ever read it) — that key still stands as a `retiredKey()` tombstone in `app.zod.ts`, so an author who wrote the KEY already meets a prescription; this removes the value shape that outlived it. ⚠️ The retirement is per SCHEMA, not per file: `ui/sharing.zod` KEEPS `SharingConfigSchema`, a live door carried by `FormViewSchema.sharing` and read by `rest-server.ts` to mount the anonymous form routes, and `ui/notification.zod` keeps its three presentation enums. objectui consumed `NotificationActionSchema.shape.variant` as a VOCABULARY (never a parse) to pin its own hand-written `NotificationActionButton` interface — which is exactly why \"has a consumer\" never meant \"has an authoring door\" here; that pin is adapted objectui-side when it refreshes this dependency. ADR-0049."
1749+
"rationale": "Both shapes were published `@objectstack/spec/ui` vocabulary with NO AUTHORING DOOR. The v17 unknown-key strictness sweep, which measured each ui/ file for an authoring door before closing any shape, measured them three ways on 2026-08-03 and this retirement re-ran all three against `origin/main` before removing anything, each with a positive control that passed in the same run: (1) CARRIER — no schema in `packages/spec/src` declared a key of either type (`ui/notification.zod`'s only non-test importer was the barrel; `ui/sharing.zod`'s were the barrel and `ui/view.zod.ts`, which names its SIBLING `SharingConfigSchema`), measured by resolving specifiers rather than substring-matching, because the repo holds two `sharing.zod` modules and a substring test miscredits `stack.zod.ts` to the UI one; (2) REACHABILITY — a BFS from the 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema`, over `build-schemas.ts`'s own walk including its derived-clone bridge, never reached either, while `Page` / `Action` / `DashboardWidget` / `Webhook` and `SharingConfig` itself all resolved `root-graph` in the same run and an injected synthetic carrier flipped both; (3) PARSE — zero `.parse()` in objectstack, cloud or objectui outside their own unit tests. So nobody could author one and nothing ever validated one: the shape of the unwired plugin sandboxing config removed before it, an exported schema with no consumer read as a capability, and the ADR-0033 trap where an AI author takes `EmbedConfigSchema` in the published bundle as proof the platform serves iframes. Neither is stored metadata and neither has a carrier, so no `sys_metadata` row can hold one and there is no source for the D2 chain to rewrite; this entry is the D3 record. The sweep deliberately did NOT close them with `.strict()` — strictness is a property of a PARSE, and closing a shape nothing parses buys only \"a precisely-validated dead slot, the more convincing lie\" (the lesson of the datasource capability flags, whose `readOnly` was precisely validated and read by nothing) — and left the disposition to ADR-0049's enforce-or-remove, which came back REMOVE on 2026-08-04: a dead surface with no authoring door retires implementation-first, as three same-shape rulings that week had already decided. Each was orphaned by an earlier retirement one level up: `NotificationAction` lost its wrappers when the dual-source cleanup removed the `./ui` copies of `NotificationSchema` / `NotificationConfigSchema` (the same names declared differently on other entry points) — that retirement's published \"zero consumers\" evidence was later falsified for objectui, which re-exported both names, and is corrected on `ui/notification.zod`'s tombstone; the removal itself stands — and `EmbedConfig` lost its key at 17.0.0 when the 2026-06 liveness audit retired `App.embed` (no iframe route ever read it) — that key still stands as a `retiredKey()` tombstone in `app.zod.ts`, so an author who wrote the KEY already meets a prescription; this removes the value shape that outlived it. ⚠️ The retirement is per SCHEMA, not per file: `ui/sharing.zod` KEEPS `SharingConfigSchema`, a live door carried by `FormViewSchema.sharing` and read by `rest-server.ts` to mount the anonymous form routes, and `ui/notification.zod` keeps its three presentation enums. objectui consumed `NotificationActionSchema.shape.variant` as a VOCABULARY (never a parse) to pin its own hand-written `NotificationActionButton` interface — which is exactly why \"has a consumer\" never meant \"has an authoring door\" here; that pin is adapted objectui-side when it refreshes this dependency. ADR-0049."
17501750
},
17511751
{
17521752
"surface": "ui.widgetManifest / ui.widgetLifecycle / ui.widgetEvent / ui.widgetProperty / ui.widgetSource / ui.i18nObject / ui.pluralRule / ui.numberFormat / ui.dateFormat / ui.localeConfig (the widget-registration vocabulary of ui/widget.zod.ts, and the five doorless shapes of ui/i18n.zod.ts — 10 defs, 26 exported names)",

‎packages/spec/src/migrations/entries/semantic/17.ui-notification-action-embed-config-retired.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const entry: SemanticMigration = {
1515
+ 'through a new ADR — carrier key and renderer first, vocabulary second)',
1616
reason:
1717
'Both shapes were published `@objectstack/spec/ui` vocabulary with NO AUTHORING DOOR. '
18-
+ 'The v17 unknown-key strictness sweep (its ui/ batch 14), which measured each ui/ '
18+
+ 'The v17 unknown-key strictness sweep, which measured each ui/ '
1919
+ 'file for an authoring door before closing any shape, measured them three ways on '
2020
+ '2026-08-03 and this retirement re-ran all '
2121
+ 'three against `origin/main` before removing anything, each with a positive control '
@@ -36,7 +36,7 @@ export const entry: SemanticMigration = {
3636
+ 'ADR-0033 trap where an AI author takes `EmbedConfigSchema` in the published bundle '
3737
+ 'as proof the platform serves iframes. Neither is stored metadata and neither has a '
3838
+ 'carrier, so no `sys_metadata` row can hold one and there is no source for the D2 '
39-
+ 'chain to rewrite; this entry is the D3 record. That batch deliberately did NOT close '
39+
+ 'chain to rewrite; this entry is the D3 record. The sweep deliberately did NOT close '
4040
+ 'them with `.strict()` — strictness is a property of a PARSE, and closing a shape nothing '
4141
+ 'parses buys only "a precisely-validated dead slot, the more convincing lie" (the lesson '
4242
+ 'of the datasource capability flags, whose `readOnly` was precisely validated and read '

0 commit comments

Comments
 (0)