Skip to content

chore(spec)!: raise MIGRATION_SUPPORT_FLOOR from 10 to 16 — upgrades are supported from protocol 16 onward - #19302

Merged
os-steve merged 11 commits into
mainfrom
claude/issue-19056-migration-support-floor-16
Sep 20, 2026
Merged

os-steve merged 11 commits into
mainfrom
claude/issue-19056-migration-support-floor-16

Conversation

@os-steve

@os-steve os-steve commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes #19056

Clause-②: yes (narrowing)

Maintainer ruling, 2026-09-18, verbatim and untranslated:

升级只需要支持从 16.0版本开始。

MIGRATION_SUPPORT_FLOOR (packages/spec/src/migrations/registry.ts) moves 10 to 16, and step11step16 retire with it. The direction was not re-argued. 「16.0」 reads as protocol major 16 — the same unit as the constant, since PROTOCOL_VERSION is 17.0.0 while the package version is 17.4.0.

What landed

item disposition
MIGRATION_SUPPORT_FLOOR 10 to 16
step11step16 and their MIGRATIONS_BY_MAJOR registrations deleted (328 lines)
the 10 entries/semantic/ files prefixed 11. 12. 13. 15. 16. deleted, then gen:migration-registry re-emitted the marked regions — never hand-edited between the markers
replay fixtures below 16 re-pointed at the oldest hop the chain still guarantees, floor to floor+1
RETIRED_KEYS_BY_MAJOR / RETIRED_DEFS_BY_MAJOR kept whole — see below

Regenerated and committed: spec-changes.json and docs/protocol-upgrade-guide.md. (api-surface-declarations/root.txt was regenerated at the FIRST head; main then deleted that whole directory in 2277d1fcd1 (#19024), so this head takes main's deletion and the roster is 15 artifacts.) pnpm --filter @objectstack/spec check:generated proved exactly those three stale and --fix regenerated only those three; the other 13 artifacts, check:authorable-surface included, 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_MAJOR serve 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:

  1. Neither table has a row under 11–16 at all. Both literals carry exactly two keys, 17 and 18; the entries/retired-keys/ and entries/retired-defs/ directories hold only 17.* and 18.* 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.

  2. Structural. chain.ts — the floor's only enforcement point — imports MIGRATIONS_BY_MAJOR, MIGRATION_MAJORS and MIGRATION_SUPPORT_FLOOR, and neither table. The tables' only non-test importer is packages/spec/scripts/build-schemas.ts:116 (check:authorable-surface), which folds Object.entries(...) across every major into one set and never mentions MIGRATION_SUPPORT_FLOOR at all. The major is kept only to date a tombstone's aging clock.

  3. 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 — and check:authorable-surface still read it and judged it:

    ❌ 1 RETIRED_KEYS_BY_MAJOR entr(ies) name a key that is still LIVE:
         - data/Object:access  (registered at major 11)
    

    Control: the same gate is green on the unmutated tree. Restore leg: blob == HEAD (d4cb483a4fc5) and git diff HEAD empty.

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.ts so 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_metadata rows, 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.ts at e6a03e649 (17,718 lines):

block lines share
step11step16 — what leaves 328 1.9%
step17 4,699 26.5%
step18 7,565 42.7%
registration map plus the two retirement tables 5,077 28.7%
file header 49 0.3%

The 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_FLOOR is a published export (migrations/index.ts:23). After the raise applyMetaMigrations(doc, N) throws MigrationFloorError for 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 from 10 to 16.

The changeset is minor, not major — this is the repo's own convention, not a judgement call about severity: scripts/check-changeset-no-major.mjs refuses a major bump for the duration of the launch window (every publishable package is in one Changesets fixed group, so one major promotes 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.mjs reads 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.ts advertised 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 from MIGRATION_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.ts carried the same hard-coded --from 10 and is derived the same way.

Tests

  • pnpm --filter @objectstack/spec test500 files, 14,639 tests, all pass (1 file / 1 test skipped, pre-existing).
  • pnpm --filter @objectstack/spec typecheckDone, green (tsc, scripts tsconfig, and the test-layer debt gate).
  • pnpm --filter @objectstack/spec check:generated15 artifacts, all current. (15, not 16: check:api-surface-declarations left the roster when main deleted the directory in 2277d1fcd1.)
  • Gates green: 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.
  • NOT MEASURED, with reasons: packages/cli typecheck and check:type-check-debt both need the whole workspace dist/ 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 is TS2307 Cannot find module '@objectstack/metadata-protocol' at meta.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_FLOOR rather than the literal 10, 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 floor must be a usable command (the floor+1 hop has to exist); every major the raise dropped is refused by name, with fromMajor, floor and 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:103 advertises os 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:252 builds objectstack migrate meta --from TARGET from 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 6 answer — and widened from "below 10" to "below 16" by this change. Reproducible defect; worth a card.
  • The card's blast-radius table over-counts step18. It lists 12,041 lines; measured, the step18 block is 7,565. The 12,041 figure runs from step18 to end of file and therefore absorbs MIGRATIONS_BY_MAJOR plus 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,426 and content/docs/releases/v15.mdx:520 narrate migrate meta --from 10 as 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.

Seat correction, 2026-09-20T13:53Z — two claims above were true at the FIRST head and are false at this one. Both are edited in place: the regenerated-file list and the check:generated artifact count. The cause is outside this PR — main deleted packages/spec/api-surface-declarations/ in 2277d1fcd1 (#19024, ruling C) at 12:52Z, which took that gate off the roster.

The implementer flagged both rather than PATCHing this body itself: its contract writes a PR body once, on the call that opens the PR, and ⛔ never edits it. That is the correct division, and this edit is the seat's half of it.

⚠️ One requirement of the contract review (5749969134, ①-7) was MEASURED FALSE and deliberately not carried out. The review asked that api-surface-signatures.json be regenerated "in its restored hash form … because your 1016 literal moves a declaration signature". It does not: that artifact records a hash of each defineX factory signature (27 of them), and MIGRATION_SUPPORT_FLOOR is a plain const, so it is not in that artifact at all. The snapshot that WOULD have carried it is api-surface-declarations/root.txt — the very file 2277d1fcd1 removed. The implementer ran the generator anyway rather than argue from reading: gen:api-surface exit 0, git status --porcelain empty — identical bytes. ⇒ the prediction was about an artifact this repository no longer has.

⚠️ The review also said rebase; the implementer merged instead and said so, citing AGENTS.md (⛔ no rebase or force-update of a pushed, reviewed branch) and scripts/pm/os-regen-merge.sh, whose own header prescribes git merge origin/main and ⛔ never rebase / force-push — because the os-regen merge driver can drop one side at exit 0 and only that script's ORDER exposes it. The seat accepts both departures: each is measured, named, and backed by in-repo authority.


Generated by Claude Code

@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Sep 20, 2026
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/spec/spec-changes.json, packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/index.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

22 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 4b58dcf96b34b83d0c6863d837b4d947234552ef.

9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/spec-changes.json, packages/spec/src/conversions/registry.ts, packages/spec/src/migrations/index.ts, …) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4b58dcf96b34b83d0c6863d837b4d947234552efpackageMentionDocs.

Which tree this was computed on

This run read content/docs from b923021180553e4b05af629903c7aae3cf5f9e46 — the merge of head d86cf0653b42132be030fc6136c764219cffc9f5 into base 4b58dcf96b34b83d0c6863d837b4d947234552ef, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4b58dcf96b34b83d0c6863d837b4d947234552ef → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4405b10fbf45096278b0702a4eb519a4da884b7b

Isolated at-tier contract-review subagent. Reviewed against merge-base e6a03e649154252740b8e3534804e7abc453fb5c; PR base at review time 56366419e5; origin/main at 2277d1fcd1. Sources: card #19056 (body, comments 5749891889 and 5749905145), PR #19302 (body, diff, 5 commits, 39 check runs), the tree at the head in a scratch worktree, and the repo's own gates re-run there. Every conclusion handed down was treated as a claim to falsify; the direction (maintainer ruling, 升级只需要支持从 16.0版本开始) was not re-argued.

① Derived judgments

  1. Floor 10 to 16 — correct. registry.ts:65 MIGRATION_SUPPORT_FLOOR = 16; MIGRATIONS_BY_MAJOR carries exactly 17 and 18; chain.ts has no diff, so applyMetaMigrations(doc, N) throws MigrationFloorError for N in 10..15 and the 16 to 17 and 17 to 18 hops still compose. Correct.
  2. step11–step16 gone, nothing else in the chain moved. Base lines 62–393 (332 lines with separators; the dev's 328 counts bodies) deleted with their two registrations; migrations/index.ts, chain.ts, conversions/registry.ts, build-schemas.ts, build-spec-changes.ts carry no diff. ALL_CONVERSIONS is intact and the chain-replay gate now filters on toMajor > floor with an anti-vacuity case and a "below-floor conversion is excluded, not deregistered" case — verified in the migrations.test.ts diff. Correct.
  3. The 10 entry files and the generated regions — the tool's output, not hand edits. Deleted at head: semantic 11.* x2, 12.* x1, 13.* x5, 15.* x1, 16.* x1 = 10 (major 14 had none). I re-ran gen:migration-registry on the head tree: it wrote 221 semantic, 195 retired-key, 181 retired-def and git status stayed empty, so every marked region is byte-for-byte the generator's; the diff touches os-generated markers only by removing the whole semantic:11..16 regions. check:generated on the head reports migration-registry, spec-changes, upgrade-guide and authorable-surface current (its two api-surface reds in my worktree read "dist holds no .d.ts", an unbuilt closure; CI's Lint and Repo Gates, which builds first, is green, and the root.txt diff is exactly the one-line 10 to 16). Correct.
  4. ⭐ Retirement tables kept whole, and the card's premise was wrong — verified independently. Counted at head AND at the merge-base: entries/retired-keys/ = 29 17.* + 166 18.* = 195; entries/retired-defs/ = 53 17.* + 128 18.* = 181; no file under any other major. Both literals carry exactly two top-level keys, 17: and 18:, at head (lines 12471/12706 and 15738/15879) and at base (12788/13023 and 16055/16196). There were never rows under 11–16 and nothing left either table. Structural claim verified: chain.ts:18 imports MIGRATIONS_BY_MAJOR, MIGRATION_MAJORS, MIGRATION_SUPPORT_FLOOR and neither table; the only non-test source importer is packages/spec/scripts/build-schemas.ts:116, which folds Object.entries(RETIRED_KEYS_BY_MAJOR) at :939 and Object.entries(RETIRED_DEFS_BY_MAJOR) at :2527 across every major and never names the floor (every other hit in the tree is a comment or a test). Ablation re-run by me, not taken from the dev: planted 11: ['data/Object:access'] as the first row of RETIRED_KEYS_BY_MAJOR at the head and ran check:authorable-surface — exit 1, 1 RETIRED_KEYS_BY_MAJOR entr(ies) name a key that is still LIVE: - data/Object:access (registered at major 11); restored with git checkout, diff empty; the unmutated head is green on the same gate. A row under a major whose step no longer exists is still read as the retirement fact, and the set-fold means a dropped row raises nothing at the moment it is dropped — the Protocol-17 retirement cards all append to the same two registries, so they serialize badly under the merge queue — three consecutive re-merge laps measured on one PR #6957 shape. Keeping both tables is right, and retired-tables-not-floor-scoped.test.ts pins exactly the two structural facts I measured. Correct.
  5. A consumer this PR breaks was missed, and it is red on the head. packages/cli/test/migrate-meta-default-range.test.ts:224 spawns the real CLI with ['--from', '13', '--to', '14'] and asserts exit 0. At the head that is MigrationFloorError, exit 1: Test Core (6/6) is red (AssertionError: expected 1 to be +0, 1 failed / 1644 passed in @objectstack/cli, run 35505485155 job 106064491087), and the Test Core rollup with it. The base sha 56366419e5 and the merge-base e6a03e6 are both green on that shard (runs 35504288904, 35501495336), so no same-signature red excuses it. The dev's report ran cli's unit project only; this file is the integration project and its own docblock says it is queue-tier so the merge queue's required set protects it. ⚠️ Not a number swap: the case relies on step 14 having no semantic entries; with the floor at 16 the only hops are 17 and 18, both carrying semantic entries, so "a range that HAS steps and rewrote nothing prints Nothing to migrate" may no longer be reachable from the CLI at all. The dev has to decide whether that behaviour is still testable and pin it honestly, not re-point 13 to 16.
  6. Published examples this PR falsifies — the sweep was by the literal --from 10 and missed the rest. content/docs/upgrading.mdx:185 and :268 (os migrate meta --from 15) and the table at :197–199 (--from 15, --from 14, --from 12) are the live upgrade guide, not history, and content/docs/** is not on the governed register — the same class the PR fixed in meta.ts help text and build-upgrade-guide.ts, on a surface this PR can edit. Belongs in this PR. packages/services/service-package/src/hydration.test.ts:95 pins --from 10 from the handshake (same class as the handshake flag below; belongs on that card).
  7. Mergeability. mergeable_state: dirtygit merge-tree reports a modify/delete conflict on packages/spec/api-surface-declarations/root.txt: deleted on main by 2277d1fcd1 (revert(spec): take back the declaration-text snapshot, restore the 27 signature hashes #19024 revert, back to the 27 signature hashes), modified here. Rebase, then regenerate the api-surface artifact in its restored hash form (the 10 to 16 literal changes a declaration signature). Mechanical, not a contract issue.

② Semver level

@objectstack/spec: minor + chore(spec)!: bang + **BREAKING** banner + Clause-②: yes (narrowing) + adr-0087: not-required (no-migration-prescription); @objectstack/cli: patch. Agreed. The change IS breaking: public export (migrations/index.ts:23), accept-range narrowing of applyMetaMigrations's fromMajor, literal type 10 to 16. check-changeset-no-major.mjs refuses major for the launch window and names the banner plus the ADR-0087 disposition as the substitute carriers; the WHICH LEVEL rule (pr-automation.yml:753) makes the act, not the commit type, decide, and a narrowing on a published export is at least minor. All four breaking signals the ADR-0087 gate reads (bang, banner, narrowing arm; major forbidden) are present. The not-required (no-migration-prescription) disposition holds on the merits, not only mechanically: no authorable metadata key, def or field moves, so no D2 conversion or D3 entry could carry "reach protocol 16 by another path"; the body carries no source-rewrite prescription in the detector's sense (its before/after table describes behaviour, the literal-type note is advisory); runtime-interface-only would not apply because the symbol is referenced in code. CI Check Changeset is green on the head. cli: patch is right — help text only, no public surface.

③ Boundary flags

  • Rule conflict at skills/objectstack-upgrade/SKILL.md:103 — the dev's call (option A, a separate Tier H docs PR) is right, and a PR is landable with that example stale, on two conditions. skills/** is Tier H (AGENTS.md Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14, :255–269: one governed path makes the whole PR Tier H); the ruling that stales the example is the maintainer's own and only the maintainer's hand can move that file, so folding it in would hold an ordinary code change behind a Tier H approval for one line. Conditions: (a) the follow-up exists as a card or PR before this lands, not a "noted" line; (b) it is more than one line — :93 also derives --from 15 from a >=15 lower bound, and :103's sentence "there is no penalty for lateness" is now a false claim, not a stale number. The non-governed content/docs/upgrading.mdx examples (①-6) have no such excuse and belong in THIS PR.
  • protocol-handshake.ts:252 widened, not fixed — acceptable here. Pre-existing (--from 6 was already a throwing command at floor 10 and its test pins it), in another package, and the right fix is a design choice, not a clamp: a clamped --from 16 handed to a package targeting 12 would silently skip steps, so the honest output below the floor is a refusal that names the floor. That is its own card; condition: the card is filed (the report says "to file" and no number exists yet). Add hydration.test.ts:95 to it.
  • Blast-radius correction — spot-checked, holds. At e6a03e6 (17,718 lines) const step18 opens at 5110 and MIGRATIONS_BY_MAJOR at 12643: step18 is about 7,533 lines by my cut (the dev's 7,565 differs by the docblock boundary); the map plus both retirement tables, 12643 to end, is 5,076. The card's 12,041 runs step18 to end-of-file and swallows the very tables the card protects. The card's conclusion (not a slimming card) stands.
  • noted, not filed entries. ADR-0087 :181,426 and releases/v15.mdx:520 narrate history on Tier H and release-owned surfaces — agree, no carrier owed. build-upgrade-guide.ts has no test file, so the gate-script suite obligation is empty — agree (check:upgrade-guide green here and in CI). Not accepted as noted-only: content/docs/upgrading.mdx (①-6), which was not in the list.
  • Check runs at the head: every run green except Test Core (6/6) and its Test Core rollup, red on the PR-introduced failure in ①-5. The 12:49 label-triggered runs are green. The same-signature merge-base exception does not apply.
  • Served-tier reading taken from this reviewer's transcript harness per-request model stamp; it is the at-tier constant named above and no fallback notice appeared during the review. Scratch under the session scratchpad pr-19302/ (worktree, ablation and gate logs).

Implemented-by: claude/issue-19056-migration-support-floor-16
Reviewed-by: session_SEATSESSIONID

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 upgrading.mdx examples derived or corrected, rebase plus api-surface regeneration, and the two cards (Tier H skill follow-up; handshake below-floor command) filed.


Generated by Claude Code

…gration-support-floor-16

# Conflicts:
#	packages/spec/api-surface-declarations/root.txt
… of a retired hop

Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e894b0917480d4761fb7a1fca6bca5e7907383ce

Isolated adversarial contract-review subagent, second round. Reviewed the head in a detached scratch worktree cut from the fetched PR ref; PR base 2277d1fcd1 (also the merge-base, since the head merges it); origin/main at 1a2bb9ec2d at review time (11 commits past base, none touching a path this PR changes). Read first-hand: card #19056 (body + 4 comments), PR #19302 (body including the seat correction, both comments, 8 commits, 42 check runs, the full 20-file diff), AGENTS.md :255-290 / :471 / :487 / :681-700, and the governed register in scripts/pm/check-governed-merges.mjs. Every prior verdict was treated as a claim to measure; two of the prior round's requirements are falsified below. Exit codes were captured before any pipe; every zero reading has a firing control.

① Derived judgments

  1. Floor 10 to 16 — correct. registry.ts:65 MIGRATION_SUPPORT_FLOOR = 16; MIGRATION_MAJORS is [17, 18], PROTOCOL_MAJOR 17; chain.ts carries no diff, so applyMetaMigrations(doc, N) throws MigrationFloorError for N in 10..15 and the 16-to-17 and 17-to-18 hops compose. Measured by running src/migrations on the head (3 files, 143/143 pass), which includes the new by-name refusal case for 10..15.

  2. step11 to step16 deleted, nothing else in the chain moved — correct. The diff removes the six step literals and their six MIGRATIONS_BY_MAJOR rows and touches no other step; chain.ts, conversions/registry.ts, build-schemas.ts, build-spec-changes.ts carry no diff. Ablation B, run by me: floor set to 15 (no step 16 exists) turns 6 cases red in migrations.test.ts, first among them "--from floor is a usable command — the floor+1 hop exists"; restored, git diff empty. The registry-integrity pins are live, not decorative.

  3. Entry files and generated regions — the generator's bytes. 10 entries/semantic/ files deleted (11.x2, 12.x1, 13.x5, 15.x1, 16.x1; major 14 had none). On the head: check:migration-registry exit 0 ("221 semantic, 195 retired-key, 181 retired-def"), check:spec-changes exit 0, check:upgrade-guide exit 0. Correct.

  4. Retirement tables kept whole — correct, re-measured. At head both literals carry exactly 17: and 18: (registry.ts:12471/12706 and :15738/15879); entries/retired-keys/ = 29 + 166 files, entries/retired-defs/ = 53 + 128; no file under any other major. Ablation A, run by me on the head tree: planting 11: ['data/Object:access'] as the first row of RETIRED_KEYS_BY_MAJOR makes check:authorable-surface exit 1 with "1 RETIRED_KEYS_BY_MAJOR entr(ies) name a key that is still LIVE: data/Object:access (registered at major 11)"; control: the same gate exits 0 on the unmutated head; restored, diff empty. retired-tables-not-floor-scoped.test.ts pins the two structural facts that make this so. The card's "rows 11-16" never existed; the dev's reading is upheld.

  5. The re-pinned CLI integration case (prior ①-5) — correct, and not a restatement of the floor. SEMANTIC_FREE_HOP is the first m in MIGRATION_MAJORS whose m - 1 is at or above the floor and whose semantic list is empty; "m - 1 at or above the floor" is exactly the condition under which hop m is reachable by a legal --from, so the derivation reads the registry, not the constant. At head step 17 carries 77 semantic entries and step 18 carries 144 (measured), so the hop is undefined and the spawned success-sentence case is skipped — declared by the always-run sibling that asserts not.toContain(0) over the reachable hops' semantic counts, which makes the skip a measurement rather than a hole. Head run of the file (--project integration, dependency closure built): 7 passed, 1 skipped, exit 0. Ablation C, run by me: meta.ts:479 changed to choose the empty branch on applied.length === 0 alone (the os migrate meta --from 17 — the invocation the spec 17 tombstones prescribe — reports Nothing to migrate for the conversions it is meant to list, because --to defaults to the current major and the conversions are toMajor: 18 #17134 shape — a range with real todos mislabelled canonical): exactly the new always-run case "a canonical stack over the oldest SUPPORTED range is not answered as an empty range" goes red (1 failed / 6 passed / 1 skipped) on the missing "manual change(s) require your judgment" line; restored, diff empty. Two limits, stated: the CLI case alone would not notice a floor whose +1 hop is missing (its --to is derived as the next major that has a step) — that property is held by the spec-side pin in ①-2; and nothing spawns the CLI over the success sentence while no reachable hop is semantic-free, which the file says. Accepted: the input class that reached that sentence was retired by the ruling, and re-pointing 13 to 16 would have pinned the other branch under this one's name.

  6. Doc sweep by meaning — WRONG on this PR's own surface. This is the FAIL ground. The dev's second-head report says the sweep was "redone by meaning"; measured, it was four tokens (migrate meta, support floor, chain floor, reaches back to protocol) over content/docs only. Two published sites this change falsifies remain, both on non-governed files this PR already edits:

    • 6a. packages/spec/scripts/build-upgrade-guide.ts:44 and :56-59, emitted as docs/protocol-upgrade-guide.md:8 and :14: the H2 "How to upgrade — from any past major" and the sentence "Arriving several majors late is the designed-for case: timeliness is never load-bearing (ADR-0087)". At floor 16 with PROTOCOL_MAJOR 17 the supported lateness is exactly one major: --from 15 (two late) and --from 12 ("any past major") throw MigrationFloorError, which the new test in ①-1 proves by name. This is the identical claim the prior record ruled "a false claim, not a stale number" at SKILL.md:103, except this copy sits in a generator whose :48-51 hunk this PR edits three lines below it, and the PR regenerated the guide with the claim intact. packages/spec/scripts is the card's own file surface, and docs/protocol-upgrade-guide.md is not in the governed register (only docs/adr/** and docs/NORTH-STAR.md are).
    • 6b. content/docs/upgrading.mdx:218, flag table: "--to 16 | Stop at an intermediate major instead of this runtime's". Every legal --from is now 16 or above, so --to 16 composes zero steps for every input and answers the empty-range warning "No migration step exists for protocol 16 → 16" — the example that demonstrated an intermediate stop now demonstrates the os migrate meta --from 17 — the invocation the spec 17 tombstones prescribe — reports Nothing to migrate for the conversions it is meant to list, because --to defaults to the current major and the conversions are toMajor: 18 #17134 warning. In the file this PR swept in b7462a5ffc87; on this build the honest example is --to 17 (the CLI defaults --to to 18 here, per the same test file's TERMINUS derivation).
      Same class, lower severity, in the PR's own directory — recommended in the same push, not a ground on their own: migrations/types.ts:10-14 ("A consumer that slept through four majors runs … and replays every step in one command … timeliness is never load-bearing" — ships in the .d.ts), migrations/index.ts:6-7 ("carries any past major's metadata to current in one command"), conversions/registry.ts:201 ("the entry exists so migrate meta --from 10|11 rewrites old sources"). On that last one, a fact the PR's narration does not contain: five retiredFromLoadPath conversions are now carried by NO migration step (measured: object-compactLayout-to-highlightFields@11, stack-roles-to-positions@13, owd-legacy-read-aliases@13, sharing-recipient-role-to-position@13, book-audience-profile-to-permission-set@14). Their only remaining reader is the stored-row replay the body argues from, and their docblocks still state the opposite. Not a code defect — the D2 registry is deliberately not floor-scoped and conversions.test.ts:317 still passes — a stated-reason defect.
      Verified correct on the same sweep: every other content/docs invocation found says --from 16 or --from 17; every os migrate meta --from N prescription in packages/spec/src non-test source is --from 16 (137) or --from 17 (61), so 16 is precisely the highest floor that keeps every shipped tombstone prescription valid; the MigrationFloorError text; spec-changes.json supportFloor / migrateCommand; upgrading.mdx:185/196/198/275 and the added Callout, whose "an older @objectstack/cli still carries those steps" is true of every release before this lands.
  7. Merge, not rebase (prior ①-7, first half) — correct as done. Measured: between merge-base e6a03e649 and base 2277d1fcd1 main changed none of the PR's 20 files (git diff --stat over that list is empty); the merge fa10b346e9 against base is the PR's 19-file diff minus api-surface-declarations/root.txt (18 files, 330+/989-), i.e. exactly main's deletion and nothing else dropped; head against base is 20 files, all the PR's own. No api-surface-declarations reference survives at head outside four historical changesets; the check:generated roster at head is 15 rows (counted in scripts/check-generated.ts). git merge-tree --write-tree origin/main head with main at 1a2bb9ec2d exits 0 with no conflict; GitHub reads mergeable: true, clean. Merging rather than rebasing a pushed, reviewed branch is what AGENTS.md :471 and :487 prescribe; the departure from the prior record's "rebase" is upheld.

  8. api-surface-signatures.json (prior ①-7, second half) — the prior requirement was FALSE, measured two ways. The artifact holds exactly 27 keys, defineAction through defineWebhook, each a 16-hex sha256: value, and nothing else. The generator's own contract (build-api-surface.ts:13 and :25): api-surface/entry.json is "one name (kind) row per declared kind", api-surface-signatures.json is "a stable hash of each defineX factory's resolved signature", scoped to the factories. MIGRATION_SUPPORT_FLOOR appears in the api-surface artifacts only as the row "MIGRATION_SUPPORT_FLOOR (const)" at api-surface/root.json:95, which carries no value. No api-surface artifact can move on a const literal; the one snapshot that could — api-surface-declarations/root.txt — is the file main deleted. CI TypeScript Type Check (the job that runs check:generated, per AGENTS.md's "Touched packages/spec?" section) is green at this head. The dev's refusal to carry out that instruction is upheld, and the seat correction on the body is accurate.

  9. meta.ts help examples and build-upgrade-guide.ts:48-51 derived from the floor — correct. The four --from 10/11 examples would all refuse; the derivation closes the class. @objectstack/cli help text only, no public surface moves.

  10. "10 of the 99 conversion fixtures leave the chain-replay gate" — WRONG number, in the PR body and in the changeset (.changeset/19056-migration-support-floor-16.md:67-68). ALL_CONVERSIONS is 97: a runtime count at head (tsx over conversions/registry.ts) and 97 toMajor: rows at merge-base, base and head alike. Ten leave (the ten named in ①-6). The changeset compiles into the release-owned CHANGELOG.md, so the figure ships unless fixed here.

② Semver level

@objectstack/spec: minor + chore(spec)!: + **BREAKING** banner + Clause-②: yes (narrowing) + adr-0087: not-required (no-migration-prescription); @objectstack/cli: patch. Agreed with the declared level, and the act is breaking. A published export (migrations/index.ts:23) narrows the accepted fromMajor range of applyMetaMigrations, and its literal type moves 10 to 16; that is at least minor under the WHICH-LEVEL rule and would be major outside the launch window. Measured on the head tree: check-changeset-no-major.mjs exit 0 ("introduces no major bump", diffing from 2277d1fcd), check-adr-0087-registration.mjs --base 2277d1fcd1 exit 0 reading [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription), check-empty-changeset.mjs exit 0. The not-required disposition holds on the merits: no authorable key, def or field moves, so there is no D2 conversion or D3 entry for a floor move to carry, and the one instruction a stopped consumer needs is the MigrationFloorError text. cli: patch is right. CI Check Changeset is green at head. The changeset's one defect is the number in ①-10, not its level.

③ Boundary flags

  • The two follow-on cards the prior record made landing conditions — measured: neither is filed (repo issue listing at 2026-09-20T14:19Z, 66 issues updated since 12:00Z, newest [finding] the governed queue guard cleared a RULES-tier governed diff holding zero authorized approvals — measured on PR #19351's own queue entry #19367; none names the upgrade skill, the handshake or the floor). Ruling on each, since the question is whether they block: (i) skills/objectstack-upgrade/SKILL.md:93 and :103-104 — a defect this PR CREATES, not one it reveals (--from 10 worked at floor 10), but its only legal carrier is Tier H (skills/**, register row skills-catalog) and one governed path forks the whole PR to the maintainer's hand (AGENTS.md :268). So it is a landing-coordination obligation on the seat — the card must exist before the seat lands, and only the seat can file it — and NOT a defect in this diff; it is not a FAIL ground here and I do not carry the prior condition (a) as one. When filed, it should also carry docs/adr/0087-*.md:40 and :283 ("a consumer arriving three majors late replays …", "A consumer arriving from any past major has a tested, one-command path"): those are the ADR's live promise, not history like :181 and :426, they are Tier H, and the ruling narrowed them. (ii) packages/metadata-core/src/protocol-handshake.ts:252, plus hydration.test.ts:95 and protocol-handshake.test.ts:150/154/209 — pre-existing: the handshake already prescribed --from 10 (and, per its own test, --from 6) from a package's declared range with no floor clamp, and those tests pin the strings at floor 10 too. Widened, not created; a separate defect in another package; not blocking.
  • Rebase vs merge — the dev's departure is upheld on measurement (①-7).
  • api-surface regeneration — the prior requirement is falsified (①-8); nothing to regenerate; the seat correction stands.
  • What the dev reported NOT MEASUREDpackages/cli typecheck and the debt gate: CI Type Check · workspace, · debt ledger, · consumer gates and · source gates are all green at head; check:pm-dispatch-gates self-test: CI-owned, not re-run here. The full @objectstack/spec and @objectstack/cli suites: CI Test Core (1/6) to (6/6) and the rollup are green at this head, including the shard that was red at 4405b10f; locally I ran the subsets the diff touches (①-1, ①-5).
  • Blast-radius correction — not re-measured; immaterial to the verdict.
  • Check runs at head: 42, every one success or skipped; no red.
  • Gates I ran on the head worktree, exit codes captured before any pipe: check:migration-registry 0, check:spec-changes 0, check:upgrade-guide 0, check:authorable-surface 0 (1 under ablation A), vitest run src/migrations 0 (143/143; 1 under ablation B with 6 red), vitest run --project integration test/migrate-meta-default-range.test.ts 0 (7 passed / 1 skipped; 1 under ablation C with 1 red), check-adr-0087-registration.mjs 0, check-changeset-no-major.mjs 0, check-empty-changeset.mjs 0, check-clause2-carriers.mjs --pair 19302 0 (read-only). check:api-surface needs dist/*.d.ts, which my OS_SKIP_DTS closure did not emit; its gate reading is CI's, backed by the artifact and generator readings in ①-8.
  • Served-tier is written as the constant's name, as the record template prescribes; the model id is not a field of this record. Scratch under the session scratchpad pr-19302/ (worktree, ablation and gate logs).

Implemented-by: claude/issue-19056-migration-support-floor-16
Reviewed-by: session_01AmH9bKvGoLjiY86Q4Z3og2

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: build-upgrade-guide.ts:44 and :56-59 reworded relative to the floor (the guide already prints it on line 6) and the guide regenerated; upgrading.mdx:218 --to 16 corrected; the changeset figure 99 corrected to 97; optionally the three comment sites named in ①-6. The two cards are the seat's to file, not this diff's to carry.


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>
…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>

Copy link
Copy Markdown
Collaborator Author

Rework landed on the branch — gate re-hung, a fresh at-tier review is owed, 2026-09-20T15:32Z

Verified against GitHub rather than the implementer's narration.

reading value
head d86cf0653b42132be030fc6136c764219cffc9f5 (was e894b0917480d4761fb7a1fca6bca5e7907383ce)
new commits 96004b16c7 (the three FAIL-ground fixes) · 79d709d871 (Merge remote-tracking branch 'origin/main') · d86cf0653b (count re-derivation)
history ⛔ no rebase, no amend, no force-push — e894b09174 is still in the commit list, so the review anchored to it and every existing checkout survive
shape draft · blocked · 23 files · 484+/1027−

Why both carriers now carry needs:contract-review again

check-clause2-carriers --pair 19302 went to exit 4 on C3 the moment the head moved: 「the gate WAS bound and cleared (last removal 2026-09-20T14:25:51Z), but the PR's head has MOVED since […] The review that cleared this gate judged a different tree, so the clear no longer covers what would land.」 That is the 重挂-owed state 「head 后移或无结论才重挂」 names, and the re-hang is a seat's act, not the checker's.

Re-hung on PR #19302 and card #19056 in one act, read back clean on both; card state (pm:dispatched) and assignee (os-steve) untouched. --pair 19302 is back to exit 0.

⛔ Exit 0 here means the carriers are legible and consistently hung — it does not mean this PR may land. The FAIL record 5750388312 judged a tree that no longer exists, and a fresh at-tier contract review of d86cf0653b is owed before anything else.

What the next round should NOT take on trust

The implementer reports every FAIL ground fixed, the generator edited rather than its output, the CLI integration tier green (the exact tier that failed the prior round), and a merge rather than a rebase. All of that is its own narration; ⛔ the reviewer is fed the card, the PR and the FAIL record, never this summary.

Two things it raised that the next reviewer should weigh rather than inherit:

  1. The conversion count moved again, 97 → 98, because an unrelated main commit (fix(spec)!: a dataset-bound dashboard widget owns appearance, the dataset owns structure #19363) landed one more protocol-18 conversion during the rework. The changeset now says 98. That figure tracks main, so it can be stale again by the time this lands — worth asking whether a number that drifts belongs in a release-owned changeset at all.
  2. A new finding it declined to fix, correctly: content/docs/upgrading.mdx:246-250's "Nothing to migrate" example is claimed false under the real CLI default (--to defaults to CHAIN_TERMINUS_MAJOR = 18), independent of the floor raise and untouched by this diff. Out of scope here; it wants its own card.

Stale in this PR's body, and this seat did not fix it

The body still says "10 of the 99 conversion fixtures" (now 98) and its Tests section predates both the CLI-integration run and the post-merge results. ⛔ Recording rather than editing: this seat is at its usage ceiling, and a body edit made without re-reading the body against the new head is exactly the failure this seat logged repeatedly today.

Also still owed and not filed — the two landing-coordination cards the prior round named (the Tier H skills/objectstack-upgrade/SKILL.md follow-up, and packages/metadata-core/src/protocol-handshake.ts:252). This session cannot create issues; see #19362.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d86cf0653b42132be030fc6136c764219cffc9f5

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 base.sha agree at 8271c814253f (#19363), which is also the second parent of the merge 79d709d871; origin/main at review time 8ddefbc977, 17 commits past base, none touching any of the PR's 23 files (git diff --stat base..main -- the file list is empty). Read first-hand: card #19056 (body + 5 comments), PR #19302 (body with the seat correction, all 4 comments including the FAIL record 5750388312, 11 commits, 39 check runs at this head, the full 23-file diff), AGENTS.md in full, the governed register in scripts/pm/check-governed-merges.mjs. Every claim in the brief, the rework report and both prior records was treated as a claim to measure. Exit codes captured before any pipe; every zero reading names its radius and a firing control.

① Derived judgments

  1. FAIL ground 6a (generator width claim) — fixed, and fixed in the derivation, not the output. packages/spec/scripts/build-upgrade-guide.ts:61-75 computes supportedLateness = PROTOCOL_MAJOR - MIGRATION_SUPPORT_FLOOR and emits the lateness sentence from it; :44 and :49 derive the H2 and the --step example from the floor. check:upgrade-guide exit 0 on the head tree, so docs/protocol-upgrade-guide.md is the generator's bytes. Ablated three ways, restore proved by git status empty each time. Floor 17 (the next legal move, after step17 is deleted as the new registry pin demands): emits "support floor is protocol 17, which is also the current protocol — there is no supported lateness window right now; the next major to ship widens it again" and the per-major loop (:78, bounded by PROTOCOL_MAJOR) emits zero sections — self-consistent. Floor 10: emits "7 majors behind the current protocol 17" and seven sections 10→11 … 16→17 — correct width. Floor 18 (above PROTOCOL_MAJOR, a state migrations.test.ts already forbids): the supportedLateness greater than 0 guard lumps a negative into the "also the current protocol" arm, which would be false — a non-total guard for an unreachable state, noted, not a ground. One hard-coded clause survives inside the derived sentence: "an older @objectstack/cli still carries the retired steps" (:67). It is true at 16 and at every future move (the CLI being retired carries the steps being retired), and false only at the hypothetical floor 10, where nothing below step11 ever existed — a nit. Sweep for a surviving width sentence, anchored patterns (any past major, several majors late, timeliness is never load-bearing, slept through, --from 1[0-5], N ≥ 10, protocol 10, reaches back to, four majors) over the generator, the artifact, packages/spec/src/{migrations,conversions} non-test, packages/cli/src/commands/migrate, upgrading.mdx, spec-changes.json: every hit is either the new floor-qualified text (types.ts:16 now reads "from the floor forward … timeliness is never load-bearing"; the guide :18), a history-framed sentence (upgrading.mdx:204-207, the Callout), or conversions/stored.ts:22 "any past major can replay forward", which is about the stored-row replay and is TRUE (stored.ts:75 passes includeRetired: true; apply.ts:132 is the only reader of retiredFromLoadPath). Control: the same instrument finds any past major at docs/adr/0087:170 and :283, outside the radius. Correct.

  2. FAIL ground 6b (upgrading.mdx:218 --to 16 to --to 17) — fixed; 17 is a legal intermediate stop, derived from the code. meta.ts:88 CHAIN_TERMINUS_MAJOR = Math.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS) = 18; :393 toMajor = flags.to ?? CHAIN_TERMINUS_MAJOR, so the real default --to is 18, not this runtime's 17. Legal --from is every N ≥ 16 (chain.ts:73); composeMigrationChain(16, 17) = [17] (pinned by the re-pointed composes only the steps in (from, to] case, 348/348 green locally), so --from 16 --to 17 stops one hop short of the default. Correct as a number. What the number cannot fix: the row's prose "instead of this runtime's" and :189 "between that major and this runtime's" both describe the default as PROTOCOL_MAJOR, which #17462 (fb39b385f1, 2026-09-11) moved to the chain terminus; 17 IS this runtime's major, so the row now reads "stop at 17 instead of 17". Pre-existing (present at merge-base, :211), not created or widened by this PR, same class as item 5, and the PR's own new row at :196 ("17, then every later major this build carries a step for") is accurate to the real default and therefore visibly disagrees with the untouched :189 seven lines above it. Ruled with item 5.

  3. FAIL ground ①-10 (the changeset figure) — 98 is correct at this head, by runtime count. tsx over packages/spec/src/conversions/registry.ts at the head: ALL_CONVERSIONS.length = 98; below-floor (toMajor ≤ 16) = 10, named: 4 at major 11, 3 at 13, 1 at 14, 2 at 15; by toMajor {11:4, 13:3, 14:1, 15:2, 17:57, 18:31}. The PR's own diff to conversions/registry.ts is docblock-only (zero non-comment changed lines), and main has not touched that file since base, so 98 also holds against origin/main 8ddefbc977 right now. Design ruling on the drift: the denominator does not belong. The numerator (10) is a property of this change — the set graduated at protocols 11–15 is closed by the floor itself; the denominator is a property of main on the day someone counted, it moved 97→98 under an unrelated commit mid-rework, nothing gates it, and the changeset compiles into the release-owned CHANGELOG whenever the release is cut, not now. The same changeset already handles its blast-radius table correctly by pinning the reading to a tree ("at e6a03e649"); the "98" carries no tree, which by AGENTS.md's own rule makes it not a reading. It should say "10 conversion fixtures — every conversion graduated at protocol 11–15 — leave the chain-replay gate" and stop. Not a FAIL ground: the figure is measured true at head and at current main; it becomes false only if main registers another conversion before the queue lands this, which I name as the exposure. Fold the edit into any further push on this branch; it does not by itself warrant one.

  4. The five retiredFromLoadPath conversions at majors 11–14 (D2 entries now carried by no D3 step) — three right, two not. All five read at head. object-compactLayout-to-highlightFields@11 (:194-207), stack-roles-to-positions@13 (:233-245), book-audience-profile-to-permission-set@14 (:385-396): edited, and every sentence checks — "no migration step carries this conversion any more", "migrate meta --from 10|11 refuses before it would ever reach it", "survives only as the stored-row replay, which is not floor-scoped" (stored.ts:75). owd-legacy-read-aliases@13 (:269-280) and sharing-recipient-role-to-position@13 (:327-336): the rework's defence — they never claimed chain reachability — is narrowly true (neither says migrate meta rewrites anything), but both assert in the present tense that a step-13 semantic TODO exists: :276 "is delegated to the step-13 semantic TODO instead" and :334 "so it is a step-13 semantic TODO". This PR deleted step13 and both entry files (13.owd-full-alias-removed.ts, 13.position-hierarchy-flattened.ts are present at base and absent at head), so both sentences now point at nothing; the implementer recognised exactly this class when it appended "historically" to the same reference in the stack-roles docblock (:239) and applied it to one of three. Sweep step-1[1-6]|step1[1-6] over packages/spec/src non-test (control: step17 found 3× in migrations/registry.ts): two more sites — conversions/registry.ts:448 (view-visibleOn-to-visibleWhen, a LIVE below-floor conversion) promises it "will graduate into the step-16 chain when the alias is removed", a future this PR removed; :837 and ui/dashboard.zod.ts:1141 name step-13 / step16 in the past tense as history and are fine. Ruling: :276, :334, :448 are stated-reason defects of the same kind the prior record listed as recommended-not-ground, and I rule them the same way — fix in the same push as item 3, not a ground: no gate reads them, conversions.test.ts is green, and the D2 registry is correctly untouched.

  5. upgrading.mdx:246-250 ("os migrate meta --from 17 prints Nothing to migrate") — the implementer's call to leave it is right, on measured grounds. The example is false: with the default --to 18, --from 17 composes [18], and chain.ts:102-105 pushes a step's whole semantic list into todos on every hop regardless of stack content — step 18 carries 146 semantic entries at this head — so meta.ts:479 never selects the success sentence; the run prints "146 manual change(s) require your judgment". Dated by git log -S: the example landed 2026-09-06 (d83d079b4a) when the default was PROTOCOL_MAJOR and --from 17 was an empty range; #17462 moved the default on 2026-09-11 and falsified it; this branch was cut 2026-09-20. So it is not an example this PR falsifies, which was the prior record's test for "belongs in this PR", and Prime Directive chore: version packages #10 says file it, never expand scope. Its card should carry the whole #17462 class in this file, not the one example: :189 and the :218 prose (item 2) and :246-250 all state the default as this runtime's major. Landing obligation on the seat, alongside the two cards already owed; none of the three exists yet (issue listing at 17:53Z, 66 issues updated since 13:00Z, none names the upgrade skill, the handshake or the default---to class).

  6. The merge 79d709d871 — clean, automatic, and nothing dropped. Its tree 6f91fd8cd18e is byte-identical to a fresh git merge-tree --write-tree 96004b16c7 8271c81425 run locally (driver registered) AND to the same recompute from a throwaway bare --shared clone with no driver, so no hand resolution and no os-regen deferral shaped the result; the four merge=os-regen paths in the PR (spec-changes.json, protocol-upgrade-guide.md, api-surface/**, api-surface-signatures.json) were touched by one side only. main brought fix(spec)!: a dataset-bound dashboard widget owns appearance, the dataset owns structure #19363 into two files the PR also edits, conversions/registry.ts and migrations/registry.ts; its content survived (dashboard-widget-chart-config-structure 3 and 12 hits respectively, packages-list-pagination-retired 3), and the merged generated regions are the generator's: check:migration-registry exit 0 ("223 semantic, 201 retired-key, 181 retired-def"), check:spec-changes exit 0, check:upgrade-guide exit 0, git status empty after all three. Head against base is exactly the PR's 23 files (484+/1027−). d86cf0653b is one line of the changeset. Merging rather than rebasing a pushed, reviewed branch remains what AGENTS.md prescribes; upheld again.

  7. The two prior-round requirements measured false last round — still false, still upheld. api-surface-signatures.json holds factory hashes only and a const literal cannot move it; CI TypeScript Type Check (the job that runs check:generated) is green at this head. Rebase: see 6.

  8. Floor, deletions, generated regions, retirement tables (prior ①-1 to ①-4) — unchanged at this head and re-measured where cheap. MIGRATION_SUPPORT_FLOOR = 16, MIGRATION_MAJORS = [17, 18], PROTOCOL_MAJOR 17 (runtime), chain.ts no diff. Both retirement tables carry only 17: and 18:; retired-tables-not-floor-scoped.test.ts passes and reads ../../scripts/build-schemas.ts through the sanctioned dirname(fileURLToPath(import.meta.url)) seed without leaving the package. The 10 deleted entries/semantic/ files are exactly the below-floor semantic set.

  9. The re-pinned CLI integration case (prior ①-5) — unchanged; the skip is still a measurement. SEMANTIC_FREE_HOP is undefined at this head (step 17: 77 semantic, step 18: 146 — the 144 in the prior record was true at its head; main added two since), the always-run sibling asserts not.toContain(0), and CI Test Core (6/6) — the shard that was red at 4405b10f — is green at d86cf0653b. Not re-spawned locally (needs the workspace dist closure); CI is the authority and it is green.

  10. Whole-diff, on its own terms. meta.ts examples derived from the floor: correct, help text only. migrations.test.ts replay fixtures re-pointed at MIGRATION_SUPPORT_FLOOR + 1 with the step-17 shapes (executetarget, conditionalRequiredrequiredWhen, both toMajor: 17 in the D2 table), applied ids asserted by name; the chain-replay gate filters on toMajor above the floor with an anti-vacuity case and a "not deregistered" case; the by-name refusal case for 10..15 hard-codes the list, which stays true at any later floor. registry.ts header quotes the ruling verbatim and untranslated (the sanctioned exemption) and warns the next floor move off the tables. types.ts / index.ts docblocks now qualify the promise by the floor. spec-changes.json supportFloor: 16, migrateCommand … N ≥ 16. Pre-existing and not this PR's: the guide's per-major loop stops at PROTOCOL_MAJOR (17) while the CLI's default replays to the terminus (18), so "replays every step below" under-describes the default run by one in-development hop.

② Semver level

@objectstack/spec: minor + chore(spec)!: bang + **BREAKING** banner + Clause-②: yes (narrowing) + adr-0087: not-required (no-migration-prescription); @objectstack/cli: patch. Agreed with the declared level; the act is breaking. A published export (migrations/index.ts:23) narrows the accepted fromMajor range of applyMetaMigrations, and its literal type moves 10 to 16; at least minor under the WHICH-LEVEL rule, major refused for the launch window by check-changeset-no-major.mjs (exit 0 on the head tree, diffing from 8271c8142), which names the banner and the ADR-0087 disposition as the substitute carriers — both present. check-adr-0087-registration.mjs --base 8271c814 exit 0, reading [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription); the disposition holds on the merits (no authorable key, def or field moves; the one instruction a stopped consumer needs is the MigrationFloorError text, which the changeset quotes). check-empty-changeset.mjs exit 0. CI Check Changeset green. cli: patch right. The changeset's one weakness is the denominator in ①-3, not its level. Card and PR body both declare Clause-②: yes; check-clause2-carriers --pair 19302 exit 0 (read-only), both carriers hung.

③ Boundary flags

  • Governed surfaces: the 23-file list matched against the register's six rows (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md) — zero hits; Governed Surface Queue Guard green. docs/protocol-upgrade-guide.md and content/docs/** are not on the register.
  • Three landing-coordination cards owed by the seat, none filed as of 17:53Z: (i) skills/objectstack-upgrade/SKILL.md:93,:103-104 plus ADR-0087 :40/:283 — Tier H, created by this ruling, only legal carrier is a separate Tier H PR; (ii) protocol-handshake.ts:252 + hydration.test.ts:95 + protocol-handshake.test.ts:150/154/209 — pre-existing, widened; (iii) NEW from this round: the #17462 default---to class in content/docs/upgrading.mdx (:189, :218 prose, :246-250) — pre-existing, not this PR's, item ①-5. Non-blocking on this diff for the reasons the prior record gave and I re-measured; the seat's own comment says it cannot create issues ([Decision] PM 席位被权限分类器判 Self-Approval 拦住落地动作 —— 章程要求席位入队,通道却不允许(三张全绿 PR 今天为此停摆) #19362), so this stays a named obligation, not a silent one.
  • Recommended in the same push, not grounds: drop the "of the 98" denominator (①-3); "historically" or equivalent at conversions/registry.ts:276, :334, :448 (①-4).
  • Not measured locally, CI-owned: check:api-surface / check:authorable-surface / the full check:generated roster (need the built dist) — TypeScript Type Check green; the CLI integration spawn — Test Core (1/6)…(6/6) and rollup green; Lint & Repo Gates, Build Core, Dogfood Regression Gate, Temporal Conformance all green. Check runs at head: 39, every one success or skipped, no red.
  • Gates I ran on the head worktree (exit codes captured before any pipe): check:migration-registry 0, check:spec-changes 0, check:upgrade-guide 0 (and 3 generator ablations, each restored to a clean tree), vitest run src/migrations src/conversions/conversions.test.ts 0 (4 files, 348/348), check-adr-0087-registration 0, check-changeset-no-major 0, check-empty-changeset 0, check-clause2-carriers --pair 19302 0, runtime conversion count via tsx 0.
  • Served-tier is written as the constant's name per the record template; the model id is not a field of this record. Scratch under the session scratchpad pr-19302-r2/ (worktree, ablation and gate logs, the count script).

Implemented-by: claude/issue-19056-migration-support-floor-16
Reviewed-by: session_01AmH9bKvGoLjiY86Q4Z3og2

VERDICT: PASS

All three grounds of 5750388312 are fixed by measurement, not by narration: the width claim is derived and ablates correctly at the floors that matter; --to 17 is a legal intermediate stop under the real default of 18; 98 is the runtime count at this head and at current main. Nothing in the code is wrong; every gate is green. What this round adds that neither the PR nor the rework report contains: the real CLI default is 18 and it, not this PR, is what stales upgrading.mdx:189/:218-prose/:246-250 (a third card); two of the "never claimed reachability" docblocks and one live conversion still name steps this PR deleted; the changeset's denominator tracks main and should go; the generator's else-arm is not total for a floor above PROTOCOL_MAJOR.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Landing provenance — contract review PASS, preconditions measured, 2026-09-20T18:04Z

The at-tier contract review of head d86cf0653b42132be030fc6136c764219cffc9f5 is on the record as comment 5751602379, VERDICT: PASS. Adopted verbatim; ⛔ not rewritten.

line value kind
Implemented-by: claude/issue-19056-migration-support-floor-16 branch — mode:subagent
Reviewed-by: session_01AmH9bKvGoLjiY86Q4Z3og2 the adopting seat

Distinct kinds, so no SELF-REVIEW. Both carriers' needs:contract-review stripped in this same act, read back clean; card state and assignee untouched.

The three landing preconditions, measured first-hand at 2026-09-20T18:04Z

gate reading
① at-tier PASS on record 5751602379, Served-tier: CONTRACT_REVIEW_TIER, Head-sha in a span of its own matching this head, six required lines once each, zero angle brackets
--pair 19302 exit 0
③ every check green or expected skip check-expected-skips --pr 19302 exit 0 — 39 runs, 34 success · 5 skipped · 0 other, every skip in the roster. ⛔ Not a required-context subset.
path surface check-governed-merges --test on the final 23-file list: 0 of 23 hit the register — NOT governed
size 1511 changed lines, below the 5000-line human-merge threshold

A seat reading that differs from the record's, recorded beside it rather than inside it

The record rules content/docs/upgrading.mdx:218 a pre-existing incoherence inherited from #17462 (which made the CLI's real default --to the chain terminus), on the ground that the row's prose is untouched by this PR. That is accurate: only the number moved. This seat measured the same hunk and reads one thing differently — the number on that exact line is this PR's authorship:

-| `--to 16` | Stop at an intermediate major instead of this runtime's |
+| `--to 17` | Stop at an intermediate major instead of this runtime's |

Control: the same anchored pattern finds 10 changed --from lines in the same diff, so the instrument is not blind. With CHAIN_TERMINUS_MAJOR = Math.max(PROTOCOL_MAJOR, ...MIGRATION_MAJORS) (packages/cli/src/commands/migrate/meta.ts:87) resolving to 18, the row reads wrong either way: if 「this runtime's」 is the protocol major 17, then --to 17 is not an intermediate stop at all; if it means the terminus 18, then 18 is not 「this runtime's major」 but the highest major carrying a step.

⛔ 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 skills/objectstack-upgrade/SKILL.md follow-up, packages/metadata-core/src/protocol-handshake.ts:252, and a new class it found — the real default --to staling upgrading.mdx:189, the :218 row prose, and :246-250, of which the --from 17 「Nothing to migrate」 example dates to 2026-09-06, before #17462. The record's own judgement is that the third wants a card covering the class, not the one example.

Also carried forward from the record and not blocking: two retiredFromLoadPath docblocks (conversions/registry.ts:276, :334) still assert a step-13 semantic TODO in the present tense while both entry files are absent at this head, and :448 still promises a live below-floor conversion 「will graduate into the step-16 chain」.

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

@os-steve
os-steve marked this pull request as ready for review September 20, 2026 18:04
@os-steve
os-steve added this pull request to the merge queue Sep 20, 2026
Merged via the queue into main with commit f20fe29 Sep 20, 2026
44 checks passed
@os-steve
os-steve deleted the claude/issue-19056-migration-support-floor-16 branch September 20, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(spec): raise MIGRATION_SUPPORT_FLOOR from 10 to 16 — upgrades are supported from protocol 16 onward

2 participants