Repository navigation
build(spec): the migration registry is generated at build and leaves git - #22706
Conversation
packages/spec/src/migrations/registry.ts is now generated whole and git-ignored, at its unchanged path. gen:migration-registry writes it from the committed hand-written skeleton src/migrations/registry.ts.template (everything outside the os-generated markers, regions empty) and the per-entry files under src/migrations/entries/. It runs on pnpm install (the package's prepare), as the first step of build, and before typecheck, test and test:repo; turbo declares it as their prerequisite with the file as the task output. The generator's self-test runs inside every generation and now also covers ordering and determinism. check:migration-registry retires: there is no committed copy to compare. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
…e generated registry The registry's committed copy is gone, so every reader of it moves to its two committed sources: - check-adr-0087-registration reads a rev's migration ledger from the template and the entry files when the rev tracks the template (the committed registry otherwise, for merge bases from before), and its witness runs the rev's own registry generator before projecting. A new GR battery covers the generated era, including the transition merge. - check-future-spec-major drops its exemption row for the registry; the entry file that carries the sentence keeps its own. - check-generated records gen:migration-registry as an ungated generator of git-ignored build output; regen-artifacts marks the registry row untracked. - The lint.yml step and the migration_registry gate family retire, with the selector's self-test pins for that family (its floor drops to the measured 59 cases / 304 checks). - build-schemas remedies, the entries README, .gitattributes and test comments stop naming the retired check; step18-rationale-merge merges the template, the file git now merges. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
…gistry-build-time
Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
The registry left git in this PR, which reddened two scripts/pm self-tests on Lint & Repo Gates. Co-landed here, declared to the domain:skills seat: - dispatch-gates: an import hint on a generated, git-ignored module reaches that module's committed sources. GENERATED_MODULE_SOURCES declares one row, the registry and its template plus entries/, because only its turbo task names the sources and those inputs also name the manifest and the generator. The self-test pins the mapping with a control, proves the row against the tree (module untracked and ignored by a tracked ignore file, sources tracked), and keeps such hints out of the extension-narrowing agreement population. - os-regen-merge self-test case 9c pins skills/README.md and gen:skill-docs: the registry row it pinned is untracked now, and the reader skips those. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
…gistry-build-time
dispatch-gates.mjs is frozen (ruling 208 R6), and this PR touches it only under that ruling's exception: the workflow it feeds broke. As the W3 split did, the declared row GENERATED_MODULE_SOURCES and its rationale move to the data file, and the engine keeps only what matches it. That is the import and re-export, a five-line lookup and the hintCovers clause: +11/-1 against 765ae6b. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
The engine's inherited-population marker sits two lines lower (734 -> 736) because GENERATED_MODULE_SOURCES joined the data import and the re-export above it. Same marker, same text. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
…gistry-build-time # Conflicts: # packages/spec/src/migrations/registry.ts
The migration registry was the third until this PR made it git-ignored build output, an untracked row the reader skips. Comment text only. Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #22554 (body and all 10 comments, rulings 6092692730 on this card and 6078203801 on #22449 included), PR #22706 (body, 25-file list, net diff ① Derived judgmentsRuling B's mechanics, each against the diff
The premise "the entry files stay the only source", measured false, and the template
Every reader of the committed copy, each change judged — none weakens a gate beyond the retired family
The
The merge of #19939 S1 (f66fdc7) — Accept-set and public-surface changes the diff implies — none. No schema, export, authorable key, runtime path or JSON Schema moves. Repository surfaces that move, each right: the ② Semver level
③ Boundary flagsDev
Check-runs on Implemented-by: VERDICT: PASS |
维护者速读 · PR #22706(#22554,裁决 B:迁移注册表改为构建时生成,移出 git)
改了什么
为什么改
风险与代价(含回滚)
席位意见
你要做的(一个动作)
|
…gistry-build-time # Conflicts: # packages/spec/src/migrations/registry.ts
|
Regen-provenance: 6102284618 ·
|
…llar-binding-keys Resolved by hand to the #22706 model: packages/spec/src/migrations/registry.ts leaves git (deleted on main, generated at build); the order-93 STEP18_RATIONALE fragment for flow-binding-name-dollar-refused moves into packages/spec/src/migrations/registry.ts.template, sorted by key before flow-binding-variable-dollar-name-refused. The entry file stays. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
Fixes #22554
Clause-②: no
Executes ruling B on #22554 (comment 6092692730, maintainer 「同意」), which completes #22449 B′ (comment 6078203801):
packages/spec/src/migrations/registry.tsis generated at build and leaves git, at its unchanged path. The 3 source imports, the script import and the test imports are untouched. The published package is unchanged apart frompackage.jsonscripts and the input-hash stamps (measured below).Landing: +4,054 / −31,059 across 25 files, about 35,100 changed lines (GitHub's reading at head
81ae60b20a). That is over the 3,000-line line, so this PR takes the maintainer's APPROVED review, as the ruling notes. It stays draft. The two PM-tool fixes it needs co-land here (see "Two PM tools, co-landed here").A premise the ruling stated, measured false: the registry was a MIXED file
The ruling says "the entry files stay the only source". At
cb3bb9333f,registry.tshad 30,718 lines (the ruling's 30,124 is an earlier count). Of these, 27,268 sit inside the six os-generated regions, concatenated from 899 entry files (419 semantic, 258 retired-key, 222 retired-def). The other 3,451 lines are hand-written: the module header, the two value imports,MIGRATION_SUPPORT_FLOOR, step 17's rationale,STEP18_RATIONALE(about 1,937 lines of keyed fragments),step18,MIGRATIONS_BY_MAJOR,MIGRATION_MAJORSand the two tables' doc comments. The generator used the file as its own template.So the hand-written part needs a committed home. In the ruling's intent, it becomes
packages/spec/src/migrations/registry.ts.template: the old file with every region emptied, produced mechanically. The generator now writesregistry.tswhole from that template plusentries/. Regenerating from an absent file reproduced the committed blob byte for byte (feb4843913) before the header rewording below.The template is deliberately not a
.tsfile, for two reasons. With empty regions, itsTranslationDataSchemaimport is unused, and the root tsconfig'snoUnusedLocalswould redtsc. More important, a.tstemplate would be importable, and an import of it would silently receive empty tables.Measured cost of this to the ruling's "no shared file for a D3 PR": of the last 60 commits that touched
registry.ts, 33 also changed hand-written lines, mostly oneSTEP18_RATIONALEfragment. Those edits now land in the template. It is keyed and sorted, so two retirements insert at different lines (#20535, pinned bystep18-rationale-merge.test.ts, which now merges the template). Region-only PRs (the other 27 of 60) touch no shared file at all.The ruling's measure-first readings (condition 2)
tsx scripts/build-migration-registry.ts --self-test --checkand a TMPDIR-isolated tsx cache, load about 3.3 on 4 cores. Cold runs: 1,808 / 1,583 / 1,685 ms. Warm runs: 1,611 / 1,426 / 1,563 ms.pnpm run gen:migration-registrytook 1,465 ms. A freshpnpm install --frozen-lockfileincluding the generation took 11 s.prepareon a workspace package runs onpnpm install. I measured this on a fresh worktree with an uncommitted probeprepareon@objectstack/spec. pnpm 10.31.0 printedpackages/spec prepare$ …and wrote the marker with cwdpackages/spec. The probe was then restored byte-identical (blobde5c9fa2dd,git diff HEADempty). The realprepare(pnpm gen:migration-registry) was then measured on a second fresh worktree at765ae6bde4. Install printed✓ wrote src/migrations/registry.ts (419 semantic, 258 retired-key, 222 retired-def)andgit statusstayed clean. The editor is therefore not red before the first build.prepareis strict: a malformed entry fails the install with the generator's located message. A lenient one would leave the previous run's file on disk to be read stale. Also measured:pnpm packrunsprepareand leaves it out of the packed manifest.typecheckorder. The script is nowpnpm gen:migration-registry && tsc --noEmit && pnpm check:scripts-typecheck && pnpm check:test-typecheck, so all three run after generation. turbo's@objectstack/spec#typecheckalso depends on the generation task. In the fresh worktree the typecheck went green with nodist/present.What changed
registry.ts: removed from git and ignored (root.gitignore, besidepackages/spec/json-schema/), together with the staging file of the generator's atomic write.build-migration-registry.ts):registry.tsonly when its bytes differ, and writes it by rename, so a paralleltsc, vitest worker or importer never reads half a file;--self-testalone stops after it). The self-test gains an ordering case (code-unit order; a locale compare fails it) and a determinism and fixed-point case;spec-changes.jsonper-major section,docs/protocol-upgrade-guide.md): keep committing them, or generate them at publish like thereleasesection #22449 B′ (6078203801) and [Decision] the migration registryregistry.tsis the last committed generated aggregate on the ADR-0087 D3 path: keep it and finish B′, or generate it at build #22554 B (6092692730) as superseding the Option-B clause of 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's 2026-08-10 ruling.packages/spec/package.json:buildstarts withgen:migration-registry(beforegen:schema);typecheck,testandtest:repostart with it;prepareruns it;check:migration-registryis removed.turbo.json:@objectstack/spec#gen:migration-registryhasoutputs: ["src/migrations/registry.ts"], with inputs the template, the entries, the generator and the manifest.#typecheck(new),#testand#test:repodepend on it, and#buildlists the file among its outputs so a cache-hit build restores it.lint.ymlstep "Migration registry matches its entry files" and themigration_registrygate family inscripts/ci/select-gate-families.sh. Its self-test pins go too: 12 cases, with the floor moved from 71 cases / 369 checks to the measured 59 / 304. No other family's verdicts changed.Readers of the committed copy, adapted
A census of
git grep migrations/registry(112 files) plus a run of every derived gate found these path-dependent readers. Each is changed only as far as the retirement requires.scripts/check-adr-0087-registration.mjsread the ledger withgit show REV:…/registry.tsand laid out its witness withgit archive, so it went red ("ledger source not found at HEAD").ledgerAtnow reads a rev's migration ledger from the template plus the entry files when that rev tracks the template, and from the committed registry otherwise, because a merge base can predate this PR. Measured on HEAD againstcb3bb9333f: 532 / 591 ids on both sides, with 0 differences either way.--audit-stockledger-touch test counts entry and template commits.scripts/check-future-spec-major.mjs: its exemption row forregistry.tsmatched nothing once the file left the tracked corpus. The row is dropped; the entry file that carries the sentence keeps its own row, and the R2 self-test fixture is re-pointed at it.packages/spec/scripts/check-generated.ts:gen:migration-registrymoves toUNGATED_GENERATORS, with the build-output rationalegen:openapialready has.scripts/regen-artifacts.mjs: the registry'sNOT_DRIVER_MANAGEDrow becomesuntracked: true(git never merges it)..gitattributes,entries/README.md, thebuild-schemas.tsremedies and test comments: stop naming the retired check or a committed lap.check:dispatcher-error-vocabulary's row and the ESLint stack-headroom canary still see it.check:cli-command-ids,check:issue-citationsandcheck:cross-package-test-inputswere also measured green.The published package
pnpm packof@objectstack/specbuilt at BASEcb3bb9333fwas compared with the build at HEAD (merge625c4d202e; the later commit only adds the changeset). Both tarballs hold 2,070 files, and 4 differ:package/package.json, inscriptsonly:build,typecheck,testandtest:repostart with the generation, andcheck:migration-registryis gone.prepareis not in the packed manifest.dist/.build-input-hash,dist/.build-input-hash-dtsandjson-schema/.build-input-hash-schema(53a504fa…toac4356b2…). They hash the build inputs, which this PR changes by construction.Every
distJS file, declaration, source map and JSON Schema, and every shippedsrc/**/*.zod.ts, is byte-identical. Tarball sha256: BASEd3d59ac0…, HEAD362a845e….pnpm check:published-filespassed at HEAD765ae6bde4.A fresh checkout builds, typechecks and tests with no committed registry
This ran on a new detached worktree at
765ae6bde4, whereregistry.tswas absent:pnpm install --frozen-lockfile:preparegenerated the file.pnpm --filter @objectstack/spec typecheck:VERDICT command-exit 0, before any build.pnpm --filter @objectstack/spec build:VERDICT command-exit 0, with input hashac4356b2…, identical to the primary worktree's build.registry.tsdeleted, thenpnpm --filter @objectstack/spec test: the script wrote it again (✓ wrote src/migrations/registry.ts). Result:Test Files 642 passed (642),Tests 19164 passed | 1 todo (19165),VERDICT command-exit 0.Two PM tools, co-landed here (
scripts/pm, declared to thedomain:skillsseat on #7623)scripts/pm/os-regen-merge.sh: self-test case 9c pinned the registry as a TRACKEDNOT_DRIVER_MANAGEDrow, which is untracked now. It pinsskills/README.md/gen:skill-docs/skills/README.md: merge: unspecifiedinstead, the same shape (tracked, MIXED, marked regions, a generator); all cases pass. The header's class-3 roster (about:359–:366) now names two MIXED rows, with the registry noted as the former third. Comment text only.A touch on the frozen
scripts/pm/dispatch-gates.mjs, under ruling 208's exceptionRuling 208 R6, as
.claude/skills/pm-dispatch/references/instrument-discipline.mdcarries it: 「工具位只有一个,先花在删除上;dispatch-gates.mjs冻结,只在它喂的 workflow 坏了时碰」. This PR makespackages/spec/src/migrations/registry.tsgenerated and git-ignored, so the hint a gate's import of the registry yields names a file no card can touch. Two things broke as a result:check:pm-dispatch-gates, aLint & Repo Gatesleg, went red on its extension-narrowing agreement case; and a card editing only an entry file or the template stopped deriving five families that import the registry (check:spec-changes,check:upgrade-guide,check:authorable-surface,check:query-options-erasure,check:role-word). That is the workflow the file feeds breaking, so the file is touched under the ruling's exception, in the W3 split's shape:scripts/pm/dispatch-gates.data.mjs, +43/-2. The declared rowGENERATED_MODULE_SOURCES(the registry module, and its committed sourcesregistry.ts.templateandentries/) with its rationale.scripts/pm/dispatch-gates.mjs, +11/-1 against765ae6bde4. The data import and re-export, a five-linegeneratedModuleSourceslookup and one||clause inhintCovers.scripts/pm/dispatch-gates.self-test.mjs. It pins the mapping with a control, proves the row against the tree on every run (module untracked and ignored by a tracked ignore file, sources tracked), and keeps mapped hints out of the extension-narrowing agreement case. One census line pin moved 734 -> 736 with the import. Removing the clause turns exactly the mapping case red (1 of 1828).Declared to the
domain:skillsseat on #7623; that seat's answer is comment 6100894139 on #22554.Governed text this makes false (reported, not edited)
.claude/skills/spec-property-retirement/SKILL.md:213says the D3 step lives inpackages/spec/src/migrations/registry.ts, and that a step-18 retirement adds itsSTEP18_RATIONALEfragment and an earlier step'sconversionIdsandrationalethere. After this PR those hand edits belong inregistry.ts.template: an edit toregistry.tsis never committed. The proposed wording is in the os-dev report.Concurrency
claude/issue-22658-skills-package) editsturbo.json. Whichever lands second resolves the textual overlap.{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939 pass 4 S1 has landed as PR feat(spec,service-automation)!: a subflow or map input and a script's inputs are value slots — a CEL envelope per key, the {…} token refused (#19939 pass 4, S1) #22715 (f66fdc7973) and is merged here (84612b393b, throughscripts/pm/os-regen-merge.sh). Its modify/delete onregistry.tswas resolved bygit rmafter proving its 4 hunks lie inside the os-generated regions on both sides (0 hand-written lines). The registry generated on the merged tree then equalsorigin/main's committed blob9aaae83bdebyte for byte, apart from the declared header lines 19, 21–26 and 38–42; the ledger reads 591 ids (532 migration) on both sides, 0 differences. The branchclaude/issue-15204-s1-position-permission-sets(epic [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194) is unchanged: it changes only generated regions, so the samegit rmresolves it losslessly.registry.tsdiff touches lines outside the os-generated markers must port those lines intoregistry.ts.templatebeforegit rm. Otherwise the edit is silently dropped.Verification at HEAD
84612b393b(patch round) and765ae6bde4(round 1)Patch round, at
84612b393b(81ae60b20aadds only theos-regen-merge.shheader comment; its self-test reads "all cases pass" there):pnpm --filter @objectstack/spec typecheckVERDICT 0;pnpm --filter @objectstack/spec testVERDICT 0,Test Files 642 passed (642),Tests 19197 passed | 1 todo (19198);pnpm check:pm-dispatch-gates"1828 cases pass" andcheck-dispatch-gates.mjs --slow"2088 cases pass";os-regen-merge.sh --self-test"all cases pass"; the gate union re-derived on the 25 paths, 146 commands, "146 derived, 146 run, 0 NOT-MEASURED, 0 UNRUN", all exit 0 (includingcheck:dual-build-cjs-loads, measured this time). CI on84612b393b: 34 success, 2 skipped. The bullets below are round 1's.@objectstack/spec: typecheck, build and test, all green (VERDICT command-exit 0each). These ran on the fresh worktree above, at this commit, underscripts/pm/os-verify-lock.sh.Gates: the union
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives from the 21 changed paths is 146 commands, all run at this commit with exit codes captured before any pipe:check:dts-closure, first exited 1, naming the half-builtdist/of two plugins that my own earlier gate runner had left when its timeout killedcheck:type-check-debtmid-build. I removed those git-ignored partial trees and reran it: exit 0, 171/171 declaration files across 71 packages.check:pm-dispatch-gates(above).check:dual-build-cjs-loads, which exited 3 because it needs every workspace package built. This PR changes no package'sdist, and the spec tarball diff above shows its owndistis byte-identical.dispatch-gates --ranover the exit-coded record: "146 derived, 145 run, 1 NOT-MEASURED, 0 UNRUN".Self-tests run directly:
node scripts/check-adr-0087-registration.mjs --self-test: 463 assertions.bash scripts/ci/select-gate-families.selftest.sh: 59 cases, 304 checks.node scripts/regen-artifacts.mjs --self-testandnode scripts/git-merge-regen.mjs --self-test: exit 0.tsx packages/spec/scripts/build-migration-registry.ts --self-test: ok.ESLint, narrowed to the changed lintable files plus the generated
registry.ts: 11 files, 0 errors, 0 warnings, 0 fatal, run with thelintscript's own--stack-size=4000 --no-inline-config.eslint.config.mjs, whose objects glob**/*.{ts,…}.--print-configonregistry.ts.templateanswersundefined, so the template is outside it.parserOptions.project(no type-aware rules), so this diff cannot move a verdict on an untouched file. The repo-wide run is CI's.Ablations (one-time proofs, each restored to the HEAD blob under the tool's own trap)
All were run through
node scripts/ablation-replace.mjson the committed tree.migrationLedgerTextAtforced to the committed era: the ADR-0087 self-test goes red on GR1, GR3 and both GR7 cases.origin/maingoes red, with "Cannot find module '../src/migrations/registry'" in the witness.localeCompare: the generator's self-test goes red on the new ordering case.Acceptance notes
scripts/regen-artifacts.mjscites.gitignore:61,:73and:108for three other untracked rows. These were already off by 2 before this PR, and the six lines added here move two of them further. They are not gated. Noted, not changed.packages/spec/src/migrations/registry.tspath keeps its name and exports, soexport-origins/migrations.json,SYNC_ARCHITECTURE.mdand the source comments that name it stay true.Generated by Claude Code