Skip to content

docs(plugin-audit): re-anchor the dead tracker citations to the commits that decided them - #20737

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20596-plugin-audit-citations
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20596-plugin-audit-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20596
Clause-②: no

What changed

This is the ninth stage of the domain:services lane of the dead-citation sweep. It covers packages/plugins/plugin-audit/src/** and nothing else. By the seat's census at the claim (5900808881), it is the largest package in the lane that no in-flight work holds. 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 #19123), by the method of stages 1 to 8 (PR #20609 as 422db788a, PR #20626 as b80ab579d, PR #20634 as 4d04b6be3, PR #20658 as 9a4b2bb38, PR #20693 as 0e9ad74fb, PR #20708 as 9b384f63a, PR #20717 as cbaf04c1f, PR #20729 as d2820876f). That is 56 sites on 55 lines in 16 files, covering 16 numbers:

  • 23 census sites (every census site this package has);
  • 32 sites in test comments, which the census defers;
  • 1 site the gate's grammar cannot see: the slash-joined second number in #9719/#9798 (comment-access-hooks.ts:35).

Each rewritten line now cites the commit in origin/main history that decided what the line describes, and says in its own words what was decided: 15 distinct shas. No number in this package has an ADR or ruling record of its own in the repository (a grep of docs/adr/ for all 16 finds none; the rest of docs/ cites #11507 and #11374 only as evidence, in an audit table and a QA checklist), so every anchor is a commit, per ruling C's order. No number was dropped.

Only comments changed. Every touched source file keeps its line count (56 lines out, 56 in, over 16 files), so no line citation into these files moves. 1 of those 56 lines holds no dead citation: it is a reflow line, 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: #10101 (3 lines), #8287 (3), #5928 (2), #9974 (2), #4630 (2), and #8144, #9719, #12069 and #19054 once each. Each resolves. Over the whole diff, added minus removed is 0 for every number, and no number is new to the diff. No PR number is the citation on an added line: the two PR #N spellings in scope became their pull request's squash commit.

23 dead sites are left on purpose, all of them string literals (see the list below).

One more file: a patch changeset for @objectstack/plugin-audit, because some of the rewritten docblocks and inline comments ship (see Changeset below).

Census: plugin-audit, 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-audit/. 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-audit sites lines files numbers
before base d2820876f, run 2026-09-29T23:11:55Z to 23:15:11Z enumerated, 186 pages, frontier #20735 (newest #20735 before and after), 18,562 numbers 1,110 23 22 6 12
after head a9a4ea478, run 23:26:11Z to 23:29:20Z enumerated, 186 pages, frontier #20735 (newest #20735 before and after), 18,562 numbers 1,087 0 0 0 0

The before count matches the seat's census at the claim and A1 (23 sites). The whole-repo drop is 23, exactly this diff's census sites. The resolves tally is 32,995 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 a9a4ea478; the head d6e67afa5 adds only the changeset. No run was truncated or discarded: both enumerations read 186 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 namesThisRepository over every .ts file under plugin-audit/src (45 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it allocated-but-absent, and alive when that census judged it on this board anywhere (its --list extraction, 37,084 citations over 2,613 files) and did not report it. The 10 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 7 answer 200 (#602, #1532, #4186, #7291, #7333, #16312, #20494), and #8852, #12143 and #12147 answer 404, on the pulls endpoint too.

reading citations dead src comment test comment src string test string
before, d2820876f 655 77 23 32 5 17
after, a9a4ea478 600 22 0 0 5 17

Its src-comment column equals the census's 23, which is the control on the second instrument. The 572 live citations and the 6 cross-repo citations are the same in both readings, and the drop of 55 citations is exactly the rewritten sites the gate's grammar sees. A third, raw reading (every # followed by 2 to 6 digits, whatever surrounds it) finds 672 occurrences and 79 dead before, 616 and 23 after. Beyond the gate's grammar it sees 2 dead sites before (the #9719/#9798 comment, rewritten, and the [#8203/#11507] test title, left) and 1 after (that title). Its only unjudged tokens are objectui#10520, cloud#340, cloud#1395 and the decision-batch ordinal #153.

Per-number table

Sites and files count every dead occurrence 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, and git blame at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (merge-base --is-ancestor exit 0 for all 56 line and anchor pairs).

number sites / files rewritten / left anchor: what it decided
#11507 26/8 10/16 88b9d749a: sys_activity.type is declared an open, author-extensible vocabulary whose options are the built-in set, per the maintainer ruling of 2026-08-24, direction 4. Its body names #11507 twice. The spec stages' anchor
#8707 12/2 9/3 1408fe385: an audit row is stamped from the record's own organization, not the actor's, applying the maintainer's ruling on #8287; the precedence flips to recordOrgId ?? sess.tenantId, and the organization column is resolved from the schema (resolveRecordOrganizationField, first written in this file). Its subject names it. Stage 7's anchor
#9798 8/2 7/1 c7655d472 (PR #9993): the sys_comment access-hook registration declares the whole-operation dispatch #9719 built, so the #4630 unscoped multi-delete refusal reaches the handler through the wired engine; the update half is split out. Its body ends with the closing line for #9798. The lint stage's anchor
#16829 7/3 6/1 8d4690b8f: the read-audit ledger write declares preserveAudit, so a record-view row keeps the VIEW instant; isSystem is kept for the readonly strip, and the new integration pin runs the real stamp hook. Its body ends with the closing line for #16829. New to the sweep
#6575 4/2 4/0 69787f07b: the hook registration surface gains excludeObjects ("global except these objects"), refusing '*' and blank members on it. The squash commit of the pull request that was #6575 (404 on the pulls endpoint too); #5928, the card it answers, stays beside it. New to the sweep
#11374 4/3 3/1 f64668d3c, the squash commit of #12143, for the two object comments: sourced bounds on the keyed text columns sys_activity.record_id and sys_audit_log.record_id (255, the physical id column), route A. 3954fb7df, for the test's statement of the rule: the route A ruling that keyed identity columns declare a sourced maxLength; its subject names #11374 route A. Both are stage 4's anchors for the sibling lines in plugin-security
#10091 3/3 3/0 da891e0ef (PR #10169): sys_attachment's beforeUpdate gate, uploader or parent editor, with the attach rule on the NEW parent when a row is re-pointed. Its body names #10091. Stage 6's anchor
#14927 3/2 3/0 ab489388b (PR #17450): a lost audit row is reported once per cause, keyed on the error code, and the datasource remedy prints only for the missing-table cause; its message records that the measured ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED refusal had sent its operator to a working datasource. It names #14927 in its diff only (the 3 lines it wrote). New to the sweep
#8778 3/1 2/1 7901b2dd2 (PR #8905): the stamp-only tenancy.organizationField, option A per the maintainer ruling on #8778. Its subject names it. The anchor of stages 4, 6 and 7
#6523 2/2 2/0 aa4b90d9a (PR #7068): enforcement contracts take the full ExecutionContext. Its subject names #6523
#6206 2/2 2/0 aa4b90d9a: the same commit, whose body applies "the #6206 ruling default (converge on the full envelope, keep no per-site subset contracts)", written as the full-envelope ruling, the form of stages 2, 6 and 7
#8852 1/1 1/0 51bb277ef: the sys_activity.type writer census; its message records the objectui mirror as unguarded in both directions, filed as #8852, and not asserted here because this package cannot import objectui. The commit that wrote the line. New to the sweep
#11674 1/1 1/0 1cba33f16 (PR #11961): the load-time warning and the ordering constraint documented at the four pointer-pair sites. Its subject names it; blame puts the line in it. Stage 7's anchor
#12147 1/1 1/0 945e91a13: the class-level keyed-text-bounds gate over every *.object.ts, retiring the per-package rule this file carried. It names #12147 in its diff only. Stage 4's anchor for the sibling file
#12143 1/1 1/0 f64668d3c: the squash commit of the pull request that was #12143 (404 on both endpoints), where the dependency-graph measurement was made. Stage 4's anchor
#11671 1/1 1/0 09b4f4e4e (PR #12557): records which source revision a generated translation leaf was filled from. The anchor the identical translations/index.ts line already carries in five packages on main

Every cited sha matches exactly one commit (git rev-parse --disambiguate, count 1 for each of the 15), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 15; control leg: stage 1's landing 422db788a exit 0; the history is complete, --is-shallow-repository false, 15,143 commits). Each of the 16 numbers answers 404 on the issues endpoint.

Wordings to check

The 23 sites left

  • Source strings, 5 sites, all #11507: the sys_activity.type field's description (objects/sys-activity.object.ts:121) and its four generated copies (translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts:125). They are runtime strings, all five are held by the shrink-only doc-authoring-prose-id baseline, and the generated files are left as A5 says. They ship in dist (see Changeset).
  • Test strings, 18 sites, left as stages 1 to 8 left theirs:
    • describe / it titles: activity-type-vocabulary-enforcement.test.ts:315 (the gate-invisible [#8203/#11507]), sys-activity-type-open-vocabulary.test.ts:70 (#11507), audit-writers.test.ts:1420, :1659 (two sites, #8707 and #8778) and :1873 (#8707), comment-access-hooks.test.ts:690 (#9798), plugin-keyed-text-bounds.test.ts:90 (#11374), read-audit-view-instant-preservation.integration.test.ts:121 (#16829);
    • assertion and hint messages, all #11507: activity-type-vocabulary-enforcement.test.ts:249, :352, :355, :378, :380, and sys-activity-type-open-vocabulary.test.ts:83, :90, :110, :152.
  • No quoted maintainer ruling in this package carries a dead number. The package's generated *.source-hashes.generated.ts headers carry none either (PR docs(cli): re-anchor the dead tracker citations in packages/cli/src to the commits that decided them, and the source-hashes header at its producer #20656 fixed their producer).

Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base d2820876f against head. Template literals are therefore read in context. It ran over all 16 touched .ts files.

  • Real run: 19,445 base leaf tokens, 0 files with a token change (exit 0).
  • Comment control in audit-writers.ts (「the cause commit ab48938 records」 to 「… recorded」): 0 files changed, as expected (exit 0).
  • Positive control, a code token added in audit-writers.ts (createRecordOrganizationResolver(engine) given as any): DIFFER (exit 1).
  • Positive control, one digit changed inside a kept test title (audit-writers.test.ts:1873, #8707 to #8708): 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 (2dbd2059e8f5, 8ec28790da22), with git diff HEAD empty and a clean tree afterwards.

Changeset

This change ships bytes, so a patch changeset for @objectstack/plugin-audit (.changeset/20596-plugin-audit-provenance-anchors.md) is included. Its body is stage 8's, word for word, with the package name changed.

Measured on the built package (A3): files[] is dist, README.md and CHANGELOG.md. After the build, part of the rewritten prose reaches dist: c7655d472 twice in each of dist/index.js and index.mjs and once in each of index.d.ts and index.d.mts (the CommentAccessEngine option docblock is on an exported interface); 88b9d749a and f64668d3c twice, and 1cba33f16 and 8d4690b8f once, in each JS file (the object-definition comments and a read-audit.ts comment). The comments in audit-writers.ts and translations/index.ts do not reach dist (0 for each of their anchors). Positive controls: the unchanged line 「below carries into the contract; this comment carries the reasoning.」, in the same docblock as the shipped rewrite at sys-activity.object.ts:60, is found once in each JS file, and the unchanged line beside the shipped rewrite at comment-access-hooks.ts:77 once in each declaration file. A never-written negative phrase appears nowhere in dist. Of the 16 dead numbers, only #11507 is left in dist, 5 times in each JS file: the kept description string and its four generated copies.

Gates (head d6e67afa5)

  • Citation judging, as CI runs it: pnpm check:issue-citations (self-test, 114 cases, 8 batteries) exits 0. node scripts/check-issue-citations.mjs exits 0: the diff-scoped run judged 11 citations across 6 files, and all 11 resolve (they are the live numbers that already stood on the rewritten lines).
  • Doc authoring: pnpm check:doc-authoring exits 0; the sibling-package prose-id baseline holds (808 pinned sites, no growth), which includes the five kept #11507 strings.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at d6e67afa5 derived 64 commands: all 57 derived at dispatch, plus check:dispatcher-error-vocabulary, check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher. Each ran with its exit code captured before any pipe, and all 64 exit 0. --ran, fed each command with its exit code, reports 64 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.
    • The derivation warns that its tree is 3 commits behind origin/main and that one input, scripts/engine-double-contract.pinned.json, changed there: main added one pinned row for packages/objectql/src/protocol-packaged-dashboard-base.test.ts, a file outside this diff. The family is in the 64 either way and exits 0 on this tree.
  • Roster families the derivation lists outside its commands (their rosters sit in directories this diff touches): node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, 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-audit test: 26 files pass and 366 tests pass. vitest list --filesOnly names 26 files, all the tracked test files, the 10 touched ones included.
    • pnpm --filter @objectstack/plugin-audit typecheck exits 0. tsc --listFiles: tsconfig.json holds the 6 touched source files (19 src files; it excludes tests), and tsconfig.test.json, which the script's check:test-typecheck step compiles, holds all 45 files under src/, all 16 touched files included.
  • Lint, as a proven narrowing: eslint --no-inline-config --format json over the 16 touched .ts files gives 16 files, 0 errors and 0 warnings. All 16 are in eslint's own population (isPathIgnored is false for each; a dist file, as the control, is ignored). eslint.config.mjs never enables type-aware linting (no parserOptions.project, as its own lines 327-328 state), 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 17 changed files for control bytes finds none.

Acceptance notes

  • The gate-invisible spellings, grepped as the claim asked. CITATION_RE refuses a hyphen after the digits and a / before the # (check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #20636), and NON_CITATION_HEADS excuses a number after the word 「option」. In this package:
    • #N-word: none.
    • #A/#B: 11 lines, the claim's 11, over 14 distinct numbers. Two second numbers are dead: #9798 in comment-access-hooks.ts:35, rewritten, and #11507 in the test title activity-type-vocabulary-enforcement.test.ts:315, left as a string. The other 12 numbers resolve.
    • option #N: none.
      The raw scan agrees: nothing dead beyond the gate is left outside a kept string.
  • The kept description string is a runtime string with a dead number. sys_activity.type's description ships to the metadata API, the i18n bundles and dist, and ends 「(maintainer ruling 2026-08-24, [Decision] Is sys_activity.type a closed platform vocabulary or an author-extensible one? Both readings are true of the code today #11507)」. It and its four generated copies are held by the doc-authoring-prose-id baseline, so they belong to the runtime-string lane (form D), not to this stage, as stages 1, 2 and 4 left theirs.
  • 「This card」 phrases are left. 46 lines in 21 files of this package speak of 「this card」, 「that card」 or 「the card」. They carry no number and neither instrument sees them. Two were rewritten here because the rewrite on their own line removed their referent (sys-activity.object.ts:60, sys-activity-type-open-vocabulary.test.ts:14); the rest are unchanged, as in stage 8.
  • Dead #11507 and #11374 outside the census surface. docs/qa/platform-checklist/areas/records-forms.json (4 lines) and docs/audits/gate-census-2026-09.md (1 line) cite them as evidence. docs/ is outside this stage's surface; noted for [finding] dead tracker citations outside packages/spec/src have 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, the carrier of dead citations outside packages/spec/src.
  • The census instrument did not truncate in this stage. Both enumerations read 186 pages at the newest frontier.
  • Anchors the next stages can reuse, each checked here: #16829 → 8d4690b8f; #6575 → 69787f07b; #14927 → ab489388b; #8852 → 51bb277ef; #9798 → c7655d472; #11507 → 88b9d749a.
  • Base. The branch is on main at d2820876f. main has since moved three commits (f05919b82, 99786f930, 1940afdaf). They touch packages/spec, packages/metadata-protocol, one new packages/objectql test file, a design doc, three changesets and scripts/engine-double-contract.pinned.json (one added row for that test file), and no file under plugin-audit, scripts/check-issue-citations.mjs or .changeset/config.json, so no merge was taken; the merge queue rebuilds on the merged generation.

Generated by Claude Code

…ts that decided them

Stage 9 of the domain:services dead-citation sweep (ruling C+D, form C).
Every comment or docblock site under packages/plugins/plugin-audit/src 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. Comments only: each touched file keeps
its line count, and no code token moves.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
The rewritten docblocks and inline comments reach the published dist
entry files, so the package ships changed bytes.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/kernel/events.mdx (via registerHook (symbol, a method of interface CommentAccessEngine))
  • content/docs/permissions/system-context.mdx (via installCommentAccessHooks (symbol, a top-level function), installReadAuditWriter (symbol, a top-level function))
  • content/docs/plugins/development.mdx (via registerHook (symbol, a method of interface CommentAccessEngine))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via registerHook (symbol, a method of interface CommentAccessEngine))
  • content/docs/releases/v17/17-5.mdx (via registerHook (symbol, a method of interface CommentAccessEngine))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-audit/src/translations/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 9 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f927864ea056f79d04ad8d62f1a7c13afed31d07 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7c67cd23ee2a7a47fb78447250eb55cebed64984 — the merge of head d6e67afa539247334a8c1867e0128efd39acd6c8 into base f927864ea056f79d04ad8d62f1a7c13afed31d07, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7c67cd23ee2a7a47fb78447250eb55cebed64984 && git checkout 7c67cd23ee2a7a47fb78447250eb55cebed64984
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f927864ea056f79d04ad8d62f1a7c13afed31d07 d6e67afa539247334a8c1867e0128efd39acd6c8 && git checkout -B drift-repro f927864ea056f79d04ad8d62f1a7c13afed31d07 && git merge --no-ff d6e67afa539247334a8c1867e0128efd39acd6c8

node scripts/docs-audit/affected-docs.mjs --json f927864ea056f79d04ad8d62f1a7c13afed31d07

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f927864ea056f79d04ad8d62f1a7c13afed31d07 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

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

① Derived judgments

Read against main at the merge-base d2820876f (stage 8's landing). main now stands four commits past it (f05919b82, 99786f930, 1940afdaf, f927864ea — one more than the PR body counted); their 17 files touch nothing under plugin-audit, nor scripts/check-issue-citations.mjs, .changeset/config.json or scripts/doc-authoring-prose-id.baseline.json, so the net diff against main is the merge-base diff: 17 files, +66/−56 — 16 source files under packages/plugins/plugin-audit/src/** (6 modules, 10 test files) and one changeset. The head d6e67afa5 adds only the 10-line changeset over a9a4ea478, which holds every source line. The record's own board, tree and check-run reads were all taken in the minutes before 2026-09-30T00:13Z; nothing was built, run or re-run locally.

  • Accept-set: no change — right. No Zod schema, REST handler, query-parameter set, refusal text, log text or runtime string moves. 56 source lines out, 56 in; every one of the 112 changed source lines opens with a comment marker after whitespace (*, //, /**), 0 fall outside one. Each of the 16 touched source files has additions equal to deletions, so no line citation into these files moves. The dev's parser leaf-token guard (0 files with a token change; both positive controls DIFFER) says the same and is not repeated here.
  • Public surface: no change — right. No export added, removed or renamed; no packages/spec file touched, so no generated artifact is owed. The one rewritten docblock on an exported declaration — the registerHook options of CommentAccessEngine (comment-access-hooks.ts:77) — changes the documentation text carried on dist/index.d.ts, not the type; the Docs Drift Check lists that symbol for exactly this reason, advisory only.
  • Published bytes: changed — right, and it decides ②. @objectstack/plugin-audit (17.5.0, not private, publishConfig.access public, in the changesets fixed group, files = dist, README.md, CHANGELOG.md, types = dist/index.d.ts, build = tsup then check-dts-emitted) emits declarations, and the CommentAccessEngine option docblock reaches them. The dev's A3 build reading, taken after the restart, measured c7655d472 in all four dist entry files and four more anchors in the two JS files, with a positive and a negative control; this record does not repeat the build.
  • The 16 numbers are dead — right. Each of #11507 #8707 #9798 #16829 #6575 #11374 #10091 #14927 #8778 #6523 #6206 #8852 #11674 #12147 #12143 #11671 answers not-found on the issues endpoint, read one by one for this record. No ADR names any of them (docs/adr/ grep at the head: none), so ruling C's first rung is empty and a commit is the right anchor for every one.
  • The 15 anchors — each right. Each abbreviated sha resolves to exactly one commit (rev-parse --disambiguate, count 1 for all 15) and is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 15). For each, the commit's message or diff names the number it replaces, and the decision the rewritten line states is the commit's: #11507 → 88b9d749a (subject: sys_activity.type declared an open, author-extensible vocabulary; body names #11507 twice; dated the ruling's day). #8707 → 1408fe385 (subject names #8707, body names #8287: the audit row stamped from the record's own organization). #9798 → c7655d472 (the #4630 unscoped multi-delete refusal restored through the wired engine; body names #9798 and #9719). #16829 → 8d4690b8f (preserveAudit on the read-audit ledger write; body names #16829). #6575 → 69787f07b (subject carries (#5928) (#6575): excludeObjects on the hook registration face — the squash commit of the pull request that was #6575). #11374 → f64668d3c for the two object comments (subject (#12143): sourced bounds on the keyed text columns of plugin-audit and plugin-security) and 3954fb7df for the test's statement of the rule (subject names #11374 route A) — one anchor per arm, stage 5's precedent. #10091 → da891e0ef (sys_attachment beforeUpdate gated uploader-or-parent-editor; body names #10091 twice). #14927 → ab489388b (a lost audit row reported once per cause; #14927 in its diff only, three lines, as the body says). #8778 → 7901b2dd2 (subject names #8778: stamp-only tenancy.organizationField). #6523 and #6206 → aa4b90d9a (subject names #6523; body: "Apply the 同族第三处组装:share-link 路由把授权信封裁成 4 个字段后直接当 enforcement context 喂给 engine.find —— group 租户姿态下 Layer 0 墙恒判否 #6206 ruling default (converge on the full envelope, keep no per-site subset contracts)" — the lines name that ruling in words beside the sha, the form of stages 2, 6 and 7). #8852 → 51bb277ef (the writer-census pin; message names #8852). #11674 → 1cba33f16 (subject names #11674: the ordering constraint at the four pointer-pair sites). #12147 → 945e91a13 (the class-level keyed-text-bounds gate; #12147 in its diff only). #12143 → f64668d3c (the squash commit of the pull request that was #12143). #11671 → 09b4f4e4e (the source-revision record for generated translation leaves; #11671 in its diff sixteen times — the anchor the identical translations/index.ts line carries from stages 1, 2, 4, 6 and 7). Ten of the 56 rewritten lines were blamed at the base for this record: six sit in their anchor commit itself and four in a descendant of it (comment-access-hooks.ts:35 and :77 → 4639cec4d, translations/index.ts:28 → 30928a615, audit-writers.ts:193 → 0f8d16a05), consistent with the dev's reading over all 56.
  • Citation accounting — right. Over the diff: the 56 removed lines carry the 56 dead sites on 55 lines, and the one removed line carrying none is the reflow line sys-activity.object.ts:59 the body lists. The per-number removed counts equal the body's table (#11507 10, #8707 9, #9798 7, #16829 6, #6575 4, #14927 3, #10091 3, #11374 3, #6523 2, #6206 2, #8778 2, and #11674, #11671, #8852, #12147, #12143 once each). No added line carries a dead number, no number is new to the diff, and every number's added-minus-removed is 0 or negative. The nine numbers on added lines (#10101 ×3, #8287 ×3, #5928 ×2, #9974 ×2, #4630 ×2, #8144, #9719, #12069, #19054) each stood on the line they replace, and each resolves on the issues endpoint. No PR #N stands on an added line; 15 distinct shas stand on added lines and none on a removed one.
  • The gate-invisible spellings — right. The slash-joined second number of #9719/#9798 (comment-access-hooks.ts:35) is rewritten; the [#8203/#11507] describe title is left as a string; the head grep below finds no other #A/#B second number, #N-word or option #N site carrying a dead number in this package.
  • The 23 sites left — right, and the list is exact. A grep of the 16 numbers over plugin-audit/src at the head returns 22 lines carrying 23 occurrences (audit-writers.test.ts:1659 carries #8707 and #8778): the sys_activity.type description at sys-activity.object.ts:121 and its four generated copies (translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts:125), every one held by scripts/doc-authoring-prose-id.baseline.json as #11507: 1, so check:doc-authoring sees no growth; 8 describe/it titles; 9 assertion and hint message strings. Titles and messages are string tokens, left as stages 1 to 8 left theirs; the five runtime strings are form D. The verbatim ruling 「四维分析一致的,接手你的建议。」 at sys-activity-type-open-vocabulary.test.ts:15 and its quoted block are untouched; the attribution line above it (:14) was rewritten so that line 15's "Recorded on the card as:" keeps its referent.
  • The wordings — each right. sys-activity.object.ts:59-60: "that card" would have lost its referent, and "executed by commit 88b9d74 (direction 4 of the four weighed)" separates the ruling from the commit that carried it; line 59 changes only its last word and carries no number. "re-open [Decision] Is sys_activity.type a closed platform vocabulary or an author-extensible one? Both readings are true of the code today #11507" → "re-open the ruling (commit 88b9d74)" at :92 and activity-type-vocabulary-enforcement.test.ts:332: a card that answers not-found cannot be re-opened, and the instruction is about the decision. The #8707 phrases (audit-writers.test.ts:1882, :1922, audit-writers.ts:1402) name 1408fe385 as what set the order and keep #8287 as the ruling, which is what that commit's subject says. The #14927 phrases name what ab489388b records. The #6206 phrases become "the full-envelope ruling" beside aa4b90d9a. The two PR #N spellings (audit-writers.ts:193, audit-hook-object-scope.test.ts:19) and "measured on PR fix(plugin-audit,plugin-security): declare sourced bounds on the four keyed text columns that break MySQL schema-sync #12143" (plugin-keyed-text-bounds.test.ts:21) become their squash commits. The section rule at audit-writers.test.ts:1648 is shortened so the line keeps its width. read-audit.test.ts:43 "how plugin-audit's read-audit rows back-date created_at to the VIEW instant through an isSystem reliance the audit hook never honoured, so every batched sys_audit_log read row now gets the FLUSH instant #16829 shipped" → "how the defect fixed by commit 8d4690b shipped" is the one place a number stood for a defect rather than a decision, and the rewrite says so.
  • Form — consistent with the landed stages 1 to 8 (422db788a, b80ab579d, 4d04b6be3, 9a4b2bb38, 0e9ad74fb, 9b384f63a, cbaf04c1f, d2820876f): the word commit plus the abbreviated sha in the position where the number stood, the decision carried in the sentence. Eleven of the fifteen anchors reuse an anchor an earlier stage gave the same number.
  • Check-runs on the head, the gate verdicts, read 2026-09-30T00:09Z: 34 check-runs, all completed — 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in): paths-filtered or opt-in, not verdicts against), 0 failure, none in progress. Every one of the seven required contexts is success: Lint & Repo Gates (which carries check:issue-citations, check:doc-authoring and the repo-wide pnpm lint, the gates this diff answers to), TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Check Changeset, Check PR Size, Part-of PR must not also close its card, The card this PR closes must claim this branch, No other open PR may claim the same issue and No other open PR may claim the same single-writer path are success too. An earlier read during this review had Lint & Repo Gates, four Test Core shards and two Dogfood Regression Gate shards in_progress and the TypeScript Type Check aggregate queued; nothing was awaited, the read was repeated at the end and every one of them had completed success.

② Semver level

  • .changeset/20596-plugin-audit-provenance-anchors.md declares '@objectstack/plugin-audit': patch — matches what the diff publishes. The package is released and its declaration files carry the rewritten CommentAccessEngine docblock (the dev's A3 adds four more anchors in the JS entries), so bytes ship; skip-changeset would be wrong (it is for a diff that publishes nothing from any released package), and the PR carries no such label. Not minor: no accept set widens and no surface is added. The body is truthful (comments only; no type, schema, export, log or refusal text, or runtime behaviour change), carries no tracker number and no model identifier, is stage 8's landed body with the package name swapped, and the filename carries the card number. plugin-audit sits in the fixed group beside the eight packages whose stages declared the same level.
  • Clause-②: no — right. It is line 2 of the PR body under Part of #20596, and the claim (5900808881) declares the same. The diff widens no accept set, so no arm is owed and no minor is owed. Nothing breaks, so no ADR-0087 marker is owed; Check Changeset on the head is success.
  • Not a governed-surface diff (no path under docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md); 122 changed lines, under the 5,000-line human-merge threshold; head repo equals base repo; Governed Surface Queue Guard on the head is success. A draft with Part of on line 1 and no closing keyword anywhere in the body, so the card stays open for the remaining stages.

③ Boundary flags

The dev report (5901339076) has open_questions: []. Its eight deviations and three out-of-scope findings, each answered:

  1. The container restart mid-run (about 23:35Z, during the first package test run, exit 137) — answered; no gate verdict this PR relies on rests on a reading the restart could have voided. What a restart can void is process state and an uncommitted tree: a run in flight, a held lock, a dirty worktree. The readings the dev took before it and kept — the census before and after (trees d2820876f and a9a4ea478, both committed and on the remote; a9a4ea478's source is byte-identical to the head, since git diff a9a4ea478 d6e67afa5 is the 10-line changeset alone), the supplementary and raw instruments over the same two trees, the leaf-token guard with its three controls, and eslint over the 16 files — are each a function of a committed tree plus a board reading whose frontier control held (186 pages, frontier equal to the newest number before and after), and none depends on a process the restart killed. The controls mutated and restored two files; the dev proved each restore byte-identical to the HEAD blob, and after the restart re-verified the worktree clean at d6e67afa5 equal to the remote head. The remote head is what this record's diff was read from and what CI built, so a dirty worktree, had one been left, could have reached neither. Everything the restart interrupted or followed was re-run after it: the killed test run (26 files, 366 tests), typecheck, the full build, the 64 derived gates, the 4 roster families, the --ran reconciliation and A3 — so the changeset decision in ② rests on a post-restart reading. And the gate verdicts of record are the head's check-runs in ①, run in CI on the pushed head and independent of the dev's container. The dev also read the pulls endpoint before opening a PR, so the restart left no duplicate. Nothing to escalate.
  2. 32 test-comment sites beyond the census's 23, plus the one slash-joined site the gate cannot see — answered, in scope. The claim's surface is comment and docblock prose under plugin-audit/src/**; test comments are that, stages 1 to 8 rewrote theirs, and the claim itself ordered the #A/#B grep. The head grep above confirms the residue is strings only.
  3. One reflow line, two lost referents fixed on lines that already carried a dead number, and one shortened section rule — answered, right (① above). Every file keeps its line count; the verbatim maintainer ruling under the rewritten attribution line is untouched.
  4. The supplementary and raw instruments judged against the before census's own board reading plus single-number reads, by stage 6's method — answered, immaterial to the verdict. The census, the gate's own instrument, ran unchanged with its frontier control, its src-comment count is the control on the second instrument (23 = 23), and this record's own head grep confirms the residue key for key.
  5. The harness attribution reminder versus AGENTS.md's trailer pair — answered, right. Both head commits end with the model-free pair, no model identifier appears in either message, and the PR body's footer is the session-URL form the PR-body surface keeps.
  6. No pre-PR merge of main — answered, right. Now four commits rather than three (f927864ea landed after the body was written); verified above that none of their 17 files is in this diff or among the citation gate's inputs, so the queue's rebuild has nothing to reconcile by hand.
  7. Labels — answered. documentation, size/m, tests, tooling are the labeler's; no skip-changeset, which is right.
  8. Worktree removal after the report — answered, immaterial to the head. The branch stays on the remote, and the head this record names is the one fetched for it.
  9. Out-of-scope 1, dead #11507 and #11374 in docs/qa/platform-checklist/areas/records-forms.json (4 lines) and docs/audits/gate-census-2026-09.md (1 line) — verified on main (4 + 1, as the body says); escalated to its carrier, not to this PR. docs/ is outside this stage's surface and outside the citation gate's declared surfaces. One reading for the seat: its ACCEPT (5901366770) says the pointer went to [finding] dead tracker citations outside packages/spec/src have 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 stage 4's record read [finding] dead tracker citations outside packages/spec/src have 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 as already closed — a pointer on a closed card is one nobody is dispatched to read, so the seat names a live carrier for the docs/ residue when it next writes (the lane split on [finding] dead tracker citations outside packages/spec/src have 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 said which lane holds docs/; if none does, a finding of its own). Not blocking here.
  10. Out-of-scope 2, the sys_activity.type description string and its four generated copies ship in dist and the metadata API with #11507 — answered, carrier stands. A runtime string is form D, not this card's comment-only form C; all five are held by the shrink-only doc-authoring-prose-id baseline (verified at :528-541), so check:doc-authoring sees no growth, and stages 1, 2 and 4 left theirs the same way. Carrier: the runtime-string lane, whose entry is that baseline's next shrink; the generated copies follow their producer, never a hand edit.
  11. Out-of-scope 3, "this card" / "that card" / "the card" on 46 comment lines in 21 files — answered, wording only. The two whose referent this diff removed were fixed on their own lines; the rest carry no number, are seen by neither instrument, and are left as stage 8 left its 113. No runtime effect.

Nothing is escalated against this PR.

Implemented-by: claude/issue-20596-plugin-audit-citations
Reviewed-by: session_01XY5uCwTjZj7884yYtyur4H

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 00:15
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 4dfff17 Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20596-plugin-audit-citations branch September 30, 2026 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants