docs(plugin-auth): re-anchor the dead tracker citations to the commits that decided them - #20634
Conversation
…s that decided them Comment and docblock prose under packages/plugins/plugin-auth/src only. Each site that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided. 95 sites on 95 lines in 31 files, 16 numbers, 15 anchor commits; 12 further lines are reflow or a lost referent. No code token moves; every file keeps its line count. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…h ship in dist Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 15 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 c4a47225ecdc86307e57be0db6d528e010b2c3fb && git checkout c4a47225ecdc86307e57be0db6d528e010b2c3fb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3f45b6cc13fb4646fab723a517225aa911cf17b8 5ae64e8b84d7913079dd4f172d05d54f279de4dc && git checkout -B drift-repro 3f45b6cc13fb4646fab723a517225aa911cf17b8 && git merge --no-ff 5ae64e8b84d7913079dd4f172d05d54f279de4dc
node scripts/docs-audit/affected-docs.mjs --json 3f45b6cc13fb4646fab723a517225aa911cf17b8
|
…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 third stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/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 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 the method of stages 1 and 2 (PR #20609 as
422db788a, PR #20626 asb80ab579d). That is 95 sites on 95 lines in 31 files, covering 16 numbers:#13398-class, which the gate's extractor does not match at all (see Acceptance notes).Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and it says in its own words what that commit decided. No ADR or ruling-record file records the decision behind any of the 16 numbers, so every anchor is a commit: 15 distinct shas (#11477and#12029share one, because#12029was the pull request that settled#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
extractCitationsover 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
patchchangeset for@objectstack/plugin-auth, because the rewritten docblocks ship (see Changeset below).Census:
plugin-auth, 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-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.allocated-but-absentb80ab579d, run 2026-09-29T10:43:31Z to 10:47:03Z5ae64e8b8, run 11:12:05Z to 11:15:37ZThe before count matches the 52 that census
5884031174read atf11b5f20. The whole-repo drop is 52, exactly this diff's census sites. Theresolvestally is 32,832 in both runs, andresolves-as-pull-request(1,984) andcross-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) andclassifyCitationover every.tsfile underplugin-auth/src(178 files). It uses one board, enumerated by the gate's ownenumerateBoardat 10:50:47Z (185 pages, frontier #20629, equal to the newest).b80ab579d9fd0ebf10Its 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
#13398-classsites; a plain grep for the 16 numbers overplugin-auth/srcat the head finds only the 21 test titles (and the digits11477inside 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 / leftcounts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject.#8676d6e80b28b:sys_account.passwordandprevious_password_hashesare flaggedinternal: true, and every reader is recovered through the engine's privileged accessor (the adapter readback table gainspassword; plugin-auth's own raw-engine reads getrecoverInternalFieldsForSystemRead). Its subject names#8676#8734f8eb73601: the last-admin guard's standing-key lists are bound to whatresolveAuthzContextactually reads (STANDING_KEYS_BY_TABLE/STANDING_KEY_EXCLUSIONSand the correspondence gate). Its subject names#8734#10165801296050: lifecyclettlgains anonlyWhenrow filter (maintainer ruling option A on#10165, quoted in its message). The same anchor the spec stages gave this number#10366bbe643c08: the localhost trusted-origin substitution is gated to non-production. Its diff writes both rewritten lines and its changeset names#10366#11343c0714eb5d: walled platform-admin elevation requires a VERIFIED owner-email match (a fail-closed allow-list overemail_verified), the bootstrap replays on the verifyingsys_userupdate, and the dev-admin seed stamps its account verified. Its message names#11343as the card it completes#114776dd3e6968:/admin/remove-usergets the raw-mount shading/admin/ban-userhas, so authorization runs before the break-glass guard (ruled option A on#11477, as its message records)#11626a6eca9223:check:engine-double-contractadmits a single-verb engine double on the contract it DECLARES, a second admission route beside sibling inference. Its diff names that route#11626#11640bf8d129b5: 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#11640#11741b706af987:SendEmailInputgains an optionalorganizationId, 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#117574d25d22d4: the rc.1-erasys_scim_providerplatform object is retired. Every#11757site in the tree before it says the object "retires under #11757"#120296dd3e6968:#12029was the pull request itself; this is its squash commit, the gate-then-delegate mount on/admin/remove-user#13398e238c79f0: 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)#1476235e94c96b: auth OTP SMS and auth mail read the recipient's ownsys_user.locale, one rung above the request and the deployment default, in the order ruled for#14788. Its diff carries#1476224 times#1490261821e54c: a plain unique index over duplicate rows is loud and non-fatal (the boot continues), andos migrate planstops calling itsafe. Its message names#14902as the card it ends#14998f1e91595f: the batch-6 admin endpoint graphs load at module top, not inside each clocked case, which removed the cold-import timeout flake#150929e9f03abe:settleSelfRegistrationGrant's trailing filter no longer silently DROPS a malformed permission-set row; it refuses. The only commit in history that names#15092Plus 5
#13398-classsites the gate does not extract, anchored like the other#13398sites: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-repositoryfalse, 15,083 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 example4d5b4f832(the operator-provisioned stamp) and4f65837a7(the L3 re-anchor) citec0714eb5d's verified-owner rule,f074616e6(invitation locale) cites35e94c96b's stored rung,8064e6da1(the has-permission mount) cites6dd3e6968's seam, and9bd4344e4carries theaccount-identity-preflighttext that cites61821e54c.Wordings to check
#13398→e238c79f0, and not stage 2's953a81f4a. Stage 2 anchored its one#13398site at953a81f4a(2026-09-02) as the earliest application of the published-sink ruling. In this package,e238c79f0(2026-08-31) already records it: its pin indurability-swallow-repair.test.tssays 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 "[Decision] plugin-sharing's refused-backfill report lands atwarnwhere AGENTS.md puts it aterror— and the card that was supposed to carry the level is CLOSED #13398's question", which is why the lines say "the published-sink ruling (commit e238c79)" rather than claiming that commit made the ruling.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).auth-plugin.ts:2738-2739: 「(the fix(auth): authorize before the break-glass guard on /admin/remove-user #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-ledgerhas counted a shadowing mount since26dea1495; the "worked reading" was that PR's application of it to/admin/remove-user, which6dd3e6968mounts.sys-session-ttl-sweep.test.ts:230: 「the naive policy commit 8012960 existed to make avoidable」, where801296050is thettl.onlyWhenfilter the ablation removes.durability-swallow-repair.test.ts:62: the flake report became a pointer to the commit that removed the flake (f1e91595f), with#15603kept beside it.auth-manager.ts:5629: 「the pre-plugin-auth: auth SMS (OTP / invite texts) and request-less auth mail keep the deployment locale —sys_user.localeexists now and is not read #14762 deployment-default behaviour」 became 「the deployment default, as before commit 35e94c9」.The 21 sites left
describe/ittitles, which are string tokens:admin-remove-user-gate-ordering.test.ts:207,:263,:298(#11477),auth-email-locale.test.ts:528andauth-manager.test.ts:2545(#14762),auth-manager.test.ts:1562(#10366),:2866,:2880(#11741),:4105andinternal-field-readback.test.ts:219,:230,:286(#8676),auth-plugin-walled-owner-verification-path.test.ts:87,:193,:317,:384(#11640),durability-swallow-repair.test.ts:159,:567,:670(#13398),last-admin-standing-keys.test.ts:61(#8734) andwalled-owner-operator-stamp.test.ts:355(#11343). Tokens, left as they were, as stages 1 and 2 left theirs.Mechanical guard: no code token moves
The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes excluded, base
b80ab579dagainst head. Template literals are therefore read in context. It ran over all 31 touched.tsfiles.auth-manager.ts(As above — the flagged columntoLikewise — the flagged column): 0 files changed, as expected (exit 0).auth-manager.ts(a fourth element added to thefieldsprojection of the password-reuse read): DIFFER (exit 1).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), withgit diff HEADempty and a clean tree afterwards.Changeset
This change ships bytes, so a
patchchangeset 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[]isdist,README.mdandCHANGELOG.md. After the build, the rewritten comments reachdist:35e94c96bappears 8 times in each ofdist/index.d.ts,index.d.mts,index.jsandindex.mjs;f8eb73601twice in each declaration file;bf8d129b5ande238c79f0once in each of the four;d6e80b28band4d25d22d4twice in each runtime file;c0714eb5dand61821e54conce in each declaration file;b706af987once in each runtime file. Positive control: the unchanged line 「read best-effort off the identity row.」 beside a shipped rewrite is found once inindex.d.tsand once inindex.js. A never-written negative phrase appears nowhere. No dead number of the 16 is left anywhere indist.Gates (head
5ae64e8b8)pnpm check:issue-citations(self-test) exits 0.node scripts/check-issue-citations.mjsexits 0: the diff-scoped run judged 5 citations across 14 files, and all 5 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/objectstackat5ae64e8b8derived 65 commands: all 57 derived at dispatch, pluscheck: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-debtandcheck:where-matcher. It was re-derived after a freshgit fetch(origin/maina918fe7fd, 2 commits ahead, neither touchingplugin-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 fullturbo run buildof./packages/*and./packages/*/*ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.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 typecheckexits 0 (tscmain,tsconfig.examples.json, andcheck:test-typecheckheld at its ledger). The main program reads 63 non-test files; thetsconfig.test.jsonprogram reads all 178 files undersrc/, the 115 test files included, and all 31 touched files are in it (--listFiles).eslint --no-inline-config --format jsonover the 31 touched.tsfiles gives 31 files, 0 errors and 0 warnings. All 31 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 32 changed files for control bytes finds none.Acceptance notes
CITATION_REends in a lookahead that refuses a following hyphen, so#13398-classis not a citation to either the diff gate or the census, dead or alive. This stage rewrote the 5 such sites inplugin-authbecause they are the same dead number in the same comment prose. At the head, 10 dead#N-wordsites remain inpackages/**/src(a raw line scan of.tsfiles against the cached board):service-automation5 (all#13398-class),rest2,plugin-security1,runtime1,spec1. The census cannot count them, so a later stage reaching those packages has to look for them by hand. No instrument change here.#11343inplugin-security(6) andtypes(2), anchorc0714eb5d;#14902indriver-sql(7) andcli(1), anchor61821e54c;#13398inservice-automation(4, plus the 5 hyphen-joined sites), anchore238c79f0;#8734incore(2), anchorf8eb73601;#10165inobjectql(2) andplatform-objects(1), anchor801296050;#11757inplatform-objects(2), anchor4d25d22d4;#11741inplugin-email(2), anchorb706af987;#8676inplatform-objects(1), anchord6e80b28b.origin/main(a918fe7fd, read at 11:20Z). Neither touchesplugin-auth, this changeset or any of these 16 numbers, so there was no merge.Generated by Claude Code