docs(plugin-sharing): re-anchor the dead tracker citations to the commits that decided them - #20626
Conversation
…mits that decided them Comment and docblock prose under packages/plugins/plugin-sharing/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. 87 sites on 86 lines in 23 files, 13 numbers, 13 distinct commits; one more line re-points a referent the removed number left behind. Comments only: every file keeps its line count, no code token moves, and no citation number is added. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
The re-anchored comments reach both halves of dist (measured after a build, with a positive and a negative control), so the package ships changed bytes. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 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 — 9 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 7f8271f961956dafa0a687bcad4eb6bc8351c974 && git checkout 7f8271f961956dafa0a687bcad4eb6bc8351c974
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1322cc72c96f9e80240c1fe0a7d12708f3b269b9 a6d2317138a5e8e71ade0f252cc9b452547b6dfd && git checkout -B drift-repro 1322cc72c96f9e80240c1fe0a7d12708f3b269b9 && git merge --no-ff a6d2317138a5e8e71ade0f252cc9b452547b6dfd
node scripts/docs-audit/affected-docs.mjs --json 1322cc72c96f9e80240c1fe0a7d12708f3b269b9
|
…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>
… to the commits that decided them (objectstack-ai#20632) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 2 of the `domain:cli` lane of the dead-citation sweep: `packages/rest/src/**`. Every comment or docblock site in scope that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20624 (stage 1, `packages/runtime`) the precedent this follows line for line. Later stages cover `cli`, `types` and the rest of the lane, so this PR says `Part of` and the card stays open. That is **457 comment sites on 445 lines in 85 files, covering 74 numbers**: the census's 191 sites, 256 more in test comments (which the census defers), and 10 sites whose dead number is the second half of a slash-joined pair the citation grammar does not read (`objectstack-ai#3984/objectstack-ai#6241`, `objectstack-ai#9901/objectstack-ai#10255` four times, `objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292`, `objectstack-ai#11235/objectstack-ai#11242` twice, `objectstack-ai#10993/objectstack-ai#11242`, `objectstack-ai#7543/objectstack-ai#15071`). Each rewritten line cites one of **70 distinct commits**. ADR-0076 D11 is the only ADR that records any of these numbers, and it records objectstack-ai#8850 only as the extraction it names as landed in `8664a2c99`, so that commit is the anchor there. No other ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of these numbers, so every anchor is a commit. The anchors the landed stages already gave the same numbers are reused where the rest sites describe the same decision (30 numbers, for example `79c46da90` for objectstack-ai#9934, `7986d973f` / `311433f6b` for the compound-name retirement, `6a180e42d` for objectstack-ai#13279 and `cf6e0a193` for objectstack-ai#15071), so each number carries one anchor across the tree. Only comments changed. Every touched file keeps its line count (451 lines out, 451 in, over 85 files), so no line citation into these files moves. Six of the 451 lines held no dead site; each is the other half of a sentence that had to change: - `discovery-schema-conformance.test.ts:343` (「(reaffirmed by」 to 「(which commits」, because line 344 now names the two commits that landed the ruling), - `package-door-16019-raw-statement-fault-code.test.ts:51` and `error-response.ts:1485` (a trailing 「PR」 whose number wrapped onto the next line), - `error-response-structured-arm-door-parity.test.ts:463` (「That card added the limb」 to 「That commit」, because line 459's tag now names the commit), - `rest-hook-script-fault-envelope.test.ts:331` (「both sides of that card」 to 「that fix」), - `rest-server.ts:908` (「(objectstack-ai#14409, landed」 to 「(landed as commit」, the sha `3ecb7dc1a` already standing on line 909). **No citation number is added.** Every tracker number on an added line was already on the line it replaces. No PR number stands on an added line. One of the 70 shas is on a removed line, and it was there before: `rest-14078-invalid-date-total-arm.test.ts:19` read 「PR objectstack-ai#14409 (landed `3ecb7dc1a`)」 and now reads 「Commit 3ecb7dc drove」. No code token moves (see the guard below). Three dead comment sites are left on purpose, listed under "The sites left". One more file: a `patch` changeset for `@objectstack/rest`, because the rewritten docblocks ship (see Changeset below). ## Census: `packages/rest`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run with the fleet token. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count is its `allocated-but-absent` findings under `packages/rest/`. Both runs enumerated the whole board (185 pages), so neither read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | rest sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `a186aea996`, run 2026-09-29T10:28:18Z to 10:36:06Z | enumerated, 185 pages, frontier objectstack-ai#20628, 18,455 numbers | 2,015 | **191** | 186 | 14 | 51 | | after | head `93e4d69ba6`, run 11:11:30Z to 11:17:37Z | enumerated, 185 pages, frontier objectstack-ai#20630, 18,457 numbers | 1,764 | **0** | 0 | 0 | 0 | The before count equals the card's 191 at `f11b5f20a2`. The whole-repo drop is 251: this diff's 191, plus the 60 of PR objectstack-ai#20626 (`packages/plugins/plugin-sharing`, 63 to 3), which landed on `main` in between and came in with the merge. No other package moved. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `packages/rest/src` (256 files), against the board enumerated through the gate's own `enumerateBoard`. The lit controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20624 answered 200 and are on both boards; the dead controls objectstack-ai#13214, objectstack-ai#14541 and objectstack-ai#15071 answered 404 and are on neither. | reading | tree | board | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---|---| | before, 10:29Z | `a186aea996` | 185 pages, frontier objectstack-ai#20628 | 4,620 | **577** | 191 | 259 | 1 | 126 | | after, 11:21Z | `93e4d69ba6` | 185 pages, frontier objectstack-ai#20631 | 4,174 | **130** | 0 | 3 | 1 | 126 | Its src-comment column equals the census's 191 and 0, which is the control on the second instrument, and a site-by-site comparison of the two before-readings is identical. Resolving comment citations move by one (1,364 to 1,365 in src): `(objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292)` became `(objectstack-ai#10993, commit 376c70f, objectstack-ai#11292)`, so the grammar now reads the live `objectstack-ai#11292` that the slash hid. The drop is 447 grammar-read sites; the other 10 rewritten sites are the slash-joined ones the grammar never read. Separately, every one of the 77 numbers was probed on its web endpoint: 76 answer 404 (deleted) and one, #14026, answers 302 to objectstack-ai/objectui#10102 (transferred), which is why it is left (see below). ## Per-number table Sites and files are the dead comment sites in scope at the base, tests and slash-joined halves included. `left` is a site with no deciding commit (see below). `strings kept` counts string-literal sites, which are tokens and stay as they were. Every anchor was read in its message or its diff, not only in its subject: it is the commit that made the change the line describes, and its own message or diff names the number it replaces or adds the citation the line carries. | number | comment sites / files | rewritten | left | strings kept | anchor | |---|---|---|---|---|---| | `objectstack-ai#6037` | 5/3 | 5 | 0 | 0 | `18189983d` | | `objectstack-ai#6122` | 2/2 | 2 | 0 | 0 | `64cd01082` | | `objectstack-ai#6206` | 1/1 | 1 | 0 | 0 | `8e13ca876` | | `objectstack-ai#6216` | 6/2 | 6 | 0 | 2 | `f586f1a89` | | `objectstack-ai#6241` | 10/3 (1 slash-joined) | 10 | 0 | 1 | `83a3b1f2e` | | `objectstack-ai#6259` | 2/1 | 2 | 0 | 0 | `6968885ef` | | `objectstack-ai#6303` | 1/1 | 1 | 0 | 0 | `465c5fc14` | | `objectstack-ai#6306` | 9/5 | 9 | 0 | 3 | `fec784863` | | `objectstack-ai#6307` | 4/2 | 4 | 0 | 0 | `293476148` | | `objectstack-ai#6349` | 4/2 | 4 | 0 | 4 | `2443bb4c4` | | `objectstack-ai#6474` | 1/1 | 1 | 0 | 0 | `18189983d` | | `objectstack-ai#6535` | 3/2 | 3 | 0 | 0 | `a92b1793c` | | `objectstack-ai#6640` | 1/1 | 1 | 0 | 1 | `2ab1257c9` | | `objectstack-ai#6704` | 5/1 | 5 | 0 | 1 | `c3f491626` | | `objectstack-ai#8641` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#8850` | 3/3 | 3 | 0 | 0 | `8664a2c99` | | `objectstack-ai#8885` | 6/3 | 6 | 0 | 3 | `30b1c636a` | | `objectstack-ai#8919` | 7/3 | 7 | 0 | 7 | `b5378550e` | | `objectstack-ai#9741` | 12/1 | 12 | 0 | 0 | `2a29caa53` | | `objectstack-ai#9805` | 1/1 | 1 | 0 | 0 | `45862a53d` | | `objectstack-ai#9934` | 19/10 | 19 | 0 | 4 | `79c46da90` | | `objectstack-ai#9967` | 2/2 | 2 | 0 | 4 | `8f266f1cd` | | `objectstack-ai#10063` | 2/2 | 2 | 0 | 1 | `9e04c3e35` | | `objectstack-ai#10178` | 1/1 | 1 | 0 | 0 | `38cf397ea` | | `objectstack-ai#10179` | 0/0 | 0 | 0 | 1 | | | `objectstack-ai#10255` | 18/4 (4 slash-joined) | 18 | 0 | 2 | `6ce58a735` | | `objectstack-ai#10340` | 13/3 | 13 | 0 | 2 | `26f3588fb` | | `objectstack-ai#10345` | 13/6 | 13 | 0 | 6 | `cad8b42f0` | | `objectstack-ai#10350` | 1/1 | 1 | 0 | 0 | `490879ad0` | | `objectstack-ai#10485` | 2/1 | 2 | 0 | 1 | `35ad101bc` | | `objectstack-ai#10537` | 9/3 | 9 | 0 | 1 | `e634ecf6a` | | `objectstack-ai#10888` | 2/2 | 2 | 0 | 0 | `d806081dd` | | `objectstack-ai#11006` | 3/1 | 3 | 0 | 0 | `cccbe51bf` | | `objectstack-ai#11130` | 1/1 | 1 | 0 | 0 | `851909530` | | `objectstack-ai#11235` | 4/2 (1 slash-joined) | 4 | 0 | 0 | `376c70f98` | | `objectstack-ai#11242` | 3/2 (3 slash-joined) | 3 | 0 | 0 | `98ea3443f` | | `objectstack-ai#12144` | 1/1 | 1 | 0 | 0 | `3a04b0125` | | `objectstack-ai#12176` | 11/7 | 11 | 0 | 2 | `7986d973f` | | `objectstack-ai#12194` | 15/5 | 15 | 0 | 4 | `311433f6b` | | `objectstack-ai#12195` | 35/16 | 35 | 0 | 7 | `7986d973f` | | `objectstack-ai#13182` | 2/2 | 2 | 0 | 0 | `5b3ff63cc` | | `objectstack-ai#13197` | 1/1 | 1 | 0 | 0 | `56c093c4d` | | `objectstack-ai#13213` | 2/1 | 2 | 0 | 0 | `4801296e7` | | `objectstack-ai#13214` | 18/6 | 18 | 0 | 14 | `cc837dbfe`, `889ec5b42`, `3d10755f0` | | `objectstack-ai#13244` | 5/2 | 5 | 0 | 1 | `889ec5b42` | | `objectstack-ai#13255` | 4/1 | 4 | 0 | 6 | `43028a8f8` | | `objectstack-ai#13258` | 1/1 | 1 | 0 | 0 | `3d10755f0` | | `objectstack-ai#13279` | 23/5 | 23 | 0 | 5 | `6a180e42d` | | `objectstack-ai#13280` | 13/4 | 13 | 0 | 2 | `add6a1b1c` | | `objectstack-ai#13282` | 1/1 | 1 | 0 | 0 | `43028a8f8` | | `objectstack-ai#13377` | 3/2 | 3 | 0 | 0 | `e10cf3444` | | `objectstack-ai#13378` | 2/1 | 2 | 0 | 0 | `82faea03f` | | `objectstack-ai#13454` | 1/1 | 1 | 0 | 0 | `7ad57e17a` | | `#14026` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#14365` | 1/1 | 0 | 1 | 0 | — | | `objectstack-ai#14366` | 14/4 | 14 | 0 | 2 | `53cbad9f7` | | `objectstack-ai#14369` | 3/2 | 3 | 0 | 0 | `a3d5724c8`, `53cbad9f7` | | `objectstack-ai#14389` | 7/3 | 7 | 0 | 7 | `10220a7bf` | | `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | `9d7f7259f` | | `objectstack-ai#14409` | 2/2 | 2 | 0 | 0 | `3ecb7dc1a` | | `objectstack-ai#14541` | 27/4 | 27 | 0 | 5 | `6d178a408` | | `objectstack-ai#14613` | 2/2 | 2 | 0 | 0 | `81208086a` | | `objectstack-ai#14677` | 1/1 | 1 | 0 | 0 | `a4e4d2d78` | | `objectstack-ai#14683` | 8/2 | 8 | 0 | 0 | `96326040f` | | `objectstack-ai#14691` | 15/2 | 15 | 0 | 2 | `b3a63d32c` | | `objectstack-ai#14704` | 9/3 | 9 | 0 | 2 | `1c7adc73d` | | `objectstack-ai#14723` | 7/4 | 7 | 0 | 4 | `65846bc46` | | `objectstack-ai#14725` | 3/3 | 3 | 0 | 2 | `f5cc78b63` | | `objectstack-ai#14849` | 3/1 | 3 | 0 | 0 | `226e72443` | | `objectstack-ai#14907` | 1/1 | 1 | 0 | 0 | `e1d4f9e3f` | | `objectstack-ai#14908` | 1/1 | 1 | 0 | 0 | `d5cbb44f3` | | `objectstack-ai#15021` | 2/1 | 2 | 0 | 8 | `cc238db8b` | | `objectstack-ai#15034` | 6/2 | 6 | 0 | 0 | `abf9101f1` | | `objectstack-ai#15065` | 1/1 | 1 | 0 | 0 | `1c7adc73d` | | `objectstack-ai#15071` | 23/4 (1 slash-joined) | 23 | 0 | 3 | `cf6e0a193` | | `objectstack-ai#16650` | 1/1 | 1 | 0 | 0 | `001a83b04` | | `objectstack-ai#17058` | 3/1 | 3 | 0 | 4 | `94c930248` | | `objectstack-ai#18546` | 3/2 | 3 | 0 | 3 | `58f60e37e` | | **total** | **460** | **457** | **3** | **127** | **70 distinct commits** | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 70), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 70). The checkout is not shallow (`--is-shallow-repository` false); the control leg `13a6cb4ad` exits 0 and the negative control (this branch's first WIP commit, not on `main`) exits 1. Several numbers are the PR number of their own anchor commit (objectstack-ai#6122, objectstack-ai#6303, objectstack-ai#6474, objectstack-ai#11242, objectstack-ai#13213, objectstack-ai#13244, objectstack-ai#13258, objectstack-ai#13282, objectstack-ai#14409, objectstack-ai#14677, objectstack-ai#14908, objectstack-ai#15065, objectstack-ai#16650), so the sha is the same object the number named. **Numbers with more than one anchor, by site:** - `objectstack-ai#13214` (18 sites) was one card with three commits. `cc837dbfe` (the ownership gate, the 2026-08-30 ruling) for the 11 sites that describe the gate; `889ec5b42` for the 5 in `ui-view-route-identity.measurement.test.ts`, the identity measurement it created; `3d10755f0` for the tenancy file's header, the measurement it created; and `rest-server.ts:2247`, 「Driven and reported on objectstack-ai#13214 (PRs objectstack-ai#13244, objectstack-ai#13258)」, now reads 「Measured in commits 889ec5b (identity) and 3d10755 (tenancy)」: those PRs are exactly those two commits. - `objectstack-ai#14369` (3 sites): `a3d5724c8` (the liveness census it recorded) for `rest-server.ts:1172` and `rest-sub-config-parse-not-cast.test.ts:48`. `rest-server.ts:4092` said the zero read sites of `api.documentation` / `api.responseFormat` came from 「the objectstack-ai#14369 census」, but `a3d5724c8` explicitly left `api` out of that census; the zero was measured by `53cbad9f7` (its changeset: no other read site for either key), which is the anchor there. - `objectstack-ai#11235` / `objectstack-ai#11242` / `objectstack-ai#10993`: `376c70f98` derives the discovery `version` in metadata-protocol (objectstack-ai#11235), and `98ea3443f` is objectstack-ai#11242's own squash, which landed the objectstack-ai#10993 ruling on `/health` and the dispatcher's `/discovery`. So 「the objectstack-ai#10993 ruling … reaffirmed by objectstack-ai#11235/objectstack-ai#11242」 now reads 「the objectstack-ai#10993 ruling, landed by commits 98ea344 and 376c70f」 (`rest-server.ts:4528`, `discovery-schema-conformance.test.ts:343-344`). `objectstack-ai#10993`, `objectstack-ai#11292` and `objectstack-ai#11297` answer 200 and stay. - `objectstack-ai#6037` / `objectstack-ai#6474`: one commit, `18189983d` (objectstack-ai#6474 is its PR number), so 「(objectstack-ai#6037 / PR objectstack-ai#6474)」 became 「(commit 1818998)」. **Wordings to check, each true of its commit:** - A commit does not rule. Where a line said a number ruled, it now says what the commit did with the ruling: 「the ruling commit 79c46da landed says it does」, 「the ruling commit cf6e0a1 implemented fences it」, 「the ruling commit 10220a7 implemented」, 「the 2026-08-20 ruling, landed as commit 6ce58a7」, 「recorded in commit 6ce58a7's message (option A)」 (its message reads 「Ruled on objectstack-ai#10255 (2026-08-20, option A)」), and 「question was ruled on 2026-08-20 and landed as commit 6ce58a7」 where the line said 「filed as objectstack-ai#10255」. - `objectstack-ai#14541`'s contract review: 「the objectstack-ai#14541 contract review (condition N)」 now reads 「the contract review of commit 6d178a4 (condition N)」; that commit's message lists the conditions it carries. 「objectstack-ai#14541's §4」 and 「objectstack-ai#14541 §5」 in `error-response-generic-passthrough-object-parity.test.ts` are sections of `error-response-structured-arm-door-parity.test.ts` (the file `6d178a408` created), so they now name that file. 「measured on the objectstack-ai#14541 branch」 reads 「on the branch that landed as commit 6d178a4」. - A line that named a DEFECT by its number now says so: 「Before commit 9e04c3e the draft→active promotion door could not…」, 「Before commit 26f3588 the `/meta` doors decided ORGANIZATION SCOPE from the RAW url」, 「the defect commit 2443bb4 fixed」 and 「would be the defect commit 26f3588 fixed」. - `objectstack-ai#13255`: 「As written for objectstack-ai#13255 this file repaired nothing」 reads 「As first written (commit 43028a8)」, the commit that created the file and answered the measurement; 「CONTEXT-LOST family (objectstack-ai#13255), still unruled」 reads 「first measured by commit 43028a8」 (the ruling on that family never landed, which the line still says). - `objectstack-ai#13214` in the identity file: 「the half objectstack-ai#13214 marks UNMEASURED」 reads 「the half left UNMEASURED until commit 889ec5b」, and 「objectstack-ai#13214 asks for an INDEPENDENT reproduction」 reads 「commit 889ec5b is an INDEPENDENT reproduction」. - 「the objectstack-ai#8885 sweep」 reads 「the sweep behind commit 30b1c63」, the commit that registered the 9 codes the sweep found; 「objectstack-ai#14849 predicted」 reads 「The card behind commit 226e724 predicted」; 「the hazard objectstack-ai#13377 names」 reads 「the hazard commit e10cf34 was written to remove」; 「The concrete harm objectstack-ai#6704 names」 reads 「removed」. - Quoted ruling: `error-response-sandbox-arm-message.test.ts:340` sits inside a verbatim ruling quote, so the commit stands in an editorial bracket (「not from [commit 1c7adc7]'s list」), as PR objectstack-ai#20624 did. - Two markdown tables in comments (`meta-state-route-engine-outage.test.ts:76`, `objectql-slot-consumer-census.test.ts:43`): the rewritten cell is wider than its column, and its padding is reduced rather than widening the four sibling rows. ## The sites left **No deciding commit (3 sites, all in test files, so the census does not see them):** - `meta-object-owd-gate.test.ts:516` (objectstack-ai#8641): 「whether it should stay is objectstack-ai#8641's question」, an open decision. The commit that added the citation calls it a pointer to the open decision card, and no commit decides it. - `rest-sub-config-parse-not-cast.test.ts:321` (objectstack-ai#14365): the `z.partialRecord` question 「deferred to objectstack-ai#14365」 was never taken (`git log -S partialRecord`); `b3a63d32c` made it moot by retiring the record, which the other half of the same line now cites. - `import-integration.test.ts:1043` (#14026): not deleted, TRANSFERRED. The web endpoint answers 302 to objectstack-ai/objectui#10102, the REST read follows the redirect, and the board enumeration does not list it, so the census and the supplementary reading both class it `allocated-but-absent`. The line says how an issue was raised; no commit decides that, so form C has nothing to cite. **String sites kept as tokens (127).** 126 are test titles and test-code strings in 41 files. One is a non-test string: the `note` field of the REST route ledger's `GET /api/v1/meta/object/:name/state/:field` row at `rest-route-ledger.ts:290`, which ends 「(objectstack-ai#10179)」 (see Acceptance notes). ## Mechanical guard: no code token moves The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file at base `a186aea996` against the working tree at `93e4d69ba6`, over all 85 touched `.ts` files. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 272,653 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control (`error-response.ts`): 0 files changed (exit 0). - Code-insertion positive control (a declaration in the same file): DIFFER at token 0 (exit 1). - String positive control (the first string literal past offset 2000 of the same file, one character added inside it): DIFFER at token 26 (exit 1). Line balance: every touched file is +N/−N (451/451), and every line count is equal at base and head. A raw scan of the 86 changed files for control bytes finds none (its positive control on a scratch file with a U+0001 byte matches). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/rest` is included, in PR objectstack-ai#20624's form and level. It says only that the provenance comments were re-anchored. Measured on the built package: `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After `pnpm --filter @objectstack/rest build`, the rewritten docblocks reach `dist`: for example `53cbad9f7` appears 4 times in `dist/index.d.ts`, and `26f3588fb` 8 times and `b3a63d32c` 5 times in `dist/index.js`. The positive control, the unchanged sentence 「It was VALIDATE-ONLY from objectstack-ai#11637」 of the same `rest-server.ts` docblock whose first line now reads 「[commit 53cbad9] The parsed output is CONSUMED」, is in `dist/index.d.ts` beside it; a negative control phrase appears nowhere. ## Gates (head `93e4d69ba6`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each locked run at this head: ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 47s · declare it in the PR body · pnpm --filter '@objectstack/rest...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 76s (1m16s) · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/rest typecheck ``` The branch merged `origin/main` once (`93e4d69ba6`, merging `542670da6d`) before these runs, as the dispatch orders; `origin/main` has not moved since (read at 11:19Z). The merge brought PR objectstack-ai#20626 and PR objectstack-ai#20587 and touched none of this diff's files. The dependency closure was built first (`pnpm --filter '@objectstack/rest...' build`, 26 packages), then the whole workspace (`turbo run build --filter='./packages/*' --filter='./packages/*/*'`, 71 tasks, 71 successful). - **Tests:** `vitest run --project local`: 227 files, 4,382 tests passed, 50 skipped. `--project repo` (which holds the touched `meta-state-route-doc-spelling.test.ts`): 1 file, 8 tests passed. Together they are all 228 test files of the package, so every touched test file ran. - **Typecheck:** `pnpm --filter @objectstack/rest typecheck` exits 0. `tsc --listFiles` counts 28 `src` files (no tests) under `tsconfig.json` and all 228 test files under `tsconfig.test.json`, which `check:test-typecheck` judges: 0 files, 0 errors, 0 pinned signatures in the ledger. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `93e4d69ba6` (2026-09-29T11:19:30Z to 11:20:00Z). Not narrowed. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0: 19 citations judged across 14 files (18 resolve, 1 resolves as a pull request). These are the live numbers that stay on rewritten lines. It defers `*.test.ts`, so the added-minus-removed count over the whole diff covers the rest: 0 numbers added. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `93e4d69ba6` derived 68 families. All 68 exit 0, and `--ran` over a record carrying each exit code reads 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero). - `check:dual-build-cjs-loads` and `check:type-check-debt` first exited 3 (PREREQUISITE NOT MET, nothing measured) on the closure-only build; after the whole-workspace build both exited 0. - Among them: `check:doc-authoring`, `check:nul-bytes`, `check:rest-log-declared`, `check:route-envelope`, `check:system-context-census` (106 elevation read sites, the page's 102 symbols held) and `check:issue-citations` (self-test). - **Artifact rosters:** 33 of the 36 non-self-test roster rows exit 0 at `93e4d69ba6`, `check-changeset-fixed` (the one whose roster sits under `.changeset/`) and `check:route-ledger-census` among them. The other three, `check-closing-target-claim`, `check-partof-closing-keyword` and `check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull request's context; they are run against this PR once it exists and reported on the card. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 191 dead sites at `a186aea996` (186 lines, 14 files, 51 numbers), equal to the card's count at `f11b5f20a2`: no net drift, although PR objectstack-ai#20601 (merged as `eb4b17c346`, before this base) touches four files in `packages/rest`. - **H1 holds.** After the rewrite the filtered census answers 0. The supplementary reading leaves 3 test-comment sites, the three listed above: an open decision, an untaken option and a transferred issue, none with a deciding commit. No site was held for an open PR: the claim's read and this stage's two reads of the open PRs' file lists (10:27:35Z, 7 open PRs; 11:30:34Z, 8 open PRs) found none touching `packages/rest`. - **H2 holds, by the token guard.** A comment-stripped comparison of every touched file (the parser's leaf tokens, JSDoc excluded) is empty, and its code and string controls fire. The emitted `dist` is not byte-identical, because the docblocks ship, which is why the changeset is `patch`. ## Acceptance notes - **Form D, not touched here.** 127 dead numbers stand inside string literals: 126 in test titles and test-code strings, and one in the `note` of the REST route ledger's legal-next-state row (`rest-route-ledger.ts:290`, 「(objectstack-ai#10179)」), which is ledger data, not an author-shown refusal. Ruling D (no number, the lesson in words) is a string change outside this comment-only scope; the card already carries a form-D stage for the lane. - **A transferred issue among the 404s.** #14026 answers 302 to objectstack-ai/objectui#10102 on its web endpoint. The census classes it `allocated-but-absent` (deleted and transferred are only told apart under `--probe-cause`), and `scripts/check-issue-citations.mjs`'s header says the `transferred` arm has no positive specimen on this tree; this is one. Noted, not filed. - **The grammar does not read a slash-joined number.** `CITATION_RE` refuses a `#` preceded by `/`, so the second number of `#A/#B` is never judged. In `packages/rest/src` six such dead numbers stood at 10 comment sites, all rewritten here; one more, `objectstack-ai#14389` in `objectstack-ai#14095/objectstack-ai#14389`, stands inside a string (`error-response-structured-arm-door-parity.test.ts:187`) and is kept. The same shape PR objectstack-ai#20624 and PR objectstack-ai#20612 reported. Noted, not filed. - **Outside the scope and the census surface.** `packages/rest/vitest.config.ts:21` cites objectstack-ai#17853, which answers 404; `packages/rest/test-typecheck-debt.json`, written by `gen:test-typecheck-debt`, carries objectstack-ai#13470, objectstack-ai#13454, objectstack-ai#13377 and objectstack-ai#13378 in its prose, all 404. Neither is under `src/**`. The other numbers in `vitest.config.ts`, `tsconfig.json` and `tsconfig.test.json` answer 200. - **Two comments stale on their own, not touched.** The anchor research found `rest-server.ts`'s `api` docblock near `:1115` and the 「zero read sites」 sentence at `:4092` both overtaken by `80153f5a4`, whose own acceptance notes record it. This PR re-anchors their citations and leaves their claims alone. - **An attribution corrected by the anchor.** `rest-server.ts:4092` credited its zero-read-site count to 「the objectstack-ai#14369 census」, which (`a3d5724c8`) excluded `api`; it now cites `53cbad9f7`, the commit that measured it. - **Base.** One merge of `origin/main` (`93e4d69ba6`) before the `--base origin/main` run, as the dispatch orders. ## Deviations - Ten sites beyond the census's read grammar carry a slash-joined dead number and are rewritten; six more lines are the other half of a rewritten sentence (listed under What changed). - The whole-workspace build ran with `--concurrency=4`, not 2, to stay inside the ten-minute foreground cap on this host; it took 1m42s. - Anchor research for 33 of the 77 numbers ran in three read-only research subagents; every proposal was verified here against the commit's message or diff, and the wording of each changed line was reviewed and corrected by hand in a second pass. - Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…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 second stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/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 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), by stage 1's method (PR #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 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 records the decision behind any of the 13 numbers (ADR-0131 names #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」, and3f64fe6c6'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 #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
patchchangeset for@objectstack/plugin-sharing, because the rewritten docblocks ship (see Changeset below).Census:
plugin-sharing, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is itsallocated-but-absentfindings underpackages/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.allocated-but-absent422db788a, run 2026-09-29T08:04:49Z to 08:08:19Za6d231713, run 08:27:44Z to 08:31:07ZThe before count matches the 63 that census
5884031174read atf11b5f20. The whole-repo drop is 60, exactly this diff's census sites. Theresolvestally is 32,803 in both runs, andresolves-as-pull-request(1,891) andcross-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) andclassifyCitationover every.tsfile underplugin-sharing/src(74 files). It uses one board, enumerated by the gate's ownenumerateBoardat 08:12:18Z (185 pages, frontier #20614, equal to the newest).422db788aa6d231713Its 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 / leftcounts 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.#5973abeb3751f:HierarchyScopeContext.organizationIdis the tenancy authority, and it is required.#5973was the PR itself; this is its squash commit#62068e13ca876: 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 atd7e0b4212. Three sites name the ruling in words, 「the full-envelope ruling」, besideaa4b90d9a, which applied it#6523aa4b90d9a: 36 contract signatures converge on the fullExecutionContext. The same anchor the spec stages gave this number#871004d03c3a0: a deactivatedsys_positionconfers no sharing-rule shares. Its message quotes the 2026-08-15 ruling: access-conferring paths filter, addressing paths do not#879283c661d97: the bulk-write merge's missing provenance mark is recorded as ruled (2026-08-15), not oversight#88361850ebbb0: 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#1167109b4f4e4e:os i18n extract --source-hasheswrites the provenance companion (maintainer ruling #12069 Option A, which stays cited). The same anchor stage 1 gave it#116741cba33f16: 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#12493aa5994e17: the Operation Message Catalog gainsrecord_write_deniedahead of its emitters. The same anchor the spec stages gave this number#132796a180e42d: a permission-store read that throws raisesAuthzStoreUnavailableError(503), and each transport re-raises it rather than laundering it into a 401#13398953a81f4a: the class ruling on published logger sinks, applied at this site.erroris reachable only because the sink already declares it; growingerror?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#13608fc9ba76a5:publicSharing.eligibilityis held at redemption, not only at mint. The same anchor the spec stages gave this number#144843f64fe6c6:organization_idis stamped on everysys_record_sharewrite, the stranded rows are backfilled, and the object is admitted to the tenancy ledger (the 2026-09-02 ruling, decision batch #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-repositoryfalse, 15,073 commits). A line-origin pickaxe (git log -Son 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,65759bacais the consumer half that citesaa5994e17's key, andb70a55d62cites3f64fe6c6's ledger admission.Wordings to check, each true of its commit:
backfill-sys-record-share-organizations.ts:5: 「rows thatSharingService.grant, before commit 3f64fe6, stranded」.3f64fe6c6is the writer fix, and this module is its backfill.backfill-sys-record-share-organizations.ts:36and:124: 「the 2026-09-02 ruling commit 3f64fe6 applies (decision batch Migrate documentation site to Fumadocs with monorepo structure and shared content #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-13andsharing-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
sharing-service.ts:1674sits inside the operator-facingwarntext for a hierarchy scope that was not widened (「… resolveOwnerIds, fix(spec)!: HierarchyScopeContext 声明 organizationId 为权威租户字段并转必填 (#5858) #5973); …」). It is a runtime string, so it is form D, not form C, and the shrink-onlydoc-authoring-prose-idbaseline already holds it (sharing-service.ts→#5973: 1). Left and listed, as stage 1 left its refusal strings.describetitles inbackfill-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#14484titles),share-link-eligibility.test.ts:607(#13608),share-link-enforcement-context.test.ts:226(#6206) andsharing-rule.test.ts:1898(#8710). Tokens, left as they were.share-link-service.test.ts:478is point 2 of the maintainer's 2026-09-01 ruling, quoted verbatim and untranslated. It carries 「沿 sharing:publicSharing.eligibilityis evaluated only at mint — a link keeps serving a record after it stops being eligible #13608 先例」. AGENTS.md keeps a quoted Chinese ruling in its original words, and rewriting the quote would rewrite the ruling. Left.es-ES,ja-JPandzh-CN.source-hashes.generated.tsfiles carries 「([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, so the fix belongs at the producer, the carrier stage 1 named. The hand-writtentranslations/index.ts:26is 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
422db788aagainst head. Template literals are therefore read in context. It ran over all 23 touched.tsfiles.share-link-service.ts: 0 files changed, as expected (exit 0). The first attempt was a no-op: its replacement still contained the anchor, soscripts/ablation-replace.mjsrefused it before the guard ran. It was redone with an anchor the replacement does not contain.share-link-service.ts(Boolean(eligibility),toBoolean(eligibility) && true,): DIFFER (exit 1).sharing-service.ts:1674warn string: DIFFER (exit 1).Every mutation went through
scripts/ablation-replace.mjs. Each restore was proven byte-identical to the HEAD blob (ba7fba2e8199,2833b9a1616d), withgit diff HEADempty and a clean tree afterwards.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/plugin-sharingis included. It says only that the provenance comments were re-anchored.Measured on the built package (A3):
files[]isdist,README.mdandCHANGELOG.md. After the build, the rewritten comments reach both halves ofdist:3f64fe6c6appears 6 times indist/index.d.tsand 8 indist/index.js,fc9ba76a53 and 3,04d03c3a02 and 2,8e13ca876twice inindex.d.ts, and1cba33f164 times inindex.js. esbuild keeps only some comments, so the positive controls are unchanged lines beside rewritten ones that shipped.share-link-service.ts:891is found once in each half, andsharing-service.ts:1269once inindex.js. A never-written negative phrase appears nowhere. The only dead number left indistis the kept#5973warn string.Gates (head
a6d231713)pnpm check:issue-citations(self-test) exits 0.node scripts/check-issue-citations.mjsexits 0: the diff-scoped run judged 9 citations across 11 files, and all 9 resolve.pnpm check:doc-authoringexits 0, with the sibling-package prose ids at their baseline and no growth.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackata6d231713(re-derived after a freshgit fetchat 09:58Z: the same 65, and none of the 8 newmaincommits touch anything it derives from) derived 65 commands. They include all 50 derived at dispatch, plus 15 more. All 65 exit 0.--ranreports 65 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), and all three exited 0 on their rerun.check:dts-closure,check:sourcemap-no-sources-contentandcheck:lean-entry-closurewere rerun too, over 71, 68 and 15 built packages, and exited 0.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 typecheckexits 0. Its maintscprogram reads the 37 non-test files, and itscheck:test-typecheckprogram (tsconfig.test.json) reads all 74 files undersrc/, the 37 test files included (--listFiles).eslint --no-inline-config --format jsonover the 23 touched.tsfiles gives 23 files, 0 errors and 0 warnings. All 23 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 24 changed files for control bytes finds none.Acceptance notes
packages/spec/srchave no carrier: #20234 sweeps only the spec tree, and PR #20554 makes 26 more visible (pre-#N/Pre-#N) in cli, drivers, metadata, objectql, plugins, runtime and types #20556, and this stage changes no instrument.#11671headers, whose producer ispackages/cli/src/utils/i18n-extract.ts.#5973warn string (form D, held by thedoc-authoring-prose-idbaseline), the 14 test titles and the verbatim ruling quotation.packages/**/src:#6206at 53 sites and#11674at 46 (the ordering-constraint note has two sibling copies outside this package, insys-approval-request.object.tsandsys-audit-log.object.ts).8e13ca876/d7e0b4212/aa4b90d9aand1cba33f16are the anchors used here.origin/main(1322cc72c, read at 09:58Z). None of them touchesplugin-sharingor this changeset, and none re-anchors any of these 13 numbers, so there was no merge.Generated by Claude Code