Skip to content

Commit 2114942

Browse files
committed
chore(spec): regenerate the migration registry, spec-changes.json and the upgrade guide
Generator output only (gen:migration-registry, gen:spec-changes, gen:upgrade-guide) for the three entry edits in the previous commit. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4045eda commit 2114942

3 files changed

Lines changed: 11 additions & 7 deletions

File tree

‎docs/protocol-upgrade-guide.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ This is a RUNTIME registration API, not stored metadata, so — like `hook-conte
453453
- Why not automatic: Five `@objectstack/spec/ui` modules declared a full interaction-configuration vocabulary — 22 `z.object` sites across touch/gesture, drag-and-drop, focus/keyboard, animation/motion and offline/sync — and NOTHING in the protocol carried them. This is the ADR-0049 false-compliance shape in its most inviting form for an AI author (ADR-0033), and worse than the ordinary declared-but-unread defect: `authorable-surface.json` listed 109 keys under these defs and `content/docs/references/ui/{touch,dnd,keyboard,animation,offline}.mdx` rendered them as authoring tables, so the published documentation advertised a vocabulary with no carrier key anywhere. An author following `dnd.mdx` and writing a `dnd:` block onto a page component was rejected by `PageComponentSchema` for an unrecognized key — the docs and the schema disagreeing about the platform (Prime Directive #10). Three independent measurements, each with its controls passing in the same run: (1) no module under `packages/spec/src` imported any of the five except the `ui/index.ts` barrel, so no schema declared a carrier key; (2) a BFS over the in-memory Zod graph from all 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema` (25 roots, 4742 nodes) reached none of the 21 named object shapes, while `PageSchema`, `WebhookSchema` and `StateMachineSchema` all resolved `direct` and a synthetic carrier flipped all 21 — so unreachability was a fact about the graph, not a broken walker; (3) zero `.parse()` / `.safeParse()` in objectstack, objectui or cloud outside these modules' own unit tests. objectui holds TYPE re-exports and parity ratchets, never validators, and says so (its types package deliberately dropped the spec/ui zod-validator re-exports and keeps type-only ones). The 2026-08-04 ruling retired the family — touch, drag-and-drop, keyboard and motion are renderer built-in behaviour and offline belongs to a sync engine, none of it per-page metadata — and weighed wiring a carrier key (option B) and rejected it: that is a feature with a renderer behind it, not ledger clean-up. It also weighed tightening the shapes to `strictObject` and rejected that explicitly — strictness is a property of a PARSE and there is no parse, so it would spend a breaking change to leave "a precisely validated dead slot, the more convincing lie" (the lesson of the datasource capability flags: `readOnly` was precisely validated and read by nothing, while a shipped example called a datasource a read replica and wrote through it). Because there was no carrier key there is nothing to tombstone and no `sys_metadata` row or source file for a D2 conversion to rewrite: this entry is the D3 record, the same route 3 as `plugin-runtime-family-retired` (the kernel plugin-runtime family) and the `HttpServerConfig` retirement (seven keys no runtime read and no authoring door reached, retired with their container). ⚠️ Not to be confused with the theme-token retirement (theme-driven typography is not a near-term capability, so nine token groups nothing consumed were retired), which retired the THEME `animation` block — a different file, different defs, and that one did have a carrier key and therefore a tombstone. ADR-0049.
454454
- Done when: No code imports any of the 64 retired names from `@objectstack/spec` or `@objectstack/spec/ui` — `TouchTargetConfig(Schema)`, `GestureType(Schema)`, `SwipeDirection(Schema)`, `SwipeGestureConfig(Schema)`, `PinchGestureConfig(Schema)`, `LongPressGestureConfig(Schema)`, `GestureConfig(Schema)`, `TouchInteraction(Schema)`, `TransitionPreset(Schema)`, `EasingFunction(Schema)`, `TransitionConfig(Schema)`, `AnimationTrigger(Schema)`, `ComponentAnimation(Schema)`, `PageTransition(Schema)`, `MotionConfig(Schema)`, `DragHandle(Schema)`, `DropEffect(Schema)`, `DragConstraint(Schema)`, `DropZone(Schema)`, `DragItem(Schema)`, `DndConfig(Schema)`, `FocusTrapConfig(Schema)`, `KeyboardShortcut(Schema)`, `FocusManagement(Schema)`, `KeyboardNavigationConfig(Schema)`, `OfflineStrategy(Schema)`, `ConflictResolution(Schema)`, `SyncConfig(Schema)`, `PersistStorage(Schema)`, `EvictionPolicy(Schema)`, `OfflineCacheConfig(Schema)`, `OfflineConfig(Schema)` — every one is TS2305 after upgrade, on every public entry (pinned by resolved symbol identity in `ui/interaction-config-retirement.test.ts`). No metadata document needs editing, because none could ever carry one of these blocks: a stack that parsed before parses byte-for-byte the same after. If you consumed the bare `ConflictResolution` from `@objectstack/spec/ui` as a TYPE for your own offline code, declare that union locally — it is your client's policy, not the platform's. `@objectstack/spec/integration`'s `ConnectorConflictResolution` (connector sync) and `@objectstack/spec/api`'s `ConflictResolutionStrategy` (route merge policy) are different concepts and are untouched.
455455
- **`ui-notification-action-embed-config-retired`** — `ui.notificationAction / ui.embedConfig` → (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)
456-
- Why not automatic: 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.
456+
- Why not automatic: 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.
457457
- Done when: No code imports `NotificationActionSchema`, `NotificationAction`, `EmbedConfigSchema` or `EmbedConfig` from `@objectstack/spec` or `@objectstack/spec/ui` — both are TS2305 after upgrade, on every public entry (pinned by resolved symbol identity in `notification-embed-retirement.test.ts`). The same pin asserts the SURVIVORS in the same run, and that half is equally load-bearing: `NotificationTypeSchema` / `NotificationSeveritySchema` / `NotificationPositionSchema` and `SharingConfigSchema` must still be exported from `./ui`, and both modules must still load — a retirement that deleted either file would satisfy the absence half while destroying working surface. Nothing regresses at runtime, because nothing ever ran: no notification action was ever parsed from metadata and no iframe route ever read an embed config. Public form sharing is unaffected — `FormView.sharing` still gates the anonymous endpoints on `allowAnonymous` + `publicLink`.
458458
- **`ui-widget-i18n-family-retired`** — `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)` → (removed — there is no replacement key, because there was never a key. A custom field widget is still named the same way it always was: `field.widget` is a plain string naming a component the RENDERER has registered, and objectui's registry has always carried its own runtime manifest for that (`RuntimeWidgetManifest` / `RuntimeWidgetSource` in `@object-ui/types`, renamed off the spec's names under objectui's rule that a symbol named like a spec export must import it or take a name of its own), which models different keys and never derived from these. For localisation: write the default-language string on `label` / `description` — the framework generates the translation key at registration time from the naming convention — and put translations in translation files, which is the LIVE `system/translation.zod.ts` surface. Widget registration and locale formatting as authorable protocol metadata return via the ENFORCE route of ADR-0049 through a new ADR — the registry / loader / formatter first, the vocabulary second)
459459
- Why not automatic: `ui/widget.zod.ts` published a complete widget-registration vocabulary — a manifest with lifecycle hooks, custom events, configurable properties and an npm/remote/inline implementation-source union — and `ui/i18n.zod.ts` published a structured-label, plural-rule and locale-formatting vocabulary. NOTHING in the protocol carried either. Three independent measurements, re-run on `origin/main` immediately before the removal with their controls passing in the SAME run: (1) no module under `packages/spec/src` imported `widget.zod` at all, and the only imports of `i18n.zod` anywhere name `I18nLabelSchema` / `AriaPropsSchema` (both KEPT), so no schema declared a carrier key — `field.widget` is a `z.string()` naming a registered component and has never referenced `WidgetManifest`; (2) a BFS over the in-memory Zod graph from all 24 metadata-type roots plus `defineStack`'s `ObjectStackSchema` reached none of them, while `PageSchema` / `ObjectListViewSchema` resolved `direct` in the same run and a synthetic carrier flipped every one of them; (3) zero `.parse()` / `.safeParse()` in objectstack, objectui or cloud outside these files' own unit tests. `NumberFormat` / `DateFormat` DID have a carrier key (`LocaleConfig.numberFormat` / `.dateFormat`) but the carrier was itself doorless, so the subtree was `no door` rather than `no gate` and goes whole — leaving the two leaves behind would strand exported schemas with no consumer, which an author reads as a capability. `I18nObjectSchema` was additionally superseded by its own file-neighbour: `I18nLabelSchema`'s documentation already says translation keys are generated at registration time and translations live in translation files, and the live translation surface is `system/translation.zod.ts`, which uses none of these shapes. The 2026-08-06 ruling weighed giving them a carrier (option B) and rejected it: that is a feature with a registry and a renderer behind it, not ledger clean-up. Tightening them to `strictObject` was rejected earlier and explicitly, by the batch of the v17 unknown-key strictness sweep that measured this file as having no authoring door — strictness is a property of a PARSE and there is no parse, so it would spend a breaking change to leave "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). With no carrier key there is nothing to tombstone and no `sys_metadata` row or source file for a D2 conversion to rewrite: this entry is the D3 record, route 3, the same shape as `ui-interaction-config-family-retired`, `plugin-runtime-family-retired` and the `HttpServerConfig` retirement. ⚠️ `WidgetManifest.performance`'s own `retiredKey()` tombstone (left by the close-out sweep that removed the inert `performance` keys no renderer applied) is SUBSUMED here, the way the kernel `activationEvents` tombstone went with the removed plugin-runtime family: it goes with the shape that carried it, which is strictly stronger than the tombstone, because there is no longer a manifest to author the key INTO. ⚠️ One of the nine widget sites is deliberately NOT retired. `FieldWidgetPropsSchema` survives: it is a REACT PROPS CONTRACT rather than authorable metadata (it never appeared in `authorable-surface/` or `json-schema.manifest/` — its `onChange` is a `z.function()`), so "zero parse" is its design and not its defect, and it acquired a live cross-repo compile-time consumer one day before that sweep batch measured: an objectui fix of 2026-08-03, made to follow the spec, renamed `@object-ui/fields`' validation slot onto the spec's `error` with no alias, the form renderer began producing it, and `packages/fields/src/__tests__/spec-symbol-batch7.test.ts` pins the shape against `import type { FieldWidgetProps } from '@objectstack/spec/ui'` as an intentional tripwire. Re-verified on objectui `origin/main` 2026-08-07. ADR-0049.

0 commit comments

Comments
 (0)