Repository navigation
fix(spec): registration remedies name the entry file and gen:migration-registry, never the generated registry.ts - #21021
Conversation
…n-registry, never the generated table Gate (b), the def-removal gate, the manifest disappearance check and the (b2)/(b3) mirrors printed lines to paste into, or delete from, src/migrations/registry.ts, whose tables are generated from src/migrations/entries/. Each remedy now names the entry file (from the generator's own shardNameFor, made importable behind an entry guard) and the generator command. The guidance-slot docblocks say what the slot is for now: keys the shape does not declare. Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
… paste line; changeset for the shipped docblocks Claude-Session: https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2febc4b64f71e7f5683d26c78b61a34ed03bcad2 && git checkout 2febc4b64f71e7f5683d26c78b61a34ed03bcad2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2f2fa11d756f665a4c06160480c1dce15b9d67a4 c2e5b3b8146a6917038bb6647b658e29fc7ce1ee && git checkout -B drift-repro 2f2fa11d756f665a4c06160480c1dce15b9d67a4 && git merge --no-ff c2e5b3b8146a6917038bb6647b658e29fc7ce1ee
node scripts/docs-audit/affected-docs.mjs --json 2f2fa11d756f665a4c06160480c1dce15b9d67a4 |
Contract reviewServed-tier: Reviewed as an isolated subagent on PR #21021 (card #20809), from the card's body and its three comments (the pointer, the claim, the os-dev report), the PR body, its 7-file list, the net diff against ① Derived judgmentsAccept set — unchanged, judged right. The three Published surface — text only, and it does ship, judged right. Printed remedies in The card's ⛔ on restating the naming convention — held. The generator Tests — the card's two pins are present, judged right. Coordination with the kit — judged right. The PR quotes the retirement kit's section 3 as it reads on ② Semver level
③ Boundary flags
Check-runs on the head, as read at 2026-10-01T02:20Z33 check-runs: 22 Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #20809
Clause-②: no
What this changes
RETIRED_KEYS_BY_MAJOR,RETIRED_DEFS_BY_MAJORand every step'ssemanticlist are generated intopackages/spec/src/migrations/registry.tsfrom one file per entry underpackages/spec/src/migrations/entries/. Gate (b) ofpackages/spec/scripts/build-schemas.ts, the def-removal gate and four sibling remedies still told an author to paste lines into, or delete lines from, that generated file. An author who followed them typed between generated markers, which the nextgen:migration-registryrun reverts andcheck:migration-registryfails first.Every such remedy now names the entry file and the generator command. Where the gate knows the id and the major, it prints the exact file and the one declaration it carries. Where the major is the author's to establish (check (c) proof 1), it names the directory and
entries/README.md. This is the route the retirement kit's checklist landed first (PR #20828, merged as745ac30a), quoted from.claude/skills/spec-property-retirement/SKILL.mdonmain:Gate (b) before, as printed at
05be35259:After (the template rendered for the test's fixture key; the pins below assert it from a real run):
The two docblocks the card names now say what the
guidanceslot is for: prescriptions for keys the shape does not declare (wrong-layer pointers, and the upgrade for a spelling removed from the shape). A key retired in place is aretiredKey()tombstone in the shape, never a guidance row, becauseshared/alias-integrity.test.ts("no guidance key is itself a declared key") makes a guidance row for a declared key dead. No guidance table entry changes.The dispatch's hypotheses, measured
H1 — every printed sentence that routes an author into
migrations/registry.ts.git grep -n -E 'migrations/registry\.ts|copy these lines|step in src/migrations' -- packages/spec/scripts/build-schemas.tsreturns 10 lines at05be35259and 2 at this head (both code comments). Positive control: the PM's four named sites (:789,:1128,:2637,:2836) are among the 10. I also read the lines that name a table without a file. Verdicts (line numbers at05be35259)::789manifest disappearanceentries/retired-defs/file per def, plus the generator.:1089check (a), aged-out route:1100:1127-1130gate (b):1183-1185(b2):1222-1224(b3):2578-2579def (b2) mirror:2619:2636-2639def-removal gate5908348030). Fixed like gate (b).:2781check (c) violation row:2824-2825check (c) proof 1entries/README.mdand the generator. It names the README rather than a file because the true major is the author's to establish.:2835-2836check (c) proof 3:3261-3262default-change remedysemanticentry to this major's step in src/migrations/registry.ts"H2 — the naming source.
entries/README.mddefines a file asMAJOR.ID.tswith/and:replaced by__. The generator'sshardNameForis, in its own words, "the whole naming rule, in one place", andloadEntriesrefuses a file whose name does not match its id. Every remedy prints its file name throughshardNameFor(withKINDS/ENTRIES_DIRfor the directory), imported from the generator. No second copy of the convention exists, and the README stays the one human description.H3 — whether the docblock text ships. Measured after
pnpm --filter @objectstack/spec build, withnpm pack --dry-run:src/ai/tool.zod.tsships verbatim throughfiles[](src/**/*.zod.ts).src/shared/strict-object.tsdoes not ship as source, but its edited JSDoc is indist/shared/index.d.tsanddist/shared/index.d.mts. Positive control: the untouchedaliasesdocblock is in the same two files.So this PR takes a
patchchangeset,.changeset/20809-guidance-slot-docblocks.md, and noskip-changeset.Landing site beyond the claimed surface: the generator
build-migration-registry.tsran its CLI at module top level, and in write mode it rewritesregistry.ts, so nothing could importshardNameForfrom it. The fix is at the producer: the CLI body moves intomain(), behind an entry guard spelled the waycheck-generated.ts(same directory) spells it, with realpath on both sides.shardNameFor's docblock now names its second reader.Why not move the rule into a new
scripts/lib/module, or importscripts/invoked-as.mjs: CI runscheck:migration-registryonly when a changed path is in its selector arm (scripts/ci/select-gate-families.sh,migration_registry). That arm lists the generator's read set path by path. A new import would add an input outside the arm, and a later edit to that input alone would skip the gate. The guard adds onlynode:fs'srealpathSync, so the generator's read set is unchanged.New test
packages/spec/scripts/build-migration-registry-entry.test.tspins both directions of the guard, each as a realtsxprocess: a direct run reaches the generator, a run through a symlink also reaches it, and an import prints nothing beyond what the importer asked for.Tests (at
c2e5b3b81)pnpm --filter @objectstack/spec exec vitest run --project repo scripts/build-schemas-check-mode.test.ts, the whole file: 88 passed (88), Duration 995 s, lock verdictcommand-exit 0.entries/retired-keys/plusshardNameFor(major, key), the declaration and the generator command. It contains no paste line, nocopy these linesand nomigrations/registry.ts.pnpm --filter @objectstack/spec test(thelocalproject, which includes the new guard test): 585 files, 17208 passed | 1 todo,command-exit 0.pnpm --filter @objectstack/spec typecheck(tscon src,tsconfig.scripts.jsoncovering both changed scripts and the new test, and the test-typecheck ledger):command-exit 0.pnpm --filter @objectstack/spec check:migration-registry:--self-test: ok,src/migrations/registry.ts is current (330 semantic, 242 retired-key, 211 retired-def). This is byte-identical to the reading taken before any edit.Ablations (one-shot; each leg went through
scripts/ablation-replace.mjs, which proves the mutation landed and the restore matched HEAD)main()called unconditionally). The import probe goes red: the importer printed✓ wrote src/migrations/registry.ts (330 semantic, 242 retired-key, 211 retired-def). Both run probes stay green. The first A1 attempt was refused by the tool before any run, because the replacement was a substring of the anchor and its count could not rise. It is recorded here as a no-op, and the re-run used a distinct marker.argv[1]comparison (no realpath). The symlink run probe goes red: exit 0 with empty output, the silent no-op the guard spelling exists to prevent. The other two stay green.[]pairs parsed, against the 2 expected). The other 4 stay green.git diff HEADempty.Gates
Derived from this diff with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: the change set is 7 paths against the merge base05be35259, giving 88 commands. All 88 were run atc2e5b3b81. The--ranreconciliation reads 88 derived — 86 run, 2 NOT-MEASURED, 0 UNRUN.check:migration-registry,check:authorable-surface,check:api-surface,check:docs,check:doc-authoring,check:nul-bytes,check:empty-changeset,check:changeset-no-major,check:cross-package-test-inputs,check:test-source-alias,check:type-check-coverageandcheck:issue-citations. Three of them needed a second run:check:doc-formula-expressionsandcheck:lean-entry-closurefirst refused with exit 3 (prerequisite not met). Both passed after aturbo run buildof their 14-package closure.check:pm-dispatch-gateswas first cut by this run's own 900 s per-command cap, which is not counted. The uncapped re-run printeddispatch-gates self-test: 1976 cases passin 1259 s. Its exit 0 is read from the absence of pnpm's failure marker, not from a captured status.check:dual-build-cjs-loadsandcheck:type-check-debt. Both exit 3 (prerequisite not met): they read every workspace package'sdist/, whichlint.ymlbuilds first, and that whole-tree build was not run in this worktree on a shared 4-core box. CI runs them.pnpm --filter @objectstack/spec check:generated(named by the dispatch): "All 15 generated artifacts are up to date", exit 0.origin/mainadvanced 11 commits past the base during these runs. None of them touches these 7 paths orsrc/migrations/, so they are not merged here; the PR's CI runs on the merge ref.Acceptance notes
scripts/lib/sharded-artifacts.ts(about:353) writes the manifest sharddescriptioninto everyjson-schema.manifest/shard, and that text still says "must be declared in RETIRED_DEFS_BY_MAJOR (src/migrations/registry.ts)". It is the same family as the H1 rows. It is not changed here, because changing it regenerates every manifest shard, the hottest generated artifact. Carrier: none.src/data/object.zod.ts'sUNKNOWN_KEY_GUIDANCEhas a section comment, "Tombstones for RETIRED keys", that uses the same loose term the two docblocks used. The card scoped this to two docblocks with no table edits. Carrier: none.scripts/lib/default-changes.ts(about:52) has a code comment that still says "semantic entry to the major's step in src/migrations/registry.ts". The printed remedy it describes is fixed here. Carrier: none.patch) and AGENTS.md'sskip-changesetcriterion ("a diff that publishes nothing from any released package"). os-dev's fast path lists comments as non-publishing; this PR measured the shipped bytes instead.Review
A docblock edit in
packages/spec/srcowes an at-tier contract review at landing (run by the seat). No governed surface is touched.Generated by Claude Code