docs(service-messaging): re-anchor the dead tracker citations to the commits that decided them - #20609
Conversation
…commits that decided them Every comment and docblock site in packages/services/service-messaging/src that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes, in ruling C+D's form C, and says in its own words what that commit decided. 127 comment sites on 109 lines in 28 files, 13 numbers, 13 distinct commits. Comments only: every touched file keeps its line count, so no line citation into these files moves. No citation number is added. The three generated *.source-hashes.generated.ts headers (their producer is the CLI's i18n extract template) and the twelve string-literal sites are left as they were. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
The rewritten docblocks reach the published dist: the built index.d.ts and index.js carry the new commit anchors, so the change ships bytes. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 5 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 04c984fb2537e95b691fb3c64403f7a96a619235 && git checkout 04c984fb2537e95b691fb3c64403f7a96a619235
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f 267c1156211c9351d9963594808cf6db1f675d14 && git checkout -B drift-repro 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f && git merge --no-ff 267c1156211c9351d9963594808cf6db1f675d14
node scripts/docs-audit/affected-docs.mjs --json 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f
|
Contract reviewServed-tier: Reviewed for card #20596 (stage ① Derived judgmentsAccept set: unchanged — right. Every one of the 232 changed lines in the 28 Citations: 0 added — right. The 13 anchors — each right. Every sha resolves to exactly one commit (
Ruling C's order, ADR or ruling record first — the commit rung is right. The Seven reflow / referent lines — right. All comment lines; each hunk keeps its count. Fifteen sites left — right to leave. Verified at the head: ② Semver level
Nit, not verdict-bearing. The file is ③ Boundary flags
Check-runs on the head, as read in the act that wrote this record (2026-09-29T07:38Z): 25 success — among them Implemented-by: VERDICT: PASS Generated by Claude Code |
…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>
…mits that decided them (objectstack-ai#20626) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the second stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-sharing/src/**` and nothing else. By census, it is the largest package in the lane that no open PR or in-flight claim holds (the claim, `5886159115`, 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), by stage 1's method (PR objectstack-ai#20609, landed as `422db788a`). That is **87 sites on 86 lines in 23 files, covering 13 numbers**: the 60 census sites outside the generated headers, and 27 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 records the decision behind any of the 13 numbers (ADR-0131 names objectstack-ai#14484 only as evidence, not as the record of its ruling), so every anchor is a commit: **13 distinct shas**. No number was dropped. Only comments changed. Every touched source file keeps its line count (87 lines out, 87 in, over 23 files), so no line citation into these files moves. One of those 87 lines held no dead citation: `backfill-sys-record-share-organizations.ts:14`, where 「the cliff the card names」 lost its referent once line 10 named a commit instead of a card. It now reads 「the cliff that commit pins」, and `3f64fe6c6`'s backfill test is the one titled 「the cliff」. 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. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line. The one PR spelling in scope (`PR objectstack-ai#5973`, dead) became its squash commit. Nineteen dead sites are left on purpose: 1 string literal, 14 test titles, 1 verbatim ruling quotation and 3 generated file headers (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-sharing`, because the rewritten docblocks ship (see Changeset below). ## Census: `plugin-sharing`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-sharing/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-sharing sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `422db788a`, run 2026-09-29T08:04:49Z to 08:08:19Z | enumerated, 185 pages, frontier objectstack-ai#20614 (newest objectstack-ai#20614), 18,441 numbers | 2,300 | **63** | 62 | 14 | 13 | | after | head `a6d231713`, run 08:27:44Z to 08:31:07Z | enumerated, 185 pages, frontier objectstack-ai#20616 (newest objectstack-ai#20616), 18,443 numbers | 2,240 | **3** | 3 | 3 | 1 | The before count matches the 63 that census `5884031174` read at `f11b5f20`. The whole-repo drop is 60, exactly this diff's census sites. The `resolves` tally is 32,803 in both runs, and `resolves-as-pull-request` (1,891) and `cross-repo-unjudged` (983) did not move either. The 3 left are the generated headers below. No run was truncated or discarded: all three enumerations in this stage (two census runs and the supplementary board below) read 185 pages at the newest frontier. **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 `plugin-sharing/src` (74 files). It uses one board, enumerated by the gate's own `enumerateBoard` at 08:12:18Z (185 pages, frontier objectstack-ai#20614, equal to the newest). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `422db788a` | 1,187 | **106** | 63 | 28 | 1 | 14 | | after, `a6d231713` | 1,100 | **19** | 3 | 1 | 1 | 14 | Its src-comment column equals the census's 63, which is the control on the second instrument. The 989 resolving, 87 pull-request and 5 cross-repo citations are the same in both readings. ## Per-number table Sites and files count all dead sites in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. It is the commit that decided what the line describes: its own message or diff names the number it replaces, or, for a squash-merged PR, it is the merge of that PR. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#5973` | 4/2 | 3/1 | `abeb3751f`: `HierarchyScopeContext.organizationId` is the tenancy authority, and it is required. `objectstack-ai#5973` was the PR itself; this is its squash commit | | `objectstack-ai#6206` | 12/7 | 11/1 | `8e13ca876`: the share-link routes hand enforcement the whole authz envelope, per maintainer ruling A of 2026-08-07. It is the plugin-sharing half; the spec stages anchor the contract half at `d7e0b4212`. Three sites name the ruling in words, 「the full-envelope ruling」, beside `aa4b90d9a`, which applied it | | `objectstack-ai#6523` | 3/3 | 3/0 | `aa4b90d9a`: 36 contract signatures converge on the full `ExecutionContext`. The same anchor the spec stages gave this number | | `objectstack-ai#8710` | 10/3 | 9/1 | `04d03c3a0`: a deactivated `sys_position` confers no sharing-rule shares. Its message quotes the 2026-08-15 ruling: access-conferring paths filter, addressing paths do not | | `objectstack-ai#8792` | 2/1 | 2/0 | `83c661d97`: the bulk-write merge's missing provenance mark is recorded as ruled (2026-08-15), not oversight | | `objectstack-ai#8836` | 1/1 | 1/0 | `1850ebbb0`: it pins 「no filter object that can be vouched 'author' may outlive the request that vouched it」, the invariant the line names. The same anchor the spec stages gave this number | | `objectstack-ai#11671` | 5/5 | 2/3 | `09b4f4e4e`: `os i18n extract --source-hashes` writes the provenance companion (maintainer ruling objectstack-ai#12069 Option A, which stays cited). The same anchor stage 1 gave it | | `objectstack-ai#11674` | 4/2 | 4/0 | `1cba33f16`: the seed loader warns at load time when a required column is deferred, and the ordering constraint is written at the four pointer-pair sites, these two among them | | `objectstack-ai#12493` | 2/2 | 2/0 | `aa5994e17`: the Operation Message Catalog gains `record_write_denied` ahead of its emitters. The same anchor the spec stages gave this number | | `objectstack-ai#13279` | 3/2 | 3/0 | `6a180e42d`: a permission-store read that throws raises `AuthzStoreUnavailableError` (503), and each transport re-raises it rather than laundering it into a 401 | | `objectstack-ai#13398` | 1/1 | 1/0 | `953a81f4a`: the class ruling on published logger sinks, applied at this site. `error` is reachable only because the sink already declares it; growing `error?` onto a published sink is forbidden. No record of the ruling exists in the repo, and this commit, which wrote this heading, is its earliest application in history | | `objectstack-ai#13608` | 23/3 | 21/2 | `fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only at mint. The same anchor the spec stages gave this number | | `objectstack-ai#14484` | 36/8 | 25/11 | `3f64fe6c6`: `organization_id` is stamped on every `sys_record_share` write, the stranded rows are backfilled, and the object is admitted to the tenancy ledger (the 2026-09-02 ruling, decision batch objectstack-ai#11 item 3) | 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 is complete (`--is-shallow-repository` false, 15,073 commits). A line-origin pickaxe (`git log -S` on each dead line's exact text) found each line entering either in its anchor commit or in a later commit that cites that commit's decision. For example, `65759baca` is the consumer half that cites `aa5994e17`'s key, and `b70a55d62` cites `3f64fe6c6`'s ledger admission. Wordings to check, each true of its commit: - `backfill-sys-record-share-organizations.ts:5`: 「rows that `SharingService.grant`, before commit 3f64fe6, stranded」. `3f64fe6c6` is the writer fix, and this module is its backfill. - `backfill-sys-record-share-organizations.ts:36` and `:124`: 「the 2026-09-02 ruling commit 3f64fe6 applies (decision batch objectstack-ai#11 item 3, …)」. The verbatim maintainer quotation on line 37 is untouched. - `share-link-service.ts:841`: 「(published-sink level ruling, commit 953a81f)」. The heading's body already states the ruling (option C allowed, option B forbidden). - `exec-context-annotation.pin.ts:7-8`, `sharing-rule-service.ts:12-13` and `sharing-service.ts:20`: 「since commit aa4b90d (the full-envelope ruling: no per-site subset contracts)」. `aa4b90d9a`'s message: 「Apply the … ruling default (converge on the full envelope, keep no per-site subset contracts)」. - `share-link-routes.ts:81`: 「[commit 8e13ca8, full-envelope ruling]」, so that 「the whole point of the ruling」 five lines down still has a referent. ## The 19 sites left - **Non-test string (1 site).** `sharing-service.ts:1674` sits inside the operator-facing `warn` text for a hierarchy scope that was not widened (「… resolveOwnerIds, objectstack-ai#5973); …」). It is a runtime string, so it is form D, not form C, and the shrink-only `doc-authoring-prose-id` baseline already holds it (`sharing-service.ts` → `objectstack-ai#5973: 1`). Left and listed, as stage 1 left its refusal strings. - **Test titles (14 sites).** `describe` titles in `backfill-sys-record-share-organizations.test.ts:185`, `:274`, `:324`, `:367`, `record-share-organization-stamp.test.ts:194`, `:239`, `:278`, `:315`, `:353`, `:436`, `sharing-service.test.ts:1798` (the `objectstack-ai#14484` titles), `share-link-eligibility.test.ts:607` (`objectstack-ai#13608`), `share-link-enforcement-context.test.ts:226` (`objectstack-ai#6206`) and `sharing-rule.test.ts:1898` (`objectstack-ai#8710`). Tokens, left as they were. - **A verbatim ruling quotation (1 site).** `share-link-service.test.ts:478` is point 2 of the maintainer's 2026-09-01 ruling, quoted verbatim and untranslated. It carries 「沿 objectstack-ai#13608 先例」. AGENTS.md keeps a quoted Chinese ruling in its original words, and rewriting the quote would rewrite the ruling. Left. - **Generated headers (3 sites).** Line 8 of the `es-ES`, `ja-JP` and `zh-CN` `.source-hashes.generated.ts` files carries 「(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`, so the fix belongs at the producer, the carrier stage 1 named. The hand-written `translations/index.ts:26` is rewritten here, with the same wording stage 1 used. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base `422db788a` against head. Template literals are therefore read in context. It ran over all 23 touched `.ts` files. - Real run: 96,665 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control in `share-link-service.ts`: 0 files changed, as expected (exit 0). The first attempt was a no-op: its replacement still contained the anchor, so `scripts/ablation-replace.mjs` refused it before the guard ran. It was redone with an anchor the replacement does not contain. - Positive control, a code token changed in `share-link-service.ts` (`Boolean(eligibility),` to `Boolean(eligibility) && true,`): DIFFER (exit 1). - Positive control, one digit changed inside the kept `sharing-service.ts:1674` warn string: DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`. Each restore was proven byte-identical to the HEAD blob (`ba7fba2e8199`, `2833b9a1616d`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-sharing` 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 the build, the rewritten comments reach both halves of `dist`: `3f64fe6c6` appears 6 times in `dist/index.d.ts` and 8 in `dist/index.js`, `fc9ba76a5` 3 and 3, `04d03c3a0` 2 and 2, `8e13ca876` twice in `index.d.ts`, and `1cba33f16` 4 times in `index.js`. esbuild keeps only some comments, so the positive controls are unchanged lines beside rewritten ones that shipped. `share-link-service.ts:891` is found once in each half, and `sharing-service.ts:1269` once in `index.js`. A never-written negative phrase appears nowhere. The only dead number left in `dist` is the kept `objectstack-ai#5973` warn string. ## Gates (head `a6d231713`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 9 citations across 11 files, and all 9 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the sibling-package prose ids at their baseline and no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `a6d231713` (re-derived after a fresh `git fetch` at 09:58Z: the same 65, and none of the 8 new `main` commits touch anything it derives from) derived 65 commands. They include all 50 derived at dispatch, plus 15 more. All 65 exit 0. `--ran` reports 65 run, 0 NOT MEASURED, 0 unrun, and exits 0. - Three gates first exited 3 (PREREQUISITE NOT MET) because the workspace was only partly built: `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), and all three exited 0 on their rerun. `check:dts-closure`, `check:sourcemap-no-sources-content` and `check:lean-entry-closure` were rerun too, over 71, 68 and 15 built packages, and exited 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-sharing test`: 37 files and 913 tests pass. That is every test file in the package, the 12 touched ones included. - `pnpm --filter @objectstack/plugin-sharing typecheck` exits 0. Its main `tsc` program reads the 37 non-test files, and its `check:test-typecheck` program (`tsconfig.test.json`) reads all 74 files under `src/`, the 37 test files included (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 23 touched `.ts` files gives 23 files, 0 errors and 0 warnings. All 23 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 24 changed files for control bytes finds none. ## Acceptance notes - **The census instrument did not truncate in this stage.** Three enumerations read 185 pages each at the newest frontier. The truncation stage 1 saw (1 run in 5) is carried on objectstack-ai#20556, and this stage changes no instrument. - **What stays for later stages.** - The 3 generated `objectstack-ai#11671` headers, whose producer is `packages/cli/src/utils/i18n-extract.ts`. - The `objectstack-ai#5973` warn string (form D, held by the `doc-authoring-prose-id` baseline), the 14 test titles and the verbatim ruling quotation. - **Anchors the next stages can reuse.** The same numbers stand elsewhere in `packages/**/src`: `objectstack-ai#6206` at 53 sites and `objectstack-ai#11674` at 46 (the ordering-constraint note has two sibling copies outside this package, in `sys-approval-request.object.ts` and `sys-audit-log.object.ts`). `8e13ca876` / `d7e0b4212` / `aa4b90d9a` and `1cba33f16` are the anchors used here. - **Base.** The branch is 8 commits behind `origin/main` (`1322cc72c`, read at 09:58Z). None of them touches `plugin-sharing` or this changeset, and none re-anchors any of these 13 numbers, so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…s that decided them (objectstack-ai#20634) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the third stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-auth/src/**` and nothing else. By census, it is the largest package in the lane that no open PR or in-flight claim holds (the claim, `5888562941`, 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), by the method of stages 1 and 2 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`). That is **95 sites on 95 lines in 31 files, covering 16 numbers**: - the 52 census sites (all of this package's census sites); - 38 sites in test comments, which the census defers; - 5 sites in the hyphen-joined spelling `objectstack-ai#13398-class`, which the gate's extractor does not match at all (see Acceptance notes). 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 records the decision behind any of the 16 numbers, so every anchor is a commit: **15 distinct shas** (`objectstack-ai#11477` and `objectstack-ai#12029` share one, because `objectstack-ai#12029` was the pull request that settled `objectstack-ai#11477`). No number was dropped. Only comments changed. Every touched source file keeps its line count (107 lines out, 107 in, over 31 files), so no line citation into these files moves. 12 of those 107 lines hold no dead citation; they are reflow or a lost referent, listed under Wordings below. 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. Over the whole diff, added minus removed is 0 or negative for every number (the gate's own `extractCitations` over the diff: 103 citations removed, 13 added, all 13 kept resolving numbers), and no number is new to the diff. No PR number stands on an added line. Twenty-one dead sites are left on purpose, all of them test titles (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-auth`, because the rewritten docblocks ship (see Changeset below). ## Census: `plugin-auth`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-auth/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-auth sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `b80ab579d`, run 2026-09-29T10:43:31Z to 10:47:03Z | enumerated, 185 pages, frontier objectstack-ai#20629 (newest objectstack-ai#20628 before, objectstack-ai#20629 after), 18,456 numbers | 1,955 | **52** | 52 | 13 | 12 | | after | head `5ae64e8b8`, run 11:12:05Z to 11:15:37Z | enumerated, 185 pages, frontier objectstack-ai#20630 (newest objectstack-ai#20630 before and after), 18,457 numbers | 1,903 | **0** | 0 | 0 | 0 | The before count matches the 52 that census `5884031174` read at `f11b5f20`. The whole-repo drop is 52, exactly this diff's census sites. The `resolves` tally is 32,832 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. No run was truncated or discarded: all three enumerations in this stage (two census runs and the supplementary board below) read 185 pages at the newest frontier. **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 `plugin-auth/src` (178 files). It uses one board, enumerated by the gate's own `enumerateBoard` at 10:50:47Z (185 pages, frontier objectstack-ai#20629, equal to the newest). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `b80ab579d` | 2,150 | **111** | 52 | 38 | 0 | 21 | | after, `9fd0ebf10` | 2,060 | **21** | 0 | 0 | 0 | 21 | Its src-comment column equals the census's 52, which is the control on the second instrument. The 1,966 resolving, 46 pull-request and 27 cross-repo citations are the same in both readings. Neither instrument sees the 5 `objectstack-ai#13398-class` sites; a plain grep for the 16 numbers over `plugin-auth/src` at the head finds only the 21 test titles (and the digits `11477` inside test fixture e-mail addresses and a password, which are code tokens, not citations). ## Per-number table Sites and files count all dead sites the gate sees in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#8676` | 22/6 | 18/4 | `d6e80b28b`: `sys_account.password` and `previous_password_hashes` are flagged `internal: true`, and every reader is recovered through the engine's privileged accessor (the adapter readback table gains `password`; plugin-auth's own raw-engine reads get `recoverInternalFieldsForSystemRead`). Its subject names `objectstack-ai#8676` | | `objectstack-ai#8734` | 4/2 | 3/1 | `f8eb73601`: the last-admin guard's standing-key lists are bound to what `resolveAuthzContext` actually reads (`STANDING_KEYS_BY_TABLE` / `STANDING_KEY_EXCLUSIONS` and the correspondence gate). Its subject names `objectstack-ai#8734` | | `objectstack-ai#10165` | 1/1 | 1/0 | `801296050`: lifecycle `ttl` gains an `onlyWhen` row filter (maintainer ruling option A on `objectstack-ai#10165`, quoted in its message). The same anchor the spec stages gave this number | | `objectstack-ai#10366` | 3/2 | 2/1 | `bbe643c08`: the localhost trusted-origin substitution is gated to non-production. Its diff writes both rewritten lines and its changeset names `objectstack-ai#10366` | | `objectstack-ai#11343` | 19/8 | 18/1 | `c0714eb5d`: walled platform-admin elevation requires a VERIFIED owner-email match (a fail-closed allow-list over `email_verified`), the bootstrap replays on the verifying `sys_user` update, and the dev-admin seed stamps its account verified. Its message names `objectstack-ai#11343` as the card it completes | | `objectstack-ai#11477` | 6/3 | 3/3 | `6dd3e6968`: `/admin/remove-user` gets the raw-mount shading `/admin/ban-user` has, so authorization runs before the break-glass guard (ruled option A on `objectstack-ai#11477`, as its message records) | | `objectstack-ai#11626` | 1/1 | 1/0 | `a6eca9223`: `check:engine-double-contract` admits a single-verb engine double on the contract it DECLARES, a second admission route beside sibling inference. Its diff names that route `objectstack-ai#11626` | | `objectstack-ai#11640` | 11/6 | 7/4 | `bf8d129b5`: a walled deployment whose declared owner has no verification path gets a loud, named warning at boot, and boot proceeds (maintainer ruling 2026-08-25, option A). Its subject names `objectstack-ai#11640` | | `objectstack-ai#11741` | 4/2 | 2/2 | `b706af987`: `SendEmailInput` gains an optional `organizationId`, threaded from the producers that hold one (the invitation among them). The same anchor stages 1 and 2 and the spec stages gave this number | | `objectstack-ai#11757` | 4/4 | 4/0 | `4d25d22d4`: the rc.1-era `sys_scim_provider` platform object is retired. Every `objectstack-ai#11757` site in the tree before it says the object "retires under objectstack-ai#11757" | | `objectstack-ai#12029` | 2/2 | 2/0 | `6dd3e6968`: `objectstack-ai#12029` was the pull request itself; this is its squash commit, the gate-then-delegate mount on `/admin/remove-user` | | `objectstack-ai#13398` | 6/2 | 3/3 | `e238c79f0`: the published-sink ruling, that raising a log level must never widen a published sink. No record of the ruling exists in the repo; this commit's pin is the earliest text in history that records it (see Wordings) | | `objectstack-ai#14762` | 21/4 | 19/2 | `35e94c96b`: auth OTP SMS and auth mail read the recipient's own `sys_user.locale`, one rung above the request and the deployment default, in the order ruled for `objectstack-ai#14788`. Its diff carries `objectstack-ai#14762` 24 times | | `objectstack-ai#14902` | 3/2 | 3/0 | `61821e54c`: a plain unique index over duplicate rows is loud and non-fatal (the boot continues), and `os migrate plan` stops calling it `safe`. Its message names `objectstack-ai#14902` as the card it ends | | `objectstack-ai#14998` | 2/1 | 2/0 | `f1e91595f`: the batch-6 admin endpoint graphs load at module top, not inside each clocked case, which removed the cold-import timeout flake | | `objectstack-ai#15092` | 2/1 | 2/0 | `9e9f03abe`: `settleSelfRegistrationGrant`'s trailing filter no longer silently DROPS a malformed permission-set row; it refuses. The only commit in history that names `objectstack-ai#15092` | Plus 5 `objectstack-ai#13398-class` sites the gate does not extract, anchored like the other `objectstack-ai#13398` sites: `boot-sign-in-reachability.ts:109`, `:512`, `boot-sign-in-reachability.test.ts:595`, `tenancy-service.ts:249`, `:257-258`. 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 15; the history is complete, `--is-shallow-repository` false, 15,083 commits). A line-origin pickaxe (`git log -S` on each dead line's exact text) found each line entering either in its anchor commit or in a later commit that cites that commit's decision: for example `4d5b4f832` (the operator-provisioned stamp) and `4f65837a7` (the L3 re-anchor) cite `c0714eb5d`'s verified-owner rule, `f074616e6` (invitation locale) cites `35e94c96b`'s stored rung, `8064e6da1` (the has-permission mount) cites `6dd3e6968`'s seam, and `9bd4344e4` carries the `account-identity-preflight` text that cites `61821e54c`. ## Wordings to check - **`objectstack-ai#13398` → `e238c79f0`, and not stage 2's `953a81f4a`.** Stage 2 anchored its one `objectstack-ai#13398` site at `953a81f4a` (2026-09-02) as the earliest application of the published-sink ruling. In this package, `e238c79f0` (2026-08-31) already records it: its pin in `durability-swallow-repair.test.ts` says raising the level "means widening a published sink — refused as actively harmful by the maintainer's" ruling. It is earlier, and it is in this package, so it is the anchor here. Its own commit message still calls the level "objectstack-ai#13398's question", which is why the lines say "the published-sink ruling (commit e238c79)" rather than claiming that commit made the ruling. - **Reflow, 11 lines with no dead site** (every file keeps its line count): - `auth-manager.ts:7554-7557`: 「routes that LEVEL question to the published-sink ruling (commit e238c79) and tells this batch to fix the SILENCE only」, the rest of the paragraph reflowed unchanged (3 lines). - `durability-swallow-repair.test.ts:36-40` (4 lines) and `:527-529` (2 lines): the same substitution, and 「which routes that question there」 became 「which keeps that question」, because "there" pointed at the number. - `tenancy-service.ts:257-258`: 「exactly what the sink ruling (commit e238c79) forbids」 (1 line). - `find-envelope-limb-removal.test.ts:47-48`: 「also carried the silent-DROP shape, and commit 9e9f03a fixed it in the OPPOSITE direction」 (1 line). - **A lost referent, 1 line.** `auth-plugin.ts:2738-2739`: 「(the objectstack-ai#12029 worked reading — a shadow is accounted for …)」 became 「(as it read commit 6dd3e69's remove-user mount — a shadow is accounted for …)」. `check:auth-mount-ledger` has counted a shadowing mount since `26dea1495`; the "worked reading" was that PR's application of it to `/admin/remove-user`, which `6dd3e6968` mounts. - `sys-session-ttl-sweep.test.ts:230`: 「the naive policy commit 8012960 existed to make avoidable」, where `801296050` is the `ttl.onlyWhen` filter the ablation removes. - `durability-swallow-repair.test.ts:62`: the flake report became a pointer to the commit that removed the flake (`f1e91595f`), with `objectstack-ai#15603` kept beside it. - `auth-manager.ts:5629`: 「the pre-objectstack-ai#14762 deployment-default behaviour」 became 「the deployment default, as before commit 35e94c9」. ## The 21 sites left - **Test titles (21 sites).** `describe` / `it` titles, which are string tokens: `admin-remove-user-gate-ordering.test.ts:207`, `:263`, `:298` (`objectstack-ai#11477`), `auth-email-locale.test.ts:528` and `auth-manager.test.ts:2545` (`objectstack-ai#14762`), `auth-manager.test.ts:1562` (`objectstack-ai#10366`), `:2866`, `:2880` (`objectstack-ai#11741`), `:4105` and `internal-field-readback.test.ts:219`, `:230`, `:286` (`objectstack-ai#8676`), `auth-plugin-walled-owner-verification-path.test.ts:87`, `:193`, `:317`, `:384` (`objectstack-ai#11640`), `durability-swallow-repair.test.ts:159`, `:567`, `:670` (`objectstack-ai#13398`), `last-admin-standing-keys.test.ts:61` (`objectstack-ai#8734`) and `walled-owner-operator-stamp.test.ts:355` (`objectstack-ai#11343`). Tokens, left as they were, as stages 1 and 2 left theirs. - There is no non-test string, no generated file and no quoted ruling carrying a dead number in this package. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base `b80ab579d` against head. Template literals are therefore read in context. It ran over all 31 touched `.ts` files. - Real run: 158,646 base tokens, **0 files with a token change** (exit 0). - Comment control in `auth-manager.ts` (`As above — the flagged column` to `Likewise — the flagged column`): 0 files changed, as expected (exit 0). - Positive control, a code token changed in `auth-manager.ts` (a fourth element added to the `fields` projection of the password-reuse read): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`admin-remove-user-gate-ordering.test.ts:207`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`c0bdef025a39`, `ec83f09f556e`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-auth` (`.changeset/20596-plugin-auth-provenance-anchors.md`) 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 the build, the rewritten comments reach `dist`: `35e94c96b` appears 8 times in each of `dist/index.d.ts`, `index.d.mts`, `index.js` and `index.mjs`; `f8eb73601` twice in each declaration file; `bf8d129b5` and `e238c79f0` once in each of the four; `d6e80b28b` and `4d25d22d4` twice in each runtime file; `c0714eb5d` and `61821e54c` once in each declaration file; `b706af987` once in each runtime file. Positive control: the unchanged line 「read best-effort off the identity row.」 beside a shipped rewrite is found once in `index.d.ts` and once in `index.js`. A never-written negative phrase appears nowhere. No dead number of the 16 is left anywhere in `dist`. ## Gates (head `5ae64e8b8`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 5 citations across 14 files, and all 5 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the sibling-package prose ids at their baseline and no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `5ae64e8b8` derived 65 commands: all 57 derived at dispatch, plus `check:duration-unit-keys`, `check:engine-double-contract`, `check:logger-receiver-detach`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. It was re-derived after a fresh `git fetch` (`origin/main` `a918fe7fd`, 2 commits ahead, neither touching `plugin-auth`): the same 65. Each ran with its exit code captured before any pipe, and all 65 exit 0. `--ran`, fed each command with its exit code, reports 65 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-auth test`: 115 files and 2,464 tests pass. That is every test file in the package, the 17 touched ones included. - `pnpm --filter @objectstack/plugin-auth typecheck` exits 0 (`tsc` main, `tsconfig.examples.json`, and `check:test-typecheck` held at its ledger). The main program reads 63 non-test files; the `tsconfig.test.json` program reads all 178 files under `src/`, the 115 test files included, and all 31 touched files are in it (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 31 touched `.ts` files gives 31 files, 0 errors and 0 warnings. All 31 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 32 changed files for control bytes finds none. ## Acceptance notes - **The gate's extractor does not see a hyphen-joined number.** `CITATION_RE` ends in a lookahead that refuses a following hyphen, so `objectstack-ai#13398-class` is not a citation to either the diff gate or the census, dead or alive. This stage rewrote the 5 such sites in `plugin-auth` because they are the same dead number in the same comment prose. At the head, 10 dead `#N-word` sites remain in `packages/**/src` (a raw line scan of `.ts` files against the cached board): `service-automation` 5 (all `objectstack-ai#13398-class`), `rest` 2, `plugin-security` 1, `runtime` 1, `spec` 1. The census cannot count them, so a later stage reaching those packages has to look for them by hand. No instrument change here. - **The census instrument did not truncate in this stage.** Three enumerations read 185 pages each at the newest frontier. - **Anchors the next stages can reuse.** These numbers stand elsewhere on the census at the head: `objectstack-ai#11343` in `plugin-security` (6) and `types` (2), anchor `c0714eb5d`; `objectstack-ai#14902` in `driver-sql` (7) and `cli` (1), anchor `61821e54c`; `objectstack-ai#13398` in `service-automation` (4, plus the 5 hyphen-joined sites), anchor `e238c79f0`; `objectstack-ai#8734` in `core` (2), anchor `f8eb73601`; `objectstack-ai#10165` in `objectql` (2) and `platform-objects` (1), anchor `801296050`; `objectstack-ai#11757` in `platform-objects` (2), anchor `4d25d22d4`; `objectstack-ai#11741` in `plugin-email` (2), anchor `b706af987`; `objectstack-ai#8676` in `platform-objects` (1), anchor `d6e80b28b`. - **Base.** The branch is 2 commits behind `origin/main` (`a918fe7fd`, read at 11:20Z). Neither touches `plugin-auth`, this changeset or any of these 16 numbers, so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…mmits and ADRs that decided them (objectstack-ai#20658) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the fourth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-security/src/**` and nothing else. By census it is the largest package in the lane; it waited while its own fixes were in flight, and the claim (`5890784382`) records that they have all landed. 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), by the method of stages 1 to 3 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`). That is **266 sites on 258 lines in 51 files, covering 40 numbers**: - 140 census sites (all of this package's census sites except the 3 generated headers, see below); - 123 sites in test comments, which the census defers; - 3 sites in comment prose that the gate's extractor does not match at all: one hyphen-joined (`objectstack-ai#8919-era`) and two slash-joined second numbers (`objectstack-ai#6483/objectstack-ai#6608`, `objectstack-ai#11184/objectstack-ai#11343`), see Acceptance notes. Each rewritten line now cites the record in this repository that decided what the line describes, and says in its own words what was decided. Two numbers have an in-repo decision record, and it is preferred: `objectstack-ai#11082` cites **ADR-0055's amendment** (2026-09-07, transitive chains compose), and `objectstack-ai#6609` cites **ADR-0094 D5-R**, which records that conflict ruling (option A, accept the tightening). Every other number cites the commit in `origin/main` history that decided it: **36 distinct shas**. Three pairs share one anchor because one number was the pull request that settled the other (`objectstack-ai#6483` and `objectstack-ai#6608`, `objectstack-ai#16607` and `objectstack-ai#16722`, `objectstack-ai#16608` and `objectstack-ai#16805`); `objectstack-ai#12143` was itself a pull request, and its squash commit `f64668d3c` is also where route A (`objectstack-ai#11374`) reached this plugin's key columns. No number was dropped. Only comments changed. Every touched source file keeps its line count (266 lines out, 266 in, over 51 files), so no line citation into these files moves. 8 of those 266 lines hold no dead citation; they are reflow, listed under Wordings below. 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. Over the whole diff, added minus removed is 0 or negative for every number (the gate's own `extractCitations` over the diff: 277 citations removed, 14 added, all 14 kept resolving numbers on the lines they already stood on), and no number is new to the diff. No PR number stands on an added line. Sixty-five dead sites are left on purpose: 60 test strings, 2 operator log strings and 3 generated headers (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-security`, because the rewritten docblocks ship (see Changeset below). ## Census: `plugin-security`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-security/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-security sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `cd901d7a5`, run 2026-09-29T13:00:53Z to 13:04:37Z | enumerated, 185 pages, frontier objectstack-ai#20647 (newest objectstack-ai#20646 before, objectstack-ai#20647 after), 18,474 numbers | 1,707 | **143** | 140 | 22 | 28 | | after | head `aa067dad3`, run 13:29:02Z to 13:32:37Z | enumerated, 185 pages, frontier objectstack-ai#20649 (newest objectstack-ai#20649 before and after), 18,476 numbers | 1,567 | **3** | 3 | 3 | 1 | The before count matches the 143 that census `5884031174` read at `f11b5f20`. The 3 left are the generated `objectstack-ai#11671` headers. The whole-repo drop is 140, exactly this diff's census sites. The `resolves` tally is 32,878 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `aa067dad3`; the head `f90c9b123` adds only the changeset. No run was truncated or discarded: all three enumerations in this stage (two census runs and the supplementary board below) read 185 pages at the newest frontier. **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 `plugin-security/src` (216 files). It uses one board, enumerated by the gate's own `enumerateBoard` at 13:08:21Z (185 pages, frontier objectstack-ai#20647, equal to the newest). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `cd901d7a5` | 2,746 | **327** | 143 | 123 | 2 | 59 | | after, `aa067dad3` | 2,483 | **64** | 3 | 0 | 2 | 59 | Its src-comment column equals the census's 143, which is the control on the second instrument. The 2,307 resolving, 88 pull-request and 24 cross-repo citations are the same in both readings. A third, raw reading (every `#` followed by digits, judged against the same board, whatever surrounds it) finds 331 dead occurrences before and 65 after: the 4 it sees beyond the gate are the three prose sites above and one more second number inside a kept test title. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included, gate-invisible spellings included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6206` | 2/1 | 1/1 | `8e13ca876`: share-link enforcement takes the whole authz envelope (option-A ruling); it adds this package's `group`-posture repro. Stage 2's anchor | | `objectstack-ai#6216` | 1/1 | 1/0 | `f586f1a89`: one `ExecutionContext` assembler, with the closed-field-set pin. The anchor the spec, runtime and rest stages gave it | | `objectstack-ai#6483` | 14/5 | 14/0 | `ee58392e1`: ADR-0005's allow-list enforced, nine unapproved types (`permission` among them) rolled back to `allowOrgOverride: false`. Its message records the zero-row measurement, the `allowRuntimeCreate` boundary and this suite's stub blind spot. The spec stages' anchor | | `objectstack-ai#6564` | 1/1 | 1/0 | `54299caad`: the per-row `ISharingService` write verdict becomes tri-state (allow / abstain / deny); `objectstack-ai#6564` was that pull request | | `objectstack-ai#6608` | 11/5 | 11/0 | `ee58392e1`: `objectstack-ai#6608` was the pull request itself; this is its squash commit | | `objectstack-ai#6609` | 3/2 | 3/0 | ADR-0094 D5-R: the record of that conflict ruling (option A, accept the tightening), executed by objectstack-ai#6858 | | `objectstack-ai#8692` | 10/3 | 9/1 | `712e185db`: the 2026-08-15 ruling, option A: the seed insert stamps `managed_by: 'platform'` explicitly, forward only, and the resync skip warn stops claiming intent | | `objectstack-ai#8714` | 15/2 | 10/5 | `42b05af89`: explain reports a deactivated permission set or position through the shared held-state vocabulary. The anchor the spec stage gave it | | `objectstack-ai#8757` | 14/3 | 10/4 | `6feac910b`: the 2026-08-15 ruling: the master gate is the sole row-write authority for a `controlled_by_parent` detail; delegated writes keep both floors | | `objectstack-ai#8772` | 5/2 | 5/0 | `8abada3ba`: the freeze note, Direction 4 of the 2026-08-16 master-reference ruling; it names the two ramp legs and the three shapes this guard alone refuses | | `objectstack-ai#8778` | 2/1 | 1/1 | `7901b2dd2`: option A, a stamp-only, read-neutral `tenancy.organizationField`. The spec stage's anchor | | `objectstack-ai#8804` | 2/1 | 2/0 | `db923a3a8`: `objectstack-ai#8804` was the measurement pull request: a seeder-created row is stored `'admin'`, and resync reports resynced 0 / resyncSkipped 8 | | `objectstack-ai#8839` | 7/3 | 6/1 | `c25b2d52a`: the 2026-08-15 ruling, reading 1: one per-object `sys_comment` delete policy, so moderation stops being dead behind the floor | | `objectstack-ai#8865` | 14/2 | 12/2 | `498f4e884`: the 2026-08-15 ruling, direction 1: leg 1 of the master gate drops the platform ownership floor on a sharing `allow` | | `objectstack-ai#8919` | 1/1 | 1/0 | `b5378550e`: `/meta` publish and rollback gated on `manage_metadata`; it created the write-door census whose count rule the line applies. The rest stage's anchor | | `objectstack-ai#11082` | 18/2 | 13/5 | ADR-0055's amendment (2026-09-07): `controlled_by_parent` composes across a chain, bounded, failing closed. One implementation-only line (the factory split) cites `61713314e`, the commit that landed it | | `objectstack-ai#11343` | 13/6 | 12/1 | `c0714eb5d`: walled elevation requires a VERIFIED owner-email match, and the bootstrap replays on the verifying `sys_user` update. Stage 3's anchor | | `objectstack-ai#11374` | 3/2 | 2/1 | `3954fb7df`: route A, the 2026-08-24 ruling to declare a sourced `maxLength` on every keyed text column; the object-file line cites `f64668d3c`, which applied it to this plugin's key columns | | `objectstack-ai#11451` | 20/4 | 18/2 | `c33f18592`: the curated half's existence read becomes one batched `$in` carrying the `objectstack-ai#8470` predicate; the reconcile is equality-gated; the derived half's batching is filed, not decided | | `objectstack-ai#11518` | 35/7 | 31/4 | `e1d773eb7`: the unscoped existence page cap is measured, not trusted: one row more than the budget, and an overflowing page degrades loudly to the per-item read | | `objectstack-ai#11520` | 17/2 | 15/2 | `1a6855226`: the derived half is batched too, unnarrowed, on its own index; a derived name whose read cannot answer is declined | | `objectstack-ai#11671` | 4/4 | 1/3 | `09b4f4e4e`: generated translation leaves record the source revision they were filled from. Stages 1 and 2's anchor | | `objectstack-ai#11702` | 1/1 | 0/1 | a test title only; nothing to rewrite | | `objectstack-ai#11703` | 15/3 | 13/2 | `5cb62d88b`: `clone_permission_set` carries all five copied facets; the params list is the payload. The runtime stage's anchor | | `objectstack-ai#11725` | 3/1 | 2/1 | `1e79aa4f8`: the probe of the trash and restore door, which pinned its unreachability and measured the residual | | `objectstack-ai#11753` | 2/2 | 2/0 | `0e4e51b0a`: `ActionParamSchema.carryOver`, the carry-over ruling's schema half. The spec stage's anchor | | `objectstack-ai#11843` | 5/4 | 4/1 | `5619aace3`: the 2026-08-25 ruling, option B: the packaged-permission-set lock registered at the metadata door. The verbatim quotation 「11843 同意」 is kept as written | | `objectstack-ai#12020` | 7/2 | 7/0 | `9cfc1f7e9`: the lock extended to the restore leg, refusing on the durability channel; the residual tripwire inverted in the same change | | `objectstack-ai#12143` | 2/1 | 2/0 | `f64668d3c`: `objectstack-ai#12143` was the pull request itself: each plugin's keyed-text-bounds pin reads the widths off its own registration path | | `objectstack-ai#12144` | 11/1 | 6/5 | `3a04b0125`: the shared identifier schemas pinned to the storage columns that bound them; the ceiling is storage-owned | | `objectstack-ai#12147` | 1/1 | 1/0 | `945e91a13`: the class-level keyed-text-bounds gate over every `*.object.ts`, superseding the per-package pins | | `objectstack-ai#13176` | 6/5 | 6/0 | `a68c61267`: this package's test files put in front of tsc through the sibling `tsconfig.test.json` | | `objectstack-ai#14484` | 2/1 | 1/1 | `3f64fe6c6`: `organization_id` stamped on every `sys_record_share` write, with the backfill and the tenancy-ledger admission; it adds this test file. Stage 2's anchor | | `objectstack-ai#16518` | 7/2 | 3/4 | `470746ae4`: `current_user.accessible_org_ids` resolved into the RLS variable bag | | `objectstack-ai#16607` | 8/3 | 4/4 | `1d73d45c1`: RLS membership staged on the write `check` path, so a membership-keyed check resolves on a bare insert | | `objectstack-ai#16608` | 13/4 | 6/7 | `a016f08b8`: the insert-side RLS `check` judges the row that will be stored, after `beforeInsert` | | `objectstack-ai#16682` | 22/4 | 18/4 | `9b9581b11`: the `single`-posture promotion target is chosen, not sampled: the order stated to the driver, the declared owner preferred and required verified, bounded pages with a loud ceiling | | `objectstack-ai#16722` | 1/1 | 1/0 | `1d73d45c1`: `objectstack-ai#16722` was the pull request itself | | `objectstack-ai#16805` | 1/1 | 1/0 | `a016f08b8`: `objectstack-ai#16805` was the pull request itself; its message records the contract review's findings | | `objectstack-ai#16861` | 7/2 | 6/1 | `1c83ca226`: the `already_have_admin` guard stops letting the org-admin row count decide: two ordered, bounded legs that warn with the number examined | | `objectstack-ai#19307` | 5/3 | 4/1 | `8f6d83147`: the duplicate-name refusal on `sys_permission_set` carries `UNIQUE_VIOLATION`, and the packaged-set lock answers first. The spec stage's anchor | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 36), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 36; the history is complete, `--is-shallow-repository` false, 15,092 commits). Where an earlier stage already anchored a number, this stage reuses that anchor after checking it against this package's lines. ## Wordings to check - **Two ADR anchors.** `objectstack-ai#11082`: ADR-0055's only amendment (2026-09-07) is the in-repo record of the chain decision, so the tags read `[ADR-0055 amendment]`; `security-plugin.ts:8027` (the thrower split into a factory) is an implementation detail the ADR does not record, so it cites `61713314e`. `objectstack-ai#6609`: the lines already named ADR-0094 D5-R, and now say it records ruling A (`permission-set-projection.ts:32`, `:535`, `permission-set-projection.test.ts:498`). - **A stale claim corrected, `errors.ts:184-185`.** The line said the publish-time lint was 「open and unruled」. The ruling of 2026-08-16 made that false; `8abada3ba` corrected the sibling paragraph in `security-plugin.ts` and missed this one. It now says the ruling (commit `8abada3ba`) orders the lint ramp and that the ramp has not landed, which matches the `security-plugin.ts` paragraph (1 reflow line). - **A vanished pull-request body, `permission-set-projection.test.ts:14-16`.** The lines quoted the body of the pull request, which answers 404. They now state what `ee58392e1`'s own message records about the same blind spot: this suite stubs `saveMetaItem`, and the real gate is pinned by the dogfood cases and a dedicated 403 suite (2 reflow lines). - **The same, `packaged-permission-set-restore-leg.test.ts:47`.** 「recorded on objectstack-ai#12020's PR」 became 「was measured for commit 9cfc1f7」; the line itself already states the measurement. - **A referent, `bootstrap-system-capabilities.test.ts:1148`.** 「this file's own objectstack-ai#8919-era rule」: the count rule it applies lives in the write-door census that `b5378550e` created (the rule's text is `bb920ee08`'s), not in this file. The line now says so. - **Dead comment ids dropped with their issues.** `comment 5306089973` (`security-plugin.ts:8093`) and `comment 5587754690` (`bootstrap-platform-admin-walled-owner.test.ts:482`). The verbatim maintainer quotation under the second is untouched. - **Words where the anchor is one line away.** `bootstrap-platform-admin.ts:630` (「a pre-ruling install」, anchor on `:628`), `bootstrap-platform-admin-walled-owner.test.ts:493` (「the TRIAGE seat's」, anchors on `:463` and `:482`), `security-plugin.ts:8137` (「that ruling」, anchor on `:8132`), `identifier-storage-ceiling-pin.test.ts:51` (「the triage fence at the top of this file」, anchors on `:13` and `:25`), `packaged-permission-set-lock.test.ts:94` (anchor on `:95`). - **Reflow, 8 lines with no dead site** (every file keeps its line count): `bootstrap-platform-admin.ts:267-268`, `errors.ts:185`, `identifier-storage-ceiling-pin.test.ts:26` (「dispatch」 became 「scope」, because the dispatch was the card's), `packaged-permission-set-lock.test.ts:95`, `permission-set-projection.test.ts:15-16`, `security-plugin.ts:8094`. - **Box-drawing rulers.** `security-plugin.ts:3078` and `bootstrap-platform-admin.ts:715`, `:1110`, `:1149` gave up as many trailing rule characters as the anchor added, keeping at least one. ## The 65 sites left - **Test titles, 57 sites.** `describe` / `it` titles, which are string tokens, left as stages 1 to 3 left theirs: `bootstrap-declared-capabilities.test.ts:454`; `bootstrap-platform-admin-existing-holder-scan.test.ts:297`; `bootstrap-platform-admin-promotion-selection.test.ts:281`; `bootstrap-platform-admin-seeded-provenance.test.ts:184`; `bootstrap-platform-admin-walled-owner.test.ts:504`, `:569`, `:587`; `bootstrap-seed-round-trips.test.ts:795` (two numbers), `:980`; `bootstrap-system-capabilities.test.ts:968`, `:1096`; `controlled-by-parent-chain.test.ts:460`, `:540`, `:578`; `controlled-by-parent-detail-write-authority.test.ts:594`, `:650`, `:669`, `:708`, `:724`, `:813`; `explain-engine.test.ts:171`, `:203`, `:853`, `:873`, `:893`; `identifier-storage-ceiling-pin.test.ts:125`, `:143`, `:160`; `insert-check-post-image.test.ts:573`, `:612`, `:662`, `:775`, `:838`, `:875`, `:939`; `objects/default-permission-sets.test.ts:299`; `packaged-permission-set-lock-gate.test.ts:183`; `packaged-permission-set-lock.test.ts:647`, `:812`, `:813`; `packaged-permission-set-restore-leg.test.ts:264`, `:265`; `permission-set-duplicate-name-refusal.test.ts:195`; `plugin-keyed-text-bounds.test.ts:67`; `record-share-tenant-wall.test.ts:149`; `rls-accessible-org-ids-plumbing.test.ts:191`, `:256`, `:325`, `:382`; `rls-check-membership-staging.test.ts:388`, `:400`, `:439`, `:505`; `security-plugin.test.ts:153`; `share-link-tenant-wall.test.ts:239`; `tenant-layer.test.ts:237`. - **Test assertion messages, 3 sites.** String literals passed to `expect`: `identifier-storage-ceiling-pin.test.ts:172`, `:193` (`objectstack-ai#12144`) and `packaged-permission-set-lock.test.ts:694` (`objectstack-ai#11703`). - **Operator log strings, 2 sites.** `security-plugin.ts:7864` and `:7872`, the two `logger.error` lines of the chain guards (`objectstack-ai#11082`). Runtime strings are form D, and the shrink-only `doc-authoring-prose-id` baseline already holds both (`security-plugin.ts`, `objectstack-ai#11082: 2`). - **Generated headers, 3 sites.** `translations/{es-ES,ja-JP,zh-CN}.source-hashes.generated.ts:8` (`objectstack-ai#11671`). Their producer is a string literal in `packages/cli`, outside this lane; the pointer is on objectstack-ai#20594. - There is no quoted ruling carrying a dead number in this package: the one verbatim quotation, 「11843 同意」, carries no `#`. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base `cd901d7a5` against head. Template literals are therefore read in context. It ran over all 51 touched `.ts` files. - Real run: 221,086 base tokens, **0 files with a token change** (exit 0). - Comment control in `seed-name-lookup.ts` (`TWO events, ONE consequence` to `TWO events, ONE result`): 0 files changed, as expected (exit 0). - Positive control, a code token added in `seed-name-lookup.ts` (an extra key in the batched read's `where`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`bootstrap-system-capabilities.test.ts:1096`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`36e6be731e6a`, `e1f66feaa6fc`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-security` (`.changeset/20596-plugin-security-provenance-anchors.md`) 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 the build, the rewritten comments reach `dist`: `ADR-0055 amendment` appears 4 times in each of `dist/index.d.ts`, `index.d.mts`, `index.js` and `index.mjs`; `6feac910b`, `498f4e884` twice in each of the four; `c0714eb5d`, `e1d773eb7`, `db923a3a8`, `470746ae4`, `1d73d45c1`, `9b9581b11` once in each of the four; `ee58392e1` 3 times and `1c83ca226` twice in each declaration file; `5cb62d88b` 4 times and `c25b2d52a` 3 times in each runtime file. Positive control: the unchanged line 「declared the key's SHAPE」 beside a shipped rewrite (`rls-compiler.ts:88-89`) is found once in `index.d.ts`, beside 「Until commit 470746a nobody did」. A never-written negative phrase appears nowhere. The only dead numbers left in `dist` are the two kept `objectstack-ai#11082` log strings in the runtime files. ## Gates (head `f90c9b123`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 9 citations across 19 files, and all 9 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the sibling-package prose ids at their baseline and no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `f90c9b123` derived 66 commands: all 57 derived at dispatch, plus `check:duration-unit-keys`, `check:dispatcher-error-vocabulary`, `check:engine-double-contract`, `check:logger-receiver-detach`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. It was re-derived after a fresh `git fetch` (`origin/main` `c6b37cd08`, 3 commits ahead): the same 66. Each ran with its exit code captured before any pipe, and all 66 exit 0. `--ran`, fed each command with its exit code, reports 66 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-security test`: 147 files pass, 3,202 tests pass and 23 skip. That is every test file in the package, the 32 touched ones included. - `pnpm --filter @objectstack/plugin-security typecheck` exits 0 (`tsc` main, `tsconfig.scripts.json`, and `check:test-typecheck` at zero). The main program reads 69 non-test files; the `tsconfig.test.json` program reads all 216 files under `src/`, the 147 test files included, and all 51 touched files are in it (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 51 touched `.ts` files gives 51 files, 0 errors and 0 warnings. All 51 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 52 changed files for control bytes finds none. ## Acceptance notes - **The gate's extractor does not see a number after a slash either.** `CITATION_RE` opens with a lookbehind that refuses a `/` before the `#` (`scripts/check-issue-citations.mjs:449`), so in `#A/#B` only `#A` is a citation to the diff gate and the census, dead or alive. It is the same blind-spot family as the hyphen spelling (objectstack-ai#20636). This stage rewrote the two such prose sites in `plugin-security` (`permission-set-overlay-discard.ts:25`, `platform-owner-wall-bypass.ts:69`) because they are the same dead numbers in the same comment prose. A raw scan of `packages/**/src` `.ts` files against the board finds 33 dead second numbers of this shape at the base and 31 at the head (one of them the kept title `bootstrap-seed-round-trips.test.ts:795`), in 14 packages. The census cannot count them, so a later stage has to look for them by hand. No instrument change here. - **The hyphen spelling in this package** (objectstack-ai#20636 names 1 here on `main`) was `bootstrap-system-capabilities.test.ts:1148`, rewritten. 9 dead `#N-word` sites remain in `packages/**/src` at the head, none in this package. - **The census instrument did not truncate in this stage.** Three enumerations read 185 pages each at the newest frontier. - **Anchors the next stages can reuse.** These numbers stand elsewhere on the census at the head: `objectstack-ai#11374` in `drivers` (16), `platform-objects` (14) and `plugin-audit` (2), anchor `3954fb7df` (route A); `objectstack-ai#6216` in `core` (8), `mcp` (1) and `plugin-hono-server` (1), anchor `f586f1a89`; `objectstack-ai#6483` (8) and `objectstack-ai#6608` (4) in `metadata-protocol`, anchor `ee58392e1`; `objectstack-ai#6206` in `core` (2), `plugin-approvals` (3), `plugin-audit` (1) and `service-storage` (1), anchor `8e13ca876`; `objectstack-ai#8778` in `metadata-core`, `plugin-approvals` and `service-storage`, anchor `7901b2dd2`; `objectstack-ai#16608` (4) and `objectstack-ai#16805` (2) in `objectql`, anchor `a016f08b8`; `objectstack-ai#11343` in `types` (2), anchor `c0714eb5d`; `objectstack-ai#8692` in `cli` (2), anchor `712e185db`; `objectstack-ai#12144` in `metadata-protocol` (1), anchor `3a04b0125`; `objectstack-ai#16682` in `core` (1), anchor `9b9581b11`. - **Base.** The branch is 3 commits behind `origin/main` (`c6b37cd08`, read at 13:54Z). None touches `plugin-security` or any of these numbers; they add three unrelated changesets and move one row of `scripts/doc-authoring-prose-id.baseline.json` (a `packages/lint` entry), so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20596
Clause-②: no
What changed
This is the first stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/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 saysPart ofand 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 #19123), the way the landed
packages/spec/srcstages apply it (PR #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 inorigin/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 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
patchchangeset for@objectstack/service-messaging, because the rewritten docblocks ship (see Changeset below).Census:
service-messaging, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only, unchanged. Its surface is comment prose inpackages/**/src/**/*.tswith string literals blanked, and it defers*.test.ts. The count below is itsallocated-but-absentfindings underpackages/services/service-messaging/.allocated-but-absent7a1faf1a5, run 2026-09-29T06:31:54Z to 06:35:25Z685200760, run 06:48:33Z to 06:52:17ZThe before count matches the 100 that census
5884031174read atf11b5f20. The whole-repo drop is 97, exactly this diff's census sites, and theresolvestally 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) andclassifyCitationover every.tsfile underservice-messaging/src(87 files), against the same enumerated board.7a1faf1a5685200760Its 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 / leftcounts 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.#62068e13ca876: 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 behindassembleExecutionContext#636317d095413:listInbox'sunreadCountcounts the total unread, not the fetched window (maintainer ruling 2026-08-07, Option A: make the declaration true); it addscountUnreadTotal. The same anchor the spec stages gave this number#97222074b2651: corrects thesys_notification_subscriptionindex note —role:andteam:resolve againstsys_memberandsys_team_member#980744738f7af: marks the subscription-to-recipient expansion NOT WIRED and alignsprincipalwith the formsRecipientResolver.resolveOne()accepts, email kept verbatim#11374maxLengthsourced from its producer. Written as 「route A, ruling 2026-08-24」 besidee4902d2b9, the commit that applied it here.scripts/check-keyed-text-bounds.mjs's header states route A in words#114523b5f0360c: the plugin-facinglistInboxAsCaller, scoped to the authenticated caller#114531a47a5368:ack()refuses a row that is notin_flight(NotificationAckError,DELIVERY_NOT_ELIGIBLE), as a compare-and-set in the SQL outbox. The same anchor stage 2 gave it#1167109b4f4e4e:os i18n extract --source-hasheswrites the per-locale provenance companion (maintainer ruling #12069 Option A, which stays cited)#11741b706af987:SendEmailInputgainsorganizationId, and the email channel threads it on both arms. The same anchor stage 1 gave it#11859d9cf78eaa:ack()takes the claimed record back and binds its claim credential in the compare-and-set. The same anchor stage 2 gave it#121443a04b0125: identifier ceilings are storage-owned (sys_metadata.nameis 255)#12147945e91a13: the class-levelcheck-keyed-text-boundsgate#12978e4902d2b9: declares the sourcedmaxLengthon all 15 keyed text columns of thesys_notification_*objects. No commit message names the card; its diff is where every[#12978]marker entered the tree#18424879b51270: an email or SMS channel with no transport refuses withtransport_not_configuredinstead of reporting successEvery 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: 「(两处手写的 ExecutionContext 组装已漂移:REST 传输不带principalKind/onBehalfOf,而 explain / security 会读它 #6071, dispatcher 面的 /share-links 把已解析完整的 ExecutionContext 重新裁成两个字段再喂给 enforcement —— 与 #6206 同一条 enforcement 路径的另一张脸 #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.」sys_notification_*bound comments:[commit e4902d2b9] ... (route A, ruling 2026-08-24).e4902d2b9's message opens 「Every bound names its producer in the declaration」, and3954fb7df's records the ruling's date and its A and C routes.notification-keyed-text-bounds.test.ts:82-83: the#9807pointer becomes 「Every other arm of the grammar commit 44738f7 documented is narrower」, because44738f7afis where the email arm of the selector grammar was written down.outbox-ack-claim-ownership.integration.test.ts:40: 「the INotificationOutbox has no cancellation, andack()on an unclaimedpendingrow silently succeeds in both implementations #11453 file beside this one」 names the file itself,outbox-ack-precondition.integration.test.ts.The 15 sites left
outbox.ts:187(「see INotificationOutbox has no cancellation, andack()on an unclaimedpendingrow silently succeeds in both implementations #11453」) andoutbox.ts:210(「(INotificationOutbox has no cancellation, andack()on an unclaimedpendingrow silently succeeds in both implementations #11453,INotificationOutbox.ack()carries nonodeId, so its compare-and-set can prove a claim exists but not whose #11859)」) are insidenotificationAckNotClaimedMessageandnotificationAckLostClaimMessage, the messagesNotificationAckErrorcarries. They are runtime strings, so they are form D, not form C. The landed packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 stages left every string site as a token and rewrote no refusal text, so these are left and listed, as PR docs(spec): re-anchor the dead tracker citations in data/ to the commits that decided them (stage 3) #20533 did. The form D stages that did rewrite strings (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) coveros migrate metaguidance, a different class.describetitles inemail-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:110andsms-channel.test.ts:90. Tokens, left as they were.es-ES,ja-JPandzh-CN.source-hashes.generated.tsreads 「([finding]check:i18nverifies key presence, not that an untranslated leaf still matches the source string it was filled from — and the drift is sticky #11671, maintainer ruling [Decision] 把 #8765 的 source-hash sidecar 扩展到生成的 i18n bundle —— #9672 写明的升级条件已满足 #12069 Option A, extending i18n: a source-string edit still leaves zh-CN / ja-JP / es-ES silently stale — and pinningento the source makes the asymmetry sharper, not smaller (needs a maintainer decision) #8765 Option B)」.os i18n extractwrites that line frompackages/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-writtentranslations/index.ts:29is rewritten here.Mechanical guard: no code token moves
The check compares leaf tokens with comments stripped, base
7a1faf1a5against 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.tsfiles.outbox.ts: 0 files changed, as expected (exit 0).outbox.ts: DIFFER (exit 1). The first attempt was a no-op: its anchor text was still inside the replacement, soscripts/ablation-replace.mjsrefused it before the guard ran. It was redone with a hitting anchor.outbox.ts:210refusal string: DIFFER (exit 1).Every mutation went through
scripts/ablation-replace.mjs, and each restore was proven byte-identical to the HEAD blob (80618f8711e2) withgit diff HEADempty.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/service-messagingis included. It says only that the provenance comments were re-anchored.Measured on the built package (A3):
files[]isdist,README.mdandCHANGELOG.md. Afterpnpm --filter @objectstack/service-messaging build, the rewritten comments reach both halves ofdist.d9cf78eaaappears 8 times indist/index.d.ts,1a47a53686 times and17d0954136 times, ande4902d2b9appears 15 times indist/index.js. The positive control, an unchangednotification-subscription.object.tsdocblock sentence, appears indist/index.d.ts, and a negative control phrase appears nowhere. The only dead numbers left indistare the two kept refusal strings.Gates (head
267c11562)pnpm check:issue-citations(self-test, 114 cases in 8 batteries) exits 0, andnode scripts/check-issue-citations.mjsexits 0. The diff-scoped run judged 5 citations across 19 files, and all 5 resolve.pnpm check:doc-authoringexits 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat267c11562derived 64 families. They include all 50 derived at dispatch, plus 14 more. All 64 exit 0.--ranreports 64 run, 0 NOT MEASURED, 0 unrun, and exits 0.check:dual-build-cjs-loads,check:i18nandcheck:type-check-debt. A fullturbo run buildof./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-debtexited 3 once more:outbox.ts's mtime had moved during the guard controls, although its bytes had not, so turbo's cache hit leftdistolder than the source. A directpnpm --filter @objectstack/service-messaging buildthen let it exit 0 (4 ledger entries re-measured, none above its number).pnpm --filter @objectstack/service-messaging test: 46 files and 507 tests pass, covering every touched test file.pnpm --filter @objectstack/service-messaging typecheckexits 0. Itstscprogram lists all 46 test files and 87 files undersrc/in total (--listFiles).eslint --no-inline-config --format jsonover the 28 touched.tsfiles gives 28 files, 0 errors and 0 warnings. All 28 are in eslint's own population (isPathIgnoredis false for each).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as 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.pnpm check:nul-bytesexits 0, and a raw scan of the 28 files for control bytes finds none.Acceptance notes
--census --jsonrun of this stage (06:25:55Z, base7a1faf1a5) readenumerated (85 pages), frontier fix(runtime,mcp,service-datasource): the #6504 consumer sweep — three list consumers stop claiming what a known-partial read cannot support #8854, 8,444 numbers, when the newest number was above [finding] nextUtcCalendarDay('9999-12-31') answers '10000-01-01', so on SQLite a datetime $lte '9999-12-31' or a $between maximum on that day answers no rows #20600. TheLinkheader of its 85th page had carried norel="next", soenumerateBoardstopped and classified 16,187 citations asnever-issued. A reader counting onlyallocated-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 docs(spec): re-anchor the dead tracker citations in the packages/spec/src remainder to the commits and ADRs that decided them (stage 6) #20606, and the counts above come from those. Nothing inenumerateBoardcompares its frontier with the newest issue number, whichprobeBoarddoes read. Reported to the seat, not changed here: this stage makes no instrument change.#11671headers, whose producer ispackages/cli/src/utils/i18n-extract.ts:2294. That line is the repo-wide carrier (27 generated files).outbox.ts(form D) and the 9 test-title sites.origin/main(0f6dcac5e, read at 07:23Z). One of them,8c87d26a5(the version packages release), touchesservice-messaging, but only itsCHANGELOG.mdandpackage.json, and neither is in this diff. So there was no merge.17d095413(notification 响应侧:unreadCount声明「总未读数」实测只数 limit 窗口内;响应cursor从无 producer 发出 #6363),1a47a5368(INotificationOutbox has no cancellation, andack()on an unclaimedpendingrow silently succeeds in both implementations #11453),d9cf78eaa(INotificationOutbox.ack()carries nonodeId, so its compare-and-set can prove a claim exists but not whose #11859) andb706af987(WidenSendEmailInputwithorganizationIdsosys_emailcan be stamped at its producers (Decision 2 of #11303) #11741) are the anchors stages 1 and 2 already gave the same numbers inpackages/spec/src, so each number carries one anchor across the tree.Generated by Claude Code