Commit 0e1afe8
fix(spec): os migrate meta guidance for the field-*, export-*, api-*, dataset-*, hook-* and metadata-* migration entries states each lesson in words, not tracker numbers (stage 5) (#20522)
Part of #20233
Stage 5: the field-, export-, api-, dataset-, hook- and metadata-
families.
Clause-②: no
**Stage 5 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `from` / `to`,
conversion or matching logic moves, and the chain rewrites exactly what
it rewrote before. One `surface` moves, under ruling A of the stage-1
ACCEPT (`5858839916`): it carried two tracker numbers.
## What this does
`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on #19123 (`5749154545`) sets the shape: the
lesson in words, and no number, dead or alive; a cross-repo number is
still a tracker number.
This stage covers the next six families, `field-`, `export-`, `api-`,
`dataset-`, `hook-` and `metadata-`: **110 sites → 0** in the three
prose fields and **2 → 0** in `surface`, across 25 entry files. Each
site now says what the cited ruling, measurement or fix decided. ADR ids
stay. `registry.ts`, `spec-changes.json` and
`docs/protocol-upgrade-guide.md` are regenerated from the entries
(`gen:migration-registry`, `gen:spec-changes`, `gen:upgrade-guide`),
never hand-edited. The pin now holds seventeen families.
## Census — tracker ids in the author-shown fields
**Instrument.** Stage 4's TypeScript-AST census, the same script: for
each entry object literal under
`packages/spec/src/migrations/entries/**` it evaluates `replacement`,
`reason`, `acceptanceCriteria` and (separately) `surface`, joining
string literals with `+`, and counts `#` followed by 4 or 5 digits at a
word boundary. On base `9e9bb464` it reads the whole tree at **571**
sites / 7 `surface` / 61 short, which is stage 4's recorded after-count.
Unevaluable fields: 0.
**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), before and after.
- **Dark (comment lines):** 823 `//` / docblock lines in entry files
carry a tracker id, and none is counted; 823 before and after. Comment
lines are #20234's surface, and this PR touches none (proved below).
**Base `9e9bb464`:** `field-` 10 entries, **28** sites (5 / 20 / 3);
`export-` 3, **21** (0 / 21 / 0); `hook-` 4, **17** (0 / 17 / 0); `api-`
5, **16** (0 / 14 / 2); `metadata-` 7, **16** (1 / 15 / 0); `dataset-`
3, **12** (2 / 8 / 2), plus **2** in `surface`. **110** sites (8 / 95 /
7) in 24 of the 32 entries; 75 distinct ids (71 bare, 1 spelled
`framework#`, 3 `objectui#`). Short numbers: 14.
**After this PR:** all six families **0**, `surface` 0; the eleven
earlier families still 0; whole tree **571 → 461**, `surface` **7 → 5**,
short **61 → 50**. The PM's rough line count (126 sites, 25 files) is a
wider instrument; the AST reading is 110 in 24 files, and the 25th file
carries only short decision-batch numbers and ruling-record ids.
| entry | sites (replacement / reason / acceptanceCriteria) | surface |
short numbers |
|---|---|---|---|
| `17.api-runtime-create-withdrawn` | 9 (0 / 7 / 2) | | |
| `17.export-axis-opt-in` | 7 (0 / 7 / 0) | | |
| `17.export-field-meta-constraints-retired` | 8 (0 / 8 / 0) | | |
| `17.field-runtime-create-withdrawn` | 9 (0 / 6 / 3) | | |
| `17.hook-context-session-roles-retired` | 6 (0 / 6 / 0) | | |
| `17.hook-register-empty-object-target-refused` | 8 (0 / 8 / 0) | | |
| `18.api-assembled-entry-split` | 1 (0 / 1 / 0) | | 1 |
| `18.api-error-retry-after-unit-in-key` | 3 (0 / 3 / 0) | | 1 |
| `18.api-runtime-config-durations-unit-in-key` | 3 (0 / 3 / 0) | | 1 |
| `18.dataset-filter-nested-relation-equality-array-refused-at-save` | 2
(0 / 2 / 0) | | |
| `18.dataset-measure-aggregate-field-type-refused` | 5 (1 / 2 / 2) | 2
| 2 (1 kept) |
| `18.dataset-measure-selecting-aggregate-field-type-refused` | 5 (1 / 4
/ 0) | | 3 (1 kept) |
| `18.export-job-family-retired` | 6 (0 / 6 / 0) | | 2 |
| `18.field-currency-scale-refused` | 0 | | 2 |
| `18.field-max-length-malformed-or-misplaced-refused` | 8 (3 / 5 / 0) |
| |
| `18.field-min-length-malformed-or-misplaced-refused` | 3 (1 / 2 / 0) |
| |
| `18.field-multiple-non-capable-type-refused` | 4 (0 / 4 / 0) | | 1 |
| `18.field-predicate-reference-traversal-refused` | 2 (0 / 2 / 0) | | |
| `18.field-scale-precision-integer-refused` | 2 (1 / 1 / 0) | | 1
(kept) |
| `18.hook-register-undispatched-lifecycle-event-refused` | 3 (0 / 3 /
0) | | |
| `18.metadata-customization-protocol-retired` | 4 (0 / 4 / 0) | | |
| `18.metadata-endpoints-switch-radius-repartitioned` | 3 (0 / 3 / 0) |
| |
| `18.metadata-manager-config-cache-ttl-unit-in-key` | 3 (1 / 2 / 0) | |
|
| `18.metadata-manager-config-inert-cache-keys-retired` | 3 (0 / 3 / 0)
| | |
| `18.metadata-plugin-additional-types-retired` | 3 (0 / 3 / 0) | | |
| **total, 25 entries** | **110 (8 / 95 / 7)** | **2** | **14 (3 kept)**
|
The seven entries of these families that carried no number are
untouched: `api-endpoint-cache-ttl-unit-in-key`,
`field-inline-and-related-list-columns-closed`,
`field-master-detail-set-null-refused`,
`field-reference-to-spelling-retired`, `hook-timeout-unit-in-key`,
`metadata-changed-event-payload-retired`,
`metadata-item-name-grammar-enforced`.
## Text only — proved by a base-vs-head AST comparison
For every entry file this PR changes, both versions (`9e9bb464` and the
head) are parsed and compared: every import declaration; every property
other than the three prose fields, by evaluated value (so `id`, `from` /
`to` and any matcher); every comment token in the file; and the code
skeleton, token by token with each run of joined string literals
collapsed to one. `surface` is allowed to differ only where the base
value carried a tracker id and the head value carries none. **25 files
compared, 0 with a non-prose change**; one note, the ruling-A `surface`
of `18.dataset-measure-aggregate-field-type-refused`. The instrument is
shown able to fail first: on an in-memory copy it reports DETECTED for a
mutated `id`, a mutated comment, a mutated `surface` whose base carried
no tracker id, a mutated code token and a mutated import, and stays dark
on a prose-only mutation. So none of #20234's comment lines moved, and
no entry's identity or matching moved.
## Every citation read, and what the text now says
Each cited id was read with a single-card REST read (body plus the
ruling, measurement or landing comments), resolved against the
repository its sentence names: 72 in this repository (one spelled
`framework#`, the repository's old directory name) and 3 in
`objectstack-ai/objectui`. Ids are in code spans so this body posts no
cross-references. **4 ids answer 404** on both the issues and the pulls
endpoint (re-probed with a 200 control, `14478`); those sentences are
rewritten from what `main` records, listed in Acceptance notes.
**`api-` (11 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `5488` | Maintainer, 2026-08-07: flip `api` to `allowRuntimeCreate:
false` and refuse at the write inlet (remove, not converge the read
path); re-entry only with a real consumption path. | the measurement and
the ruling were already in the sentence; the id is dropped |
| `5040` | The declarative endpoint executor project; its acceptance
step moved showcase's endpoints to the artifact route, live. | "showcase
uses the artifact route, and its declared endpoints serve live" |
| `4052` | `BatchOptions.validateOnly`, retired the same way (a runtime
verdict, no D2). | named by the key, which the sentence already carried
|
| `5279` (PR), `5189`, `5203` (PR) | The publish gate for `api` drafts;
`publishPackage` and load-time `buildEndpointIndex` running the endpoint
gate. | named by the functions the sentence already carried |
| `2657` | Studio metadata coverage: Part B asks which un-typed concepts
(`apis` among them) become registered types. | "if the Studio
metadata-coverage work promotes `apis` to a registered type WITH A REAL
CONSUMPTION PATH" |
| `5311` | The direct-active `saveMetaItem` write was a third path past
the namespace and duplicate gates; closed as subsumed by the `5488`
ruling. | "The same refusal closes the direct-active write too, which
had been a third path past the endpoint namespace and duplicate-path
gates." |
| `18576` | Maintainer, 2026-09-17, option B: narrow the `./api` entry,
rather than add a bundle-weight rule (A) or accept the weight (C). |
"Maintainer ruling of 2026-09-17, option B (narrow the entry, rather
than add a bundle-weight rule to the browser-reachability ledger or
accept the weight as it stood)" |
| `14478`, `15677` | Maintainer ruling B of 2026-09-02: a duration's
unit lives in the key name, no offender grandfathered; the `api/` stack
of that ruling. | "Maintainer ruling B of 2026-09-02 on duration-shaped
keys"; trailing ids dropped |
**`export-` (15 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `6350` | The stock reconciliation of the v17 train's breaking
changesets against the ledger, which backfilled this entry. |
"Registered (backfilled) by the stock reconciliation that compared the
breaking changesets already on the v17 release train against this
ledger" |
| `3544`, `3710` | The user-level export axis, and its extension to the
CSV attachments scheduled reports mail out. | "the export axis, and its
extension to the CSV attachments scheduled reports mail out" |
| `6148` | **404** — see Acceptance notes. | "the gate that makes a
breaking changeset state its ADR-0087 disposition" |
| `3956` (spelled `framework#`) | The import dry run skipped the
field-level validation the real write ran; the hand-copied pre-check
mirror was added to close it. | "added when the dry run was found
skipping the field-level validation the real write ran" |
| `4633`, `6532` (PR) | Maintainer, 2026-08-06, ruling D: a
validate-only protocol operation, so the dry run's prediction is the
engine's verdict; the mirror retired. | "the maintainer's 2026-08-06
ruling D (a validate-only protocol operation, so the dry run's
prediction is the engine's verdict by construction)" |
| `4484`, `5540`, `6011` | The `findStream`, `IStorageService.list` and
`actor-user-roles-to-positions` retirements. | named by their surfaces,
which the sentence already carried |
| `6536` | The eight keys left read by nothing after the mirror retired,
deferred to their own sweep. | "this is the removal the dry-run change
deliberately deferred to a sweep of its own" |
| `17158` | Maintainer, 2026-09-12, ruling A: retire the family,
`IExportService` and `ScheduleExportInput`; `ScheduleState` with it
unless a live consumer is measured. Landing route A, 2026-09-24; scope
note, 2026-09-25. | "maintainer ruling A of 2026-09-12 (…), the landing
route the maintainer ruled on 2026-09-24 (route A: …), and a scope note
the maintainer agreed on 2026-09-25" |
| `objectui#10247` | The console retires its own async-export path
first. | "objectui retires its own side of the unimplemented
async-export path first"; "which carries objectui's own retirement" |
| `19543` | Three sibling list doors declared `limit` / `cursor` and
never read them; the export-job list's door folded into this retirement.
| "one of three sibling list doors found declaring them and never
reading them" |
| `16320` | The seven cron-typed positions nothing read, retired (three
on these defs). | "once the retirement of the cron-typed positions
nothing read had deleted theirs"; "Those earlier cron-position
deletions" |
**`field-` (20 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7893` | Maintainer, 2026-08-12: retire the runtime `field` write
channel rather than build a read path. | the measurement and the ruling
were already in the sentence; the id is dropped |
| `5488`, `4052` | The `api` and `validateOnly` withdrawals. | "the
`api` withdrawal's rationale reused (`api-runtime-create-withdrawn`)";
named by key |
| `7743`, `7894` | The field overlay lock (`NOT_OVERRIDABLE`); the
plural `/meta/fields/` spelling folded onto the singular. | "The field
overlay refusal"; the plural door named by its path |
| `8169` | The `_diagnostics` envelope asserts well-formedness only; it
has no "in effect" axis. | "(the envelope has no "in effect" axis)" |
| `11566`, `11989` (PR), `11950` | Maintainer, 2026-08-24: tighten both
halves of `maxLength` (value shape and applicable types); shipped on
17.x; registered in a follow-up. | "Maintainer ruling of 2026-08-24,
tightening both halves — the value's shape and the types the key applies
to"; "registration was deferred to a follow-up" |
| `11875` | Maintainer, 2026-08-25, option 1: the write seam enforces
`maxLength` for `signature` / `qrcode`, then both join the
bounded-string set. | "which joined once the write seam enforced a
declared bound on them" |
| `11431` | The SQL driver stops reading a malformed bound as
authoritative (the `varchar(0)` plan). | "until it was taught to stop
reading a malformed bound as authoritative" |
| `8321` | `scale` / `precision` refused as non-integer or negative —
the house pattern. | "the house pattern the `precision`/`scale` integer
refusal set" |
| `11949` | Maintainer, 2026-08-25, option B: `minLength` is
`int().min(1)`, zero refused, the `maxLength` template in full. |
"Maintainer ruling of 2026-08-25 (option B, the lower bound at 1) … the
defect pair the 2026-08-24 ruling closed for `maxLength`" |
| `17469`, `11437` | Maintainer, 2026-09-13, option 1′: `multiple: true`
refused outside the multi-capable types — the earlier `radio` rule
generalised; the driver derives its JSON column from the spec predicate.
| "Maintainer ruling of 2026-09-13, option 1′ (the earlier rule refusing
an authored `radio` with `multiple: true`, generalised)" |
| `objectui#8886`, `objectui#8937` | The console's related list shaped
its parent filter from the spec predicate; its follow-up recorded the
driver half as owed. | "the console's related list pinned the divergence
on the consumer side when it began shaping that filter from the spec
predicate, and its follow-up recorded the driver half as owed and not
filed" |
| `20078` | Triage, 2026-09-25, remedy A: refuse the traversal at
authoring with a prescription; hydrating the field level is a capability
of its own. | "Triage routed this on 2026-09-25 to remedy A: refuse the
traversal at authoring, with a prescription." |
| `18682` | A validation rule or visibility predicate reads one hop
through a lookup. | "is served, one hop deep, and stays accepted" |
| `7501` | `scale` enforced at write time: an over-scale write refused,
never rounded. | "when `scale` was made enforced at write time (an
over-scale write refused, never rounded)"; "the write-time `scale`
enforcement" |
**`hook-` (12 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4839`, `5049` (PR) | Both `session.roles` admin exemptions removed;
the record lock and the delegation guard back on the one permission
vocabulary. | "An earlier fix removed both readers, returning the record
lock and the delegation guard to the one permission vocabulary" |
| `4579`, `4657` | The `openApi31` and `activationEvents` retirements. |
named by their surfaces, which the sentence already carried |
| `3733` | Measured: a key removed from a non-strict schema parses clean
and is silently dropped. | "as a removed field key was measured to be" |
| `5050` | This retirement's own card. | trailing id dropped |
| `4281` (PR) | An empty hook target is not "no target": closed at the
two metadata doors. | "An earlier breaking fix established that an empty
hook target is not "no target""; "that fix's headline failure mode" |
| `5928` | The `excludeObjects` face (global except named objects); it
declined to change the matcher's read in passing. | "The later
`excludeObjects` face (a hook global except for the objects it names)";
"the `excludeObjects` change declined to do it in passing" |
| `6573` | **404** — see Acceptance notes. | trailing id dropped; the
entry states the change |
| `4001` | The unknown-key strictness campaign (ADR-0078). | trailing id
dropped (ADR-0078 kept) |
| `3195` | The hook taxonomy collapsed from 18 events to the 8
dispatched; a registration guard warns on the rest. | "the change that
collapsed the hook taxonomy to the eight dispatched events made this
branch a warn" |
| `17713` | This refusal's own card. | trailing id dropped |
**`metadata-` (11 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `12057` | Maintainer, 2026-08-29: retirement adopted, re-scope
rejected (the card's ruling comment is no longer on it; `main` records
it). | "the maintainer's ruling of 2026-08-29 adopted retirement and
rejected a re-scope" |
| `13135` | **404** — see Acceptance notes. | "executed widened to the
full coupling set the fork report on that ruling measured" |
| `11513` | **404** — see Acceptance notes. | "the 2026-08-24
lock-and-clone ruling (lock the packaged base, customize a clone) left
deliberately unchartered" |
| `15542`, `15854` | Maintainer, 2026-09-06, ruled together (2 + A):
every `endpoints.*` switch gates exactly the face its name states; the
whole-store family gets its own key. | "The maintainer ruled the two
together on 2026-09-06 as one principle: …" |
| `15543` | No shipped boot path constructs a `RestServerConfig`. | the
measurement was already in the sentence; the id is dropped |
| `15624` | The outer cache keys read by nothing, retired on their own
(the owning seat's ruling). | "(the owning seat's ruling, conditioned on
the measurement below and re-taken on the merged ref)"; "retired on its
own under ADR-0049" |
| `14478` | Maintainer ruling B of 2026-09-02 on duration units. |
"Maintainer ruling B of 2026-09-02 on duration-shaped keys"; "the
duration-unit rename" |
| `8586`, `8421` | Maintainer, 2026-08-14, jointly: remove
`additionalTypes`, and refuse unknown `/meta` types by the static
registry. | "maintainer ruling of 2026-08-14: remove the key, jointly
with refusing unknown types at the `/meta` boundary by the static
registry" |
| `4212` | Four of five declared plugin lifecycle hooks were never
invoked, `onInstall` among them. | "the plugin lifecycle's `onInstall`
(a documented hook with no invocation site)" |
**`dataset-` (9 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `19889` | Ruling A of 2026-09-24: the schema door refuses what the
compile face refuses; a field spec with no `$` key stays undescended. |
"ruling A of 2026-09-24, which made the schema door refuse what the
compile face refuses, drew the line there" |
| `20080` | Triage, 2026-09-25, remedy A: refine the two analytics
carriers; remedy B (stop the analytics door descending) changes what a
nested list means. | "Triage on 2026-09-25 routed the fix to the two
analytics carriers instead, rather than stop the analytics door
descending, which would change what a nested list means" |
| `16737`, `16099` | The measured defect: `AVG()` over a datetime is an
average year on SQLite and an error on Postgres. | the measurement was
already in the sentence; the ids are dropped |
| `16353` | The aggregate × field-type table, declared in the spec. |
named by the table, which the sentence already carried |
| `16099` (in `surface` and `acceptanceCriteria`) | The widening of the
refusal to `sum` / `avg` over every field class, registered
`not-required` against this entry. | "followed in a later change";
"widened by a later change" |
| `17560` | Director ruling B of 2026-09-13: the compile door enforces
the table for every aggregate. | "Director ruling B of 2026-09-13: …";
the sibling entry named by id |
| `15768`, `16236` | `measureResultType` typing `min` / `max` over
strings and over a `formula` return type. | the sentence already states
both |
| `17513` | Closed as a duplicate with zero rulings on it. | "the card
it cited is closed as a duplicate with zero rulings on it" |
## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`,
widened
`COVERED_PREFIXES` gains `field-`, `export-`, `api-`, `dataset-`,
`hook-` and `metadata-` (17 prefixes; `data-` still selects neither
`datasource-` nor `dataset-`, and `api-` does not select `apimethod-`:
the match is `startsWith`). The `REWRITTEN` floor rises from **88 to
113** ids: the 25 entries this stage rewrote. The three `it` blocks are
textually unchanged. The file keeps its stage-1 name; the header lists
the seventeen covered families.
## Ablation — the widened pin can fail on a new-family block
From committed state, HEAD `d24a253bd0`, in one lock turn, with
`scripts/ablation-replace.mjs` in wrap mode (it owns the mutation's
restore; the leg script adds its own `trap … EXIT INT TERM` that
restores `registry.ts` from `HEAD` by absolute path and checks the blob)
and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is
built from the generated `registry.ts`, so that is the file mutated.
- **Mutation.** In `registry.ts`, the `reason` of
`hook-register-undispatched-lifecycle-event-refused`: anchor `made this
branch a warn — ` → `made this branch a warn (#3195) — `. The tool read
anchor 1 → 0 and replacement 0 → 1, blob `94bc4938` → `b045dbfd`.
- **Mutate leg.** Spec build exit 0. Preflight: marker present in 4
built files. Pin: **red**, `1 failed | 2 passed` —
`hook-register-undispatched-lifecycle-event-refused: the printed
guidance cites a tracker id: expected '#3195' to be undefined`.
- **Restore.** Tool-proven: blob `94bc4938` == HEAD, `git diff HEAD`
empty.
- **Restore leg.** Spec build exit 0. The `--absent` preflight found the
marker in none of 224 built files, with the working tree clean against
HEAD. Pin: **green**, `3 passed`. Whole tree afterwards: 0 dirty paths.
## Verification
Final head **`d24a253bd0`**; every reading below was taken there. Every
heavy run went through `scripts/pm/os-verify-lock.sh`, with per-step
exit codes recorded separately.
- **Build:** `pnpm exec turbo run build --concurrency=2
--filter='@objectstack/cli^...'` gives `Tasks: 58 successful, 58 total`;
the ten packages outside that closure (for `check:dual-build-cjs-loads`)
give `Tasks: 68 successful, 68 total`.
- **Pin with its neighbour:** `pnpm --filter @objectstack/cli exec
vitest run --project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`,
`Tests 10 passed | 1 skipped` (the skip is the default-range file's own
`skipIf`).
- **CLI unit:** `test/vitest-tiers-partition.test.ts` and
`src/utils/spec-release-changes.test.ts` give `Test Files 2 passed`,
`Tests 28 passed`.
- **Spec, the whole `local` project:** `pnpm --filter @objectstack/spec
exec vitest run --project local --maxWorkers=2` gives `Test Files 573
passed (573)`, `Tests 16801 passed | 1 todo`. **The `repo` project:**
`Test Files 38 passed`, `Tests 690 passed`.
- **Typecheck:** `pnpm --filter @objectstack/spec typecheck` exits 0
(test layer: 53 files / 251 errors held in its ledger); `pnpm --filter
@objectstack/cli typecheck` exits 0 (3 files / 28 errors held).
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives **89** families. `--ran` over
the recorded exit codes reads **89 derived, 89 run, 0 NOT-MEASURED, 0
UNRUN**, all exit 0. They include `check:doc-authoring` ("16735
customer-facing string(s) across 1168 spec sources clean"),
`check:issue-citations`, `check:migration-registry` ("registry.ts is
current (311 semantic, 230 retired-key, 206 retired-def)"),
`check:spec-changes`, `check:upgrade-guide`, `check:generated` ("All 15
generated artifacts are up to date"), `check:org-identifier` ("no
removed session.tenantId alias"), `check:nul-bytes`,
`check:dual-build-cjs-loads` (104 require entry points across 66
packages load), `check:type-check-debt`, `check:adr-0087-registration`
and `check:changeset-no-major`. `check:dual-build-cjs-loads` first
exited 3 (`PREREQUISITE NOT MET`: ten packages had no `dist/`); after
the ten were built it exits 0, and that is the reading recorded.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 27 changed `.ts`
files reports 27 files, 0 errors, 0 warnings.
- The population is read from `eslint.config.mjs`:
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 27
are in it (no file-ignored notice).
- Invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so a text edit cannot move the
verdict on a file it does not touch.
- **Mergeability:** `main` moved one commit past the base, to
`0bbe4005`: the landed `20511` (a `qa-` entry, a retired key and
`registry.ts`). A driver-free bare-clone `merge-tree --write-tree` of
`d24a253bd0` against `0bbe4005` exits 0 with no conflicted path, and the
census over that merged tree reads 0 sites in all six families (whole
tree 461), so `main` was not merged in; CI's merge ref runs the registry
gates on the merged tree.
## Acceptance notes
- **Four dead ids, rewritten from what `main` records.** Each answers
404 on both the issues and the pulls endpoint, with a 200 control
(`14478`).
- `6148`: `scripts/check-adr-0087-registration.mjs`'s header (a
declared-breaking changeset must state its ADR-0087 disposition in
writing) — the same reading stage 4 used.
- `6573`: the objectql CHANGELOG entry "`engine.registerHook` refuses an
empty `object` target and a scope whose two faces cancel out", and
`engine.ts`'s refusal docblocks. It is this entry's own change, so the
trailing id is dropped; the entry already states the change.
- `13135`: `packages/metadata/CHANGELOG.md` ("retire the paper
metadata-customization protocol with its full coupling set … re-charter
of" the `12057` ruling, "the maintainer adopted retirement … 2026-08-29
… and" it "charters the full coupling set the fork report measured").
- `11513`: ADR-0126 names it the lock-and-clone ruling of 2026-08-24
(Salesforce-style: lock the packaged base, customize a clone), and its
section 9 records the per-field overlay layer as explicitly not
chartered.
- **One ruling read from `main`, not from its card.** `12057` answers
200 but carries only its triage comment; the 2026-08-29 ruling the entry
names is recorded in `packages/metadata/CHANGELOG.md`, and the sentence
says only what that record says.
- **Short numbers, ruling-record ids and acknowledgements went too
(invisible to the regex).** Eleven decision-batch numbers (`#43` ×2,
`#59` ×2, `#122`, `#127`, `#128`, `#145`, `#215`, `#218`, `#221`) and
five ruling-record comment ids (in `field-currency-scale-refused`,
`field-predicate-reference-traversal-refused` and
`dataset-filter-nested-relation-equality-array-refused-at-save`) are
numbers an author is shown and cannot follow, so each is dropped. So are
five maintainer acknowledgements (「同意,其他也同意」 in
`api-assembled-entry-split`, 「同意」 ×3 in `export-job-family-retired`,
「同意」 in `metadata-customization-protocol-retired`): they record only
that a batch was approved, and each sentence now states the ruling's
date and content instead. The two quotes that carry the lesson stay
verbatim: "both legs, table in spec" and 「`min`/`max` numeric plus
`date`/`datetime`; everything else refused」.
- **Three `Prime Directive #12` / `PD #12` spellings are kept.** They
name a rule in this repository's AGENTS.md, not a tracker item, like the
ADR ids; the earlier stages kept the same spellings in the `ui-`,
`plugin-` and `system-` families.
- **`surface`, per ruling A.**
`18.dataset-measure-aggregate-field-type-refused` was the only entry of
these families whose `surface` carried tracker ids (two). Its header now
names the later widening and the sibling entry in words, and the AST
comparison shows nothing else in it moved. No test or tool reads that
`surface`: outside the migration tree, the pin and the generated
projections, the id appears only in three earlier changesets' ADR-0087
disposition markers (and this PR's changeset).
- **"issue NNNN" / "PR NNNN" spellings, checked by hand.** A scan of the
six families' evaluated prose for any run of three or more digits and
for `issue` / `card` / `PR` / `batch` / `record` / `summon` / `item`
plus a number now finds only HTTP statuses, ports, byte counts,
durations, dates, commit shas, SQLSTATE and TS error codes, ADR ids and
example values.
- **No test pinned a removed tracker number of these entries.** A search
of test files for the 25 entry ids finds only the pin,
`export-job-family-retirement.test.ts` (it asserts `not a D2 conversion`
and the backtick-free `surface`, both unchanged) and comment lines; a
search for the 75 cited numbers in `toMatch` / `toContain` assertions
finds only unrelated digit runs and runtime strings outside this card
(`api-endpoint-step.test.ts` and `endpoint-executor.test.ts` assert a
`5040` hint, which is #20513's surface).
- **No open PR touches these six families.** Read twice: at the start of
this stage, 10 open PRs and 634 file rows; again just before opening
this one, 13 open PRs and 633 rows (the Version Packages PR `17076`
included both times). None carries a
`migrations/entries/semantic/NN.(field|export|api|dataset|hook|metadata)-*`
file. PR `20512` adds a retired-key file
`18.api__RestApiConfig__documentation.version.ts`; a retired key has no
id and is not in `step.semantic`, so the widened `api-` prefix does not
select it. PRs `20512`, `20504`, `20460` and `20458` add entries in
other families (`rest-`, `turso-`, `stack-`, `cube-`) and regenerate
`registry.ts`: ordinary concurrency, regenerate on merge.
- **Generated projections** (`spec-changes.json`,
`docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1–4;
only the protocol-17 entries appear in them.
- **What later stages pick up** (whole tree at this head, same
instrument): **461** prose-field sites in the other families, **50**
short numbers, **5** `surface` sites.
## Line budget
Entry files: **265 changed lines** (+147 / −118) across 25 files,
against the stage-1 ≈400 budget. The whole diff is **631 lines** (+372 /
−259) in 30 files. Of the rest, `registry.ts` is 265, the two
projections are 44 (`spec-changes.json` 24, the upgrade guide 20), the
widened pin is 29 and the changeset 28.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 397572e commit 0e1afe8
30 files changed
Lines changed: 372 additions & 259 deletions
File tree
- .changeset
- docs
- packages
- cli/test
- spec
- src/migrations
- entries/semantic
Lines changed: 28 additions & 0 deletions
| 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 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
79 | 81 | | |
80 | 82 | | |
81 | 83 | | |
| |||
89 | 91 | | |
90 | 92 | | |
91 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
92 | 98 | | |
93 | 99 | | |
94 | 100 | | |
95 | 101 | | |
96 | 102 | | |
97 | 103 | | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
98 | 107 | | |
99 | 108 | | |
100 | 109 | | |
| |||
118 | 127 | | |
119 | 128 | | |
120 | 129 | | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
121 | 140 | | |
122 | 141 | | |
123 | 142 | | |
| |||
129 | 148 | | |
130 | 149 | | |
131 | 150 | | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
132 | 154 | | |
133 | 155 | | |
134 | 156 | | |
| |||
138 | 160 | | |
139 | 161 | | |
140 | 162 | | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
141 | 168 | | |
142 | 169 | | |
143 | 170 | | |
| |||
Large diffs are not rendered by default.
Lines changed: 12 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
34 | | - | |
35 | | - | |
| 35 | + | |
| 36 | + | |
36 | 37 | | |
37 | | - | |
38 | | - | |
39 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
40 | 43 | | |
41 | 44 | | |
42 | 45 | | |
| |||
45 | 48 | | |
46 | 49 | | |
47 | 50 | | |
48 | | - | |
49 | | - | |
| 51 | + | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
52 | 55 | | |
| |||
Lines changed: 4 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
43 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
Lines changed: 7 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
| |||
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
33 | | - | |
34 | | - | |
| 35 | + | |
| 36 | + | |
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
| |||
45 | 47 | | |
46 | 48 | | |
47 | 49 | | |
48 | | - | |
| 50 | + | |
| 51 | + | |
49 | 52 | | |
50 | 53 | | |
51 | 54 | | |
| |||
Lines changed: 8 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
| 33 | + | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| |||
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
42 | | - | |
43 | | - | |
| 43 | + | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
50 | | - | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
57 | | - | |
| 58 | + | |
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
65 | | - | |
| 66 | + | |
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
| |||
Lines changed: 7 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
28 | | - | |
| 29 | + | |
29 | 30 | | |
30 | | - | |
| 31 | + | |
| 32 | + | |
31 | 33 | | |
32 | | - | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
0 commit comments