chore(spec)!: raise MIGRATION_SUPPORT_FLOOR from 10 to 16 — upgrades are supported from protocol 16 onward - #19302
Conversation
Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 142 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 b923021180553e4b05af629903c7aae3cf5f9e46 && git checkout b923021180553e4b05af629903c7aae3cf5f9e46
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4b58dcf96b34b83d0c6863d837b4d947234552ef d86cf0653b42132be030fc6136c764219cffc9f5 && git checkout -B drift-repro 4b58dcf96b34b83d0c6863d837b4d947234552ef && git merge --no-ff d86cf0653b42132be030fc6136c764219cffc9f5
node scripts/docs-audit/affected-docs.mjs --json 4b58dcf96b34b83d0c6863d837b4d947234552ef
|
Contract reviewServed-tier: Isolated at-tier contract-review subagent. Reviewed against merge-base ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Grounds, in order: ①-5 (head red on a queue-tier test this PR breaks, no base signature); ①-6 (same-class published examples missed on an editable surface); ①-7 (dirty merge on a regenerated artifact). Judgments ①-1 to ①-4 and ② are upheld and need no re-argument at the next head. The next head needs: the cli integration case re-pinned honestly, the Generated by Claude Code |
…gration-support-floor-16 # Conflicts: # packages/spec/api-surface-declarations/root.txt
…oor claim to 16 Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
… of a retired hop Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Isolated adversarial contract-review subagent, second round. Reviewed the head in a detached scratch worktree cut from the fetched PR ref; PR base ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Grounds: ①-6a and ①-6b — the support promise this card narrows is still stated at the old width in the live generated upgrade guide (a generator this PR edits), and one flag example in the page this PR swept is now an empty-range invocation; both are the class the prior round already ruled a defect at its ①-6, on surfaces this PR can edit. ①-10 — a wrong figure in a changeset that ships. Upheld and needing no re-argument at the next head: ①-1 to ①-5, ①-7 to ①-9 and ②, including the two prior-round requirements this round measured false (rebase; api-surface regeneration). The next head needs exactly: Generated by Claude Code |
…or (#19056) Contract review 5750388312 (round 2, FAIL): the doc-facing lateness claim survived the doc sweep by token-matching instead of meaning. At floor 16 / protocol 17 the supported lateness is exactly one major (derived: PROTOCOL_MAJOR - MIGRATION_SUPPORT_FLOOR = 17 - 16 = 1) — `--from 15` and `--from 12` both throw MigrationFloorError. - packages/spec/scripts/build-upgrade-guide.ts: the generator (not its generated output) drops "from any past major" and "arriving several majors late is the designed-for case" and derives the real supported-lateness sentence from MIGRATION_SUPPORT_FLOOR/PROTOCOL_MAJOR; regenerated docs/protocol-upgrade-guide.md with the repo's own gen:upgrade-guide. - content/docs/upgrading.mdx: `--to 16` is an empty-range invocation for every legal `--from` now (floor >= 16); corrected to `--to 17`, an actual intermediate stop (CLI default `--to` is 18). - .changeset/19056-migration-support-floor-16.md: ALL_CONVERSIONS is 97 at runtime, not 99 (a runtime count, not the unanchored `toMajor:` grep this card warns against) — "10 of the 99" corrected to "10 of the 97". - packages/spec/src/conversions/registry.ts, migrations/{index,types}.ts: recommended-tier fix — the 3 of 5 below-floor retiredFromLoadPath docblocks that claimed migrate-meta/chain reachability now say what is true (no migration step carries them below the floor; only the not-floor-scoped stored-row replay still walks them). Co-Authored-By: Claude <noreply@anthropic.com>
…gration-support-floor-16
…n/main The origin/main merge (79d709d) brought in a new conversion registered at protocol 18 (dashboard-widget-chart-config-structure-removed, #19363), moving ALL_CONVERSIONS from 97 to 98 at this head. Re-counted at runtime post-merge; the below-floor count (10) is unaffected since the new entry is above the floor. Co-Authored-By: Claude <noreply@anthropic.com>
Rework landed on the branch — gate re-hung, a fresh at-tier review is owed, 2026-09-20T15:32ZVerified against GitHub rather than the implementer's narration.
Why both carriers now carry
|
Contract reviewServed-tier: Isolated adversarial contract-review subagent, third round. Reviewed in a detached scratch worktree at the head, fetched into a ref of my own; merge-base and GitHub ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS All three grounds of Generated by Claude Code |
Landing provenance — contract review PASS, preconditions measured, 2026-09-20T18:04ZThe at-tier contract review of head
Distinct kinds, so no SELF-REVIEW. Both carriers' The three landing preconditions, measured first-hand at 2026-09-20T18:04Z
A seat reading that differs from the record's, recorded beside it rather than inside itThe record rules Control: the same anchored pattern finds 10 changed ⛔ This does not void the record and is not asserted as a FAIL ground. The record's own attribution is defensible and 「作废的门槛是核验失败,不是席位判得不同」 — the parent session adopts verbatim or voids wholesale, and ⛔ never rewrites. It is logged so the next reader inherits both readings rather than one. Owed and not filed — this session cannot create issues (#19362)The record names three cards as still nonexistent as of its run: the Tier H Also carried forward from the record and not blocking: two Flipping to ready and arming the queue on the readings above. ⛔ No approval was sought or given; this PR is not a governed surface, so none is owed. Generated by Claude Code |
Fixes #19056
Clause-②: yes (narrowing)
Maintainer ruling, 2026-09-18, verbatim and untranslated:
MIGRATION_SUPPORT_FLOOR(packages/spec/src/migrations/registry.ts) moves 10 to 16, andstep11–step16retire with it. The direction was not re-argued. 「16.0」 reads as protocol major 16 — the same unit as the constant, sincePROTOCOL_VERSIONis17.0.0while the package version is17.4.0.What landed
MIGRATION_SUPPORT_FLOOR10to16step11–step16and theirMIGRATIONS_BY_MAJORregistrationsentries/semantic/files prefixed11. 12. 13. 15. 16.gen:migration-registryre-emitted the marked regions — never hand-edited between the markersRETIRED_KEYS_BY_MAJOR/RETIRED_DEFS_BY_MAJORRegenerated and committed:
spec-changes.jsonanddocs/protocol-upgrade-guide.md. (api-surface-declarations/root.txtwas regenerated at the FIRST head;mainthen deleted that whole directory in2277d1fcd1(#19024), so this head takes main's deletion and the roster is 15 artifacts.)pnpm --filter @objectstack/spec check:generatedproved exactly those three stale and--fixregenerated only those three; the other 13 artifacts,check:authorable-surfaceincluded, were green throughout.The starred undecided item: both retirement tables are KEPT, and here is the proof
The card asked whether
RETIRED_KEYS_BY_MAJOR/RETIRED_DEFS_BY_MAJORserve only the migration chain, or are also read as an independent retirement fact. They are read as an independent fact, and the reading does not depend on the floor. Three measurements:Neither table has a row under 11–16 at all. Both literals carry exactly two keys,
17and18; theentries/retired-keys/andentries/retired-defs/directories hold only17.*and18.*files (195 and 181 of them). The card's change-table row "rows 11–16" describes rows that do not exist, so there was nothing to delete even before the question of whether it would be safe.Structural.
chain.ts— the floor's only enforcement point — importsMIGRATIONS_BY_MAJOR,MIGRATION_MAJORSandMIGRATION_SUPPORT_FLOOR, and neither table. The tables' only non-test importer ispackages/spec/scripts/build-schemas.ts:116(check:authorable-surface), which foldsObject.entries(...)across every major into one set and never mentionsMIGRATION_SUPPORT_FLOORat all. The major is kept only to date a tombstone's aging clock.Ablation (mutation and restore both proved on disk by
scripts/ablation-replace.mjs). A row naming a live key was planted under major 11 — a major whose migration step this PR deletes — andcheck:authorable-surfacestill read it and judged it:Control: the same gate is green on the unmutated tree. Restore leg:
blob == HEAD (d4cb483a4fc5)andgit diff HEADempty.So a row below the floor is still the live proof that its retirement was declared, and dropping one errors nowhere at the moment it is dropped — exactly the silent loss #6957 measured. Both facts are now pinned in
packages/spec/src/migrations/retired-tables-not-floor-scoped.test.tsso the next floor move reads them before reaching for the delete key.The D2 conversion registry is untouched for the same class of reason: every rehydration seam replays the full conversion chain over stored
sys_metadatarows, retired entries included, so the protocol-11/13/14/15 conversions keep converting rows at rest long after the source-side chain stops reaching them. What the floor removed is the D3 step that carried them — which is why they leave the chain-replay gate and nothing else. The test says so where a future reader will look.This is not a slimming change
The card corrects its own filer and this PR keeps that correction. Measured on
src/migrations/registry.tsate6a03e649(17,718 lines):step11–step16— what leavesstep17step18The value is a narrowed support promise: six permanently-replayable chains no longer have to be maintained, and the CI replay shrinks to the range the project actually promises — 10 of the 99 conversion fixtures leave the chain-replay gate because the chain no longer reaches the major that graduated them.
Cost, and where it is written down
MIGRATION_SUPPORT_FLOORis a published export (migrations/index.ts:23). After the raiseapplyMetaMigrations(doc, N)throwsMigrationFloorErrorfor N in 10..15, so a consumer stopped at protocol 10–15 loses the one-command upgrade path; the remedy is to reach protocol 16 by another path first, which is what the refusal message already says. The literal TYPE of the constant also narrows from10to16.The changeset is
minor, notmajor— this is the repo's own convention, not a judgement call about severity:scripts/check-changeset-no-major.mjsrefuses amajorbump for the duration of the launch window (every publishable package is in one Changesetsfixedgroup, so onemajorpromotes the whole stack), and it names the two mandatory carriers that replace the bump level meanwhile — the**BREAKING**banner and the ADR-0087 disposition. Both are in the changeset. Measured:check-adr-0087-registration.mjsreads it as[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription)and exits 0.One deliberate out-of-surface fix
packages/cli/src/commands/migrate/meta.tsadvertised four examples —--from 10,--from 10 --step,--from 11 --to 12,--from 10 --out …— and this change makes every one of them refuse. That is a published defect this PR creates, in help text that ships in@objectstack/cli, so it is fixed here rather than filed. The examples are now derived fromMIGRATION_SUPPORT_FLOOR, which closes the class instead of the instance: the next floor move cannot leave them advertising commands that throw.packages/spec/scripts/build-upgrade-guide.tscarried the same hard-coded--from 10and is derived the same way.Tests
pnpm --filter @objectstack/spec test— 500 files, 14,639 tests, all pass (1 file / 1 test skipped, pre-existing).pnpm --filter @objectstack/spec typecheck— Done, green (tsc, scripts tsconfig, and the test-layer debt gate).pnpm --filter @objectstack/spec check:generated— 15 artifacts, all current. (15, not 16:check:api-surface-declarationsleft the roster whenmaindeleted the directory in2277d1fcd1.)check:migration-registry,check:authorable-surface,check:api-surface,check:api-surface-declarations,check:spec-changes,check:upgrade-guide,check:docs,check:published-files,check:cross-package-test-inputs,check:test-source-alias,check:type-check-coverage,check:nul-bytes,check:merge-driver,check:spec-parsed-alias,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,check-closing-keyword-parity,check-undeclared-dep-imports,check-ci-filter-parity,check-spec-docblock-symbol-anchors,check-comment-mask-adoption,check-keyed-text-bounds,docs-audit/check-affected-docs.packages/clitypecheck andcheck:type-check-debtboth need the whole workspacedist/closure built first. The debt gate says so itself and refuses with its own exit code 3 —PREREQUISITE NOT MET, explicitly "NOT a pass and NOT a finding". The closure build did not get a turn on this container's shared verify lock (exit 99, queue timeout after 9 minutes). The one error the cli typecheck reports inside the edited file isTS2307 Cannot find module '@objectstack/metadata-protocol'atmeta.ts:709— an unbuilt-closure symptom on a line this PR does not touch, alongside 230 more of the same code across the package. CI builds the closure before this step and is the authority here.Test changes are re-pointings, not deletions: the replay fixtures, the composability gate and the manifest-composition cases all now read
MIGRATION_SUPPORT_FLOORrather than the literal10, so the next floor move re-points them instead of inviting another delete. Four assertions were added where the old ones could not see the move: a step at or below the floor is dead code and must not survive;--from floormust be a usable command (the floor+1 hop has to exist); every major the raise dropped is refused by name, withfromMajor,floorand the message; and the chain-replay gate states why a below-floor conversion leaves it, with an anti-vacuity case so the filter cannot empty the gate silently.Acceptance notes
Out of scope for this PR, recorded rather than fixed:
skills/objectstack-upgrade/SKILL.md:103advertisesos migrate meta --from 10, which refuses after this change.skills/**is a Tier H governed surface and one governed path forks the whole PR to Tier H, so fixing it here would hold this diff for the maintainer's hand. Deliberately left; a one-line docs-only change. This is a real conflict between two binding rules (fix what this change falsifies, versus do not fork a code PR to Tier H) and it is named here rather than quietly resolved.packages/metadata-core/src/protocol-handshake.ts:252buildsobjectstack migrate meta --from TARGETfrom the incompatible package's own declared target major, with no clamp to the floor, so a package targeting a major below the floor is handed a command that throws. Pre-existing — its own test already pins a--from 6answer — and widened from "below 10" to "below 16" by this change. Reproducible defect; worth a card.step18. It lists 12,041 lines; measured, thestep18block is 7,565. The 12,041 figure runs fromstep18to end of file and therefore absorbsMIGRATIONS_BY_MAJORplus both retirement tables — about 5,077 lines, i.e. precisely the tables the same card says must not be deleted. The card's conclusion is unaffected and stands. Noted, not filed.docs/adr/0087-...md:181,426andcontent/docs/releases/v15.mdx:520narratemigrate meta --from 10as history. Both are correct as history and both are on surfaces a code PR must not edit (governed; release-owned). Noted, not filed. Carrier: none — no open PR touches either file.Generated by Claude Code