From 63466dc35e20c568f8fd42c0abc2a6a72f7d0fed Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 09:40:33 +0000 Subject: [PATCH 1/3] docs(spec): registry.ts hand-written parts drop their deleted tracker numbers (stage 11) Ruling C+D on the hand-written parts of packages/spec/src/migrations/registry.ts. Form C, two comments: the dead #8495 now cites commit 4bfe1a539 (PR #8666) and says what it refused; the dead #6239 cites commit f549a0d4a, the sweep that retired the viewId-addressed view CRUD. Form D, nine author-shown rationale units (step 17's mongodb paragraph and eight step-18 fragments) that carried the eleven dead numbers. Each unit now takes no number: the dead ones go, the live ones in the same unit go with them, and the sentence states the cited decision in words. Text only: no step, fragment id, order, entry or code token moves. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude --- packages/spec/src/migrations/registry.ts | 67 ++++++++++++++---------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index f0f2613e59f..d4adb357c6b 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -341,7 +341,8 @@ const step17: MigrationStep = { 'but because the authoring gate already rejects the spellings loudly; the chain and the ', 'stored-row replay are the seams that accept them.\n\n', 'Finishing the same datasource surface, the canonical driver id `mongo` is renamed to ', - '`mongodb` (#6345). The two spellings have both been accepted since #4410 and both still ', + '`mongodb`. The two spellings have both been accepted since `datasource.config` was first ', + 'parsed against its driver\'s own contract, and both still ', 'are, so no boot breaks and no data moves — what changed is which one is CANONICAL, and ', 'that string is published as `DRIVER_CATALOG.id` and is what the Studio connection form ', 'writes into `datasource.driver`. Every row written before the rename therefore carries ', @@ -5177,7 +5178,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ id: 'connector-error-mapping-retired', order: 22, text: - 'It also retires `connector.errorMapping` (#14676, ADR-0049 enforce-or-remove; triage ' + 'It also retires `connector.errorMapping` (ADR-0049 enforce-or-remove; triage ' + 'ruling 2026-09-02): `ErrorMappingConfig` (4 keys) and its `ErrorMappingRule[]` (7 keys) ' + 'were authorable through `ConnectorSchema` — and, via `DeclarativeConnectorEntrySchema`, ' + 'through `stack.connectors[]` and the `/meta/connector` door — and read by nothing: no ' @@ -5598,14 +5599,16 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ order: 19, text: 'Finally, it narrows the per-option `default` key OUT of the form-view options ' - + 'vocabulary (#12868, ADR-0049 declared-but-unenforced; maintainer ruling 2026-08-28 ' - + 'on the objectui#6263 analysis, disposition 甲): `SelectOptionSchema` serves two ' - + 'surfaces and only the OBJECT-field face reads `default` (#7246 / PR #7388 — ' - + '`applyFieldDefaults` falls back to the option marked `default: true`; that face, its ' - + 'alias rows and its precedence pin are untouched). On a form-view field\'s option list ' + + 'vocabulary (ADR-0049 declared-but-unenforced; maintainer ruling 2026-08-28 ' + + 'on the console form renderer\'s analysis, disposition 甲): `SelectOptionSchema` serves ' + + 'two surfaces and only the OBJECT-field face reads `default` (enforced there by a ' + + 'maintainer ruling of 2026-08-10 — `applyFieldDefaults` falls back to the option marked ' + + '`default: true`; that face, its alias rows and its precedence pin are untouched). On a ' + + 'form-view field\'s option list ' + 'the key parsed clean and nothing read it — the insert-path fallback consults the ' + 'object definition\'s options, never a form view\'s, and no form renderer seeds a value ' - + 'from it (measured on objectui#6263; the ruled census found ZERO authored occurrences ' + + 'from it (measured against the console\'s form controls, none of which reads the key; ' + + 'the ruled census found ZERO authored occurrences ' + 'across the tree, the example apps and the published *.form.ts corpus). The FormView ' + 'vocabulary\'s own option shape (`FormSelectOptionSchema`, ui/view.zod.ts) now refuses ' + 'the key with the prescription; the mechanical conversion strips it from stored ' @@ -5700,8 +5703,9 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ id: 'mapping-lookup-params-retired', order: 13, text: - 'It also retires the import mapping `lookup` transform\'s steering params (#10329, ' - + 'ADR-0049 enforce-or-remove — the sub-walk half of 17.0.0\'s #4509 mapping cleanup): ' + 'It also retires the import mapping `lookup` transform\'s steering params (ADR-0049 ' + + 'enforce-or-remove — the sub-walk half of the 17.0.0 mapping cleanup that retired ' + + '`extractQuery` / `errorPolicy` / `batchSize`): ' + '`fieldMapping[].params.object` / `.fromField` / `.toField` / `.autoCreate` declared a ' + 'per-entry reference-resolution dialect the import path never implemented — `lookup` ' + 'copies the cell through and resolution runs off the target field\'s own metadata — ' @@ -5715,10 +5719,11 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ id: 'memory-persistence-placeholder-refused', order: 1, text: - 'Protocol 18 extends the #8336 unresolved-placeholder refusal to the memory ' + 'Protocol 18 extends the publish-time refusal of unresolved placeholders, which protocol ' + + '17 applied to datasource connection config, to the memory ' + 'driver\'s config-material persistence keys: `persistence.path` (file persistence ' + 'and the `auto` override) and `persistence.key` (localStorage and the `auto` ' - + 'override) refuse `${…}` placeholder syntax at publish (#8495). Nothing resolves a ' + + 'override) refuse `${…}` placeholder syntax at publish. Nothing resolves a ' + 'placeholder there — the driver would create a literal `./${DATA_DIR}/…` path or ' + 'write under the literal localStorage key — the same authored-under-a-false-belief ' + 'shape, one surface over. The memory driver\'s `initialData` stays deliberately ' @@ -5729,8 +5734,8 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ id: 'metadata-customization-protocol-retired', order: 20, text: - 'It also retires the paper metadata-customization protocol whole (#13135, re-charter ' - + 'of #12057; ADR-0049 enforce-or-remove, maintainer ruling 2026-08-29): ' + 'It also retires the paper metadata-customization protocol whole (ADR-0049 ' + + 'enforce-or-remove, maintainer ruling 2026-08-29): ' + '`kernel/metadata-customization.zod.ts` — the three-layer platform/user patch-overlay ' + 'model with field-level change tracking and a 3-way-merge story — was exported, ' + 'documented as the customization architecture, and implemented ONLY by an unreachable ' @@ -5960,20 +5965,22 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ order: 16, text: 'Finally, it retires nine of the eleven members of the plugin manifest\'s ' - + '`contributes` block (#10724, ADR-0049 enforce-or-remove; triage graded 2026-08-21, ' + + '`contributes` block (ADR-0049 enforce-or-remove; triage graded 2026-08-21, ' + 'cloud census leg discharged clean 2026-08-24): `events`, `menus`, `themes`, ' + '`translations`, `actions`, `drivers`, `fieldTypes`, `functions` and `commands`. ' - + '#10627 measured — three repos, controlled — that the whole monorepo contains exactly ' - + 'one non-test read of `manifest.contributes`, and it reads `kinds`; the other nine ' + + 'A census of all three repos, with controls, measured that the whole monorepo ' + + 'contains exactly one non-test read of `manifest.contributes`, and it reads `kinds`; ' + + 'the other nine ' + 'members parsed, entered the manifest, and changed nothing, while published docs and ' + 'the schema\'s own JSDoc kept teaching them (`commands` documented Commander.js ' + 'resolution the CLI dropped for oclif; `fieldTypes` advertised a registration seam ' + 'that never existed). All nine are retiredKey tombstones mirroring `loading`; ' - + '`kinds` survives (live reader) and `routes` is untouched pending its own fork ' - + '(#10726). D3 semantic, no D2 conversion: a manifest is not a stack collection ' + + '`kinds` survives (live reader), and `routes` was left to a ruling of its own, which ' + + 'retired it as well (the `plugin-manifest-contributes-routes-retired` entry). D3 ' + + 'semantic, no D2 conversion: a manifest is not a stack collection ' + 'member, so a conversion would be a transform with no seam that ever runs. ' - + 'On the surviving `kinds` bucket it also retires the `globs` sub-field (#11169, ' - + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-08-24): the schema promised ' + + 'On the surviving `kinds` bucket it also retires the `globs` sub-field (ADR-0049 ' + + 'enforce-or-remove; maintainer ruling 2026-08-24): the schema promised ' + 'that declaring `globs` enables file-type discovery, but discovery globs ' + '`filePatterns` off the metadata type registry — which `contributes.kinds` does ' + 'not extend, as `metadata-plugin.zod.ts` records outright — so an authored ' @@ -6017,8 +6024,8 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ id: 'stack-themes-carrier-retired', order: 11, text: - 'Finally, it retires the stack `themes` carrier and `ThemeSchema` whole (#10485, ' - + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-08-21, disposition B: 退役授权面): ' + 'Finally, it retires the stack `themes` carrier and `ThemeSchema` whole (ADR-0049 ' + + 'enforce-or-remove; maintainer ruling 2026-08-21, disposition B: 退役授权面): ' + 'the pipeline was live from the authoring gate through artifact ingest and stopped ' + 'there — zero non-test readers of stored `theme` items, `theme` never a registered ' + 'metadata type, no first-party app mounting the spec-aware provider, nothing ' @@ -6046,16 +6053,17 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ order: 14, text: 'Finally, it retires the component-translation copy key ' - + '`pages..components..submitLabel` and its `submit` alias (#10926, ADR-0049; ' + + '`pages..components..submitLabel` and its `submit` alias (ADR-0049; ' + 'maintainer ruling 2026-08-22): the face is measured, not mirrored — each copy key ' + 'exists because some component in `ComponentPropsMap` declares it — and ' - + '`submitLabel`\'s only declarer was `element:form`, retired whole above (#9249), so ' + + '`submitLabel`\'s only declarer was `element:form`, retired whole above, so ' + 'the key had no declared component left to translate and the resolver overlay was ' + 'its only reader. Retire won over re-anchor because the live form surface ' + '(`object-form`) speaks `submitText` (`I18nLabelSchema`), localizable at its own ' + 'authoring site; re-anchoring would have widened the face for one word. The ' + 'mechanical conversion strips the key from stored bundles and items (pure lossless ' - + 'delete — nothing read it since #9249), at the acknowledged cost of dropping the ' + + 'delete — nothing read it once `element:form` was retired), at the acknowledged cost ' + + 'of dropping the ' + 'bespoke-component route for that one word.', }, { @@ -6298,8 +6306,9 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ * could act on, so removing it is behaviour-preserving; retired from the * load path (the schema refuses live authors), replayed by `migrate meta` * and the stored-row rehydration seam. Semantic: the memory-driver - * persistence placeholder refusal (#8495) — the #8336 parent adjudication - * applied to the two config-material memory keys its deliberate + * persistence placeholder refusal (commit 4bfe1a539, PR #8666: `${…}` refused + * at publish in `persistence.path` / `persistence.key`) — the #8336 parent + * adjudication applied to the two config-material memory keys its deliberate * `initialData` exclusion never covered — the * `MetadataPluginConfig.additionalTypes` retirement (#8586, ADR-0049): the * inert plugin kind-declaration key, tombstoned on the 17.x line, with no D2 @@ -24932,7 +24941,7 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // The 2026-08-08 ADR-0049 sweep (#6486) adds twenty-three more across three // members (4 + 10 + 9), all route 3 and all whole-def: `system/http-server.zod.ts`'s // runtime vocabulary (#5295, D3 `http-server-runtime-vocabulary-retired`), - // `api/protocol.zod.ts`'s viewId-addressed view CRUD (#6239, D3 + // `api/protocol.zod.ts`'s viewId-addressed view CRUD (commit f549a0d4a, D3 // `view-management-protocol-retired`) and the whole L2 ETL layer (#6414, D3 // `etl-pipeline-layer-retired`). None had a carrier key and none was ever // parsed outside its own unit tests, so again there is no tombstone and no D2 From 005c541e53648b67eece3ef86195e2619887531c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 09:47:28 +0000 Subject: [PATCH 2/3] docs(spec): regenerate the upgrade guide from the step-17 rationale (stage 11) `pnpm --filter @objectstack/spec check:generated --fix`, which proved only docs/protocol-upgrade-guide.md stale and regenerated only it: line 68, the projection of step 17's mongodb paragraph, drops its two numbers. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude --- docs/protocol-upgrade-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/protocol-upgrade-guide.md b/docs/protocol-upgrade-guide.md index 945b3c37c5a..c16f61ca593 100644 --- a/docs/protocol-upgrade-guide.md +++ b/docs/protocol-upgrade-guide.md @@ -65,7 +65,7 @@ Closing the same audit on the data side, `datasource.readReplicas` is removed (# The datasource close-out also graduates the four legacy `datasource.config` spellings the shared driver factory still tolerated via undeclared read-side `??` fallbacks (#4456, the #4410 follow-up): sqlite `file`/`database` (use `filename`), postgres/mysql `connectionString` (use `url`) and `user` (use `username`), and mongo `uri` (use `url`) and `user` (use `username`). #4410 made the authoring gate reject each with a rename hint, but a runtime datasource persisted in `sys_metadata` before the gate kept working only because the factory read leniently — and deleting that tolerance without a conversion would have silently moved data (a stored sqlite `file:` row falls back to `:memory:`). The `datasource-config-driver-key-aliases` conversion rewrites the stored shape to the canonical keys at every rehydration seam, the factory now reads exactly one spelling per key, and the four `??` chains are deleted. Driver-aware by construction: `database` renames only under sqlite, where it aliased the file path — for every other driver it is a canonical key and is untouched. Retired from the load path not for lying but because the authoring gate already rejects the spellings loudly; the chain and the stored-row replay are the seams that accept them. -Finishing the same datasource surface, the canonical driver id `mongo` is renamed to `mongodb` (#6345). The two spellings have both been accepted since #4410 and both still are, so no boot breaks and no data moves — what changed is which one is CANONICAL, and that string is published as `DRIVER_CATALOG.id` and is what the Studio connection form writes into `datasource.driver`. Every row written before the rename therefore carries `mongo` while the form now emits `mongodb`, leaving one deployment with two spellings of one driver and any reader that matches a stored driver against the published catalog id silently missing the older rows. The `datasource-driver-mongo-to-mongodb` conversion converges the stored value at every rehydration seam; it stays on the LIVE load path (unlike the config-key aliases beside it) precisely because `mongo` is still legal — there is no loud rejection for it to pre-empt, and nothing to lose by converging early. The rename is what let the driver-selection id and the config-contract id become one string: `packages/spec`'s driver vocabulary is now a single table both boot hosts read, which closed the last fork where `OS_DATABASE_DRIVER=pg` booted under `os start` and was refused by `os migrate`. `turso`/libSQL joins the same table with a real config contract, so a libSQL `config` is validated instead of waved through. +Finishing the same datasource surface, the canonical driver id `mongo` is renamed to `mongodb`. The two spellings have both been accepted since `datasource.config` was first parsed against its driver's own contract, and both still are, so no boot breaks and no data moves — what changed is which one is CANONICAL, and that string is published as `DRIVER_CATALOG.id` and is what the Studio connection form writes into `datasource.driver`. Every row written before the rename therefore carries `mongo` while the form now emits `mongodb`, leaving one deployment with two spellings of one driver and any reader that matches a stored driver against the published catalog id silently missing the older rows. The `datasource-driver-mongo-to-mongodb` conversion converges the stored value at every rehydration seam; it stays on the LIVE load path (unlike the config-key aliases beside it) precisely because `mongo` is still legal — there is no loud rejection for it to pre-empt, and nothing to lose by converging early. The rename is what let the driver-selection id and the config-contract id become one string: `packages/spec`'s driver vocabulary is now a single table both boot hosts read, which closed the last fork where `OS_DATABASE_DRIVER=pg` booted under `os start` and was refused by `os migrate`. `turso`/libSQL joins the same table with a real config contract, so a libSQL `config` is validated instead of waved through. The `script` flow node converges on its one real path (#4343). It had four ways to name what it ran and only one of them ran anything: `config.actionType: 'email' | 'slack'` were logger-backed stubs that wrote a line, reported success and delivered nothing under any configuration — with `config.template` / `.recipients` / `.variables` feeding a message no channel ever sent; inline `config.script` was recognized and never executed (the built-in runtime has no server-side JS sandbox), so the node warned and no-op'd; and every other `actionType` value was shorthand for a registered-function name, a second spelling of `config.function`. All five keys are retired and `function` becomes required, which is also what finally made the contract PARSEABLE: while the legal key set depended on `actionType`, a flat parse would either reject valid shapes or wave everything through, so `script` (with `subflow`) now runs through the same execute-time contract parse #4277 gave the flat builtins. A shorthand `actionType` CONVERTS into `function` — that is what it meant — unless `function` is already set, in which case it was dead metadata the executor never reached. The other four are dropped outright: nothing read them, so there is no value to preserve, and rebuilding the intent is an authoring decision the tombstones prescribe per branch (a `notify` node for mail — it delivers through the messaging service, the in-app inbox by default and real email once `@objectstack/plugin-email` is installed; a `connector_action` with the Slack connector, or an `http` node posting to a webhook, for Slack; a registered function for an inline body). Retired from the load path for the same reason as the rest: absorbing `actionType: 'email'` silently would let an author keep believing the flow sends mail. From ee14924cb4abf968a8e7af2681c6612d2733ef8b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Oct 2026 09:48:10 +0000 Subject: [PATCH 3/3] chore(changeset): patch for the stage-11 rationale and comment rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The step rationales ship in @objectstack/spec's dist (dist/migrations/index.js and .mjs carry the new phrases, the old ones in 0 files, an unchanged control phrase in the same 2), so the release takes a patch note. Clause-②: no. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude --- ...n-registry-rationale-decisions-in-words.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .changeset/spec-migration-registry-rationale-decisions-in-words.md diff --git a/.changeset/spec-migration-registry-rationale-decisions-in-words.md b/.changeset/spec-migration-registry-rationale-decisions-in-words.md new file mode 100644 index 00000000000..783452a11b1 --- /dev/null +++ b/.changeset/spec-migration-registry-rationale-decisions-in-words.md @@ -0,0 +1,19 @@ +--- +'@objectstack/spec': patch +--- + +Nine migration-step rationale passages state their decisions in words instead of tracker numbers, and two registry comments cite the commit that decided them + +Clause-②: no + +The protocol 17 and protocol 18 step rationales are what `os migrate meta` shows per hop +and what the protocol upgrade guide prints. Nine of their passages named GitHub issues +that no longer exist, so an upgrading author met a number with nothing behind it. Each of +those passages now carries no number at all and says what was decided: why `mongo` and +`mongodb` are both accepted, why the form-view option `default` and `connector.errorMapping` +were retired, which earlier cleanup the import mapping `lookup` params finish, what the +memory driver's placeholder refusal extends, how the plugin manifest's `contributes` +members and `routes` were retired, and why the stack `themes` carrier and the +component-translation `submitLabel` key went. Two source comments of the migration +registry now cite the commit behind them. Text only: no migration step, entry, retired key +or def, conversion, schema, export or runtime behaviour changes.