Repository navigation
Commit ebb66aa
fix(spec): registration remedies name the entry file and gen:migration-registry, never the generated registry.ts (#21021)
Fixes #20809
Clause-②: no
## What this changes
`RETIRED_KEYS_BY_MAJOR`, `RETIRED_DEFS_BY_MAJOR` and every step's
`semantic` list are generated into
`packages/spec/src/migrations/registry.ts` from one file per entry under
`packages/spec/src/migrations/entries/`. Gate (b) of
`packages/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 next `gen:migration-registry` run
reverts and `check:migration-registry` fails 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 as `745ac30a`), quoted from
`.claude/skills/spec-property-retirement/SKILL.md` on `main`:
> 在 `packages/spec/src/migrations/entries/retired-keys/` 加一个文件(命名见
`entries/README.md`),再跑 `pnpm --filter @objectstack/spec
gen:migration-registry` 把它生成进 `registry.ts`,⛔ 永不在那里手打。
Gate (b) before, as printed at `05be35259`:
```text
1. Declare each retirement by its EXACT key in RETIRED_KEYS_BY_MAJOR
(packages/spec/src/migrations/registry.ts) — copy these lines in:
'api/HttpFindQueryParams:distinct',
under `17: [ … ]` (create the major's array if it is the first).
```
After (the template rendered for the test's fixture key; the pins below
assert it from a real run):
```text
1. Register each retirement by its EXACT key in RETIRED_KEYS_BY_MAJOR. That table
is generated: add one entry file per key (named per
packages/spec/src/migrations/entries/README.md; these names are the generator's own) —
packages/spec/src/migrations/entries/retired-keys/17.api__HttpFindQueryParams__distinct.ts
export const entry = 'api/HttpFindQueryParams:distinct';
— with a `//` comment above the `export` saying why the key was retired (it
lands above the entry in the table), then run
`pnpm --filter @objectstack/spec gen:migration-registry`. ⛔ Never type an entry into the
generated table by hand: the next run reverts it, and check:migration-registry
fails it first. ...
```
The two docblocks the card names now say what the `guidance` slot 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 a `retiredKey()` tombstone in the shape, never a
guidance row, because `shared/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.ts` returns 10 lines at `05be35259`
and 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
at `05be35259`):
| Site | Printed | Verdict |
|---|---|---|
| `:789` manifest disappearance | "declare each one in
RETIRED_DEFS_BY_MAJOR (src/migrations/registry.ts)" | Same route. It now
prints one `entries/retired-defs/` file per def, plus the generator. |
| `:1089` check (a), aged-out route | "requires the exact key be
declared in RETIRED_KEYS_BY_MAJOR" | Correct as it stands. It states a
precondition, names no file and asks for no edit. |
| `:1100` | code comment, where the gate READS the table | Correct. Not
printed. |
| `:1127-1130` gate (b) | "copy these lines in" | Fixed (this card). |
| `:1183-1185` (b2) | "delete the entry from
packages/spec/src/migrations/registry.ts" | Same route. Each row now
names its entry file (from the entry's own id and major, so a key
carried through a declared def rename still names the file on disk),
plus the generator. |
| `:1222-1224` (b3) | same | Same route, same fix. It also says that a
respelled id renames the file. |
| `:2578-2579` def (b2) mirror | same | Same route, same fix. |
| `:2619` | info line "RETIRED_DEFS_BY_MAJOR, major N" | Correct. A
report, not a remedy. |
| `:2636-2639` def-removal gate | "copy these lines in" | Same route
(pointer `5908348030`). Fixed like gate (b). |
| `:2781` check (c) violation row | "no entry in RETIRED_KEYS_BY_MAJOR
names this EXACT key" | Correct. A diagnosis; the route is in the
footer's proof 1. |
| `:2824-2825` check (c) proof 1 | "(src/migrations/registry.ts) …
writes it down" | Same route. It now names the entry directory,
`entries/README.md` and the generator. It names the README rather than a
file because the true major is the author's to establish. |
| `:2835-2836` check (c) proof 3 | "(src/migrations/registry.ts)" | Same
route. It now names the entry directory; the manifest gate it defers to
prints the file. |
| `:3261-3262` default-change remedy | "add a `semantic` entry to this
major's step in src/migrations/registry.ts" | Same defect on the third
generated table. Fixed in place under the bounded rule: same defect
class, same file, mechanical, same gate family. |
**H2 — the naming source.** `entries/README.md` defines a file as
`MAJOR.ID.ts` with `/` and `:` replaced by `__`. The generator's
`shardNameFor` is, in its own words, "the whole naming rule, in one
place", and `loadEntries` refuses a file whose name does not match its
id. Every remedy prints its file name through `shardNameFor` (with
`KINDS` / `ENTRIES_DIR` for 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`, with `npm pack --dry-run`:
- `src/ai/tool.zod.ts` ships verbatim through `files[]`
(`src/**/*.zod.ts`).
- `src/shared/strict-object.ts` does not ship as source, but its edited
JSDoc is in `dist/shared/index.d.ts` and `dist/shared/index.d.mts`.
Positive control: the untouched `aliases` docblock is in the same two
files.
- Neither script ships.
So this PR takes a `patch` changeset,
`.changeset/20809-guidance-slot-docblocks.md`, and no `skip-changeset`.
## Landing site beyond the claimed surface: the generator
`build-migration-registry.ts` ran its CLI at module top level, and in
write mode it rewrites `registry.ts`, so nothing could import
`shardNameFor` from it. The fix is at the producer: the CLI body moves
into `main()`, behind an entry guard spelled the way
`check-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 import
`scripts/invoked-as.mjs`: CI runs `check:migration-registry` only 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 only `node:fs`'s `realpathSync`, so the
generator's read set is unchanged.
New test `packages/spec/scripts/build-migration-registry-entry.test.ts`
pins both directions of the guard, each as a real `tsx` process: 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 verdict `command-exit 0`.
- Gate (b): the remedy names `entries/retired-keys/` plus
`shardNameFor(major, key)`, the declaration and the generator command.
It contains no paste line, no `copy these lines` and no
`migrations/registry.ts`.
- New control: every file and declaration pair is parsed off the red
run's own output and written into the sandbox. The sandbox's own
generator (which refuses a misnamed file) then runs, and gate (b) is
silent on the re-run. The existing "registered by exact name ⇒ (b)
silent" case still passes.
- The def-removal gate, the manifest disappearance check, (b2), def
(b2), (b3) and the end-to-end unpublish repro each pin their entry file
and the generator.
- `pnpm --filter @objectstack/spec test` (the `local` project, which
includes the new guard test): **585 files, 17208 passed | 1 todo**,
`command-exit 0`.
- `pnpm --filter @objectstack/spec typecheck` (`tsc` on src,
`tsconfig.scripts.json` covering 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)
- **A1 — guard deleted** (`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.
- **A3 — raw `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.
- **B — gate (b) reverted to paste lines.** Of the six cases in gate
(b)'s describe block, two go red: the remedy case (no entry-file path)
and the new follow-the-printed-route control (`[]` pairs parsed, against
the 2 expected). The other 4 stay green.
- Every restore was proven: blob equal to HEAD and `git diff HEAD`
empty.
## 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 base `05be35259`, giving 88 commands. All 88 were run
at `c2e5b3b81`. The `--ran` reconciliation reads **88 derived — 86 run,
2 NOT-MEASURED, 0 UNRUN**.
- **86 exit 0.** These include `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-coverage` and
`check:issue-citations`. Three of them needed a second run:
- `check:doc-formula-expressions` and `check:lean-entry-closure` first
refused with exit 3 (prerequisite not met). Both passed after a `turbo
run build` of their 14-package closure.
- `check:pm-dispatch-gates` was first cut by this run's own 900 s
per-command cap, which is not counted. The uncapped re-run printed
`dispatch-gates self-test: 1976 cases pass` in 1259 s. Its exit 0 is
read from the absence of pnpm's failure marker, not from a captured
status.
- **NOT MEASURED:** `check:dual-build-cjs-loads` and
`check:type-check-debt`. Both exit 3 (prerequisite not met): they read
every workspace package's `dist/`, which `lint.yml` builds 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/main` advanced 11 commits past the base during these runs.
None of them touches these 7 paths or `src/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
shard `description` into every `json-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`'s `UNKNOWN_KEY_GUIDANCE` has 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.
- The changeset follows the dispatch's H3 rule (shipped text ⇒ `patch`)
and AGENTS.md's `skip-changeset` criterion ("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/src` owes an at-tier contract review
at landing (run by the seat). No governed surface is touched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8f78495 commit ebb66aa
7 files changed
Lines changed: 417 additions & 98 deletions
File tree
- .changeset
- packages/spec
- scripts
- src
- ai
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
147 | 151 | | |
148 | 152 | | |
149 | 153 | | |
| |||
423 | 427 | | |
424 | 428 | | |
425 | 429 | | |
426 | | - | |
| 430 | + | |
427 | 431 | | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
428 | 452 | | |
429 | | - | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
430 | 456 | | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
435 | | - | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
436 | 463 | | |
437 | | - | |
438 | | - | |
439 | | - | |
440 | 464 | | |
441 | | - | |
442 | | - | |
443 | | - | |
| 465 | + | |
| 466 | + | |
444 | 467 | | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
455 | 473 | | |
456 | | - | |
457 | | - | |
458 | | - | |
459 | | - | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
460 | 493 | | |
461 | 494 | | |
462 | | - | |
463 | | - | |
464 | | - | |
465 | | - | |
466 | | - | |
467 | | - | |
468 | | - | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
475 | 502 | | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
0 commit comments