docs(spec): re-anchor the dead tracker citations in data/ to the commits that decided them (stage 3) - #20533
Conversation
…its that decided them (stage 3) Every comment and docblock site under packages/spec/src/data that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes, and says in words what that commit decided. The files an open PR or an in-flight claim holds are left out. Comment-only: every file keeps its line count and no code token or test string moves. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…geset (stage 3) check:docs proved content/docs/references/data/feed.mdx stale: two lines project from the rewritten feed.zod.ts docblock. check:generated --fix regenerated only that artifact. The rewritten docblocks ship in src/**/*.zod.ts and dist, so @objectstack/spec takes a patch changeset. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
… anchor the sentence one line down api-derivation.test.ts splits DATA_ACTION_TO_API_OPERATION's TSDoc on the literal `[#6259]`, so that marker stays. Writing the deciding commit onto the marker's own line made the diff-scoped check-issue-citations verdict read the kept number as an added citation; the commit now sits on the next line of the same sentence, which carries no number. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…it (stage 3) The #20437 PR opened after this stage's claim and edits packages/spec/src/data/driver/turso.test.ts, so that file joins the stage's exclusions beside driver/turso.zod.ts. Its two comment sites return to the base bytes (blob 7fe99eb) and wait for a later stage. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…t (stage 3) The #20432 PR opened after this stage's claim and edits packages/spec/src/data/object.form.ts, so that file joins the stage's exclusions. Its one comment site returns to the base bytes (blob 60713e0) and waits for a later stage. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…e open PRs that now hold them (stage 3) The #20494 PR (object.zod.ts) and the #20444 PR (filter-logic-conformance.ts) opened after this stage's claim, so both files join the stage's exclusions. Their comment sites return to the base bytes (blobs befde04 and c9b32ac) and wait for a later stage. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6dee3f05c6b667049827c3f5b9a9378b117755ce && git checkout 6dee3f05c6b667049827c3f5b9a9378b117755ce
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1378ec7c0cce7f77155ec84eec06ab41e0ec9268 96fd49caa2e88a14b1c4a598323b5cf245c79e20 && git checkout -B drift-repro 1378ec7c0cce7f77155ec84eec06ab41e0ec9268 && git merge --no-ff 96fd49caa2e88a14b1c4a598323b5cf245c79e20
node scripts/docs-audit/affected-docs.mjs --json 1378ec7c0cce7f77155ec84eec06ab41e0ec9268
|
Contract reviewServed-tier: Read: card #20234 (body; every comment: triage ① Derived judgments(a) Scope and file surface: right. 47 files: 45 (b) Comment-only, no test string moves: right. (c) Anchor truth: right. 38 distinct sha-shaped tokens stand on added lines and none on removed lines; every one is a commit,
(d) Form C and D: right. Added-minus-removed tracker numbers is empty for every file, tests included: 50 distinct numbers on removed lines, 40 net-removed (163 sites), the other 10 ( (e) (f) Left-behind sites: right. (g) Census arithmetic: the diff's own count is 163 sites over 40 numbers in 44 files, equal to the dev's totals row by row; four of the numbers were spot-probed ( ② Semver level
③ Boundary flagsBlocking: none. Dev flags, each answered:
Non-blocking wording, each true of its commit: Escalated: none. CI at this head, the judging read: 34 check-runs, 31 success, 2 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
… commits that decided them (stage 4) (objectstack-ai#20548) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 4 of the staged sweep: the `data/` remainder. It covers the six `packages/spec/src/data/` files stage 3 (PR objectstack-ai#20533, landed `03b19d9cfd`) left out because an open PR held them, and nothing else. They are `object.zod.ts`, `filter-logic-conformance.ts`, `object.form.ts`, `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts`. Later stages cover the other areas, so this PR says `Part of`. The census below measured all six. Three of them carry comment or docblock sites that cite a tracker number answering 404. `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts` carry none, so they are not in the diff. Every such site has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123). That is **19 sites on 19 lines in 3 files, covering 9 numbers**. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and it says in its own words what that commit decided. Where a PR number was already on the line (`PR objectstack-ai#13529`), it stays beside the commit as the link. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 9 numbers: a search for each number, with and without `#`, finds nothing there. So every anchor is a commit: **9 distinct shas**. Stage 3 had already read these commits and recorded them in PR objectstack-ai#20533's body. They were not copied from there. Each one was re-read against the current line it anchors: its own message or diff names the number it replaces, and it made the change the line describes. `object.zod.ts` and `filter-logic-conformance.ts` moved on `main` after stage 3 read them (PRs objectstack-ai#20521 and objectstack-ai#20523). Each site was therefore re-read at this base, `03b19d9cfd`. Only comments changed. Every source file keeps its line count (20 lines out, 20 in, over 3 files), so no line citation into these files moves. One of the 20 lines held no dead citation: `filter-logic-conformance.ts:249`, the first half of a sentence reflowed onto `:250`. No code token moves (see the guard below). **No tracker number is added.** Every tracker number on an added line was already in the hunk it replaces. `PR objectstack-ai#13529` stands on three added lines, and on the three removed lines of the same hunks. It is the link beside commit `9dac1ae01`, which stage 3 recorded the same way. No reference page under `content/docs/references/` moved: none of the rewritten docblocks projects into one (`check:docs` at the head: `226 generated files in sync`). The PR adds one `patch` changeset for `@objectstack/spec` (see Changeset below). ## Census: the six files, before and after **Instrument.** This is the instrument of stages 1 to 3. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct number cited in `packages/spec/src/data`. The population is: - the citation gate's own exported `CITATION_RE` and `NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - widened case-insensitively to `Pre-`, `POST-` and `Framework`, as in stage 3; - N of 100 or more, excluding `summon` heads. Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string. Two cross-checks close the population. First, a raw `#N` count in each of the six files equals the census rows plus the cross-repo rows in five files. In the other two it is one higher, and the extra is a second number after a slash inside a string (`objectstack-ai#5322/objectstack-ai#5134` in a `note`, `objectstack-ai#6262/objectstack-ai#6433` in a test title). Both answer 200. Second, no spelled citation (`issue N`, `PR N`, `card N`) occurs in any of the six. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 24 of 24 lit (200) and 24 of 24 dead (404) over 8 checkpoints in the base run, and 21 of 21 lit and 21 of 21 dead over 7 checkpoints in the head run. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites, all of `data/` | dead sites, the six files | lines | files | numbers | |---|---|---|---|---|---|---|---|---|---|---| | before | base `03b19d9cfd`, probed 2026-09-29T01:11:59Z to 01:15:49Z | 601 | 572 | 29 | 0 | **77** | 19 | 19 | 3 | 9 | | after | head `53c9070dfd`, probed 2026-09-29T01:25:55Z to 01:29:35Z | 597 | 572 | 25 | 0 | **58** | 0 | 0 | 0 | 0 | The head probe found no number newly dead since the base probe: the same 572 numbers answer 200. The base reading of 77 equals stage 3's after reading at `96fd49caa2`. **Per file.** Cited sites here are every in-repo citation the population reads, live or dead. | file | cited sites (base) | dead sites before | by class | dead sites after | |---|---|---|---|---| | `object.zod.ts` | 120 | 15 | 8 docblock, 7 line comment | 0 | | `filter-logic-conformance.ts` | 97 | 3 | 2 docblock, 1 line comment | 0 | | `object.form.ts` | 31 | 1 | 1 line comment | 0 | | `data-engine.zod.ts` | 48 | 0 | | 0 | | `data-engine.test.ts` | 29 | 0 | | 0 | | `hook.form.ts` | 0 | 0 | | 0 | None of the 19 sites is a string, so this stage leaves no string token behind. ## Per-number table | number | sites / lines | anchor: what it decided | |---|---|---| | `objectstack-ai#8772` | 4 / 4, `object.zod.ts:2718`, `:2731`, `:2744`, `:2910` | `75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling. `ObjectSchema.create()` forces `required: true` on a `master_detail` reference under `controlled_by_parent` and refuses an explicit `required: false`. Raw parse stays tolerant, and runtime tolerance is the ruling's other half. Its changeset records the measurement that only the security gate closed that shape while the declaration surface accepted it (`:2731`). ADR-0055 stays cited beside it. It is the same anchor stage 3 gave `object.test.ts` | | `objectstack-ai#10165` | 2 / 2, `object.zod.ts:818`, `:1036` | `801296050`: `ttl.onlyWhen` with the canonical null predicate (maintainer ruling 2026-08-20, option A). One shared `onlyWhen` union, and both of `retention.onlyWhen`'s conflicts mirrored. Its diff wrote both `[objectstack-ai#10165]` blocks | | `objectstack-ai#10347` | 3 / 3, `object.zod.ts:1006`, `:1042`, `:1049` | `530c1df65`: the Archiver honours a declared `ttl`. It selects by the ttl cutoff on `ttl.field` when `ttl` is declared, and by `created_at` / `archive.after` otherwise (maintainer ruling 2026-08-20) | | `objectstack-ai#10527` | 1 / 1, `object.zod.ts:1005` | `5649efbf9`: refuses a diverging retention + ttl + archive triple at parse time. Its diff wrote this very paragraph | | `objectstack-ai#11195` | 1 / 1, `object.zod.ts:1791` | `b37231883`: `UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor` (the "last three" the line names) | | `objectstack-ai#11408` | 1 / 1, `object.zod.ts:2189` | `f11fc61c5`: declares `editMode` on the object document (maintainer ruling 2026-08-24, the `objectstack-ai#10144` declare-or-rule-out family, which stays cited) | | `objectstack-ai#13608` | 3 / 3, `object.zod.ts:2317`, `:2354`, `:2366` | `fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only at mint, fail-closed, with the undifferentiated `null` refusal. Its changeset heads with objectstack-ai#13608. It is the same anchor stage 1 gave `contracts/share-link-service.ts` | | `objectstack-ai#13195` | 3 / 3, `filter-logic-conformance.ts:190`, `:250`, `:525` | `9dac1ae01`, PR objectstack-ai#13529's squash commit, which stays as the link: `$exists` means has-a-value on driver-memory's live mingo path, its analytics face and driver-mongodb's `translateFilter` (the "last three key-presence exits") | | `objectstack-ai#12868` | 1 / 1, `object.form.ts:256` | `c459da6bc`: narrows the per-option `default` key out of the form-view options vocabulary, which offered a key nothing on that surface read. Commit `e808890958`, which wrote this line, names objectstack-ai#12868 as the same offer-vs-door class | The shas were checked at the base and again at `origin/main` `288611e3e5`. Every one matches exactly one commit (`git rev-parse --disambiguate`, count 1). Every one is an ancestor (`git merge-base --is-ancestor`, exit 0 for 9 of 9). The control leg `e9584681a4` also exits 0, and the repository is not shallow. For each commit, a grep of its own message or diff finds the number it replaces. Seven of the nine name it in the message. `fc9ba76a5` names it in its diff (20 lines, including its changeset heading), and so does `c459da6bc` (8 lines, including its changeset heading). Wordings to check, each true of its commit: - `object.zod.ts:2731` now reads 「closes that shape, and commit 75b7c24 records that the declaration and the enforcement disagree」. The measurement was the card's. The commit's changeset records it: "only the security gate closed that shape while the declaration surface accepted it". - `object.zod.ts:2189` reads 「Declared here by commit f11fc61's maintainer ruling」, and `:2744` reads 「the other half of commit 75b7c24's ruling」. This is stage 3's wording for the same relation (`object.test.ts`, 「the other half of commit 75b7c24's ruling」): the commit that landed the ruling and quotes it. - `object.zod.ts:1049` reads 「That is the whole of what [commit 530c1df] changed here」. Commit `52db1d1f2a` wrote the paragraph. `530c1df65` is the change it describes. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `03b19d9cfd` against head `53c9070dfd`. It uses the TypeScript parser's leaf tokens (TypeScript from the head's lockfile), so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 3 touched `.ts` files. It is the stage-3 instrument, unchanged. - Real run: 13,624 base tokens (object.zod.ts 8,774, object.form.ts 3,226, filter-logic-conformance.ts 1,624), **0 files with a token change** (exit 0). - Comment-insertion control (`object.form.ts`): 0 files changed, as expected (exit 0). - Positive control (a declaration inserted into `object.zod.ts`): 1 file reads DIFFER at token 1629 (exit 1). - Positive control (one digit changed inside the `objectstack-ai#5322/objectstack-ai#5134` `note` string in `filter-logic-conformance.ts`): 1 file reads DIFFER at token 889 (exit 1). Line balance: `object.zod.ts` +15 / -15, `filter-logic-conformance.ts` +4 / -4, `object.form.ts` +1 / -1. Line counts are equal at base and head: 3,240, 621 and 751. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. `Clause-②: no`: no export, key, value or type moves (the guard above). Measured on the head's built package: `object.zod.ts` is `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten comments also reach `dist`: - `9dac1ae01` appears in `dist/data/index.d.ts` (the `filter-logic-conformance.ts` docblock) and in 4 bundled `.js` files; - `fc9ba76a5`, `f11fc61c5` and `b37231883` each appear in 22 bundled `.js` files, and `c459da6bc` in 12; - the positive control, the pre-existing `object.zod.ts` sentence 「Fail-CLOSED at both points」, appears in 11 bundled `.js` files. ## Gates (head `53c9070dfd`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. The self-test passes 73 cases in 7 batteries. The live run judged 6 citations across 3 files: 3 resolve (`objectstack-ai#9138` twice, `objectstack-ai#11410`) and 3 resolve as a pull request (`objectstack-ai#13529`, the link). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at the head derived 79 families, and all 79 exit 0. `--ran` reports 79 run, 0 NOT MEASURED, 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` ran first, under the shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate met an unbuilt prerequisite. - `pnpm --filter @objectstack/spec run check:generated`: under the lock against that build, `All 15 generated artifacts are up to date`, VERDICT command-exit 0. - **Tests and typecheck:** - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data` under the lock: Test Files 107 passed (107), Tests 3527 passed, 1 todo (3528), VERDICT command-exit 0. It covers every test in `data/`, among them `object.test.ts`, which reads these schemas. - The 13 spec suites outside `src/data` that read the touched files' source text or pin their line numbers, under the lock: Test Files 13 passed (13), Tests 544 passed (544). They are stage 3's 12 (`scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`, `src/api/api-entry-graph.pin.test.ts`, `src/contracts/scoped-context.test.ts`, `src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`, `src/system/constants/platform-object-names.test.ts`, `src/type-alias-convention.pin.test.ts`, `src/ui/dashboard.test.ts`) plus `src/shared/union-author-message-pins.test.ts`, which pins `data/object.zod.ts:855`. - `pnpm --filter @objectstack/spec typecheck` under the lock exits 0, including `check:test-typecheck` (53 files, 251 errors, 138 pinned signatures held). - **Lint, as a proven narrowing at the head:** `eslint --no-inline-config --format json` over the 3 touched `.ts` files gives 3 files, 0 errors and 0 warnings. All 3 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, which its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. ## Acceptance notes - **Base.** The branch forked from `03b19d9cfd`, stage 3's landing. `origin/main` then moved two commits (`05077d4c26`, PR objectstack-ai#20532, and `288611e3e5`, PR objectstack-ai#20536), and neither touches `data/`. `dispatch-gates` flagged its derivation as stale because `scripts/regen-artifacts.mjs` had moved, so `origin/main` was merged in (`53c9070dfd`, a clean merge with no driver-deferred path) before the gates ran. The PR's delta against `origin/main` is exactly its 4 files. `origin/main` has since moved two more commits: `7e36a3cd7c` (PR objectstack-ai#20531) and `ba5927f714` (PR objectstack-ai#20460). Neither touches `data/` or anything the gate derivation reads, and a re-derivation prints the same 79 commands. A no-driver `merge-tree` of the head onto `ba5927f714`, from a bare shared clone, exits 0. So there is no second merge. - **Open PRs, re-read at 2026-09-29T02:01Z:** 9 open PRs, and none touches any of the six files. The `data/` files open PRs touch are objectstack-ai#20458's `analytics*` files, objectstack-ai#20504's `driver/turso.*`, and objectstack-ai#20545's `filter-number-comparand-declared-type.*`, which is disjoint. Since the claim, PR objectstack-ai#20460 has landed (`ba5927f714`) without touching `filter-subtree-provenance.ts`. That file's 3 dead sites are outside this claim's fence, so they are left for a later stage. - **The rung.** Two anchored changes also have ADR-0087 entries in `packages/spec/src/migrations`: `cbp-master-detail-required-forced` for objectstack-ai#8772, and `form-view-option-default-retired` for objectstack-ai#12868. The second entry's own header names commit `c459da6bc`. This PR takes the commit rung, as stages 1 to 3 did. The D3 id is the more durable in-repo record, if the ruling's first rung is later read to include those entries. - **What stays in `data/` after this stage: 58 dead sites.** - **12 comment sites in files other open work still holds.** `analytics.zod.ts`, `analytics-strictness-batchd.test.ts` and `analytics-date-range-two-bound-window.test.ts` hold 5 (objectstack-ai#20300, PR objectstack-ai#20458). `driver/turso.zod.ts` and `driver/turso.test.ts` hold 4 (objectstack-ai#20437, PR objectstack-ai#20504). `filter-subtree-provenance.ts` holds 3. It was held by objectstack-ai#20367 and is now free (see above). - **3 comment sites stage 3 left on purpose.** They are the test-read `[objectstack-ai#6259]` marker at `api-derivation.ts:163`, the test comment at `api-derivation.test.ts:232` that names it, and `field.zod.ts:370`, whose `objectstack-ai#6111` is objectui's number. - **43 string sites**, left as tokens: 41 test strings (2 of them in the held analytics and turso test files) and the 2 exported `AGGREGATION_CASES` note strings in `aggregation-conformance.ts` (`:398`, `:407`, objectstack-ai#11065), which objectstack-ai#20489's claim holds. - **Outside `data/`,** the card's other remaining items are unchanged: the migrations and ui areas, the `liveness/**` notes, the `why` strings, the `PROVENANCE_WAIVERS` reason, and `rest-server.zod.ts`. - **The citation gate's reach.** It defers `packages/**/*.test.ts`. No test file is touched here, so all 3 touched files are in its judging population. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…commits that decided them (objectstack-ai#20609) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the first stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-messaging/src/**` and nothing else, the largest package in the lane that no open PR or in-flight claim holds (the claim, `5884863234`, gives the order). Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the way the landed `packages/spec/src` stages apply it (PR objectstack-ai#20533 is the method). That is **127 sites on 109 lines in 28 files, covering 14 numbers**: the 97 census sites outside the generated headers, and 30 sites in test comments, which the census defers. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and it says in its own words what that commit decided. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 14 numbers, so every anchor is a commit: **13 distinct shas**. No number was dropped. Only comments changed. Every touched source file keeps its line count (116 lines out, 116 in, over 28 files), so no line citation into these files moves. Seven of those 116 lines held no dead citation: they are the other half of a sentence that had to be reflowed (`inbox-caller.ts:87`, `:88`, `messaging-service.test.ts:972`, `notification-keyed-text-bounds.test.ts:83`, `notification-subscription.object.ts:81`, `:82`), or a pointer that lost its referent (`sql-outbox.ts:281`, 「the race the card describes」 to 「the race that commit describes」, because line 278 now names the commit). No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces (added-minus-removed over the whole diff: 0). No PR number stands on an added line. Fifteen dead sites are left on purpose: 12 string literals and 3 generated file headers (see the list below). One more file: a `patch` changeset for `@objectstack/service-messaging`, because the rewritten docblocks ship (see Changeset below). ## Census: `service-messaging`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only, unchanged. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count below is its `allocated-but-absent` findings under `packages/services/service-messaging/`. | reading | tree | board | whole-repo `allocated-but-absent` | service-messaging sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `7a1faf1a5`, run 2026-09-29T06:31:54Z to 06:35:25Z | enumerated, 185 pages, frontier objectstack-ai#20606, 18,433 numbers | 2,457 | **100** | 82 | 22 | 13 | | after | head `685200760`, run 06:48:33Z to 06:52:17Z | enumerated, 185 pages, frontier objectstack-ai#20606, 18,433 numbers | 2,360 | **3** | 3 | 3 | 1 | The before count matches the 100 that census `5884031174` read at `f11b5f20`. The whole-repo drop is 97, exactly this diff's census sites, and the `resolves` tally is 32,744 in both runs. The 3 left are the generated headers below. `267c11562`, the final head, adds only the changeset, which is outside the census surface. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes both. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `service-messaging/src` (87 files), against the same enumerated board. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `7a1faf1a5` | 613 | **142** | 100 | 30 | 3 | 9 | | after, `685200760` | 486 | **15** | 3 | 0 | 3 | 9 | Its src-comment column equals the census's 100, which is the control on the second instrument. The 450 resolving citations and 21 pull-request citations are the same in both readings. ## Per-number table Sites and files are all dead sites in scope at the base (comments and strings, tests included). `rewritten / left` counts comment sites rewritten and sites left. Every anchor was read in its diff or message, not only in its subject: it is the commit that made the change the line describes, and its own diff or message names the number it replaces. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6206` | 1/1 | 1/0 | `8e13ca876`: the share-link routes pass the whole authz envelope into enforcement instead of a four-field trim. The line lists it as one member of the defect family behind `assembleExecutionContext` | | `objectstack-ai#6363` | 14/2 | 13/1 | `17d095413`: `listInbox`'s `unreadCount` counts the total unread, not the fetched window (maintainer ruling 2026-08-07, Option A: make the declaration true); it adds `countUnreadTotal`. The same anchor the spec stages gave this number | | `objectstack-ai#9722` | 1/1 | 1/0 | `2074b2651`: corrects the `sys_notification_subscription` index note — `role:` and `team:` resolve against `sys_member` and `sys_team_member` | | `objectstack-ai#9807` | 4/3 | 4/0 | `44738f7af`: marks the subscription-to-recipient expansion NOT WIRED and aligns `principal` with the forms `RecipientResolver.resolveOne()` accepts, email kept verbatim | | `objectstack-ai#11374` | 17/6 | 16/1 | route A of the maintainer's 2026-08-24 ruling: a keyed text column declares a `maxLength` sourced from its producer. Written as 「route A, ruling 2026-08-24」 beside `e4902d2b9`, the commit that applied it here. `scripts/check-keyed-text-bounds.mjs`'s header states route A in words | | `objectstack-ai#11452` | 6/3 | 5/1 | `3b5f0360c`: the plugin-facing `listInboxAsCaller`, scoped to the authenticated caller | | `objectstack-ai#11453` | 26/13 | 23/3 | `1a47a5368`: `ack()` refuses a row that is not `in_flight` (`NotificationAckError`, `DELIVERY_NOT_ELIGIBLE`), as a compare-and-set in the SQL outbox. The same anchor stage 2 gave it | | `objectstack-ai#11671` | 4/4 | 1/3 | `09b4f4e4e`: `os i18n extract --source-hashes` writes the per-locale provenance companion (maintainer ruling objectstack-ai#12069 Option A, which stays cited) | | `objectstack-ai#11741` | 6/2 | 5/1 | `b706af987`: `SendEmailInput` gains `organizationId`, and the email channel threads it on both arms. The same anchor stage 1 gave it | | `objectstack-ai#11859` | 29/13 | 27/2 | `d9cf78eaa`: `ack()` takes the claimed record back and binds its claim credential in the compare-and-set. The same anchor stage 2 gave it | | `objectstack-ai#12144` | 2/2 | 2/0 | `3a04b0125`: identifier ceilings are storage-owned (`sys_metadata.name` is 255) | | `objectstack-ai#12147` | 1/1 | 1/0 | `945e91a13`: the class-level `check-keyed-text-bounds` gate | | `objectstack-ai#12978` | 17/6 | 16/1 | `e4902d2b9`: declares the sourced `maxLength` on all 15 keyed text columns of the `sys_notification_*` objects. No commit message names the card; its diff is where every `[objectstack-ai#12978]` marker entered the tree | | `objectstack-ai#18424` | 14/4 | 12/2 | `879b51270`: an email or SMS channel with no transport refuses with `transport_not_configured` instead of reporting success | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13). The history was unshallowed first (`git fetch --unshallow`, 15,062 commits), so no anchor was read from a truncated log. Wordings to check, each true of its commit: - `inbox-caller.ts:86-88`: 「(objectstack-ai#6071, objectstack-ai#6551, and the share-link envelope trim commit 8e13ca8 undid)」. `8e13ca876`'s message records the trim (four fields kept, five dropped) and the whole-envelope fix. - `outbox.ts:72`: 「the option-A shape commit d9cf78e's ruling refused」. `d9cf78eaa`'s message: 「The caller never supplies an identity: ownership is proven by round-tripping what claim() returned.」 - The fifteen `sys_notification_*` bound comments: `[commit e4902d2] ... (route A, ruling 2026-08-24)`. `e4902d2b9`'s message opens 「Every bound names its producer in the declaration」, and `3954fb7df`'s records the ruling's date and its A and C routes. - `notification-keyed-text-bounds.test.ts:82-83`: the `objectstack-ai#9807` pointer becomes 「Every other arm of the grammar commit 44738f7 documented is narrower」, because `44738f7af` is where the email arm of the selector grammar was written down. - `outbox-ack-claim-ownership.integration.test.ts:40`: 「the objectstack-ai#11453 file beside this one」 names the file itself, `outbox-ack-precondition.integration.test.ts`. ## The 15 sites left - **Non-test strings (3 sites, 2 lines), refusal text.** `outbox.ts:187` (「see objectstack-ai#11453」) and `outbox.ts:210` (「(objectstack-ai#11453, objectstack-ai#11859)」) are inside `notificationAckNotClaimedMessage` and `notificationAckLostClaimMessage`, the messages `NotificationAckError` carries. They are runtime strings, so they are form D, not form C. The landed objectstack-ai#20234 stages left every string site as a token and rewrote no refusal text, so these are left and listed, as PR objectstack-ai#20533 did. The form D stages that did rewrite strings (objectstack-ai#20233's) cover `os migrate meta` guidance, a different class. - **Test titles (9 sites, 8 lines).** `describe` titles in `email-channel.test.ts:90`, `:592`, `messaging-service.test.ts:809`, `:1286`, `notification-keyed-text-bounds.test.ts:38` (2 numbers), `outbox-ack-claim-ownership.integration.test.ts:109`, `outbox-ack-precondition.integration.test.ts:110` and `sms-channel.test.ts:90`. Tokens, left as they were. - **Generated headers (3 sites).** Line 8 of `es-ES`, `ja-JP` and `zh-CN` `.source-hashes.generated.ts` reads 「(objectstack-ai#11671, maintainer ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B)」. `os i18n extract` writes that line from `packages/cli/src/utils/i18n-extract.ts:2294`, and 27 generated files across the repo carry it. A hand edit here would be undone by the next extract, so the fix belongs at the producer in a later stage, which regenerates every copy. The hand-written `translations/index.ts:29` is rewritten here. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `7a1faf1a5` against head. It uses the TypeScript parser's leaf nodes, so template literals are read in context, and it excludes JSDoc nodes. It ran over all 28 touched `.ts` files. - Real run: 52,337 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control, in `outbox.ts`: 0 files changed, as expected (exit 0). - Positive control, a declaration inserted into `outbox.ts`: DIFFER (exit 1). The first attempt was a no-op: its anchor text was still inside the replacement, so `scripts/ablation-replace.mjs` refused it before the guard ran. It was redone with a hitting anchor. - Positive control, one digit changed inside the kept `outbox.ts:210` refusal string: DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each restore was proven byte-identical to the HEAD blob (`80618f8711e2`) with `git diff HEAD` empty. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-messaging` is included. It says only that the provenance comments were re-anchored. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After `pnpm --filter @objectstack/service-messaging build`, the rewritten comments reach both halves of `dist`. `d9cf78eaa` appears 8 times in `dist/index.d.ts`, `1a47a5368` 6 times and `17d095413` 6 times, and `e4902d2b9` appears 15 times in `dist/index.js`. The positive control, an unchanged `notification-subscription.object.ts` docblock sentence, appears in `dist/index.d.ts`, and a negative control phrase appears nowhere. The only dead numbers left in `dist` are the two kept refusal strings. ## Gates (head `267c11562`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) exits 0, and `node scripts/check-issue-citations.mjs` exits 0. The diff-scoped run judged 5 citations across 19 files, and all 5 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `267c11562` derived 64 families. They include all 50 derived at dispatch, plus 14 more. All 64 exit 0. `--ran` reports 64 run, 0 NOT MEASURED, 0 unrun, and exits 0. - Three gates first exited 3 (PREREQUISITE NOT MET) because the workspace was unbuilt: `check:dual-build-cjs-loads`, `check:i18n` and `check:type-check-debt`. A full `turbo run build` of `./packages/*` and `./packages/*/*` then ran under the shared verify lock (71 tasks, exit 0). The first two exited 0 on their rerun. - `check:type-check-debt` exited 3 once more: `outbox.ts`'s mtime had moved during the guard controls, although its bytes had not, so turbo's cache hit left `dist` older than the source. A direct `pnpm --filter @objectstack/service-messaging build` then let it exit 0 (4 ledger entries re-measured, none above its number). - **Tests and typecheck:** - `pnpm --filter @objectstack/service-messaging test`: 46 files and 507 tests pass, covering every touched test file. - `pnpm --filter @objectstack/service-messaging typecheck` exits 0. Its `tsc` program lists all 46 test files and 87 files under `src/` in total (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 28 touched `.ts` files gives 28 files, 0 errors and 0 warnings. All 28 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 28 files for control bytes finds none. ## Acceptance notes - **The census instrument returned a truncated board once, at exit 0.** The first `--census --json` run of this stage (06:25:55Z, base `7a1faf1a5`) read `enumerated (85 pages)`, frontier objectstack-ai#8854, 8,444 numbers, when the newest number was above objectstack-ai#20600. The `Link` header of its 85th page had carried no `rel="next"`, so `enumerateBoard` stopped and classified 16,187 citations as `never-issued`. A reader counting only `allocated-but-absent`, as this stage's count does, would have got 8 service-messaging sites instead of 100, silently. The next four enumerations in this session read 185 pages and frontier objectstack-ai#20606, and the counts above come from those. Nothing in `enumerateBoard` compares its frontier with the newest issue number, which `probeBoard` does read. Reported to the seat, not changed here: this stage makes no instrument change. - **What stays for later stages.** - The 3 generated `objectstack-ai#11671` headers, whose producer is `packages/cli/src/utils/i18n-extract.ts:2294`. That line is the repo-wide carrier (27 generated files). - The 3 refusal-string sites in `outbox.ts` (form D) and the 9 test-title sites. - **Base.** The branch is 9 commits behind `origin/main` (`0f6dcac5e`, read at 07:23Z). One of them, `8c87d26a5` (the version packages release), touches `service-messaging`, but only its `CHANGELOG.md` and `package.json`, and neither is in this diff. So there was no merge. - **Anchors shared with the spec stages.** `17d095413` (objectstack-ai#6363), `1a47a5368` (objectstack-ai#11453), `d9cf78eaa` (objectstack-ai#11859) and `b706af987` (objectstack-ai#11741) are the anchors stages 1 and 2 already gave the same numbers in `packages/spec/src`, so each number carries one anchor across the tree. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…me/src to the commits that decided them (objectstack-ai#20624) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 1 of the `domain:cli` lane of the dead-citation sweep: `packages/runtime/src/**`, the lane's largest package. Every comment or docblock site in scope that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20609 the closest sibling. Later stages cover `rest`, `cli`, `types` and the rest of the lane, so this PR says `Part of` and the card stays open. That is **513 comment sites on 508 lines in 118 files, covering 96 numbers**: 194 of the census's 217 sites, and 319 more in test comments, which the census defers. Three more sites carried a slash-joined dead number the citation grammar does not read (`objectstack-ai#10629/objectstack-ai#10630`, `objectstack-ai#5811/objectstack-ai#12281`, `objectstack-ai#8421/objectstack-ai#12194`), and they are rewritten too. Each rewritten line cites one of **95 distinct commits**. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of these numbers. ADR-0126 and ADR-0131 name objectstack-ai#10243 only as the incident, ADR-0126 names objectstack-ai#11513 only for the flow-clone half, and ADR-0112 names objectstack-ai#12281 only as another card. So every anchor is a commit. The anchors the landed stages already gave the same numbers are reused (24 numbers, for example `f19475c0a` for objectstack-ai#14143, `e2798fab7` for objectstack-ai#6345 and `79c46da90` for objectstack-ai#9934), so each number carries one anchor across the tree. Only comments changed. Every touched file keeps its line count (508 lines out, 508 in, over 118 files), so no line citation into these files moves. Seven of the 508 lines held no census site. Five are the other half of a sentence that had to change: `action-governance-scope-divergence.test.ts:6` (「the card names」 to 「that diverged」, because line 4 no longer names the card), `action-record-load-denied.test.ts:560`, `dispatcher-5xx-demoted-code-withhold.test.ts:45` (「that card's change」 to 「that commit's change」), `hook-input-writeback-readonly-provenance.integration.test.ts:380` (「that card」 to 「that commit」) and `standalone-stack-seeder-declaration-copy.test.ts:88` (a trailing 「PR」 whose number wrapped onto line 89). Two carry only a slash-joined number: `dispatcher-plugin.ts:688` and `meta-compound-arity-mint-door.test.ts:4`. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces. No PR number stands on an added line, and none of the 95 shas is on a removed line. Twenty-eight dead comment sites are left on purpose: - **20 in `domains/meta.ts`.** PR objectstack-ai#20615 (objectstack-ai#20590's) opened at 2026-09-29T08:12:40Z, after this stage's claim and first read, and edits that file. So the file went back to its base blob (`b4ddb362cc`) in `a5cdfd8a46`, as PR objectstack-ai#20612 did with `authoring-rules.ts`. The anchors are verified and listed below for the follow-up. - **8 with no deciding commit, or with a literal reader.** See "The sites left" below. One more file: a `patch` changeset for `@objectstack/runtime`, because the rewritten docblocks ship (see Changeset below). ## Census: `packages/runtime`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run with the fleet token. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count is its `allocated-but-absent` findings under `packages/runtime/`. Both runs enumerated the whole board (185 pages), so neither read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | runtime sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `eb4b17c346`, run 2026-09-29T07:55:51Z to 08:05:47Z | enumerated, 185 pages, frontier objectstack-ai#20614, 18,441 numbers | 2,397 | **217** | 216 | 29 | 59 | | after | head `a5cdfd8a46`, run 09:08:23Z to 09:14:11Z | enumerated, 185 pages, frontier objectstack-ai#20623, 18,450 numbers | 2,027 | **23** | 23 | 4 | 12 | The before count equals the card's 217 at `f11b5f20a2`. The 23 left are the 20 held `domains/meta.ts` sites and 3 deliberate ones (`api-exposure.ts:108`, `domains/mcp.ts:360`, `route-ledger.ts:300`). The whole-repo drop is 370: this diff's 194, plus the 97 and 79 of PR objectstack-ai#20609 and PR objectstack-ai#20612, which landed on `main` in between and came in with the merge. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `packages/runtime/src` (373 files), against a board probed by REST for every number cited there. The lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 and the dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404 in both runs. | reading | tree | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---| | before, 08:17:55Z | `eb4b17c346` | 5,364 | **641** | 217 | 324 | 5 | 95 | | after, 09:24:24Z | `a5cdfd8a46` | 4,851 | **128** | 23 | 5 | 5 | 95 | Its src-comment column equals the census's 217 and 23, which is the control on the second instrument. The 4,499 resolving citations, the 195 that resolve as pull requests and the 29 cross-repo ones are the same in both readings. The drop is 513, exactly this diff's grammar-read sites. ## Per-number table Sites and files are the dead comment sites in scope at the base, tests included. `held` is `domains/meta.ts` (see above) and `left` is a site with no deciding commit or with a literal reader. `strings kept` counts string-literal sites, which are tokens and stay as they were. Every anchor was read in its message or its diff, not only in its subject: it is the commit that made the change the line describes, and its own message or diff names the number it replaces. | number | comment sites / files | rewritten | held | left | strings kept | anchor | |---|---|---|---|---|---|---| | `objectstack-ai#6065` | 1/1 | 1 | 0 | 0 | 0 | `026101660` | | `objectstack-ai#6123` | 1/1 | 1 | 0 | 0 | 0 | `59d1933f9` | | `objectstack-ai#6206` | 5/2 | 5 | 0 | 0 | 0 | `8e13ca876` | | `objectstack-ai#6216` | 2/1 | 2 | 0 | 0 | 1 | `f586f1a89` | | `objectstack-ai#6220` | 1/1 | 1 | 0 | 0 | 0 | `83df2fd73` | | `objectstack-ai#6238` | 2/2 | 2 | 0 | 0 | 2 | `c8d6f6e08` | | `objectstack-ai#6259` | 4/2 | 3 | 0 | 1 | 1 | `6968885ef` | | `objectstack-ai#6265` | 12/2 | 12 | 0 | 0 | 4 | `cfb549db8` | | `objectstack-ai#6268` | 9/3 | 9 | 0 | 0 | 0 | `68f5eccb1` | | `objectstack-ai#6287` | 1/1 | 1 | 0 | 0 | 0 | `84c86fb45` | | `objectstack-ai#6307` | 1/1 | 1 | 0 | 0 | 0 | `293476148` | | `objectstack-ai#6316` | 6/3 | 6 | 0 | 0 | 0 | `448ac9565` | | `objectstack-ai#6345` | 10/3 | 10 | 0 | 0 | 0 | `e2798fab7` | | `objectstack-ai#6361` | 4/2 | 4 | 0 | 0 | 6 | `90bbf2510` | | `objectstack-ai#6363` | 6/2 | 6 | 0 | 0 | 2 | `17d095413` | | `objectstack-ai#6483` | 3/2 | 3 | 0 | 0 | 0 | `ee58392e1` | | `objectstack-ai#8722` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#8724` | 1/1 | 1 | 0 | 0 | 0 | `ff4ba6a06` | | `objectstack-ai#8726` | 8/4 | 7 | 1 | 0 | 1 | `e783e163d` | | `objectstack-ai#8796` | 13/3 | 13 | 0 | 0 | 4 | `a4331227b` | | `objectstack-ai#8848` | 3/2 | 1 | 2 | 0 | 1 | `4fc4a3c0b` | | `objectstack-ai#8919` | 1/1 | 0 | 1 | 0 | 0 | `b5378550e` (held file) | | `objectstack-ai#9934` | 17/7 | 17 | 0 | 0 | 4 | `79c46da90` | | `objectstack-ai#9967` | 1/1 | 1 | 0 | 0 | 0 | `8f266f1cd` | | `objectstack-ai#10179` | 1/1 | 0 | 0 | 1 | 2 | — | | `objectstack-ai#10243` | 40/13 | 40 | 0 | 0 | 9 | `266436a7f`, `02b41232d` | | `objectstack-ai#10293` | 3/3 | 3 | 0 | 0 | 0 | `92a69d813` | | `objectstack-ai#10338` | 1/1 | 1 | 0 | 0 | 0 | `d2619fd0c` | | `objectstack-ai#10340` | 3/2 | 2 | 1 | 0 | 1 | `26f3588fb` | | `objectstack-ai#10380` | 12/2 | 12 | 0 | 0 | 0 | `dd8172ee2` | | `objectstack-ai#10485` | 3/3 | 3 | 0 | 0 | 0 | `35ad101bc` | | `objectstack-ai#10503` | 8/2 | 3 | 5 | 0 | 1 | `67ceb9aef` | | `objectstack-ai#10537` | 2/1 | 2 | 0 | 0 | 0 | `e634ecf6a` | | `objectstack-ai#10554` | 1/1 | 1 | 0 | 0 | 0 | `6abc4df03` | | `objectstack-ai#10629` | 75/23 | 75 | 0 | 0 | 0 | `13a6cb4ad` | | `objectstack-ai#10630` | 4/1 | 4 | 0 | 0 | 0 | `dd8172ee2` | | `objectstack-ai#10789` | 2/1 | 2 | 0 | 0 | 1 | `38bc74ed1` | | `objectstack-ai#10886` | 1/1 | 1 | 0 | 0 | 1 | `809e61221` | | `objectstack-ai#10888` | 3/3 | 2 | 1 | 0 | 1 | `d806081dd` | | `objectstack-ai#10961` | 5/3 | 5 | 0 | 0 | 3 | `222d06fc1` | | `objectstack-ai#10965` | 2/1 | 2 | 0 | 0 | 1 | `ab47f6974` | | `objectstack-ai#10978` | 1/1 | 1 | 0 | 0 | 0 | `4c9780c7a` | | `objectstack-ai#10983` | 3/2 | 3 | 0 | 0 | 0 | `6a4e929f5` | | `objectstack-ai#11006` | 4/4 | 3 | 1 | 0 | 0 | `cccbe51bf` | | `objectstack-ai#11015` | 3/1 | 3 | 0 | 0 | 0 | `82cb6e849` | | `objectstack-ai#11166` | 8/3 | 8 | 0 | 0 | 4 | `735f5c709` | | `objectstack-ai#11333` | 1/1 | 1 | 0 | 0 | 0 | `ea4d16420` | | `objectstack-ai#11504` | 3/2 | 3 | 0 | 0 | 0 | `f90e82024` | | `objectstack-ai#11513` | 2/2 | 2 | 0 | 0 | 0 | `e170b0ae5` | | `objectstack-ai#11703` | 8/3 | 8 | 0 | 0 | 1 | `5cb62d88b` | | `objectstack-ai#12010` | 1/1 | 1 | 0 | 0 | 0 | `77b91bdb4` | | `objectstack-ai#12176` | 5/5 | 5 | 0 | 0 | 0 | `7986d973f` | | `objectstack-ai#12194` | 11/4 | 8 | 3 | 0 | 0 | `311433f6b` | | `objectstack-ai#12195` | 9/4 | 4 | 5 | 0 | 10 | `7986d973f` | | `objectstack-ai#12281` | 20/5 | 20 | 0 | 0 | 5 | `0783d7b80` | | `objectstack-ai#12943` | 7/3 | 7 | 0 | 0 | 0 | `090f2302e` | | `objectstack-ai#13037` | 8/2 | 8 | 0 | 0 | 5 | `e7dfb1d69` | | `objectstack-ai#13233` | 5/1 | 5 | 0 | 0 | 0 | `3800e4293` | | `objectstack-ai#13241` | 5/4 | 5 | 0 | 0 | 1 | `a21d2a9cf` | | `objectstack-ai#13273` | 11/3 | 11 | 0 | 0 | 0 | `3a86a65e7` | | `objectstack-ai#13279` | 3/2 | 3 | 0 | 0 | 0 | `6a180e42d` | | `objectstack-ai#13325` | 3/1 | 3 | 0 | 0 | 0 | `2e0b7b18f` | | `objectstack-ai#13644` | 5/4 | 5 | 0 | 0 | 1 | `34ce8e7db` | | `objectstack-ai#13657` | 13/1 | 13 | 0 | 0 | 1 | `b003cf2e8` | | `objectstack-ai#14143` | 26/8 | 26 | 0 | 0 | 4 | `f19475c0a` | | `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | 0 | `9d7f7259f` | | `objectstack-ai#14398` | 3/1 | 3 | 0 | 0 | 0 | `317132495` | | `objectstack-ai#14403` | 6/1 | 6 | 0 | 0 | 0 | `93d2d679b` | | `objectstack-ai#14421` | 2/1 | 2 | 0 | 0 | 0 | `bd8795ea1` | | `objectstack-ai#14422` | 4/2 | 4 | 0 | 0 | 4 | `dc7c226b9` | | `objectstack-ai#14423` | 3/1 | 3 | 0 | 0 | 1 | `a56baa2bd` | | `objectstack-ai#14474` | 1/1 | 1 | 0 | 0 | 0 | `df657d9df` | | `objectstack-ai#14667` | 2/1 | 2 | 0 | 0 | 0 | `dc7c226b9` | | `objectstack-ai#14678` | 2/1 | 2 | 0 | 0 | 2 | `73ad0bba7` | | `objectstack-ai#14683` | 2/2 | 2 | 0 | 0 | 0 | `96326040f` | | `objectstack-ai#14723` | 1/1 | 1 | 0 | 0 | 0 | `65846bc46` | | `objectstack-ai#14745` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#14748` | 1/1 | 1 | 0 | 0 | 1 | `92b5d7f00` | | `objectstack-ai#14758` | 15/5 | 15 | 0 | 0 | 1 | `84199cb87` | | `objectstack-ai#14760` | 6/2 | 6 | 0 | 0 | 2 | `ee32e1cb8` | | `objectstack-ai#14864` | 3/3 | 3 | 0 | 0 | 3 | `066dd3bd0` | | `objectstack-ai#14878` | 2/1 | 2 | 0 | 0 | 1 | `29db3cd2a` | | `objectstack-ai#14908` | 3/2 | 3 | 0 | 0 | 0 | `d5cbb44f3` | | `objectstack-ai#14921` | 2/1 | 2 | 0 | 0 | 0 | `c1d274de7` | | `objectstack-ai#15063` | 2/1 | 2 | 0 | 0 | 0 | `ad35745e8` | | `objectstack-ai#15068` | 2/2 | 2 | 0 | 0 | 4 | `8744de9e9` | | `objectstack-ai#15071` | 5/2 | 5 | 0 | 0 | 0 | `cf6e0a193` | | `objectstack-ai#16610` | 3/1 | 3 | 0 | 0 | 0 | `316a20fc5` | | `objectstack-ai#16649` | 4/1 | 4 | 0 | 0 | 0 | `44c917a47`, `613bfbd3d` | | `objectstack-ai#16755` | 1/1 | 1 | 0 | 0 | 0 | `44c849c7d` | | `objectstack-ai#16758` | 1/1 | 1 | 0 | 0 | 0 | `6e9bee640` | | `objectstack-ai#16783` | 1/1 | 1 | 0 | 0 | 0 | `854639b31` | | `objectstack-ai#16919` | 1/1 | 1 | 0 | 0 | 0 | `2cd4c548e` | | `objectstack-ai#17038` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#17039` | 1/1 | 1 | 0 | 0 | 0 | `edf59e359` | | `objectstack-ai#17041` | 2/2 | 0 | 0 | 2 | 0 | — | | `objectstack-ai#17114` | 2/2 | 2 | 0 | 0 | 2 | `4af758d47` | | `objectstack-ai#17147` | 1/1 | 1 | 0 | 0 | 0 | `aaacf1d5c` | | `objectstack-ai#17148` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#17195` | 1/1 | 1 | 0 | 0 | 0 | `d2c1d1980` | | `objectstack-ai#17219` | 1/1 | 1 | 0 | 0 | 0 | `706ad0fcc` | | `objectstack-ai#19364` | 2/2 | 2 | 0 | 0 | 0 | `ada701220` | | `objectstack-ai#19394` | 5/2 | 5 | 0 | 0 | 0 | `0862063ba` | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 95), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 95). The checkout is not shallow (`--is-shallow-repository` false), and the control leg `13a6cb4ad` exits 0 too. **Numbers with more than one anchor, by site:** - `objectstack-ai#10243` (40 sites): `266436a7f` for the 26 sites that describe the 2026-08-23 ruling it implements (the enablement door joins the `manage_metadata` write set, with the `trigger` exclusion), and `02b41232d` for the 14 that name the leak itself (「the leak commit 02b4123 measured」). That commit recorded the measurement over HTTP and says it is part of that card. - `objectstack-ai#16649` (4 sites): `613bfbd3d` for the first half (the fourteen remaining `boot-refusal` rows registered) and `44c917a47` for the second (the face refusal widened to every published package, and `boot-refusal` retired). - `objectstack-ai#12176`, `objectstack-ai#12194`, `objectstack-ai#12195`: the stages of one ruled retirement. `311433f6b` is stage 1 (the item-name grammar refused at the publish door) and `7986d973f` is stage 3 (the compound arities un-mounted). These are the anchors the spec stages gave. **Wordings to check, each true of its commit:** - `objectstack-ai#10293` (3 sites) cited the p1 flake whose signature had the expected-noise lines lifted into it. They now read 「(a vitest teardown race, fixed by commit 92a69d8)」. `92a69d813` names that number in its subject and fixed the flake by disarming vitest's console-forwarding teardown race, which is why the noise pointed the dispatch at the wrong mechanism. - `objectstack-ai#16755` and `objectstack-ai#16783` each cited an open PR that held a file at the time. They now read 「the change that landed as commit 44c849c held that file」 and 「then held by the change that landed as commit 854639b」. Each commit's diff edits the named file (`domains/automation.ts`, `seed-loader.test.ts`). - Quoted rulings keep their words. `dispatcher-plugin.declared-5xx-prose-withhold.test.ts:13` and `dispatcher-plugin.declared-user-message.test.ts:35` quote the 2026-08-27 ruling, and `dispatcher-5xx-demoted-code-withhold.test.ts:281` quotes an older note. There the commit stands in an editorial bracket (`[commit 79c46da]`, `[commit 0783d7b]`) in place of the number. - `objectstack-ai#9934`'s 「second constraint」 and 「third constraint」 now read 「the ruling's second constraint, commit 79c46da」. That commit's own diff calls status-agnosticism 「the ruling's second constraint」. - `domains/packages.ts:841` read 「declares, since objectstack-ai#19364:」 above the `enabled` line, but that line predates `ada701220` (objectstack-ai#19364's commit). It now reads 「declares — a key commit ada7012 kept rather than retired:」. - `route-ledger.ts:288`: 「objectstack-ai#16758 filed the second kind」 now reads 「Commit 6e9bee6 gated the second kind」, because that commit added the row census after the index-slice incident the sentence goes on to describe. - `flow-clone.ts:7` and `domains/automation.ts:2403` cite `e170b0ae5` for objectstack-ai#11513: the commit that landed 「lock package-declared permission sets at the save door; clone to customize」, whose changeset names the number. ## The sites left **No deciding commit, or a literal reader (8 sites):** - `api-exposure.ts:108` (objectstack-ai#6259): `api-exposure.test.ts:152` splits this `@param` block on the literal `'objectstack-ai#6259'`, so rewriting the comment would change what the test measures. Its deciding commit is `6968885ef`, which the three test-comment sites of the same number now cite. - `domains/mcp.ts:360` (objectstack-ai#8722): a wider contract change 「archived unscheduled」. It never landed, so no commit decided it. - `domains/meta-state-plural-tolerance.test.ts:130` (objectstack-ai#10179): an untaken option on a tracking card. The only commit naming the card, `53a48c93f`, recorded the opposite state. - `package-door-namespace-conflict-code.test.ts:30` (objectstack-ai#14745): a residue item on a review card. The only commit carrying the token is the one that added this file. - `route-ledger.conformance.test.ts:33` (objectstack-ai#17038): an ablation measured on a PR whose squash commit, `6a7910abb`, neither records nor performs it. - `route-ledger.conformance.test.ts:38` and `route-ledger.ts:300` (objectstack-ai#17041): a maintainer decision the lines call open. - `security/artifact-granted-permissions.test.ts:291` (objectstack-ai#17148): a question the line itself says is unsettled. **Held with `domains/meta.ts` (20 sites), anchors verified for the follow-up:** `objectstack-ai#8726` `:116` to `e783e163d`; `objectstack-ai#8848` `:200`, `:1398` to `4fc4a3c0b`; `objectstack-ai#8919` `:1247` to `b5378550e`; `objectstack-ai#10340` `:1309` to `26f3588fb`; `objectstack-ai#10503` `:14`, `:1143`, `:1159`, `:1250`, `:1308` to `67ceb9aef`; `objectstack-ai#10888` `:1337` to `d806081dd`; `objectstack-ai#11006` `:103` to `cccbe51bf`; `objectstack-ai#12194` `:831`, `:1050`, `:1173` to `311433f6b`; `objectstack-ai#12195` `:819`, `:827`, `:1046`, `:1167`, `:1960` to `7986d973f`. PR objectstack-ai#20615's one hunk there is at `:1874`, disjoint from these lines, but the rule is file-level. **String sites kept as tokens (100).** 95 are test titles and test-code strings in 43 files. Five are non-test strings: the `route-ledger.ts` `note` fields at `:435`, `:441` and `:505`, a string at `dispatcher-error-vocabulary.ts:349`, and the enablement door's refusal text at `domains/activation-gate.ts:279`, which ends 「(objectstack-ai#10243).」 (see Acceptance notes). ## Mechanical guard: no code token moves The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, so template literals are read in context) of each touched file at base `eb4b17c346` against the working tree at `a5cdfd8a46`, over all 118 touched `.ts` files. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 301,081 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control (`domains/activation-gate.ts`): 0 files changed (exit 0). - Code-insertion positive control (a declaration in the same file): DIFFER at token 34 (exit 1). - String positive control (`(objectstack-ai#10243)` to `(objectstack-ai#10244)` inside the kept refusal string): DIFFER at token 339 (exit 1). Line balance: every touched file is +N/−N (508/508), and every line count is equal at base and head. A raw scan of the 119 changed files for control bytes finds none. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/runtime` is included, in PR objectstack-ai#20609's form and level. It says only that the provenance comments were re-anchored. Measured on the built package: `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After `pnpm --filter @objectstack/runtime build`, the rewritten docblocks reach `dist`: for example `e2798fab7` appears 3 times and `68f5eccb1` 6 times in `dist/index.d.ts`, and `f19475c0a` 4 times in `dist/index.js`. The positive control, the unchanged sentence 「drags `@libsql/client` (native bindings included)」 of the same `turso-driver-factory.ts` docblock, is in `dist/index.d.ts`, and a negative control phrase appears nowhere. The only dead number left in `dist` is the kept refusal string's `objectstack-ai#10243`. ## Gates (head `a5cdfd8a46`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each locked run at this head: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/runtime build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 99s (1m39s) · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 6s · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project repo --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 8s · declare it in the PR body · pnpm --filter @objectstack/runtime typecheck ``` The dependency closure and the whole workspace were built first, at the merge head `ca6d13d6ab`, the same way: `turbo run build --filter='@objectstack/runtime...'` (30 tasks, exit 0) and `turbo run build --filter='./packages/*' --filter='./packages/*/*'` (71 tasks, exit 0). `a5cdfd8a46` differs from that head only in `domains/meta.ts`, which went back to base bytes, and `@objectstack/runtime` was rebuilt at `a5cdfd8a46`. - **Tests:** `vitest run --project local`: 288 files, 4,190 tests passed, 1 skipped. `--project repo` (which holds the touched `action-owner-key-single-source.test.ts`): 3 files, 727 tests passed. Together they cover every touched test file. - **Typecheck:** `pnpm --filter @objectstack/runtime typecheck` exits 0. `tsc --listFiles` counts 82 `src` files (no tests) under `tsconfig.json` and all 291 test files under `tsconfig.test.json`, which `check:test-typecheck` judges: 27 files, 190 errors, 68 pinned signatures held. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `a5cdfd8a46` (2026-09-29T09:23:06Z to 09:23:36Z). A narrowed run over the 118 touched `.ts` files through eslint's API agrees: 118 linted, 0 ignored, 0 errors, 0 warnings. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0. The diff-scoped run judged 23 citations across 28 files, and all 23 resolve. These are the live numbers that stay on rewritten lines. It defers `*.test.ts`, so the added-minus-removed count over the whole diff covers the rest: 0 numbers added. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `a5cdfd8a46` derived 67 families, the same set as at the merge head. All 67 exit 0. `--ran` reads 「67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN」. - At the merge head, `check:dual-build-cjs-loads` and `check:type-check-debt` first exited 3 (PREREQUISITE NOT MET) on a partly built workspace. After the whole-workspace build both exited 0, and both exit 0 at the final head. - Among them: `check:doc-authoring` (the sibling prose-id baseline holds, 810 pinned sites, no growth), `check:nul-bytes` (9,250 files, no raw control bytes), `check:route-ledger-census`, `check:dispatcher-error-vocabulary` and `check:issue-citations` (self-test, 114 cases in 8 batteries). - **Artifact rosters:** 38 of the 41 non-self-test roster rows exit 0 at the merge head. The other three, `check-closing-target-claim`, `check-partof-closing-keyword` and `check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull request's context, and are run against this PR and reported on the card. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 217 dead sites at `eb4b17c346` (29 files, 59 numbers), equal to the card's count at `f11b5f20a2`: no drift. - **H1 holds, with the listed exceptions.** After the rewrite the filtered census answers 23: the 20 sites held with `domains/meta.ts` for an open PR, and 3 deliberate ones (a literal reader, a card never landed, an open decision). The supplementary reading adds 5 test-comment sites of the same two kinds. - **H2 holds, by the token guard.** A comment-stripped comparison of every touched file (the parser's leaf tokens, JSDoc excluded) is empty, and its controls fire. The emitted `dist` is not byte-identical, because the docblocks ship, which is why the changeset is `patch`. ## Acceptance notes - **The held file.** The claim's read (07:51Z) and this stage's first read of the open PRs' file lists (08:06:32Z, 8 open PRs) found none touching `packages/runtime/src`. PR objectstack-ai#20615 opened at 08:12:40Z and edits `domains/meta.ts`. The re-read at 09:07:08Z (7 open PRs) found it, and it is the only open PR touching the package. The file went back to its base blob in `a5cdfd8a46`, and `git hash-object` equals `b4ddb362cc`, the blob at the base and at `origin/main`. The 20 anchors above are ready for the follow-up once that PR lands. - **Form D, not touched here.** `domains/activation-gate.ts:279` is part of the enablement door's refusal message and ends 「(objectstack-ai#10243).」. An author sees it, so it is ruling D's (no number, the lesson in words), a string change outside this comment-only scope. It needs a form-D carrier. The other four non-test string sites are ledger `note` data and a gate's own string. - **The grammar does not read a slash-joined number.** `CITATION_RE` refuses a `#` preceded by `/`, so the second number of `#A/#B` is never judged. In `packages/runtime/src`, 3 such dead numbers exist (`objectstack-ai#10630`, `objectstack-ai#12281`, `objectstack-ai#12194`), and all 3 are rewritten here. The other 36 distinct slash-joined numbers there were probed by REST and answer 200. One more dead one, `objectstack-ai#17219`, stands slash-joined inside a test title, a string, and is kept. This is the same shape as PR objectstack-ai#20612's slash-joined `objectstack-ai#5775/objectstack-ai#6629`. It is noted, not filed. - **Outside the scope and the census surface.** `packages/runtime/vitest.config.ts:54` cites `objectstack-ai#17853`, which answers 404. The file is outside `src/**`, so it is left for whoever owns the package's config. The other numbers there, and those in `tsup.config.ts` and `README.md`, answer 200. - **Base.** The branch merged `origin/main` once (`ca6d13d6ab`, merging `c1d8051e0a`) before the `--base origin/main` run, as the dispatch orders. That merge brought PR objectstack-ai#20609's and PR objectstack-ai#20612's landed stages and touched none of this diff's files. `origin/main` has since moved to `ed6f7348f9`, one commit that touches only `packages/cli`, so there was no second merge. - **Anchors shared with the landed stages.** 24 numbers keep the anchor the spec, lint or service-messaging stages already gave them, for example `f19475c0a` (objectstack-ai#14143), `b003cf2e8` (objectstack-ai#13657), `311433f6b` (objectstack-ai#12194), `8e13ca876` (objectstack-ai#6206) and `17d095413` (objectstack-ai#6363). ## Deviations - Three changed lines hold only a slash-joined dead number, beyond the census's sites (see Acceptance notes). Five more are the other half of a rewritten sentence (listed under What changed). - Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
… to the commits that decided them (objectstack-ai#20632) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 2 of the `domain:cli` lane of the dead-citation sweep: `packages/rest/src/**`. Every comment or docblock site in scope that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20624 (stage 1, `packages/runtime`) the precedent this follows line for line. Later stages cover `cli`, `types` and the rest of the lane, so this PR says `Part of` and the card stays open. That is **457 comment sites on 445 lines in 85 files, covering 74 numbers**: the census's 191 sites, 256 more in test comments (which the census defers), and 10 sites whose dead number is the second half of a slash-joined pair the citation grammar does not read (`objectstack-ai#3984/objectstack-ai#6241`, `objectstack-ai#9901/objectstack-ai#10255` four times, `objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292`, `objectstack-ai#11235/objectstack-ai#11242` twice, `objectstack-ai#10993/objectstack-ai#11242`, `objectstack-ai#7543/objectstack-ai#15071`). Each rewritten line cites one of **70 distinct commits**. ADR-0076 D11 is the only ADR that records any of these numbers, and it records objectstack-ai#8850 only as the extraction it names as landed in `8664a2c99`, so that commit is the anchor there. No other ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of these numbers, so every anchor is a commit. The anchors the landed stages already gave the same numbers are reused where the rest sites describe the same decision (30 numbers, for example `79c46da90` for objectstack-ai#9934, `7986d973f` / `311433f6b` for the compound-name retirement, `6a180e42d` for objectstack-ai#13279 and `cf6e0a193` for objectstack-ai#15071), so each number carries one anchor across the tree. Only comments changed. Every touched file keeps its line count (451 lines out, 451 in, over 85 files), so no line citation into these files moves. Six of the 451 lines held no dead site; each is the other half of a sentence that had to change: - `discovery-schema-conformance.test.ts:343` (「(reaffirmed by」 to 「(which commits」, because line 344 now names the two commits that landed the ruling), - `package-door-16019-raw-statement-fault-code.test.ts:51` and `error-response.ts:1485` (a trailing 「PR」 whose number wrapped onto the next line), - `error-response-structured-arm-door-parity.test.ts:463` (「That card added the limb」 to 「That commit」, because line 459's tag now names the commit), - `rest-hook-script-fault-envelope.test.ts:331` (「both sides of that card」 to 「that fix」), - `rest-server.ts:908` (「(objectstack-ai#14409, landed」 to 「(landed as commit」, the sha `3ecb7dc1a` already standing on line 909). **No citation number is added.** Every tracker number on an added line was already on the line it replaces. No PR number stands on an added line. One of the 70 shas is on a removed line, and it was there before: `rest-14078-invalid-date-total-arm.test.ts:19` read 「PR objectstack-ai#14409 (landed `3ecb7dc1a`)」 and now reads 「Commit 3ecb7dc drove」. No code token moves (see the guard below). Three dead comment sites are left on purpose, listed under "The sites left". One more file: a `patch` changeset for `@objectstack/rest`, because the rewritten docblocks ship (see Changeset below). ## Census: `packages/rest`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run with the fleet token. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count is its `allocated-but-absent` findings under `packages/rest/`. Both runs enumerated the whole board (185 pages), so neither read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | rest sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `a186aea996`, run 2026-09-29T10:28:18Z to 10:36:06Z | enumerated, 185 pages, frontier objectstack-ai#20628, 18,455 numbers | 2,015 | **191** | 186 | 14 | 51 | | after | head `93e4d69ba6`, run 11:11:30Z to 11:17:37Z | enumerated, 185 pages, frontier objectstack-ai#20630, 18,457 numbers | 1,764 | **0** | 0 | 0 | 0 | The before count equals the card's 191 at `f11b5f20a2`. The whole-repo drop is 251: this diff's 191, plus the 60 of PR objectstack-ai#20626 (`packages/plugins/plugin-sharing`, 63 to 3), which landed on `main` in between and came in with the merge. No other package moved. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `packages/rest/src` (256 files), against the board enumerated through the gate's own `enumerateBoard`. The lit controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20624 answered 200 and are on both boards; the dead controls objectstack-ai#13214, objectstack-ai#14541 and objectstack-ai#15071 answered 404 and are on neither. | reading | tree | board | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---|---| | before, 10:29Z | `a186aea996` | 185 pages, frontier objectstack-ai#20628 | 4,620 | **577** | 191 | 259 | 1 | 126 | | after, 11:21Z | `93e4d69ba6` | 185 pages, frontier objectstack-ai#20631 | 4,174 | **130** | 0 | 3 | 1 | 126 | Its src-comment column equals the census's 191 and 0, which is the control on the second instrument, and a site-by-site comparison of the two before-readings is identical. Resolving comment citations move by one (1,364 to 1,365 in src): `(objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292)` became `(objectstack-ai#10993, commit 376c70f, objectstack-ai#11292)`, so the grammar now reads the live `objectstack-ai#11292` that the slash hid. The drop is 447 grammar-read sites; the other 10 rewritten sites are the slash-joined ones the grammar never read. Separately, every one of the 77 numbers was probed on its web endpoint: 76 answer 404 (deleted) and one, #14026, answers 302 to objectstack-ai/objectui#10102 (transferred), which is why it is left (see below). ## Per-number table Sites and files are the dead comment sites in scope at the base, tests and slash-joined halves included. `left` is a site with no deciding commit (see below). `strings kept` counts string-literal sites, which are tokens and stay as they were. Every anchor was read in its message or its diff, not only in its subject: it is the commit that made the change the line describes, and its own message or diff names the number it replaces or adds the citation the line carries. | number | comment sites / files | rewritten | left | strings kept | anchor | |---|---|---|---|---|---| | `objectstack-ai#6037` | 5/3 | 5 | 0 | 0 | `18189983d` | | `objectstack-ai#6122` | 2/2 | 2 | 0 | 0 | `64cd01082` | | `objectstack-ai#6206` | 1/1 | 1 | 0 | 0 | `8e13ca876` | | `objectstack-ai#6216` | 6/2 | 6 | 0 | 2 | `f586f1a89` | | `objectstack-ai#6241` | 10/3 (1 slash-joined) | 10 | 0 | 1 | `83a3b1f2e` | | `objectstack-ai#6259` | 2/1 | 2 | 0 | 0 | `6968885ef` | | `objectstack-ai#6303` | 1/1 | 1 | 0 | 0 | `465c5fc14` | | `objectstack-ai#6306` | 9/5 | 9 | 0 | 3 | `fec784863` | | `objectstack-ai#6307` | 4/2 | 4 | 0 | 0 | `293476148` | | `objectstack-ai#6349` | 4/2 | 4 | 0 | 4 | `2443bb4c4` | | `objectstack-ai#6474` | 1/1 | 1 | 0 | 0 | `18189983d` | | `objectstack-ai#6535` | 3/2 | 3 | 0 | 0 | `a92b1793c` | | `objectstack-ai#6640` | 1/1 | 1 | 0 | 1 | `2ab1257c9` | | `objectstack-ai#6704` | 5/1 | 5 | 0 | 1 | `c3f491626` | | `objectstack-ai#8641` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#8850` | 3/3 | 3 | 0 | 0 | `8664a2c99` | | `objectstack-ai#8885` | 6/3 | 6 | 0 | 3 | `30b1c636a` | | `objectstack-ai#8919` | 7/3 | 7 | 0 | 7 | `b5378550e` | | `objectstack-ai#9741` | 12/1 | 12 | 0 | 0 | `2a29caa53` | | `objectstack-ai#9805` | 1/1 | 1 | 0 | 0 | `45862a53d` | | `objectstack-ai#9934` | 19/10 | 19 | 0 | 4 | `79c46da90` | | `objectstack-ai#9967` | 2/2 | 2 | 0 | 4 | `8f266f1cd` | | `objectstack-ai#10063` | 2/2 | 2 | 0 | 1 | `9e04c3e35` | | `objectstack-ai#10178` | 1/1 | 1 | 0 | 0 | `38cf397ea` | | `objectstack-ai#10179` | 0/0 | 0 | 0 | 1 | | | `objectstack-ai#10255` | 18/4 (4 slash-joined) | 18 | 0 | 2 | `6ce58a735` | | `objectstack-ai#10340` | 13/3 | 13 | 0 | 2 | `26f3588fb` | | `objectstack-ai#10345` | 13/6 | 13 | 0 | 6 | `cad8b42f0` | | `objectstack-ai#10350` | 1/1 | 1 | 0 | 0 | `490879ad0` | | `objectstack-ai#10485` | 2/1 | 2 | 0 | 1 | `35ad101bc` | | `objectstack-ai#10537` | 9/3 | 9 | 0 | 1 | `e634ecf6a` | | `objectstack-ai#10888` | 2/2 | 2 | 0 | 0 | `d806081dd` | | `objectstack-ai#11006` | 3/1 | 3 | 0 | 0 | `cccbe51bf` | | `objectstack-ai#11130` | 1/1 | 1 | 0 | 0 | `851909530` | | `objectstack-ai#11235` | 4/2 (1 slash-joined) | 4 | 0 | 0 | `376c70f98` | | `objectstack-ai#11242` | 3/2 (3 slash-joined) | 3 | 0 | 0 | `98ea3443f` | | `objectstack-ai#12144` | 1/1 | 1 | 0 | 0 | `3a04b0125` | | `objectstack-ai#12176` | 11/7 | 11 | 0 | 2 | `7986d973f` | | `objectstack-ai#12194` | 15/5 | 15 | 0 | 4 | `311433f6b` | | `objectstack-ai#12195` | 35/16 | 35 | 0 | 7 | `7986d973f` | | `objectstack-ai#13182` | 2/2 | 2 | 0 | 0 | `5b3ff63cc` | | `objectstack-ai#13197` | 1/1 | 1 | 0 | 0 | `56c093c4d` | | `objectstack-ai#13213` | 2/1 | 2 | 0 | 0 | `4801296e7` | | `objectstack-ai#13214` | 18/6 | 18 | 0 | 14 | `cc837dbfe`, `889ec5b42`, `3d10755f0` | | `objectstack-ai#13244` | 5/2 | 5 | 0 | 1 | `889ec5b42` | | `objectstack-ai#13255` | 4/1 | 4 | 0 | 6 | `43028a8f8` | | `objectstack-ai#13258` | 1/1 | 1 | 0 | 0 | `3d10755f0` | | `objectstack-ai#13279` | 23/5 | 23 | 0 | 5 | `6a180e42d` | | `objectstack-ai#13280` | 13/4 | 13 | 0 | 2 | `add6a1b1c` | | `objectstack-ai#13282` | 1/1 | 1 | 0 | 0 | `43028a8f8` | | `objectstack-ai#13377` | 3/2 | 3 | 0 | 0 | `e10cf3444` | | `objectstack-ai#13378` | 2/1 | 2 | 0 | 0 | `82faea03f` | | `objectstack-ai#13454` | 1/1 | 1 | 0 | 0 | `7ad57e17a` | | `#14026` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#14365` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#14366` | 14/4 | 14 | 0 | 2 | `53cbad9f7` | | `objectstack-ai#14369` | 3/2 | 3 | 0 | 0 | `a3d5724c8`, `53cbad9f7` | | `objectstack-ai#14389` | 7/3 | 7 | 0 | 7 | `10220a7bf` | | `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | `9d7f7259f` | | `objectstack-ai#14409` | 2/2 | 2 | 0 | 0 | `3ecb7dc1a` | | `objectstack-ai#14541` | 27/4 | 27 | 0 | 5 | `6d178a408` | | `objectstack-ai#14613` | 2/2 | 2 | 0 | 0 | `81208086a` | | `objectstack-ai#14677` | 1/1 | 1 | 0 | 0 | `a4e4d2d78` | | `objectstack-ai#14683` | 8/2 | 8 | 0 | 0 | `96326040f` | | `objectstack-ai#14691` | 15/2 | 15 | 0 | 2 | `b3a63d32c` | | `objectstack-ai#14704` | 9/3 | 9 | 0 | 2 | `1c7adc73d` | | `objectstack-ai#14723` | 7/4 | 7 | 0 | 4 | `65846bc46` | | `objectstack-ai#14725` | 3/3 | 3 | 0 | 2 | `f5cc78b63` | | `objectstack-ai#14849` | 3/1 | 3 | 0 | 0 | `226e72443` | | `objectstack-ai#14907` | 1/1 | 1 | 0 | 0 | `e1d4f9e3f` | | `objectstack-ai#14908` | 1/1 | 1 | 0 | 0 | `d5cbb44f3` | | `objectstack-ai#15021` | 2/1 | 2 | 0 | 8 | `cc238db8b` | | `objectstack-ai#15034` | 6/2 | 6 | 0 | 0 | `abf9101f1` | | `objectstack-ai#15065` | 1/1 | 1 | 0 | 0 | `1c7adc73d` | | `objectstack-ai#15071` | 23/4 (1 slash-joined) | 23 | 0 | 3 | `cf6e0a193` | | `objectstack-ai#16650` | 1/1 | 1 | 0 | 0 | `001a83b04` | | `objectstack-ai#17058` | 3/1 | 3 | 0 | 4 | `94c930248` | | `objectstack-ai#18546` | 3/2 | 3 | 0 | 3 | `58f60e37e` | | **total** | **460** | **457** | **3** | **127** | **70 distinct commits** | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 70), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 70). The checkout is not shallow (`--is-shallow-repository` false); the control leg `13a6cb4ad` exits 0 and the negative control (this branch's first WIP commit, not on `main`) exits 1. Several numbers are the PR number of their own anchor commit (objectstack-ai#6122, objectstack-ai#6303, objectstack-ai#6474, objectstack-ai#11242, objectstack-ai#13213, objectstack-ai#13244, objectstack-ai#13258, objectstack-ai#13282, objectstack-ai#14409, objectstack-ai#14677, objectstack-ai#14908, objectstack-ai#15065, objectstack-ai#16650), so the sha is the same object the number named. **Numbers with more than one anchor, by site:** - `objectstack-ai#13214` (18 sites) was one card with three commits. `cc837dbfe` (the ownership gate, the 2026-08-30 ruling) for the 11 sites that describe the gate; `889ec5b42` for the 5 in `ui-view-route-identity.measurement.test.ts`, the identity measurement it created; `3d10755f0` for the tenancy file's header, the measurement it created; and `rest-server.ts:2247`, 「Driven and reported on objectstack-ai#13214 (PRs objectstack-ai#13244, objectstack-ai#13258)」, now reads 「Measured in commits 889ec5b (identity) and 3d10755 (tenancy)」: those PRs are exactly those two commits. - `objectstack-ai#14369` (3 sites): `a3d5724c8` (the liveness census it recorded) for `rest-server.ts:1172` and `rest-sub-config-parse-not-cast.test.ts:48`. `rest-server.ts:4092` said the zero read sites of `api.documentation` / `api.responseFormat` came from 「the objectstack-ai#14369 census」, but `a3d5724c8` explicitly left `api` out of that census; the zero was measured by `53cbad9f7` (its changeset: no other read site for either key), which is the anchor there. - `objectstack-ai#11235` / `objectstack-ai#11242` / `objectstack-ai#10993`: `376c70f98` derives the discovery `version` in metadata-protocol (objectstack-ai#11235), and `98ea3443f` is objectstack-ai#11242's own squash, which landed the objectstack-ai#10993 ruling on `/health` and the dispatcher's `/discovery`. So 「the objectstack-ai#10993 ruling … reaffirmed by objectstack-ai#11235/objectstack-ai#11242」 now reads 「the objectstack-ai#10993 ruling, landed by commits 98ea344 and 376c70f」 (`rest-server.ts:4528`, `discovery-schema-conformance.test.ts:343-344`). `objectstack-ai#10993`, `objectstack-ai#11292` and `objectstack-ai#11297` answer 200 and stay. - `objectstack-ai#6037` / `objectstack-ai#6474`: one commit, `18189983d` (objectstack-ai#6474 is its PR number), so 「(objectstack-ai#6037 / PR objectstack-ai#6474)」 became 「(commit 1818998)」. **Wordings to check, each true of its commit:** - A commit does not rule. Where a line said a number ruled, it now says what the commit did with the ruling: 「the ruling commit 79c46da landed says it does」, 「the ruling commit cf6e0a1 implemented fences it」, 「the ruling commit 10220a7 implemented」, 「the 2026-08-20 ruling, landed as commit 6ce58a7」, 「recorded in commit 6ce58a7's message (option A)」 (its message reads 「Ruled on objectstack-ai#10255 (2026-08-20, option A)」), and 「question was ruled on 2026-08-20 and landed as commit 6ce58a7」 where the line said 「filed as objectstack-ai#10255」. - `objectstack-ai#14541`'s contract review: 「the objectstack-ai#14541 contract review (condition N)」 now reads 「the contract review of commit 6d178a4 (condition N)」; that commit's message lists the conditions it carries. 「objectstack-ai#14541's §4」 and 「objectstack-ai#14541 §5」 in `error-response-generic-passthrough-object-parity.test.ts` are sections of `error-response-structured-arm-door-parity.test.ts` (the file `6d178a408` created), so they now name that file. 「measured on the objectstack-ai#14541 branch」 reads 「on the branch that landed as commit 6d178a4」. - A line that named a DEFECT by its number now says so: 「Before commit 9e04c3e the draft→active promotion door could not…」, 「Before commit 26f3588 the `/meta` doors decided ORGANIZATION SCOPE from the RAW url」, 「the defect commit 2443bb4 fixed」 and 「would be the defect commit 26f3588 fixed」. - `objectstack-ai#13255`: 「As written for objectstack-ai#13255 this file repaired nothing」 reads 「As first written (commit 43028a8)」, the commit that created the file and answered the measurement; 「CONTEXT-LOST family (objectstack-ai#13255), still unruled」 reads 「first measured by commit 43028a8」 (the ruling on that family never landed, which the line still says). - `objectstack-ai#13214` in the identity file: 「the half objectstack-ai#13214 marks UNMEASURED」 reads 「the half left UNMEASURED until commit 889ec5b」, and 「objectstack-ai#13214 asks for an INDEPENDENT reproduction」 reads 「commit 889ec5b is an INDEPENDENT reproduction」. - 「the objectstack-ai#8885 sweep」 reads 「the sweep behind commit 30b1c63」, the commit that registered the 9 codes the sweep found; 「objectstack-ai#14849 predicted」 reads 「The card behind commit 226e724 predicted」; 「the hazard objectstack-ai#13377 names」 reads 「the hazard commit e10cf34 was written to remove」; 「The concrete harm objectstack-ai#6704 names」 reads 「removed」. - Quoted ruling: `error-response-sandbox-arm-message.test.ts:340` sits inside a verbatim ruling quote, so the commit stands in an editorial bracket (「not from [commit 1c7adc7]'s list」), as PR objectstack-ai#20624 did. - Two markdown tables in comments (`meta-state-route-engine-outage.test.ts:76`, `objectql-slot-consumer-census.test.ts:43`): the rewritten cell is wider than its column, and its padding is reduced rather than widening the four sibling rows. ## The sites left **No deciding commit (3 sites, all in test files, so the census does not see them):** - `meta-object-owd-gate.test.ts:516` (objectstack-ai#8641): 「whether it should stay is objectstack-ai#8641's question」, an open decision. The commit that added the citation calls it a pointer to the open decision card, and no commit decides it. - `rest-sub-config-parse-not-cast.test.ts:321` (objectstack-ai#14365): the `z.partialRecord` question 「deferred to objectstack-ai#14365」 was never taken (`git log -S partialRecord`); `b3a63d32c` made it moot by retiring the record, which the other half of the same line now cites. - `import-integration.test.ts:1043` (#14026): not deleted, TRANSFERRED. The web endpoint answers 302 to objectstack-ai/objectui#10102, the REST read follows the redirect, and the board enumeration does not list it, so the census and the supplementary reading both class it `allocated-but-absent`. The line says how an issue was raised; no commit decides that, so form C has nothing to cite. **String sites kept as tokens (127).** 126 are test titles and test-code strings in 41 files. One is a non-test string: the `note` field of the REST route ledger's `GET /api/v1/meta/object/:name/state/:field` row at `rest-route-ledger.ts:290`, which ends 「(objectstack-ai#10179)」 (see Acceptance notes). ## Mechanical guard: no code token moves The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file at base `a186aea996` against the working tree at `93e4d69ba6`, over all 85 touched `.ts` files. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 272,653 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control (`error-response.ts`): 0 files changed (exit 0). - Code-insertion positive control (a declaration in the same file): DIFFER at token 0 (exit 1). - String positive control (the first string literal past offset 2000 of the same file, one character added inside it): DIFFER at token 26 (exit 1). Line balance: every touched file is +N/−N (451/451), and every line count is equal at base and head. A raw scan of the 86 changed files for control bytes finds none (its positive control on a scratch file with a U+0001 byte matches). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/rest` is included, in PR objectstack-ai#20624's form and level. It says only that the provenance comments were re-anchored. Measured on the built package: `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After `pnpm --filter @objectstack/rest build`, the rewritten docblocks reach `dist`: for example `53cbad9f7` appears 4 times in `dist/index.d.ts`, and `26f3588fb` 8 times and `b3a63d32c` 5 times in `dist/index.js`. The positive control, the unchanged sentence 「It was VALIDATE-ONLY from objectstack-ai#11637」 of the same `rest-server.ts` docblock whose first line now reads 「[commit 53cbad9] The parsed output is CONSUMED」, is in `dist/index.d.ts` beside it; a negative control phrase appears nowhere. ## Gates (head `93e4d69ba6`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each locked run at this head: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 47s · declare it in the PR body · pnpm --filter '@objectstack/rest...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 76s (1m16s) · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/rest typecheck ``` The branch merged `origin/main` once (`93e4d69ba6`, merging `542670da6d`) before these runs, as the dispatch orders; `origin/main` has not moved since (read at 11:19Z). The merge brought PR objectstack-ai#20626 and PR objectstack-ai#20587 and touched none of this diff's files. The dependency closure was built first (`pnpm --filter '@objectstack/rest...' build`, 26 packages), then the whole workspace (`turbo run build --filter='./packages/*' --filter='./packages/*/*'`, 71 tasks, 71 successful). - **Tests:** `vitest run --project local`: 227 files, 4,382 tests passed, 50 skipped. `--project repo` (which holds the touched `meta-state-route-doc-spelling.test.ts`): 1 file, 8 tests passed. Together they are all 228 test files of the package, so every touched test file ran. - **Typecheck:** `pnpm --filter @objectstack/rest typecheck` exits 0. `tsc --listFiles` counts 28 `src` files (no tests) under `tsconfig.json` and all 228 test files under `tsconfig.test.json`, which `check:test-typecheck` judges: 0 files, 0 errors, 0 pinned signatures in the ledger. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `93e4d69ba6` (2026-09-29T11:19:30Z to 11:20:00Z). Not narrowed. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0: 19 citations judged across 14 files (18 resolve, 1 resolves as a pull request). These are the live numbers that stay on rewritten lines. It defers `*.test.ts`, so the added-minus-removed count over the whole diff covers the rest: 0 numbers added. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `93e4d69ba6` derived 68 families. All 68 exit 0, and `--ran` over a record carrying each exit code reads 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero). - `check:dual-build-cjs-loads` and `check:type-check-debt` first exited 3 (PREREQUISITE NOT MET, nothing measured) on the closure-only build; after the whole-workspace build both exited 0. - Among them: `check:doc-authoring`, `check:nul-bytes`, `check:rest-log-declared`, `check:route-envelope`, `check:system-context-census` (106 elevation read sites, the page's 102 symbols held) and `check:issue-citations` (self-test). - **Artifact rosters:** 33 of the 36 non-self-test roster rows exit 0 at `93e4d69ba6`, `check-changeset-fixed` (the one whose roster sits under `.changeset/`) and `check:route-ledger-census` among them. The other three, `check-closing-target-claim`, `check-partof-closing-keyword` and `check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull request's context; they are run against this PR once it exists and reported on the card. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 191 dead sites at `a186aea996` (186 lines, 14 files, 51 numbers), equal to the card's count at `f11b5f20a2`: no net drift, although PR objectstack-ai#20601 (merged as `eb4b17c346`, before this base) touches four files in `packages/rest`. - **H1 holds.** After the rewrite the filtered census answers 0. The supplementary reading leaves 3 test-comment sites, the three listed above: an open decision, an untaken option and a transferred issue, none with a deciding commit. No site was held for an open PR: the claim's read and this stage's two reads of the open PRs' file lists (10:27:35Z, 7 open PRs; 11:30:34Z, 8 open PRs) found none touching `packages/rest`. - **H2 holds, by the token guard.** A comment-stripped comparison of every touched file (the parser's leaf tokens, JSDoc excluded) is empty, and its code and string controls fire. The emitted `dist` is not byte-identical, because the docblocks ship, which is why the changeset is `patch`. ## Acceptance notes - **Form D, not touched here.** 127 dead numbers stand inside string literals: 126 in test titles and test-code strings, and one in the `note` of the REST route ledger's legal-next-state row (`rest-route-ledger.ts:290`, 「(objectstack-ai#10179)」), which is ledger data, not an author-shown refusal. Ruling D (no number, the lesson in words) is a string change outside this comment-only scope; the card already carries a form-D stage for the lane. - **A transferred issue among the 404s.** #14026 answers 302 to objectstack-ai/objectui#10102 on its web endpoint. The census classes it `allocated-but-absent` (deleted and transferred are only told apart under `--probe-cause`), and `scripts/check-issue-citations.mjs`'s header says the `transferred` arm has no positive specimen on this tree; this is one. Noted, not filed. - **The grammar does not read a slash-joined number.** `CITATION_RE` refuses a `#` preceded by `/`, so the second number of `#A/#B` is never judged. In `packages/rest/src` six such dead numbers stood at 10 comment sites, all rewritten here; one more, `objectstack-ai#14389` in `objectstack-ai#14095/objectstack-ai#14389`, stands inside a string (`error-response-structured-arm-door-parity.test.ts:187`) and is kept. The same shape PR objectstack-ai#20624 and PR objectstack-ai#20612 reported. Noted, not filed. - **Outside the scope and the census surface.** `packages/rest/vitest.config.ts:21` cites objectstack-ai#17853, which answers 404; `packages/rest/test-typecheck-debt.json`, written by `gen:test-typecheck-debt`, carries objectstack-ai#13470, objectstack-ai#13454, objectstack-ai#13377 and objectstack-ai#13378 in its prose, all 404. Neither is under `src/**`. The other numbers in `vitest.config.ts`, `tsconfig.json` and `tsconfig.test.json` answer 200. - **Two comments stale on their own, not touched.** The anchor research found `rest-server.ts`'s `api` docblock near `:1115` and the 「zero read sites」 sentence at `:4092` both overtaken by `80153f5a4`, whose own acceptance notes record it. This PR re-anchors their citations and leaves their claims alone. - **An attribution corrected by the anchor.** `rest-server.ts:4092` credited its zero-read-site count to 「the objectstack-ai#14369 census」, which (`a3d5724c8`) excluded `api`; it now cites `53cbad9f7`, the commit that measured it. - **Base.** One merge of `origin/main` (`93e4d69ba6`) before the `--base origin/main` run, as the dispatch orders. ## Deviations - Ten sites beyond the census's read grammar carry a slash-joined dead number and are rewritten; six more lines are the other half of a rewritten sentence (listed under What changed). - The whole-workspace build ran with `--concurrency=4`, not 2, to stay inside the ten-minute foreground cap on this host; it took 1m42s. - Anchor research for 33 of the 77 numbers ran in three read-only research subagents; every proposal was verified here against the commit's message or diff, and the wording of each changed line was reviewed and corrected by hand in a second pass. - Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…o the commits that decided them, and the source-hashes header at its producer (objectstack-ai#20656) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 3 of the `domain:cli` lane of the dead-citation sweep: `packages/cli/src/**`, plus the generated-header producer the `domain:services` pointer on the card hands this lane. Every comment or docblock site in scope that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. PR objectstack-ai#20533 is the method; PR objectstack-ai#20624 (stage 1, `packages/runtime`) and PR objectstack-ai#20632 (stage 2, `packages/rest`) are the precedents this follows. Later stages cover `types` and the rest of the lane, so this PR says `Part of` and the card stays open. That is **313 comment sites on 304 lines in 64 files, covering 63 numbers**: the census's 170 rewritable sites (of its 174), 142 more in test comments (which the census defers), and one site whose dead number is the second half of a slash-joined pair the citation grammar does not read (`serve.ts:1173`, `objectstack-ai#10943/objectstack-ai#11157`). Each rewritten line cites one of **62 distinct commits**, except the six `objectstack-ai#15041` sites, which cite ADR-0104's 2026-09-05 addendum: that ADR records the maintainer ruling the lines describe, and the ruling allows the ADR to be cited instead of a commit. Only comments changed in `packages/cli/src`, apart from the two string literals this stage declares (next section). Every touched file keeps its line count (319 lines out, 319 in, over 65 files), so no line citation into these files moves. Thirteen of the 319 lines held no dead site; each is the other half of a sentence that had to change: `create.ts:326`, `doctor-organizations-message-spelling.test.ts:11`, `environments.test.ts:162`, `generate.ts:153`, `serve-cluster-host-resolution.test.ts:816`, `serve-host-fallback-base.test.ts:5`, `validate.ts:336`, `validate.ts:813`, `validate.ts:815`, `hook-body-lowering.test.ts:10`, `format.ts:1132`, `format.ts:1435` and `i18n-extract.ts:34` (mostly 「that card」 to 「that commit」 once the antecedent became a commit). **No citation number is added.** Every tracker number on an added line was already on the line it replaces. One PR number stands on an added line, and it was there before: `doctor-organizations-message-spelling.test.ts:9` read 「PR objectstack-ai#12463 (objectstack-ai#12151) single-sourced」 and now reads 「Commit 27b6902 (PR objectstack-ai#12463) single-sourced」, keeping the live PR as a convenience link beside the commit, as the ruling allows (objectstack-ai#12463 is that commit's own PR). No code token moves (see the guard below). Four dead comment sites are left on purpose, listed under "The sites left". Two more files outside `packages/cli`: a `patch` changeset for `@objectstack/cli`, and the shrink-only `check:doc-authoring` prose-id ledger (see Deviations). ## The source-hashes producer and its 27 generated companions The claim declares these as the only strings this stage moves, and the regeneration of the files they write. - **The producer.** `packages/cli/src/utils/i18n-extract.ts:2294` is the string literal `renderSourceHashModule` writes as line 8 of every `LOCALE.source-hashes.generated.ts`. It read 「bundles (objectstack-ai#11671, maintainer ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B).」 and now reads 「bundles (commit 09b4f4e, maintainer ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B).」. `09b4f4e4e` is the commit that extended the objectstack-ai#8765 Option B source-hash mechanism to the generated bundles per maintainer ruling objectstack-ai#12069 Option A; its message says exactly that, and it is the anchor stages 1 and 2 of objectstack-ai#20596 gave `objectstack-ai#11671`. objectstack-ai#12069 and objectstack-ai#8765 answer 200 (REST and web) and stay. The comment at `:249` beside `previousSourceHashes` cites the same commit. - **The flag's help text** (`packages/cli/src/commands/i18n/extract.ts:227`, author-shown CLI help), in form D: the lesson in words, no number. It read 「the provenance companion that lets a stale fill be told from a translation (objectstack-ai#11671).」 and now reads 「the provenance companion that records which source revision each generated leaf is still a copy of, so a stale fill can be told from a translation.」. The rest of the description is unchanged. - **The regeneration.** `node scripts/check-i18n-bundles.mjs --write`, which runs each package's documented `os i18n extract` command from its `i18n-extract.config.ts` (every one of the nine carries the source-hashes flag), on a CLI built from `47241dd80e`. Before it, the bundle check reported the nine packages DRIFTED, 3 bundles each, against the new producer. After it: - exactly 27 files changed, `+27 −27`: 3 locales in `packages/platform-objects/src/apps/translations`, `plugins/plugin-{approvals,audit,security,sharing,webhooks}` and `services/service-{messaging,realtime,storage}`; - every hunk is `@@ -8 +8 @@`, and the one removed and one added line are the same in all 27 files; - each file with line 8 deleted hashes identically at base and head (27 of 27), so every hash entry is byte-identical; - `git status` shows nothing else in the nine packages, tracked or untracked; - `node scripts/check-i18n-bundles.mjs` then reads all nine packages in sync (7 bundles each), and `check:i18n-stale-fill` serves 27 of 27 companions with 0 stale fills. - **H3 holds.** `git grep -n "objectstack-ai#11671" -- '*.source-hashes.generated.ts'` answers 0 hits at head; the same grep at `04b202e5cb` answers 27 (the positive control). - **Not published by the nine.** The header is a comment their bundlers strip: after the build, none of the nine packages' `dist` holds `09b4f4e4e` or the header's own phrase 「Each entry is the digest of the SOURCE REVISION」, while the export name `…GeneratedSourceHashes` (the positive control) is in each `dist`. So the regeneration changes no published byte of those packages, and no changeset is owed for them. The other lanes' stages can keep leaving their generated copies alone, as the pointer asked. ## Held files `packages/cli/src/utils/sdui-manifest.ts` and `sdui-manifest.test.ts` stay at their base blobs (`41c4549ffe` and `3a242b54e0`, equal at base and head), because PR objectstack-ai#20589 edits them. They carry four citations (`objectstack-ai#4409` once, `objectstack-ai#20113` three times), and all four answer 200, so no dead site is held and there is no anchor to list for a follow-up. ## Census: `packages/cli`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run with the fleet token. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count is its `allocated-but-absent` findings under `packages/cli/`. Every run enumerated the whole board (185 pages), so none read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | cli sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `04b202e5cb`, run 2026-09-29T12:35:39Z to 12:43:03Z | enumerated, 185 pages, frontier objectstack-ai#20642, 18,469 numbers | 1,707 | **174** | 167 | 30 | 50 | | after | head `6bb4d3b531`, run 13:46:09Z to 13:54:25Z | enumerated, 185 pages, frontier objectstack-ai#20652, 18,479 numbers | 1,510 | **4** | 4 | 3 | 2 | The before count equals the card's 174 at `f11b5f20a2`. The 4 left are the deliberate sites below. The whole-repo drop is 197: this diff's 170 cli sites plus the 27 generated headers (3 in each of the nine packages; nothing else moved in any of them). The two merges of `origin/main` moved no count. An earlier after-run at `d1e09a7eed` (13:19:18Z to 13:29:41Z, frontier objectstack-ai#20649) read the same 4 and 1,510; `packages/cli` and the 27 companions are byte-identical between the two heads. One more attempt at `6bb4d3b531` (13:35:15Z) exited 3, PREREQUISITE NOT MET, on a malformed board page, and measured nothing; the run in the table is its retry. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts`/`.tsx` file under `packages/cli/src` (298 files), against a board enumerated through the gate's own `enumerateBoard`. The lit controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20632 answered 200 and are on both boards; the dead controls objectstack-ai#11671, objectstack-ai#10514 and objectstack-ai#14828 answered 404 and are on neither. | reading | tree | board | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---|---| | before, 12:43Z | `04b202e5cb` | 185 pages, frontier objectstack-ai#20642 | 3,029 | **368** | 174 | 142 | 4 | 48 | | after, 13:39Z | `6bb4d3b531` | 185 pages, frontier objectstack-ai#20650 | 2,715 | **54** | 4 | 0 | 2 | 48 | Its src-comment column equals the census's 174 and 4, which is the control on the second instrument. The resolving citations (1,468 and 755 in comments, 50 and 53 as pull requests, 32 cross-repo) are the same in both readings, so no live citation was lost; the drop of 314 is exactly the dead sites removed (312 grammar-read comment sites and the two `objectstack-ai#11671` strings). The one slash-joined dead number the grammar never reads (`objectstack-ai#11157` in `objectstack-ai#10943/objectstack-ai#11157`) is gone too: a separate scan for dead `#N` tokens the grammar skips answers 1 before and 0 after. ## Per-number table Sites and files are the dead comment sites in scope at the base, test sites counted in brackets. `left` is a site with no deciding commit (see below). `strings kept` counts string-literal sites, which are tokens and stay as they were. Every anchor was read in its message or its diff, not only in its subject: it is the commit that made the change the line describes, and its own message or diff names the number it replaces or adds the citation the line carries. | number | comment sites / files | rewritten | left | strings kept | anchor | |---|---|---|---|---|---| | `objectstack-ai#6217` | 12/8 (1 test) | 12 | 0 | 0 | `2b641ddd4` | | `objectstack-ai#6238` | 2/1 (2 test) | 2 | 0 | 2 | `c8d6f6e08` | | `objectstack-ai#6265` | 1/1 (1 test) | 1 | 0 | 0 | `cfb549db8` | | `objectstack-ai#6268` | 6/2 (2 test) | 6 | 0 | 1 | `68f5eccb1` | | `objectstack-ai#6293` | 2/2 (1 test) | 2 | 0 | 0 | `c39a911ae` | | `objectstack-ai#6344` | 2/2 (1 test) | 2 | 0 | 0 | `cfb549db8` | | `objectstack-ai#6345` | 21/6 (11 test) | 21 | 0 | 4 | `e2798fab7` | | `objectstack-ai#6535` | 1/1 | 1 | 0 | 0 | `a92b1793c` | | `objectstack-ai#8692` | 5/2 (3 test) | 5 | 0 | 3 | `712e185db` | | `objectstack-ai#10326` | 1/1 | 1 | 0 | 0 | `675ab574e` | | `objectstack-ai#10359` | 2/2 | 2 | 0 | 0 | `15b63e85a` | | `objectstack-ai#10398` | 1/1 (1 test) | 1 | 0 | 0 | `0681a76b8` | | `objectstack-ai#10485` | 1/1 | 1 | 0 | 0 | `35ad101bc` | | `objectstack-ai#10499` | 1/1 | 1 | 0 | 0 | `6d441e41f` | | `objectstack-ai#10504` | 8/1 | 8 | 0 | 0 | `ff5733e03`, `0d4bd93e7` | | `objectstack-ai#10514` | 16/2 (16 test) | 16 | 0 | 2 | `5359a9b4c` | | `objectstack-ai#10763` | 1/1 (1 test) | 1 | 0 | 0 | `c2b97c2a1` | | `objectstack-ai#10769` | 9/2 (6 test) | 9 | 0 | 0 | `3d7deb700` | | `objectstack-ai#10908` | 10/3 (6 test) | 10 | 0 | 5 | `9cc6777d3` | | `objectstack-ai#10909` | 2/1 | 2 | 0 | 0 | `5a90c56d1` | | `objectstack-ai#10917` | 1/1 | 1 | 0 | 0 | `7940de5e0` | | `objectstack-ai#10926` | 1/1 | 1 | 0 | 0 | `d173125fb` | | `objectstack-ai#10943` | 5/3 (2 test) | 5 | 0 | 0 | `46d34ab7c` | | `objectstack-ai#10944` | 9/3 (6 test) | 9 | 0 | 3 | `e598b1cbc` | | `objectstack-ai#10952` | 5/1 | 5 | 0 | 0 | `0d4bd93e7`, `ff5733e03` | | `objectstack-ai#10953` | 1/1 (1 test) | 1 | 0 | 0 | `be7262e72` | | `objectstack-ai#10967` | 6/2 (6 test) | 6 | 0 | 1 | `e4a71d418` | | `objectstack-ai#11022` | 1/1 (1 test) | 1 | 0 | 0 | `21756b325` | | `objectstack-ai#11025` | 3/2 | 3 | 0 | 0 | `1c3a46f87` | | `objectstack-ai#11048` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#11071` | 3/2 | 3 | 0 | 0 | `50fb191dc` | | `objectstack-ai#11157` | 15/4 (10 test) (1 slash-joined) | 15 | 0 | 2 | `a4cb7817f` | | `objectstack-ai#11172` | 5/1 | 5 | 0 | 0 | `05181e8cc` | | `objectstack-ai#11174` | 2/1 (2 test) | 2 | 0 | 1 | `ab23c67ab` | | `objectstack-ai#11221` | 3/1 (3 test) | 3 | 0 | 1 | `e278a2970` | | `objectstack-ai#11331` | 3/2 | 0 | 3 | 0 | — | | `objectstack-ai#11671` | 1/1 | 1 | 0 | 0 (2 moved) | `09b4f4e4e` | | `objectstack-ai#12125` | 11/3 | 11 | 0 | 0 | `79cf692b0` | | `objectstack-ai#12151` | 3/2 (3 test) | 3 | 0 | 3 | `27b690272` | | `objectstack-ai#12162` | 2/1 (2 test) | 2 | 0 | 0 | `c0f5e8f21` | | `objectstack-ai#12181` | 4/2 (3 test) | 4 | 0 | 0 | `cf71d73f8` | | `objectstack-ai#12297` | 3/1 | 3 | 0 | 0 | `9fd45a952` | | `objectstack-ai#12943` | 2/1 (2 test) | 2 | 0 | 0 | `090f2302e` | | `objectstack-ai#12961` | 1/1 | 1 | 0 | 0 | `901355c3b` | | `objectstack-ai#13109` | 2/1 | 2 | 0 | 0 | `8b236c826` | | `objectstack-ai#13193` | 6/2 (3 test) | 6 | 0 | 0 | `faff497fd` | | `objectstack-ai#13218` | 1/1 | 1 | 0 | 0 | `c45d8e6b4` | | `objectstack-ai#13347` | 3/3 (2 test) | 3 | 0 | 3 | `098a08ffa` | | `objectstack-ai#13651` | 12/7 (4 test) | 12 | 0 | 0 | `ada3834ad` | | `objectstack-ai#14192` | 2/2 | 2 | 0 | 0 | `4d0d9445a` | | `objectstack-ai#14336` | 4/1 | 4 | 0 | 0 | `79c71d29d` | | `objectstack-ai#14397` | 1/1 | 1 | 0 | 1 | `957f7bb45` | | `objectstack-ai#14657` | 20/2 (7 test) | 20 | 0 | 1 | `431979e67` | | `objectstack-ai#14667` | 1/1 | 1 | 0 | 0 | `dc7c226b9` | | `objectstack-ai#14824` | 3/1 | 3 | 0 | 0 | `cf6b67164` | | `objectstack-ai#14828` | 22/3 (7 test) | 22 | 0 | 3 | `08706f0e0` | | `objectstack-ai#14829` | 9/3 (6 test) | 9 | 0 | 3 | `ee370d318` | | `objectstack-ai#14902` | 1/1 | 1 | 0 | 0 | `61821e54c` | | `objectstack-ai#15040` | 6/3 (3 test) | 6 | 0 | 2 | `8644d1d33` | | `objectstack-ai#15041` | 6/5 (4 test) | 6 | 0 | 2 | ADR-0104 (2026-09-05 addendum, landed as 932acc3) | | `objectstack-ai#15045` | 2/2 (1 test) | 2 | 0 | 0 | `288fe9c34` | | `objectstack-ai#16887` | 2/1 (2 test) | 2 | 0 | 0 | `9cdffbe36` | | `objectstack-ai#17080` | 1/1 (1 test) | 1 | 0 | 0 | `8b4890343` | | `objectstack-ai#17081` | 8/3 (4 test) | 8 | 0 | 3 | `f721ef0ff`, `24d622b94` | | `objectstack-ai#17883` | 10/3 (5 test) | 10 | 0 | 3 | `b06b2db5c` | | **total** | **317** | **313** | **4** | **49** | **62 distinct commits + ADR-0104** | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 62), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 62). The checkout is not shallow (`--is-shallow-repository` false); the control leg `13a6cb4ad` exits 0 and the negative control (this branch's own `47241dd80e`, not on `main`) exits 1. ADR-0104's 2026-09-05 addendum landed as `932acc3df`, which passes the same four checks. Where an earlier stage gave a number an anchor and the cli site describes the same decision, the same anchor is reused (17 numbers, objectstack-ai#17081 for its application half only; for example `e2798fab7` for objectstack-ai#6345, `68f5eccb1` for objectstack-ai#6268, `35ad101bc` for objectstack-ai#10485, `09b4f4e4e` for objectstack-ai#11671 and `61821e54c` for objectstack-ai#14902), so each number carries one anchor across the tree. Several numbers are the PR number of their own anchor commit (objectstack-ai#6344, objectstack-ai#10398, objectstack-ai#14667, objectstack-ai#16887), so the sha is the same object the number named. **Numbers with more than one anchor, by site:** - `objectstack-ai#10504` / `objectstack-ai#10952` (`format.ts`): `ff5733e03` added the opt-in zero row for `UI:` alone and `0d4bd93e7` made it required for every section, so lines naming both now name both commits. `format.ts:1573` read 「(objectstack-ai#10504, objectstack-ai#10952, objectstack-ai#11172)」 and now reads 「(commits ff5733e, 0d4bd93, 05181e8)」. - `objectstack-ai#10943` / `objectstack-ai#11157` (`serve.ts`): `46d34ab7c` made the host importer's fallback base a caller-supplied parameter, and `a4cb7817f` made `serve` pass its own base and collapsed `importConfigPlugin` from three branches to two. The slash-joined `serve.ts:1173` 「(objectstack-ai#10943/objectstack-ai#11157)」 now reads 「(commits 46d34ab and a4cb781)」; `serve.ts:1459` 「(objectstack-ai#10908 → objectstack-ai#11157)」 reads 「(commits 9cc6777 → a4cb781)」. - `objectstack-ai#17081` (8 sites): one card with two halves. `f721ef0ff` took the platform's half (the `Dev admin` banner line says what the account sees) for 7 sites; `format.ts:1132` describes the application half and now reads 「[objectstack-ai#17556 — commit 24d622b]」, the spelling the spec stage used at `dev-login.zod.ts:10`, with `format.ts:1133` naming objectstack-ai#17081 in words (「its parent card」). - `objectstack-ai#15041` (6 sites): the decision is a maintainer ruling recorded verbatim in ADR-0104's 2026-09-05 addendum, whose Sequencing section names the three steps the lines cite. So the lines cite the addendum (「The ruling in ADR-0104's 2026-09-05 addendum decided it」, 「sequencing step 2 of ADR-0104's 2026-09-05 addendum」), not a commit. **Wordings to check, each true of its commit:** - A commit does not rule. Where a line said a number ruled, it now says what the commit did with the ruling: 「semantics by the ruling commit 68f5ecc landed」, 「Ruled at triage, landed as commit e598b1c」, 「the triage commit 9cc6777 landed requires this text be CHOSEN」, 「Rulings objectstack-ai#5728 and objectstack-ai#14412, and the ruling commit d173125 landed,」, and `resync.ts:96` keeps the ruling's date from `712e185db`'s message: 「commit 712e185 (the 2026-08-15 ruling)」. - A line that named a DEFECT by its number now says so: 「the defect commit 79cf692 fixed」, 「the defect commit 9cc6777 fixed」, 「the exact defect commit 08706f0 closed」, 「the hard-failure class of the `22P02` commit 8644d1d fixed」, 「Before commit 5359a9b (raw)」 / 「Since commit 5359a9b (masked)」. - **A stale claim, corrected by its anchor.** `validate.ts:813-815` said 「`ManifestSchema` is not `.strict()` and drops unknown keys with nothing said (objectstack-ai#14192)」, but `ManifestSchema` has been `strictObject` since `4d0d9445a`, which landed before the commit that wrote the sentence. Citing that commit in a present-tense sentence would contradict itself, so the three lines move to the past tense: 「was not `.strict()` and dropped unknown keys with nothing said until commit 4d0d944, so acting on that inference produced a manifest that looked fine」. - **Anchors found by diff, not by subject.** `objectstack-ai#10326` has no commit message naming it; `675ab574e` took the 1.7.1 measurement the line cites, and its own changeset and test name objectstack-ai#10326. `objectstack-ai#12162` is named by no message either; `c0f5e8f21` is the only commit that ever added the number, and its message states the point the lines make. `objectstack-ai#14397`'s citation was added by `957f7bb45`'s own diff, which is the change the heading describes. `objectstack-ai#10763` is added three times by `c2b97c2a1`'s diff. - `objectstack-ai#10499` (`init.ts:978`): the line uses the earlier drift between the two scaffold paths as precedent; that drift was about pnpm build approvals, and `6d441e41f` gated the two paths against each other, so the line reads 「already drifted once (closed by commit 6d441e4)」. - `objectstack-ai#6293`: `c39a911ae` is the commit that found the 「headless husk」 `JSON.stringify(stack)` leaves where a declaration was; `bf4ebe2f3`, which wrote the cli lines, only cites it. - Quoted text: `generate-field-type-vocabulary.pin.test.ts:92` sits inside a verbatim quotation of the file's former clause, so the commit stands in an editorial bracket (「([commit ee370d3]'s pin argues this in full)」), as PR objectstack-ai#20624 did. - Headings with a dash rule (`serve.ts:1125`, `:1459`, `:1521`, `:3276`, `serve-cluster-host-resolution.test.ts:831`, `generate-field-type-vocabulary.pin.test.ts:260`, `files-to-references.ts:274`) trim their trailing dashes to hold the width; `serve-cluster-host-resolution.test.ts:893` is a trailing comment whose code part is byte-identical. ## The sites left **No deciding commit (4 sites, all visible to the census):** - `init.ts:267` (objectstack-ai#11048): 「whether to admit that band at all is objectstack-ai#11048」 names an open support decision (pnpm 10.0 to 10.4). The only commit naming it, `568de194e`, files it unassigned; no later commit decides it, and the floor is still `>=10.15` at the base. - `plugin/publish.ts:118`, `osplugin.ts:21`, `osplugin.ts:49` (objectstack-ai#11331): the parenthetical points at the unpack-time integrity re-verification leg, which was never built (`b60f48b52`: 「The enforce leg points at objectstack-ai#11331」). No commit decides it; the ownership clause on the same lines comes from `f89812e4d`, but the number is not about that clause. **String sites kept as tokens (49).** 48 are test titles and test-code strings in 22 files. One is a non-test string: the `os meta resync` skip explanation at `commands/meta/resync.ts:71`, 「on installs from before objectstack-ai#8692, the platform's own seeded defaults carry that same stamp」, which an operator reads (see Acceptance notes). ## Mechanical guard: no code token moves, and exactly two string literals do The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file at base `04b202e5cb` against the working tree, over all 65 touched files in `packages/cli/src`, and lists EVERY differing token, not only the first. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 156,633 base tokens, token counts equal in every file, **exactly 2 differing tokens**, both `StringLiteral`: `commands/i18n/extract.ts:227` (the help text) and `utils/i18n-extract.ts:2294` (the header line). No other token in any file differs. - Comment-insertion control (`serve.ts`): still exactly those 2 (exit 1, no new difference). - Code-insertion positive control (a declaration in `serve.ts`): the count differs (17,815 to 17,820) and a third difference appears at token 0. - String positive control (one character added inside the first string literal past offset 2000 of `serve.ts`): a third difference appears, a `StringLiteral` at token 145. - The 27 generated companions: 2,940 base tokens, 0 differing tokens (their only change is a JSDoc line). Line balance: every touched file is +N/−N, and every line count is equal at base and head (94 files). A raw scan of the 92 changed source and generated files for control bytes finds none (its positive control, a scratch file holding a U+0001 byte, matches). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/cli` is included, in PR objectstack-ai#20632's form and level. Unlike stage 2's, it names the two strings, because 「Comments only」 would not be true here. Measured on the built package: `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, the rewritten docblocks reach `dist`: 142 `commit SHA` citations in 39 of its `.js` / `.d.ts` files. For example `storage-driver.ts:91`'s rewritten line 「(commit 68f5ecc). These are the」 is in both `dist/utils/storage-driver.d.ts` and `.js`, beside the unchanged next line of the same docblock 「runtime's declarations, not copies of them — in particular」 (the positive control); a negative control phrase appears nowhere. The new help text is in `dist/commands/i18n/extract.js`, the header literal with `09b4f4e4e` is in `dist/utils/i18n-extract.js`, and `objectstack-ai#11671` appears nowhere in the package's `dist`. ## Gates (head `6bb4d3b531`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each locked run at this head: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 110s (1m50s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 12s · declare it in the PR body · pnpm --filter @objectstack/cli typecheck os-verify-lock: VERDICT command-exit 1 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 150s (2m30s) · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/published-subpath-console.pin.test.ts test/published-subpath-hook-body.pin.test.ts os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 src/commands/generate-declared-column-default.pin.test.ts src/commands/generate-string-family-width.pin.test.ts src/commands/meta/delete-reset-carriers.test.ts os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 30s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/commands/validate-json-strict-exit.e2e.test.ts ``` The regeneration ran earlier against the same unlocked lock, on a closure build at `47241dd80e`: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/cli...' --filter '@objectstack/platform-objects...' --filter '@objectstack/plugin-approvals...' --filter '@objectstack/plugin-audit...' --filter '@objectstack/plugin-security...' --filter '@objectstack/plugin-sharing...' --filter '@objectstack/plugin-webhooks...' --filter '@objectstack/service-messaging...' --filter '@objectstack/service-realtime...' --filter '@objectstack/service-storage...' build ``` The branch merged `origin/main` twice, as the dispatch orders (`d1e09a7eed` merging `cd901d7a5f`, and `6bb4d3b531` merging `0cb72cfc72`); neither touched `packages/cli`, a translations directory or the prose-id ledger. After each merge: `pnpm install --frozen-lockfile`, then the whole workspace (`turbo run build --filter='./packages/*' --filter='./packages/*/*'`, 71 tasks, 71 successful). - **Tests** (unit tier, then every touched file outside it): - `vitest run --project unit`: 234 files, 3,342 tests; **232 files and 3,337 tests pass, 5 tests in 2 untouched files fail on this host**: `test/published-subpath-console.pin.test.ts` and `test/published-subpath-hook-body.pin.test.ts` compare a path under `os.tmpdir()` with the realpath the resolver answers, and on macOS `/var` is a symlink to `/private/var`. With `TMPDIR` set to its realpath the same two files pass, 29 of 29 (the fourth line above). Neither file is in this diff; the cli change is comments and two strings. - The unit tier holds 32 of the 36 touched test files. The other four ran by name: the three integration-tier files (`--project integration`: 3 files, 60 tests pass) and the nightly-tier `validate-json-strict-exit.e2e.test.ts` (`OS_TEST_TIERS=nightly`: 1 file, 7 tests pass). So every touched test file ran. - The producer's own tests and the companions' readers: `test/i18n-extract-source-hashes.test.ts`, `test/i18n-extract-companion-orphan.test.ts` and `test/i18n-extract-generated-apps-leaf-provenance.test.ts` (3 files, 25 tests); `@objectstack/platform-objects`'s `src/apps/translations` (24 files, 430 tests); `@objectstack/plugin-sharing`'s `src/translations` (3 files, 13 tests). All pass, at `d71ab0e27e`, whose `packages/cli` and companions are byte-identical to this head. - **Typecheck:** `pnpm --filter @objectstack/cli typecheck` exits 0. `tsc --listFiles` counts 298 `src` files under `tsconfig.json`, all 158 `src` test files among them, so every touched test file is type-checked; `check:test-typecheck` holds its ledger (3 files, 28 errors, 6 pinned signatures). - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `6bb4d3b531` (2026-09-29T13:44:40Z to 13:45:11Z). Not narrowed. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0 after the second merge: 67 citations judged across 56 files (66 resolve, 1 cross-repo). These are the live numbers that stay on rewritten lines, 54 of them the objectstack-ai#12069 and objectstack-ai#8765 pair in the 27 headers. It defers `*.test.ts`, so the added-minus-removed count over the whole diff covers the rest: 0 numbers added. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `6bb4d3b531` derived 76 families (68 before the prose-id ledger commit put a `scripts/` path in the change set). All 76 ran, and `--ran` over a record carrying each exit code reads 「76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero). - 75 exit 0. One exits 1 for this host, not for this diff: `pnpm check:bash32-floor` runs its self-test first, and 7 of its 179 cases assert that each bash-4 probe is shell THIS host can parse; the only bash here is `/bin/bash` 3.2.57, which cannot. The gate's real-tree half, run alone (`node scripts/check-bash32-floor.mjs`), exits 0: 32 tracked shell files, 0 findings. The self-test half is NOT MEASURED here, reason: no bash 4+ on this host; CI's bash measures it. This diff touches no shell file. - Among them: `check:doc-authoring` (809 pinned sibling prose-id sites, no growth, no unrecorded burn-down), `check:i18n` (9 packages in sync), `check:i18n-coverage` (13 configs, none new), `check:i18n-stale-fill` (27 of 27 companions served, 0 stale), `check:i18n-walk-parity`, `check:nul-bytes` (9,283 files, no raw control bytes), `check:published-files`, `check:cli-command-ids` and `check:issue-citations` (self-test). - **Artifact rosters:** 35 of the 38 non-self-test roster rows exit 0 at `6bb4d3b531`, `check-changeset-fixed` (its roster sits under `.changeset/`), `check:error-code-casing`, `check:filter-alias-parity` and `check:authz-resolver` (the four whose rosters share a directory with this diff) among them. The other three, `check-closing-target-claim`, `check-partof-closing-keyword` and `check-single-claim-paths`, need a pull request's context; they are run against this PR once it exists and reported on the card. The 17 checker-health-only rows were not run. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 174 dead sites at `04b202e5cb` (167 lines, 30 files, 50 numbers), equal to the card's count at `f11b5f20a2`: no drift. - **H1 holds, with the listed exceptions.** After the rewrite the filtered census answers 4, all for an unfound anchor: `objectstack-ai#11048` (an open support decision) and `objectstack-ai#11331` three times (an enforce leg never built). No site is held for an open PR: the two held files carry no dead citation. The claim's read and this stage's two reads of the open PRs' file lists (12:38:15Z, 7 open PRs; 13:58:56Z, 9 open PRs) found only PR objectstack-ai#20589 in `packages/cli/src` and none touching a companion or the prose-id ledger. PR objectstack-ai#20652, opened after the claim, edits `packages/platform-objects`'s three `LOCALE.objects.generated.ts` bundles beside the companions: no file overlap. - **H2 holds, by the token guard.** A comment-stripped comparison of every touched file (the parser's leaf tokens, JSDoc excluded, every difference listed) finds exactly the two declared `StringLiteral` tokens and nothing else, and its code and string controls each add a difference. The emitted `dist` is not byte-identical, because docblocks and the two strings ship, which is why the changeset is `patch`. - **H3 holds.** `git grep -n "objectstack-ai#11671" -- '*.source-hashes.generated.ts'`: 0 hits at head, 27 at `04b202e5cb`. ## Acceptance notes - **Form D, not touched here.** 49 dead numbers stand inside string literals: 48 in test titles and test-code strings (22 files, 21 numbers), and one an operator reads: the `os meta resync` skip explanation at `commands/meta/resync.ts:71`, 「on installs from before objectstack-ai#8692, the platform's own seeded defaults carry that same stamp」. The comments beside it (`resync.ts:55` and `:96`) now cite `712e185db`. Ruling D (no number, the lesson in words) is a string change outside this stage's two declared strings; the card already carries a form-D stage for the lane (ACCEPT 5888034755), and this string is its author-shown first. - **`objectstack-ai#11671` outside this stage's surface.** The number still stands in other lanes' files: the nine `scripts/i18n-extract.config.ts` docstrings (outside the census surface), six `src/translations/index.ts` files (`plugin-approvals`, `plugin-audit`, `plugin-security`, `plugin-webhooks`, `service-realtime`, `service-storage`), six sites in `packages/platform-objects/src` (`source-hash.ts` three times, `setup.translation.ts`, `metadata-translations/index.ts`, `source-hash.test.ts`), `packages/cli/test/i18n-extract-source-hashes.test.ts:3`, and 13 in `scripts/**` and `.github/workflows/lint.yml`. The anchor for all of them is `09b4f4e4e`. Noted for those lanes' stages, not touched. - **Outside the scope and the census surface.** `packages/cli` outside `src/**` holds 242 dead citations: `test/` 185, `scripts/` 27, `bin/` 10, `vitest.config.ts` 15, `vitest-tiers.ts` 2, `vitest-tiers.fixtures.ts`, `tsconfig.test.json` and `test-typecheck-debt.json` 1 each (whole-file projection, the before board). They stay for a later stage of this card. - **A host-dependent pin.** `test/published-subpath-console.pin.test.ts` and `test/published-subpath-hook-body.pin.test.ts` fail 5 tests on macOS, where `os.tmpdir()` is a symlink, and pass with a realpath `TMPDIR`. CI's Linux runners do not see it. Noted, not filed. - **A hex colour in the whole-file reading.** `serve.ts:5621` holds the CSS colour `#141417` in a string. The census blanks strings, so it never sees it; the supplementary whole-file projection reads it as a citation beyond the frontier (`never-issued`). It is not a citation; it is the second of the two `src string` sites left in the supplementary table, beside `objectstack-ai#8692`. - **The slash-joined grammar gap, again.** `CITATION_RE` refuses a `#` preceded by `/`, so the second number of `#A/#B` is never judged. In `packages/cli/src` one such dead number stood (`serve.ts:1173`, rewritten here). The same shape PR objectstack-ai#20624 and PR objectstack-ai#20632 reported, for the grammar family PR objectstack-ai#20533 names. ## Deviations - **One file outside the claim's surface.** `scripts/doc-authoring-prose-id.baseline.json`, the shrink-only ledger of `check:doc-authoring`'s sibling-package prose-id leg, pinned the two `objectstack-ai#11671` string sites this stage removes, so the gate went red (「the prose-id baseline is STALE — pinned entries exceed the tree」) and prescribed regenerating it in the same PR. It was regenerated with its own command (`node scripts/check-doc-authoring.mjs --census-ledger`, which refuses to grow the ledger): 4 lines removed, the two `objectstack-ai#11671` pairs and nothing else. The claim's file surface did not name this file; it is the gate's own remedy for the two strings the claim does name. - One site beyond the census's read grammar (the slash-joined `objectstack-ai#11157`) is rewritten, and thirteen more lines are the other half of a rewritten sentence (listed under What changed). - `validate.ts:813-815` moved to the past tense, because the claim they carried was false before this change (see Wordings to check). - Anchor research for 64 of the 65 numbers ran in four read-only research subagents; every proposal was checked here against the commit's message or diff and every changed line was reviewed, and eight were reworded by hand (the four lines two subagents shared, 「that commit's to」, the kept PR link, and two companions). - Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push; the harness's attribution reminder asked for a model-named trailer, which AGENTS.md overrides. The two merge commits carry git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…rc to the commits that decided them (objectstack-ai#20673) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 4 of the `domain:cli` lane of the dead-citation sweep: `packages/types/src/**`. Every comment or docblock site in scope that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. PR objectstack-ai#20533 is the method; PR objectstack-ai#20624 (`runtime`), PR objectstack-ai#20632 (`rest`) and PR objectstack-ai#20656 (`cli`) are the landed stages this follows. The card stays open for the form-D stage and the rest of the lane, so this PR says `Part of`. That is **83 comment sites on 83 lines in 17 files, covering 12 numbers**: the census's 52 (all of them) and 31 more in test comments, which the census defers. Each rewritten line cites one of **12 distinct commits**. No ADR or ruling-record file records any of these twelve decisions, so every anchor is a commit. Only comments changed. Every touched file keeps its line count (94 lines out, 94 in, over 17 files), so no line citation into these files moves. Eleven of the 94 lines held no dead site; each is the other half of a sentence that had to change: `thrown-http-error.ts:316-319`, `node.ts:1103`, `:1429`, `:1447`, `:1452`, `:1476`, and `node.test.ts:449`, `:2420` (see "Wordings to check"). **No citation number is added.** Over the 94 line pairs, every tracker number on an added line was already on the line it replaces (per-pair check: 0 added), and no PR number stands on an added line. No code token moves (see the guard below). No site was left: no dead comment site in scope lacked a deciding commit, and no open PR touches `packages/types/src`. One file outside `packages/types/src`: a `patch` changeset for `@objectstack/types`, in PR objectstack-ai#20632's form and level. ## Census: `packages/types`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run with the fleet token. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count is its `allocated-but-absent` findings under `packages/types/`. Both runs enumerated the whole board (185 pages), so neither read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | types sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `6bff748bbd`, run 2026-09-29T15:31:59Z to 15:41:36Z | enumerated, 185 pages, frontier objectstack-ai#20663, 18,490 numbers | 1,510 | **52** | 52 | 7 | 11 | | after | head `686a4c60cb`, run 2026-09-29T16:04:17Z to 16:12:39Z | enumerated, 185 pages, frontier objectstack-ai#20671, 18,498 numbers | 1,458 | **0** | 0 | 0 | 0 | The before count equals the card's 52 at `f11b5f20a2`: no drift. The whole-repo drop is 52, exactly this diff's 52 sites, and the whole-repo resolving count rises by one (32,882 to 32,883): the live objectstack-ai#12751 that `index.ts:4` now spells so the grammar reads it. Both runs read this worktree, the base and then the base plus this one commit, so no other change entered either count. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `packages/types/src` (42 files), against a board from the gate's own `probeBoard`. The lit controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20656 answered 200 and are on both boards; the dead controls objectstack-ai#11671, objectstack-ai#10514 and objectstack-ai#14828 answered 404 and are on neither. | reading | tree | board | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---|---| | before, 15:34Z | `6bff748bbd` | probed, frontier objectstack-ai#20661 | 622 | **101** | 52 | 31 | 1 | 17 | | after, 16:04Z | `686a4c60cb` | probed, frontier objectstack-ai#20668 | 540 | **18** | 0 | 0 | 1 | 17 | Its src-comment column equals the census's 52, site for site (the two site lists are identical), which is the control on the second instrument. The drop of 82 citations is the 83 dead sites removed plus one live number the grammar now reads: `index.ts:4` spelled `[objectstack-ai#11343/objectstack-ai#12751]`, whose second half the grammar skips after a slash, and now reads `[commit c0714eb / objectstack-ai#12751]` like its module doc, so the live objectstack-ai#12751 is judged (resolving src comments 273 to 274). Resolving pull requests (20), cross-repo citations (14) and the other resolving counts are unchanged. A separate scan for slash-joined pairs in `packages/types/src` found six (`objectstack-ai#11343/objectstack-ai#12751`, `objectstack-ai#3878/objectstack-ai#3899`, `objectstack-ai#7525/objectstack-ai#8016`, `objectstack-ai#4728/objectstack-ai#4825`, `objectstack-ai#8621/objectstack-ai#8622`, `objectstack-ai#5352/objectstack-ai#5367`); every second half answers 200, so no dead number hid behind a slash here. ## Per-number table Sites and files are the dead comment sites in scope at the base, test sites counted in brackets. `strings kept` counts string-literal sites, which are tokens and stay as they were. Every anchor was read in its message or its diff, not only its subject: it is the commit that made the change the line describes. | number | comment sites / files | rewritten | strings kept | anchor | |---|---|---|---|---| | `objectstack-ai#8824` | 1/1 (1 test) | 1 | 0 | `8ac232306` | | `objectstack-ai#9934` | 5/4 (2 test) | 5 | 2 | `79c46da90` | | `objectstack-ai#10943` | 10/2 (4 test) | 10 | 2 | `46d34ab7c` | | `objectstack-ai#10944` | 1/1 | 1 | 0 | `e598b1cbc` | | `objectstack-ai#11343` | 3/3 (1 test) | 3 | 1 | `c0714eb5d` | | `objectstack-ai#12281` | 1/1 | 1 | 0 | `0783d7b80` | | `objectstack-ai#13197` | 5/2 (2 test) | 5 | 2 | `56c093c4d` | | `objectstack-ai#13279` | 8/5 (2 test) | 8 | 0 | `6a180e42d` | | `objectstack-ai#13324` | 15/3 (7 test) | 15 | 5 | `4cda78c9b` | | `objectstack-ai#15044` | 8/2 (3 test) | 8 | 1 | `088f761e5` | | `objectstack-ai#15045` | 21/2 (8 test) | 21 | 1 | `288fe9c34` | | `objectstack-ai#16657` | 5/2 (1 test) | 5 | 4 | `5a95b0e93` | | **total** | **83** | **83** | **18** | **12 distinct commits** | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 12), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 12). The checkout is not shallow (`--is-shallow-repository` false); the control leg `f5a9bc2f3` (2026-08-10, older than the oldest anchor, `8ac232306` of 2026-08-15) exits 0 and the negative control (this branch's own `686a4c60cb`, not on `main`) exits 1. **Anchors reused from earlier stages**, so each number carries one anchor across the tree: `79c46da90` for objectstack-ai#9934 (stages 1 and 2, the spec lane), `46d34ab7c` for objectstack-ai#10943, `e598b1cbc` for objectstack-ai#10944 and `288fe9c34` for objectstack-ai#15045 (stage 3), `0783d7b80` for objectstack-ai#12281 (stage 1), `56c093c4d` for objectstack-ai#13197 (stage 2, the spec lane), `6a180e42d` for objectstack-ai#13279 (stages 1 and 2, `plugin-sharing`) and `c0714eb5d` for objectstack-ai#11343 (`plugin-auth`). **New anchors, and how each was found:** - `objectstack-ai#8824` → `8ac232306`: objectstack-ai#8824 is that commit's own PR number (its subject ends `(objectstack-ai#8824)`), so the sha is the object the number named. `error-leak.test.ts:180` read 「PR objectstack-ai#8824 corrected the」 and now reads 「Commit 8ac2323 corrected the」. - `objectstack-ai#13324` → `4cda78c9b`: its subject does not name the number, but its changeset heading does (「require a missing-table error to name the table that was READ (objectstack-ai#13324)」), and its diff adds `readObject` and every `[objectstack-ai#13324]` marker this module carries. It landed in `packages/metadata/src/utils/schema-sync-errors.ts`, the file `6a180e42d` then moved here (a rename at 86 percent similarity). - `objectstack-ai#15044` → `088f761e5`: 「Part of objectstack-ai#15044」, the only commit whose message names the number; it made the objectstack-ai#13330 succeeding leg recognise the package root by the name the declaration promises, and added the `BOUNDARY` pin at `node.test.ts:1863` that `:2175` and `:2419` point at. - `objectstack-ai#16657` → `5a95b0e93`: it added `operatorFacingErrorText`, `DECLARED_DATABASE_FAULT_CODE` and the raw-path fragment, and its message calls itself the fourth prose round on objectstack-ai#16657. ## Wordings to check - **A stale future tense, corrected by its anchor.** `thrown-http-error.ts:315-320` said objectstack-ai#12281 「is a separate card with its own measurement-first step, so nothing here applies it; this function is the shape it will read」. `a81aa9dd5` wrote that on 2026-08-29; `0783d7b80` landed the next day and its message says 「the door now reads `serverFaultProvenance`」. Citing the commit in the future tense would contradict itself, so the six lines now read 「Commit 0783d7b — the prose axis of the same 2026-08-27 ruling — reads the `'declared'` limb of this same function … It landed separately, after its own measurement-first step, so nothing here applies it; this function is the shape it reads rather than a second copy it would have had to grow.」 - **An open question named by a number that had already landed.** `node.ts:1447-1452` called where a relative specifier should resolve from 「an open policy question owned by objectstack-ai#10944」 and ended with 「Answering half of another card's undecided question」. `e598b1cbc` (objectstack-ai#10944's landing) had merged 40 minutes before `46d34ab7c` wrote those lines, and it refuses the relative spelling. The lines now read 「the policy question commit e598b1c settled for `serve` (it refuses a relative `plugins: [...]` entry rather than silently re-basing it)」 and 「Answering half of another change's question」. - **「the card」 once the antecedent became a commit.** `node.ts:1102-1103` 「objectstack-ai#15045 is the card about telling an operator which one was measured」 now reads 「commit 288fe9c is the change that tells an operator which one was measured」. `node.ts:1476` 「the second verification axis the card holds open」 now reads 「the second verification axis that commit left unbuilt」, which is what `288fe9c34`'s message says (「deliberately not built here」). `node.test.ts:449` 「The card's own 4-row matrix」 now reads 「The 4-row matrix behind that commit」. - **Headings that named a defect by its number now say so.** `node.test.ts:1566` reads 「Fixed by commit 088f761: the SUCCEEDING leg recognised the package by the DECLARATION KEY」 and `:1881` reads 「Reworded by commit 288fe9c: the location sub-case REFUSES correctly and EXPLAINED itself wrongly」 (`288fe9c34` changed the wording and kept the refusal). The dash-rule headings trim trailing dashes: `node.ts:1381`, `node.test.ts:1566`. - **A quoted triage.** `node.test.ts:2160` quoted 「objectstack-ai#15045's triage」; it now reads 「quoted from the triage commit 288fe9c landed」. That commit's changeset records the same decision in its own words: the key stays the expectation 「because widening it would accept any directory sitting at the key and trade a wrong REMEDY for a wrong LOAD」. `node.test.ts:2053` said objectstack-ai#15045 「asked for this sentence」; it now says `288fe9c34` 「wrote this sentence」, and that commit's own test comment says the card asked for it. - **A defect that proved a point.** `driver-error-classification.callers.test.ts:24-25` said the omission is the shape 「objectstack-ai#13324 existed to close」 and that prose 「is exactly what objectstack-ai#13324 proved insufficient」; it now reads 「the … shape commit 4cda78c closed」 and 「prose is exactly what that commit's defect proved insufficient」. - **「pre-#N」 spellings** (the card's control sites `driver-error-classification.ts:608` and `node.ts:1428`, plus `callers.test.ts:20` and `node.test.ts:594`) now say 「before commit X」. ## Mechanical guard: no code token moves The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file at base `6bff748bbd` against the working tree at `686a4c60cb`, over all 17 touched files, and lists EVERY differing token, not only the first. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 31,911 base tokens, token counts equal in every file, **0 differing tokens** (exit 0). - Comment-insertion control (a new line comment in `node.ts`): 0 differing tokens (exit 0). - Code-insertion positive control (a declaration prepended to `node.ts`): the count differs and a difference appears at token 0 (exit 1). - String positive control (one character changed inside the kept `undeclaredMessage` literal at `node.ts:383`): exactly 1 differing token, a `StringLiteral` at token 672 (exit 1). So H2 holds by the token guard. The emitted `dist` is not byte-identical, because the docblocks ship, which is why the changeset is `patch`. Line balance: every touched file is +N/−N and every line count is equal at base and head (17 files). A raw scan of the 18 changed files for control bytes finds none (its positive control, a scratch file holding a U+0001 byte, matches). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/types` is included, in PR objectstack-ai#20632's form and level: 「Comments only: no error code, refusal text, type, export or runtime behaviour changes.」 Measured on the built package: `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, the rewritten docblocks reach `dist`: `0783d7b80`, `79c46da90`, `5a95b0e93` and `c0714eb5d` are in `dist/index.d.ts` and `index.d.mts`, `4cda78c9b` in all four `index` files, `6a180e42d` in `index.js` and `index.mjs`, and `46d34ab7c` and `288fe9c34` in `dist/node.d.ts` and `node.d.mts`. The positive control, the unchanged sentence 「sanitisation REGIME is the condition, not one of its two outcomes」 of the `0783d7b80` docblock, is in `dist/index.d.ts` beside it; a negative control phrase appears nowhere. Of the twelve dead numbers, only objectstack-ai#10943 remains in `dist`, twice, and both are the kept operator-facing string at `node.ts:383` (see Acceptance notes). ## Gates (head `686a4c60cb`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each locked run at this head: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/types exec vitest run --project repo --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/types exec vitest run --project local --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/types typecheck os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 28s · declare it in the PR body · pnpm --filter '@objectstack/types...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 46s · declare it in the PR body · pnpm lint os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 93s (1m33s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4 ``` `origin/main` did not move after the branch was cut: `git merge origin/main` answered 「Already up to date」 at `6bff748bbd`, so the base is the merge base and nothing needed rebuilding. `origin/main` has since moved to `6c11ef9ecb` (PR objectstack-ai#20663: two pages under `content/docs/automation`, read 16:13Z). It touches nothing this diff or its gates read, so the branch was not merged again and every reading here stays at `686a4c60cb`. - **Build:** the dependency closure (`@objectstack/types...`: `spec` then `types`) and then the whole workspace (71 tasks, 71 successful). `check-dts-emitted` finds 2 of 2 declared declaration files. The build left the tree clean. - **Tests:** `--project local`: 22 files, 685 tests pass. `--project repo`: 1 file (`driver-error-classification.callers.test.ts`, touched here), 7 tests pass. 22 + 1 is all 23 test files in the package, so every touched test file ran. - **Typecheck:** `pnpm --filter @objectstack/types typecheck` exits 0. `tsc --listFiles` counts 42 `src` files under `tsconfig.json`, all 23 test files among them, so every touched test file is type-checked. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `686a4c60cb` (2026-09-29T16:01:23Z to 16:02:09Z). Not narrowed. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0: 5 citations judged across 7 files (4 resolve, 1 cross-repo). These are the live numbers that stay on rewritten non-test lines. It defers `*.test.ts`, so the per-pair count over the whole diff covers the rest: 0 numbers added. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `686a4c60cb` derived 61 families. All 61 ran and exit 0, and `--ran` over a record carrying each exit code reads 「61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero). Among them: `check:doc-authoring`, `check:nul-bytes`, `check:issue-citations` (self-test), `check:published-files`, `check:dts-closure`, `check:dual-build-cjs-loads`, `check:type-check-coverage` and `check:type-check-debt`. - **Artifact rosters:** all 36 non-self-test roster rows that run without a pull request exit 0 at `686a4c60cb`, the four whose rosters share a directory with this diff among them (`check-changeset-fixed`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`). The other three, `check-closing-target-claim`, `check-partof-closing-keyword` and `check-single-claim-paths`, need a pull request's context; they are run against this PR once it exists and reported on the card. The 18 checker-health-only rows were not run. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 52 dead sites at `6bff748bbd` (52 lines, 7 files, 11 numbers), equal to the card's count at `f11b5f20a2`: no drift. - **H1 holds, with no exceptions.** After the rewrite the filtered census answers 0 dead sites for `packages/types/`. No site is left for an open PR or an unfound anchor: the claim's read and this stage's read of the open PRs' file lists (15:40:08Z, 7 open PRs) found none touching `packages/types/src` (the Version Packages PR touches only `packages/types/CHANGELOG.md` and `package.json`). A second read before this PR was opened (16:13:18Z, 11 open PRs) found the same. - **H2 holds, by the token guard** above: 0 differing parser leaf tokens over the 17 touched files, with the comment control at 0 and the code and string controls each turning red. ## Acceptance notes - **Form D, not touched here.** 18 dead numbers stand inside string literals: 17 in test titles and test-code strings (8 files, 8 numbers), and one an operator reads. That one is the `undeclaredMessage` note at `node.ts:383`, 「a caller that needs its own resolution passes `{ fallbackImport: (s) => import(s) }`, objectstack-ai#10943)」, printed when the host importer's undeclared fallback fails without a caller base. It is also the only dead number left in `dist`. The comments around it (`node.ts:368`, `:1381`, `:1428`) now cite `46d34ab7c`. Ruling D (no number, the lesson in words) is a string change outside this comment-only stage; the card already carries a form-D stage for the lane (ACCEPT 5888034755), and this string is its author-shown first in `packages/types`. A second one is a remedy an author reads: the `REMEDY` text at `callers.test.ts:281`, which that gate test prints for any call site that omits `readObject` (「Without it the predicate returns the pre-objectstack-ai#13324 WIDE verdict」). - **Outside the scope and the census surface.** `packages/types` outside `src/**` holds one dead citation: `vitest.config.ts:25` cites objectstack-ai#17853 (404), the same number PR objectstack-ai#20624 and PR objectstack-ai#20632 reported in their packages' `vitest.config.ts`. The six other citations outside `src/**` (`CHANGELOG.md` excluded) resolve. It stays for a later stage of this card. ## Deviations - Eleven lines beyond the dead sites are the other half of a rewritten sentence (listed under What changed), and the six lines at `thrown-http-error.ts:315-320` move from the future tense to the present, because the claim they carried stopped being true when `0783d7b80` landed (see Wordings to check). - The anchors were researched in this session, not delegated; every one was checked against its commit's message or diff. - Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push; the harness's attribution reminder asked for a model-named trailer and a different PR footer, which AGENTS.md overrides. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
Part of #20234
Clause-②: no
What changed
This is stage 3 of the staged sweep. It covers
packages/spec/src/data/**and nothing else. It leaves out the files an open PR or an in-flight claim holds:data-engine.zod.ts,data-engine.test.ts,hook.form.ts,analytics*.ts,cube-member-inner-name-retirement.test.ts,driver/turso.zod.tsandfilter-subtree-provenance.ts, as the claim names them. It also leaves out four files that open PRs started editing after the claim:driver/turso.test.ts(PR #20504, #20437's, opened 2026-09-28T20:08Z),object.form.ts(PR #20519, #20432's, 21:55Z),object.zod.ts(PR #20521, #20494's, 22:10Z) andfilter-logic-conformance.ts(PR #20523, #20444's, 22:39Z). See Acceptance notes. Later stages cover the other areas, so this PR saysPart of.Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123). That is 163 sites on 161 lines in 44 files, covering 40 numbers. Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and it says in its own words what that commit decided.No ADR or ruling-record file in
docs/adr/orscripts/adr-anchors/records the decision behind any of the 43 dead numbers in scope. ADR-0104 names #12380 only as a reference, and ADR-0055 states the rule that #8772's ruling enforced, not the ruling itself. So every anchor is a commit: 38 distinct shas. One number was dropped rather than anchored: #17286, a tracking card that recorded an axis as undecided, under which no commit landed. The sentence keeps its reason in words.Three comment sites in scope are left on purpose (see Acceptance notes). Two are the
[#6259]marker inapi-derivation.ts:163, which a test string reads, and the test comment that names that marker. The third isfield.zod.ts:370, whose#6111is objectui's number.Only comments changed. Every source file keeps its line count (174 lines out, 174 in, over 45 files), so no line citation into these files moves. Thirteen of those 174 lines held no dead citation. Eleven are the other half of a sentence that had to be reflowed or rewritten. One is a table header (
value-roundtrip-conformance.ts:20, 「card」 to 「card or commit」, because its row now holds a commit). One isapi-derivation.ts:164, which now carries the[#6259]sentence's commit. No code token moves (see the guard below). The 41 string-literal sites that carry a dead number are tokens, so they are left as they were and listed below.No citation number is added. Every tracker number on an added line was already on the line it replaces. No PR number stands on an added line.
Two more kinds of file change, both mechanical:
feed.zod.ts:15,:18) project intocontent/docs/references/data/feed.mdx.check:docsproved that page stale, andpnpm --filter @objectstack/spec check:generated --fixregenerated only it. The diff is two lines, each the same substitution as its source line. No page a held file projects into (analytics.mdx,data-engine.mdx,hook.mdx,driver-turso.mdx) moved.patchchangeset for@objectstack/spec(see Changeset below).Census:
data/, before and afterInstrument. This is the instrument of stages 1 and 2. It sends REST
GET /repos/objectstack-ai/objectstack/issues/Nwithout following redirects, for every distinct number cited inpackages/spec/src/data. The population is:CITATION_REandNON_CITATION_HEADS, kept when the qualifier is none,objectstack,objectstack-ai/objectstack,framework,pre-orpost-;Pre-,POST-,Framework: 7 sites, one of them dead), which stage 2's case-sensitive set did not read;summonheads.Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string.
Controls. The lit controls were
#16862,#16847and#17698. The dead controls were#16714,#16715and#16697. They were probed at the start, after every 100 numbers and at the end. They read 24 of 24 lit (200) and 24 of 24 dead (404) over 8 checkpoints in both runs.data/9bf5e67af, probed 2026-09-28T19:32Z to 19:36Z96fd49caa2, probed 2026-09-28T23:19Z to 23:23ZBefore, in scope, by class. 92 non-test docblock sites and 13 non-test line comments. 16 test docblock sites and 45 test line comments. 39 test string sites. 2 non-test string sites.
After, in scope. 41 string sites and 3 comment sites remain, all three deliberate. The head probe found no number newly dead since the base probe: the same 571 numbers answer 200.
PR #20226's area table read
data239 at an earlier base; this census reads 240 at9bf5e67af. The 33 excluded sites sit inobject.zod.ts(15),analytics.zod.ts(3),analytics-strictness-batchd.test.ts(2),analytics-date-range-two-bound-window.test.ts(1),driver/turso.zod.ts(2),driver/turso.test.ts(3),filter-subtree-provenance.ts(3),filter-logic-conformance.ts(3) andobject.form.ts(1).data-engine.*andhook.form.tscarry none.Per-number table
The counts are in-scope sites and files at the base.
rewritten / leftgives comment sites rewritten and sites left. Every anchor was read in its diff or message, not only in its subject: it is the commit that made the change the line now describes, and its own diff or message names the number it replaces.#6111(objectui)#62596968885ef: retires the producer-lessbatch: 'bulk'row ofDATA_ACTION_TO_API_OPERATIONand the prose callingbatcha runtime action. The marker and 2 test strings stay (see Acceptance notes)#6345e2798fab7: one driver vocabulary; both boot hosts read the shared table;mongotomongodb; turso a builtin; the fork-1 and fork-2 refusals#65712f3e79351:$betweenendpoints accept the ISO/clock strings the platform produces, as a bare string (rider ①)#84954bfe1a539: refuses${…}placeholders in memorypersistence.path/persistence.keyat publish#8656#869690a12fb18, the card's mongodb arm: a bound secret rides beside an unmodified url as MongoClientauth. Its own pins carry the multi-host formnew URL()cannot parse and the bound secret outrankingoptions.auth#877275b7c240a: Direction 2 of the 2026-08-16 maintainer ruling. The builder forcesrequired: trueon amaster_detailundercontrolled_by_parent, and raw parse stays tolerant. ADR-0055 stays cited beside it#87787901b2dd2: stamp-onlytenancy.organizationField, declared bysys_api_key#87941850ebbb0: corrects the reuse-safety claim on the filter-subtree mark from the survey's measurement, and routes a mechanism change to a spec-seat ruling (stage 1's anchor too)#88361850ebbb0: the same commit, which pins the invariant (one line carries both numbers)#8873096106522: a boundcredentialsRefreaches the postgres server on the DSN branch. Its diff records thatpgsends a password only when the server asks#8874d70428ae7: a declared mysqlsslreachesmysql2as its own TLS options object, becausemysql2rejects a bare boolean#8876d634e665b: exportsurlUserinfoUsername, and its diff states the asymmetry that a username is not credential material#904024206416a: refuses a credential in the mongo options passthrough at publish, and redacts the passthrough secret paths on read#9041d491625c1: refuses a boundcredentialsRefwith a user-less mongoconfig.url, with the triage's fences#10165801296050:ttl.onlyWhenwith the canonical null predicate (maintainer ruling 2026-08-20, option A)#10274d1ba685ec: re-measures the objectui pin citations and gates the class#1032915d58dbf1: retires the import lookup transform's steering params (ADR-0049)#10347530c1df65: the Archiver honours a declaredttl(maintainer ruling 2026-08-20)#105275649efbf9: refuses a diverging retention + ttl + archive triple at parse time#1106520950404c: a boolean aggregand counts as 1 or 0 inavgandsum, the first face aligned. No commit message names the card; this is where the number first entered the tree#11195b37231883:UserActionsConfigSchemaadoptsgroup/hideFields/rowColor#1121542a117b88: documentsNoSQLIndexSchema.unique's deliberate scope-vocabulary omission#11350ece4dad31: records the 2026-08-23 maintainer ruling on entry nameability (stage 1's anchor too)#11408f11fc61c5: declareseditMode(maintainer ruling 2026-08-24)#1150788b9d749a: declaressys_activity.typean open, author-extensible vocabulary (maintainer ruling 2026-08-24, direction 4)#116581a6a19c31: opensRecordActivityProps.typesto author-contributed kinds#123804045b954d: makes the SQLiteField.jsoncodec injective; its message carries the measured boundary#12868object.form.ts, now held by PR #20519; its deciding commit isc459da6bc(see Acceptance notes)#13156fd289be45: strips tracker ids from function-declaration-built refusal prose (the card's A half)#1364434ce8e7db: declaresctx.referentialFieldClearonHookContextSchema#1442640a44b91b: the undefined-comparand refusal prescribes the null predicate by its ruled spellings, position-safe#1467613c48c2a5: retiresconnector.errorMapping; its test states the same assertion-set reasoning#16126859ded3ec: refuses a whitespace-onlyreferenceon lookup / master_detail#16685ed7243d52: accepts boolean / toggle for sum / avg / min / max (decision batch #80)#168670ee32edef:notNull/not_nullprescribestorage.notNull, notrequired#1701480aef8032: the one-day date-range presets prescribe a one-day window, and the table states its end-token convention#17286driver-memoryfor the open set#1734851efbf116: pins thedriver-memorytemporal text-operator divergence by name in that driver's conformance suite#17590e04a0aff2:$containson a JSON column is a per-dialect membership test (director-seat ruling 2026-09-12)#18012176b03582:$betweenrequires two non-blank endpoints (decision batch #146 item 5, letter A)#19377a60c913de: refuses a{ $field }reference as a$betweenendpoint at the runtime filter doorEvery cited sha matches exactly one commit (
git rev-parse --disambiguate, count 1), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0). That is 38 distinct shas.Wordings to check, each true of its commit:
datasource.zod.ts:352names only the card's mongo arm (90a12fb18) for "the defect class … closed", because the paragraph is about mongo. The card's mysql arm (72050cc47) is not cited anywhere in this stage.datasource.zod.ts:354: 「the triage's, as commit d491625 landed them」.d491625c1's message lists the fences as "per triage".filter.zod.ts:1021-1025: the#17286pointer becomes 「was measured on a tracking card … That card is gone: measuredriver-memoryfor the open set, ⛔ not this text.」 The warning that this paragraph is not the authority is kept.The 41 string sites left as tokens
driver/driver-credential-refusal.test.ts14,object.test.ts6,datasource-credential-redaction.test.ts3,driver/driver-placeholder-refusal.test.ts3,filter.test.ts3,api-derivation.test.ts2 (thesplit('[#6259]')literal and its message),field.test.ts2, and 1 each indate-range-presets.test.ts,driver/postgres.test.ts,field-rows-option-description.test.ts,filter-comparand-type.test.ts,hook.test.tsandobject-strictness-batch20.test.ts.aggregation-conformance.ts:398and:407, thenoteof two exportedAGGREGATION_CASESrows (#11065,#11151). They ship as data. Their only readers are driver conformance suites, which print anoteas the assertion message when a case fails, to a driver developer and never to a metadata author. So they are neither comments nor form D author-shown text. This is the same disposition stage 1 gave the twowhystrings and stage 2 thePROVENANCE_WAIVERSreason.No author-shown text in
data/carries a dead number, so nothing here is #20233's form D.Mechanical guard: no code token moves
The check compares leaf tokens with comments stripped, base
9bf5e67afagainst head96fd49caa2. It uses the TypeScript parser's leaf tokens, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 45 touched.tsfiles.feed.zod.ts): 1 file reads DIFFER (exit 1).split('[#6259]')string inapi-derivation.test.ts): 1 file reads DIFFER (exit 1).Changeset
This change ships bytes, so a
patchchangeset for@objectstack/specis included. It says only that the provenance comments were re-anchored.Measured on the built package: 14 of the touched sources are
src/**/*.zod.ts, whichfiles[]ships verbatim. The rewritten docblocks also reachdist.88b9d749a,e2798fab7and24206416aeach appear in 1 declaration file.24206416aappears in 20 bundled.jsfiles and2f3e79351in 28. The positive control, a pre-existingfeed.zod.tsdocblock sentence, appears indist/data/index.d.ts.Gates (head
96fd49caa2)pnpm check:issue-citations && node scripts/check-issue-citations.mjsexits 0. The self-test passes 73 cases in 7 batteries. The live run judged 11 citations across 25 files, and all 11 resolve.pnpm check:doc-authoringexits 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat the final head derived 108 families, and all 108 exit 0.--ranreports 108 run, 0 NOT MEASURED, 0 unrun, and exits 0. (check:i18nwas derived at the earlier heads fromobject.form.ts, and left the set when that file went back to base.)check:doc-formula-expressions,check:doc-security-posture,check:skill-examplesandcheck:docs-transcript-drift. At the final head a fullturbo run buildof./packages/*ran first (71 tasks, exit 0, under the shared verify lock), and every gate exited 0 on its first run.check:generatedwas run under the lock against that build: all 15 artifacts are up to date.pnpm --filter @objectstack/spec buildexits 0.vitest run --maxWorkers=2 src/datainpackages/specat the final head: 107 files and 3,517 tests pass (1 todo), covering every touched test file.src/datathat readdata/source text pass at the final head: 12 files, 503 tests. These arescripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts,src/api/api-entry-graph.pin.test.ts,src/contracts/scoped-context.test.ts,src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts,src/system/constants/platform-object-names.test.ts,src/type-alias-convention.pin.test.tsandsrc/ui/dashboard.test.ts.pnpm --filter @objectstack/spec typecheckat the final head exits 0, includingcheck:test-typecheck(53 files, 251 errors, 138 pinned signatures held).eslint --no-inline-config --format jsonover the 45 touched.tsfiles gives 45 files, 0 errors and 0 warnings. All 45 are in eslint's own population (isPathIgnoredis false for each).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, which its own line 328 states), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.Acceptance notes
[#6259]marker.api-derivation.test.ts:236splitsDATA_ACTION_TO_API_OPERATION's TSDoc on the literal[#6259], and a test string may not change here. So the marker lineapi-derivation.ts:163is byte-identical to the base, and the test comment at:232that names the marker stays too. The sentence's deciding commit sits on the next line instead: 「(both by commit 6968885)」. A first attempt wrote the commit onto the marker line itself. The diff-scopedcheck-issue-citationsthen read the kept#6259as an added citation and exited 1, so it was moved one line down (commitb93f08f8d0).#6111.field.zod.ts:370reads 「objectui#6110 + v17 GA 交付物:AI 一键升级客户元数据项目(升级 skill,骑在 D2 conversions 之上) #6111 (section)」. The qualifier covers only the first number, so the citation grammar reads#6111as this repository's (404 here). It is objectui's number: its introducing commitf887e5249writes(objectui#6111)in the same diff, andobjectstack-ai/objectuianswers REST 200 for v17 GA 交付物:AI 一键升级客户元数据项目(升级 skill,骑在 D2 conversions 之上) #6111 to this session (and for docs(changeset): vendored console 条目里给 flow 几何迁移补回 BREAKING (v17) 标注 (#6106) #6110 and dogfood: simulate a brand-new developer's first-run journey — README → create an app → skills-driven AI build → validate & test #10264). objectui has norefs/pull/6111/head, so it is an issue there, not a PR. The line is left unchanged. This is [finding]check-issue-citationsreads thepre-inpre-#N(andpost-inpost-#N) as a repository qualifier, so a dead number in that spelling is classed cross-repo and never judged #20330's grammar family, the same as stage 2'sobjectui PR #10264, and it is noted there, not filed.CITATION_REclassesPre-#N,POST-#NandFramework#N(7 sites indata/) as cross-repo and never judges them. This census read them as this repository's. One was dead and is rewritten here (object.test.ts:223,POST-#10347). This is the same [finding]check-issue-citationsreads thepre-inpre-#N(andpost-inpost-#N) as a repository qualifier, so a dead number in that spelling is classed cross-repo and never judged #20330 family as stage 1'spre-/post-finding.driver/turso.test.ts: PR fix(spec,driver-turso)!: refuse a forced mode replica with no syncUrl at authoring and at construction (#20437) #20504 (turso: a datasource config withmode: 'replica'on afile:url and nosyncUrlis accepted at authoring and at construction, then runs as a plain local database that never syncs #20437's) opened at 2026-09-28T20:08Z and edits it. Its two comment sites (:4,:58, both#6345) went back to blob7fe99ebf9in commit86463ed0a1. A no-drivermerge-treeof that head with PR fix(spec,driver-turso)!: refuse a forced mode replica with no syncUrl at authoring and at construction (#20437) #20504's head5dfa45e9fexits 0.object.form.ts: PR fix(driver-sql): a declared index that can never be built is logged at error and reported in drift #20519 ([finding] a misspelt field name in a field'srelatedListColumns,lookupColumns,lookupFilters[].fieldordependsOnpasses every authoring door, and fails only at view or picker time #20432's) opened at 21:55Z and edits it. Its one comment site (:256,#12868, whose deciding commit isc459da6bc) went back to blob60713e06fin commit3479600dda.object.zod.ts: PR fix(spec): activityMilestones[].type describe states the real default (#20494) #20521 ([finding]activityMilestones[].typeis documented as defaulting to "completed", but a milestone withouttypewritesupdated#20494's) opened at 22:10Z and edits one line at:2123. Its 15 comment sites (#8772,#10165,#10347,#10527,#11195,#11408,#13608) went back to blobbefde04cain commit96fd49caa2. Their deciding commits are75b7c240a,801296050,530c1df65,5649efbf9,b37231883,f11fc61c5andfc9ba76a5, all read for this stage.filter-logic-conformance.ts: PR feat(drivers,formula,objectql): the engine's filter faces answer the staged $empty operator (#20444) #20523 (filter: the engine's compile surfaces answer$emptyby the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444's) opened at 22:39Z. Its 3 comment sites (#13195) went back to blobc9b32acbain the same commit. Their deciding commit is9dac1ae01, withPR #13529as the link.data/numbers that also appear inpackages/spec/src/migrations/**. Those are os migrate meta prints tracker numbers to the author: ADR-0087 migration entries' reason / replacement / acceptanceCriteria text carries ~2,060 of them, 178 dead, which AGENTS.md's runtime-string rule forbids #20233's form D, or the migrations stage.packages/spec/src/migrations. Examples arecbp-master-detail-required-forcedfor Acontrolled_by_parentobject may declare its master reference withoutrequired, so the master-access guard is the only thing preventing an unreachable orphan detail row #8772,filter-between-blank-endpoint-refusedfor spec(ui):FieldOperatorsSchemaaccepts a$betweenrange with a blank bound —{$between: [1, '']}parses green, so a half-filled range is not refused at the authoring door #18012, thedatasource-*entries for mongoconfig.options.auth.passwordis a fourth spelling of an inline credential — authorable, persisted cleartext, unredacted, and read by the client #9040, Nothing refuses the contradictory pair "external.credentialsRefbound + aconfig.urlnaming no user" — the binding is a silent no-op at connect #9041 and A boundexternal.credentialsRefreaches the knex config but NOT the server on the postgres DSN branch —pgoverrides it with the connection string #8873, and themapping-lookup-params-removedconversion for spec(data): mappinglookuptransform params (object / fromField / toField / autoCreate) are authorable and read by nothing #10329. This PR takes the commit rung, as stages 1 and 2 did, so it is precedent-consistent. The D3 id is the more durable in-repo record, if the ruling's first rung is later read to include those entries.packages/**/*.test.ts, so 20 of the 45 touched.tsfiles never enter its judging population. The added-minus-removed count over the whole diff covers them: 0 numbers added.origin/main(1378ec7c0c, read at 2026-09-29T00:18Z). Four of those commits touchdata/, all in excluded files: fix(spec): hook condition row declares expression, not javascript #20475'shook.form.ts, feat(spec,objectql,metadata-protocol): grouped and aggregated queries honoursearch#20487'sdata-engine.*, and, since this stage excluded them, PR fix(spec): activityMilestones[].type describe states the real default (#20494) #20521'sobject.zod.ts(9e1689f8e2) and filter: the engine's compile surfaces answer$emptyby the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444'sfilter-logic-conformance.ts(fb386074f5). None touches a file in this diff, and a no-drivermerge-treeof the head onto1378ec7c0cexits 0. So there was no merge. The open-PR file lists were re-read at 00:18Z: 11 open PRs, none touching a file in this diff.Generated by Claude Code