Skip to content

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

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-5
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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.

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 [finding] runtime warnings outside the migration ledger print tracker numbers to authors and operators: the AutomationEngine resumeAuthority boot warning (#3801 / #5561 / #3823) and two objectql data-event warnings (#4639 / #4626) #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

…igration entries state each lesson in words, not tracker numbers

Stage 5 of the staged sweep: the reason / replacement / acceptanceCriteria
text (and one surface) of 25 ADR-0087 semantic entries in these six
families no longer cites a tracker id. Each site now says what the cited
ruling, measurement or fix decided; ADR ids stay. Text only.

Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH
Co-authored-by: Claude <noreply@anthropic.com>
…pi-, dataset-, hook- and metadata- families; regenerate the migration registry and its projections

COVERED_PREFIXES gains the six families and the REWRITTEN floor rises from
88 to 113 (the 25 entries this stage rewrote). The three assertions are
unchanged. registry.ts, spec-changes.json and the protocol upgrade guide are
regenerated by their generators; a patch changeset records the text change.

Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/declarative-endpoints.mdx (via /api/v1/meta/api (route, a path literal in reason; a path literal in semantic))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — 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 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 — 137 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 0bbe4005e82fce2058238720cac7ba5cd2f182d5 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0a2f5204d77f747d04b739fc213b9dc5f1fe2c54 — the merge of head d24a253bd04582f05875143dee32077168bdd193 into base 0bbe4005e82fce2058238720cac7ba5cd2f182d5, 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 0a2f5204d77f747d04b739fc213b9dc5f1fe2c54 && git checkout 0a2f5204d77f747d04b739fc213b9dc5f1fe2c54
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0bbe4005e82fce2058238720cac7ba5cd2f182d5 d24a253bd04582f05875143dee32077168bdd193 && git checkout -B drift-repro 0bbe4005e82fce2058238720cac7ba5cd2f182d5 && git merge --no-ff d24a253bd04582f05875143dee32077168bdd193

node scripts/docs-audit/affected-docs.mjs --json 0bbe4005e82fce2058238720cac7ba5cd2f182d5

⚠️ 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 0bbe4005e82fce2058238720cac7ba5cd2f182d5 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d24a253bd04582f05875143dee32077168bdd193
Local-runs: none

Head unmoved across the review (re-read at the end: same sha, open, draft, mergeable_state: clean). Merge base 9e9bb464 (the stage-4 squash); origin/main is 0bbe4005, one commit past it (the landed #20511: a qa- entry, a retired key, registry.ts). Inputs: card #20233 (body + all 19 comments), PR #20522 (body, 30-file list, net diff against main), the head's check-runs. Read-only throughout: git archive extracts of base and head parsed by my own AST and tokenising instruments; nothing built, run or re-run.

① Derived judgments

  1. Text only — holds, mechanically. For each of the 25 entry files, base vs head parsed and compared: every import declaration identical; every // / /* */ comment token identical; every property other than reason / replacement / acceptanceCriteria identical by evaluated value (id, from / to, every matcher); the code skeleton with joined-string runs collapsed identical; the property-key sequence identical. 25 files compared, 0 with a non-prose change. Only the prose fields' evaluated values differ (reason in 25, acceptanceCriteria in 2, replacement in 6). The one allowed surface move is 18.dataset-measure-aggregate-field-type-refused (base carried #16099 and #17560, head carries none) — the instrument's only note, so ruling A 5858839916 had exactly one site here. The instrument fails first on an in-memory copy: 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; dark on a prose-only edit and on a re-split string run. The 25 files are exactly the PR's entry files; the seven entries of these families that carried no number (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) read 0 on base and are absent from the file list; no entry file outside the 25 differs; no rest-, qa-, turso-, stack-, cube- or data__ file is touched. A head-prose scan of the six families for issue / card / PR / batch / record / summon / item + number, objectui# / framework# / cloud#, and any run of six or more digits finds nothing (base carried PR #5279, PR #5203, PR #6532 ×2, PR #5049, PR #11989, Card #20078, seven batch #NNN and two record NNNNNNNNNN spellings, all gone). So none of packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234's comment lines moved (823 comment lines with a tracker id on both sides), and no entry's identity or matching moved.
  2. Truth of the rewrites — holds; no lesson lost, softened or overstated. Read against single REST reads of all 75 cited ids (71 bare incl. the framework#3956 spelling, 3 objectui#; 4 answer 404 on the issues endpoint, 6148, 6573, 13135, 11513, the same four the dev names; nothing else is dead), the ruling-record comments by id, and main's own files:
  3. Census — reproduced exactly with my own instrument (AST over every file under entries/, string runs under the three keys, #\d{4,5}\b; surface apart; comment tokens apart; 747 files, 311 semantic entries, 0 unevaluable). Base 9e9bb464: whole tree 571 (36/503/32), surface 7, short 61, comment lines 823; field- 10 entries / 28 (5/20/3, 20 distinct), export- 3 / 21 (0/21/0, 15), hook- 4 / 17 (0/17/0, 12), api- 5 / 16 (0/14/2, 11), metadata- 7 / 16 (1/15/0, 11), dataset- 3 / 12 (2/8/2, 9) + 2 surface = 110 (8/95/7) in 24 of 32 entries, 75 distinct tokens (71 bare, framework#3956, 3 objectui#), short 14, cross-repo spellings 5, word+number spellings 16. Head: all six families 0, surface 0, short 3 (the #12s), cross-repo 0, word+number 0; the eleven earlier families still 0; whole tree 461 (28/408/25), surface 5, short 50, comment lines 823. Lit control 17.aggregation-node-distinct-retired 7 (1/6/0) both sides. On origin/main 0bbe4005 the tree reads 571 / 7 / 825 comment lines (the new qa- entry's two), as the dev reports for the merged tree. Nothing the three fields carry was missed, cross-repo and "issue NNNN" spellings included.
  4. Pin — widened, not weakened. COVERED_PREFIXES 11 → 17 (field-, export-, api-, dataset-, hook-, metadata-); the match is s.id.startsWith(prefix) over step.semantic (:215), so data- selects neither datasource- nor dataset-, api- does not select apimethod-, and PR feat(spec,rest)!: the served OpenAPI info carries the publisher's api.documentation identity; api.documentation.version retired #20512's retired-key file 18.api__RestApiConfig__documentation.version.ts (no id, not semantic) is not selected. No rest- prefix entered (0 occurrences). REWRITTEN 88 → 113, and the 25 added ids are exactly the 25 changed entries (set-diffed), field-currency-scale-refused included as the one that carried only batch and record numbers. The diff's only lines outside the header comment and the two lists is the one COVERED_PREFIXES line; the three it blocks (:274 detector self-test, :282 every-prefix-non-empty + REWRITTEN containment, :290 verbatim-block-in-stdout + TRACKER_ID absent) and every expect are textually identical. No other test moved: on main, no toMatch / toContain / toBe assertion cites any of the 75 removed numbers except the three #5040 runtime-hint assertions ([finding] runtime warnings outside the migration ledger print tracker numbers to authors and operators: the AutomationEngine resumeAuthority boot warning (#3801 / #5561 / #3823) and two objectql data-event warnings (#4639 / #4626) #20513's family, ③), and the only test naming one of the 25 ids, export-job-family-retirement.test.ts:91, asserts the public surface, not the prose.
  5. Generated artifacts — exact. registry.ts parsed at base and head: 311 entries both sides, comment tokens and code skeleton identical, exactly the 25 ids differ, 0 non-prose differences; all 311 head registry entries equal the head entry files byte-for-byte on surface / reason / replacement / acceptanceCriteria; 0 tracker ids remain in the seventeen covered families' four fields. spec-changes.json: 12 strings removed, 12 added, every one an entry prose field (the six protocol-17 entries, each in both sections). docs/protocol-upgrade-guide.md: 10 lines removed, 10 added, each a fragment of the corresponding base / head entry field. Only protocol-17 entries appear, as the generators project.
  6. Check-runs on d24a253b (last read 2026-09-28T22:58Z): 35 runs, 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke — roster skips), 0 failed, 0 still running. Green include Lint & Repo Gates (the registry / spec-changes / upgrade-guide / generated / doc-authoring / changeset gates), Check Changeset, Build Core, all four Type Check jobs and TypeScript Type Check, Spec property liveness, Temporal Conformance, Dogfood Verify CLI and the three Dogfood shards, all six Test Core shards, and the single-writer / single-issue / Part-of guards. Part-of PR must not also close its card is green: Part of #20233 is the body's only reference form. The dev's local runs are superseded by these verdicts.
  7. Changeset — every sentence true. '@objectstack/spec': patch; Clause-②: no on its own line; "some of which no longer resolve" (four 404s), "some in another repository" (objectui#, framework#), "ADR ids are kept", "Text only: no entry id, from / to, conversion or matching logic changes", "One entry's surface … drops the two tracker numbers it carried and names nothing else differently" (the only other wording change there is "under No layer refuses an incoherent aggregate / field-type pair — a dataset measure avg over a datetime works on SQLite and errors on Postgres #16099" → "in a later change"), "the generated … carry the same text" — all verified above.

② Semver level

patch is correct: no accept set moves, no export, key or matcher changes; only the author-shown guidance text of 25 entries, their three generated projections, a widened queue-tier pin and a changeset. Clause-②: no with no arm is the well-formed declaration: no new authorable key, no accept-set narrowing or widening, so no Clause-② review is owed beyond this at-tier record. Check Changeset and Lint & Repo Gates (check:changeset-no-major, check:adr-0087-registration) are green on the head. The two commits' trailers carry no model identifier.

③ Boundary flags

Implemented-by: claude/issue-20233-migrate-meta-tracker-free-stage-5
Reviewed-by: session_01ARcDurZ5j34RdqsGgc4jgH

VERDICT: PASS

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants