Skip to content

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

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20596
Clause-②: no

What changed

This is the seventh stage of the domain:services lane of the dead-citation sweep. It covers packages/plugins/plugin-approvals/src/** and nothing else. By the seat's census at the claim (5897866351), 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 6 (PR #20609 as 422db788a, PR #20626 as b80ab579d, PR #20634 as 4d04b6be3, PR #20658 as 9a4b2bb38, PR #20693 as 0e9ad74fb, PR #20708 as 9b384f63a). That is 41 sites on 38 lines in 13 files, covering 14 numbers:

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: 13 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 14 finds none, and a grep of the rest of docs/ finds only an audit that names #11311 as evidence), 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 (41 lines out, 41 in, over 13 files), so no line citation into these files moves. 3 of those 41 lines hold no dead citation: 1 reflow line and 2 lost-referent lines, 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: #8613 (approval-service.ts:2295, :2363, approval-service.test.ts:751), #8287 (sys-approval-request.object.ts:138, approval-node.test.ts:462), #10101 (backfill-platform-row-organizations.ts:9) and #12069 (translations/index.ts:26). Each answers 200. 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 #N spelling in scope (backfill-platform-row-organizations.ts:9) became its squash commit.

Five dead sites are left on purpose, all of them test strings (see the list below).

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

Census: plugin-approvals, 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-approvals/. 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-approvals sites lines files numbers
before base 575746371, run 2026-09-29T20:15:05Z to 20:18:28Z enumerated, 186 pages, frontier #20709 (newest #20709 before and after), 18,536 numbers 1,195 24 22 6 10
after head e698d2393, run 20:28:39Z to 20:31:58Z enumerated, 186 pages, frontier #20716 (newest #20714 before, #20716 after), 18,543 numbers 1,171 0 0 0 0

The before count matches the seat's census at the claim and A1 (24 sites). The whole-repo drop is 24, exactly this diff's census sites. The resolves tally is 32,971 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 e698d2393; the head 708244c2b 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-approvals/src (76 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) and did not report it. The 18 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 14 answer 200, and #8863, #11081, #11286 and #11308 answer 404.

reading citations dead src comment test comment src string test string
before, 575746371 981 44 24 15 0 5
after, e698d2393 942 5 0 0 0 5

Its src-comment column equals the census's 24, which is the control on the second instrument. The 902 live citations and the 32 cross-repo citations are the same in both readings, and the drop of 39 citations is exactly the rewritten sites the gate grammar sees. Three extracted tokens are not citations and stay unjudged in both readings: ' (an HTML entity) and two CSS colours, all in action-link-pages.ts string literals. A third, raw reading (every # followed by 2 to 6 digits, whatever surrounds it) finds 46 dead occurrences before and 5 after; the 2 it sees beyond the gate are the two gate-invisible sites above, and its residue equals the gate's residue site for site.

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 each pair).

number sites / files rewritten / left anchor: what it decided
#16709 10/2 8/2 8c7cca1ce: the three residues of the stranded-inspection contract review. Item 2 (the PM ruling of 2026-09-08) keeps a row whose third read threw in the report as the undifferentiated failed; item 3 moves refineFailedRunState inside the try, so a malformed host verdict costs only its own row. Its message numbers the items, which is why the lines keep 「item 2」 and 「item 3」. New to the sweep
#8710 6/2 6/0 04d03c3a0: a deactivated sys_position confers no sharing-rule shares, filtered at the sharing call site and never inside the addressing primitive. Its message quotes the 2026-08-15 ruling verbatim, the same sentence the quoted blocks here carry, and its diff writes the 「a name with no row is untouched」 fallback that approval-service.ts:2318 quotes. Stage 2's anchor, and plugin-sharing/src/position-graph.ts:42 already reads 「#8613 / commit 04d03c3」
#6523 4/3 4/0 aa4b90d9a: the 36 enforcement signatures, IApprovalService among them, converged onto the full ExecutionContext. Its subject names it. Stages 2 and 6 and the spec stage's anchor
#6206 3/3 3/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 stages 2 and 6 used
#8778 4/4 4/0 7901b2dd2: the stamp-only tenancy.organizationField, Option A of the maintainer's ruling, declared on sys_api_key as active_organization_id. The spec, plugin-security and service-storage stages' anchor
#11081 5/1 5/0 c28e4cfae: the two SqlDriver-backed fixtures of #11081 stop muting their kernel and pin the expected read-refusal noise with the runtime's shared capture. Its diff writes all five [#11081] tags. New to the sweep
#11286 5/1 2/3 b019891cd: the contract test that pins the two managerIsProvablyOutsideOrg screens to equal verdicts. Its subject names it. New to the sweep
#11674 2/1 2/0 1cba33f16: the seed loader warns at load time when a seed defers a required column, and the ordering constraint is documented at the four pointer-pair sites, this object among them. Stage 2's anchor for the same paragraph
#12493 2/2 2/0 aa5994e17: the Operation Message Catalog gains approval_recall_not_submitter (and record_write_denied) ahead of their emitters. Its diff names #12493 throughout. Stage 2's anchor
#8707 1/1 1/0 1408fe385: audit rows are stamped from the record's own organization, which its message says the maintainer's ruling on #8287 requires; the line keeps 「honouring #8287's ruling」. New to the sweep
#8863 1/1 1/0 d200b016b: the two negative pins that assert the unfiltered position expansion on the approvals side. Its body names #8863. New to the sweep
#11308 1/1 1/0 5a916c4d4: the one-off platform-row organization backfill, dry run and write, which its body calls the #11308 sweep. New to the sweep
#11311 1/1 1/0 1272f0a6b: the squash commit of the pull request that was #11311 (its subject carries the number), which moved the resolver to metadata-core and made the approval and automation-run writers stamp the subject's organization. New to the sweep
#11671 1/1 1/0 09b4f4e4e: the source-hashes provenance companion. The identical translations/index.ts line in service-messaging, plugin-sharing and plugin-security already cites it

Every cited sha matches exactly one commit (git rev-parse --disambiguate, count 1 for each of the 13), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 13; the history is complete, --is-shallow-repository false, 15,129 commits). Each of the 14 numbers answers 404 on the issues endpoint, read one by one; #11311 answers 404 on the pulls endpoint too.

Wordings to check

The 5 sites left

  • Test strings, 5 sites, left as stages 1 to 6 left theirs:
    • describe / it titles: manager-org-screen-parity.contract.test.ts:232 (#11286), stranded-request-inspection.test.ts:519 and :642 (#16709);
    • a test double's thrown message and an assertion message: manager-org-screen-parity.contract.test.ts:107 and :294 (#11286).
  • There is no operator string, generated header or quoted ruling carrying a dead number in this package. The generated *.source-hashes.generated.ts headers already cite 09b4f4e4e and are untouched. The two verbatim quotations of the 2026-08-15 ruling carry no number and are untouched.

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 575746371 against head. Template literals are therefore read in context. It ran over all 13 touched .ts files.

  • Real run: 36,204 base leaf tokens, 0 files with a token change (exit 0).
  • Comment control in sys-approval-request.object.ts (「who a row is ABOUT」 to 「whom a row is ABOUT」): 0 files changed, as expected (exit 0).
  • Positive control, a code token added in sys-approval-request.object.ts (referenceVia: 'object_name', given a trailing as const): DIFFER (exit 1).
  • Positive control, one digit changed inside a kept test title (stranded-request-inspection.test.ts:642): 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 (6cb56301a334, 757ad45900ac), with git diff HEAD empty and a clean tree afterwards.

Changeset

This change ships bytes, so a patch changeset for @objectstack/plugin-approvals (.changeset/20596-plugin-approvals-provenance-anchors.md) is included. Its body is stage 6'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 (a cache miss for this package, so dist is this head's source), the rewritten comments reach dist: 8c7cca1ce 4 times and 04d03c3a0 4 times in each of dist/index.d.ts and index.d.mts; 04d03c3a0 4 times, 1cba33f16 twice, and 8c7cca1ce, 7901b2dd2 and 1408fe385 once each in each of index.js and index.mjs. Positive controls: the unchanged line 「A step routing to nobody is」, in the same docblock as the shipped rewrite at approval-service.ts:2295, is found once in each of the four files, and the unchanged line 「itself stays unwalled (tenancy.enabled: false)」 beside the shipped rewrite at sys-approval-request.object.ts:144 once in each JS file. A never-written negative phrase appears nowhere in dist. None of the 14 dead numbers is left anywhere in dist.

Gates (head 708244c2b)

  • 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 6 files, and all 5 resolve (#8613 twice, #8287, #10101, #12069), each already on the line it replaces.
  • Doc authoring: pnpm check:doc-authoring exits 0.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 708244c2b 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.
  • 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-approvals test: 51 files pass and 791 tests pass. That is every test file in the package, the 7 touched ones included.
    • pnpm --filter @objectstack/plugin-approvals typecheck exits 0 (tsc on tsconfig.json, the scripts program, and the test layer on tsconfig.test.json, held at its ledger of 8 files, 324 errors and 27 pinned signatures). --listFiles: the tsconfig.json program holds the 25 non-test files under src/, the 6 touched ones included; the tsconfig.test.json program holds all 76 files under src/, the 51 test files and all 13 touched files included.
  • Lint, as a proven narrowing: eslint --no-inline-config --format json over the 13 touched .ts files gives 13 files, 0 errors and 0 warnings. All 13 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 14 changed files for control bytes finds none.

Acceptance notes


Generated by Claude Code

…ommits that decided them

Stage plugin-approvals of the domain:services dead-citation sweep, in ruling
C+D's form C. 41 dead comment and docblock sites on 38 lines in 13 files
under packages/plugins/plugin-approvals/src now cite the commit in this
repository's history that decided what the line describes, and say in their
own words what it decided: the 24 census sites, 15 test-comment sites the
census defers, and 2 sites the citation gate's grammar cannot see (a
slash-joined second number and an "option #N" citation).

14 numbers, 13 commits; no number has an ADR or ruling record in the repo.
Comments only: 41 lines out, 41 in, every file's line count unchanged (1
reflow line, 2 lost-referent lines). No code token changes.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
The rewritten docblocks and inline comments ship in the package's dist
(index.d.ts, index.d.mts, index.js, index.mjs), so the released package
changes bytes and takes a patch changeset.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s 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-approvals, touching 6 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/plugins/plugin-approvals/src/approval-node.ts, packages/plugins/plugin-approvals/src/exec-context-annotation.pin.ts, packages/plugins/plugin-approvals/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.

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

  • content/docs/automation/flows.mdx (via ApprovalService (symbol, a top-level class))
  • content/docs/permissions/system-context.mdx (via isOverrideActor (symbol, a method of class ApprovalService))

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

  • content/docs/releases/v17/17-1.mdx (via active_organization_id (literal, a string literal in a comment on a changed line))

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
  • 3 changed file(s) yielded no anchor (packages/plugins/plugin-approvals/src/approval-node.ts, packages/plugins/plugin-approvals/src/exec-context-annotation.pin.ts, packages/plugins/plugin-approvals/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 — 6 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 57574637129bebb4a6868c465be7e1b80eed1954 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5b95c208e4243bd03e046aa60468e7f449ec7a25 — the merge of head 708244c2b2446479422fb533191f9b76cff50231 into base 57574637129bebb4a6868c465be7e1b80eed1954, 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 5b95c208e4243bd03e046aa60468e7f449ec7a25 && git checkout 5b95c208e4243bd03e046aa60468e7f449ec7a25
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 57574637129bebb4a6868c465be7e1b80eed1954 708244c2b2446479422fb533191f9b76cff50231 && git checkout -B drift-repro 57574637129bebb4a6868c465be7e1b80eed1954 && git merge --no-ff 708244c2b2446479422fb533191f9b76cff50231

node scripts/docs-audit/affected-docs.mjs --json 57574637129bebb4a6868c465be7e1b80eed1954

⚠️ 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 57574637129bebb4a6868c465be7e1b80eed1954 → 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: 708244c2b2446479422fb533191f9b76cff50231
Local-runs: none

① Derived judgments

Read against main at the merge-base 575746371, which is also the PR's recorded base. The head was fetched into a ref this review owns (refs/pm-review/20717, tip 708244c2b) rather than read off FETCH_HEAD, which a sibling's fetch had already moved. The platform's main, read over the API for this record, stands three commits past that base (d3f88faf3, dfc8547c4, 10c36cc43); none of the three touches packages/plugins/plugin-approvals/**, this PR's changeset, .changeset/config.json or scripts/check-issue-citations.mjs (their only .changeset/ additions are two unrelated files), so the net diff against main is the merge-base diff: 14 files, +51/−41 — 13 source files under packages/plugins/plugin-approvals/src/** (6 modules, 7 test files) and one changeset. The head 708244c2b adds only the changeset on top of e698d2393, which holds every source line.

  • Accept-set: no change — right. No Zod schema, REST handler, query-parameter set, refusal text, log text or runtime string moves. 41 source lines out, 41 in; every one of the 82 changed source lines opens with a comment marker after whitespace (//, *, /**), 0 fall outside one (this record's own grep over the diff). Each of the 13 touched source files has additions equal to deletions (git diff --numstat), so no line citation into these files moves. The dev's parser leaf-token guard (0 files with a token change over 13 files; 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 docs-drift advisory on the PR names two hand-written pages by symbol (ApprovalService, isOverrideActor); a comment-only diff falsifies neither, and the release-owned page it lists is read-only by rule.
  • Published bytes: changed — right, and it decides ②. @objectstack/plugin-approvals (17.5.0, not private, files = dist, README.md, CHANGELOG.md, types = dist/index.d.ts, build = tsup then check-dts-emitted) emits declarations, and rewritten docblocks sit on declarations the entry exports: ApprovalResumeSurface.inspectConsumedSuspension (approval-service.ts:221), the refineFailedRunState docblock, the expandPositionUsers / expandMembershipTierUsers docblocks on ApprovalService, the undetermined counter's docblock (:4823), and the object-literal comments inside SysApprovalRequest. The dev's A3 build reading over all four dist entry files, with positive and negative controls, says the same; this record does not repeat the build.
  • The 14 numbers are dead — right. Each of #6206 #6523 #8707 #8710 #8778 #8863 #11081 #11286 #11308 #11311 #11671 #11674 #12493 #16709 answers 404 on the issues endpoint (board read 2026-09-29T21:07Z); #11311 answers 404 on the pulls endpoint too. No ADR names any of the 14 (git grep over docs/adr at the head: none), and the rest of docs/ names only #11311 beside #10101 in an audit table, as evidence, so ruling C's first rung is empty and a commit is the right anchor for every one.
  • The 13 anchors — each right. Each abbreviated sha resolves to exactly one commit (rev-parse --disambiguate, count 1 for all 13) and is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 13; the history is complete, --is-shallow-repository false). Twelve name the replaced number in their message; aa5994e17 (Operation Message Catalog: add refusal-situation keys for approvals recall and sharing write-denial — two measured hardcoded-English refusals waiting on the one sanctioned mechanism #12493, 7 occurrences) and 09b4f4e4e ([finding] check:i18n verifies key presence, not that an untranslated leaf still matches the source string it was filled from — and the drift is sticky #11671, 16) name it in their diff only. And the decision each rewritten line states is the commit's. #16709 → 8c7cca1ce, whose message numbers its three items — item 2 「(PM ruling, 2026-09-08)」 keeps a thrown third read's row in the report as the undifferentiated failed, item 3 moves refineFailedRunState inside the try — so the lines' 「item 2」 / 「item 3」 are the commit's own words, and git blame at the base puts approval-service.ts:4893 and :4906 in that commit itself. #8710 → 04d03c3a0, whose message quotes the 2026-08-15 ruling verbatim (「Access-conferring paths filter deactivated positions; addressing paths do not.」), the sentence the two untouched quotation blocks carry, and whose diff writes 「A name with no sys_position row is untouched」, the fallback approval-service.ts:2318 now attributes to it. #6523 and #6206 → aa4b90d9a (subject names finding: SharingExecutionContext 是同族第四个窄 enforcement 契约类型(sharing / approval / report 三个服务共用),#6206 裁决的「不留 per-site 子集」默认尚未覆盖它 #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)」), written as the full-envelope ruling, the form the landed stages 2, 4 and 6 used. #8778 → 7901b2dd2 (subject: the stamp-only tenancy.organizationField). #11081 → c28e4cfae (its diff writes the [#11081] tags; blame puts status-mirror-cascade.integration.test.ts:49 in it). #11286 → b019891cd, the commit that added manager-org-screen-parity.contract.test.ts (--diff-filter=A). #11674 → 1cba33f16 (subject: the load-time warning and the ordering constraint at the four pointer-pair sites; blame puts sys-approval-request.object.ts:200 in it). #12493 → aa5994e17 (subject: the catalog gains approval_recall_not_submitter). #8707 → 1408fe385 (subject names it; its body says the maintainer's ruling on #8287 requires the stamp, which is why the line keeps 「honouring [finding] API keys carry no organization — under the isolated posture a minted key reads no org data at all (no leak, but the key surface is inert) #8287's ruling」 — #8287 resolves). #8863 → d200b016b (subject: the deliberately-unfiltered position expansion of the Does a DEACTIVATED sys_position still receive sharing-rule shares? expandPositionUsers never reads the catalogue row, so today it does #8710 carve-out; blame puts approval-service.test.ts:749 in it). #11308 → 5a916c4d4, the commit that added both backfill files. #11311 → 1272f0a6b, the squash commit of the pull request that was #11311 (its subject carries the number), the one PR #N spelling in scope. #11671 → 09b4f4e4e, the anchor the identical translations/index.ts line already carries in service-messaging, plugin-sharing, plugin-security and service-storage on main. Seventeen blame spot-checks over the rewritten lines each land in the anchor or in a descendant of it.
  • The wordings — each right. 「the option Does a DEACTIVATED sys_position still receive sharing-rule shares? expandPositionUsers never reads the catalogue row, so today it does #8710 rejected」 → 「the option the ruling (commit 04d03c3) rejected」 names the ruling and its record apart. 「Promote resolveRecordOrganizationField to the shared platform-row resolver (approvals + automation runs), per the ruled cloud#1395 Option A #10101 (landed as PR Promote resolveRecordOrganizationField to the shared platform-row resolver: approvals + automation runs stamp the SUBJECT record's organization (cloud#1395 Option A) #11311)」 → 「(landed as commit 1272f0a)」. 「[[finding] Three residues from the #15358 contract review: an unpinned stale-hot drop, a thrown third read that leaves stranded, and a malformed host verdict that aborts the whole scan #16709 item 3]」 → 「[commit 8c7cca1, item 3]」. The two lost referents: 「this card must not do」 → 「this sweep must not do」 in the backfill test, whose anchor's subject calls the file a backfill sweep; 「the very decision this card is fenced out of」 → 「this pin」 in the parity contract test, whose anchor is the pin. approval-service.ts:53 is the one reflow line, in the paragraph :52 rewrote.
  • Citation accounting — right (this record's own count, raw # plus digits over the diff). The 41 removed source lines carry 41 dead occurrences on 38 lines (approval-node.ts:29, approval-service.ts:52 and approval-service.test.ts:749 carry two each): #16709 8, #8710 6, #11081 5, #6523 4, #8778 4, #6206 3, #11286 2, #11674 2, #12493 2, #8707 1, #8863 1, #11308 1, #11311 1, #11671 1 — the body's table. Added lines carry exactly four tracker numbers, #8613 ×3, #8287 ×2, #10101 and #12069, each on the line it already stood on (delta 0) and each resolving (200); no number is new to the diff, none grew, no PR #N stands on an added line, and 13 distinct shas stand on added lines.
  • The 5 sites left — right, and the list is exact. A grep of the 14 numbers over plugin-approvals/src at the head returns exactly 5 lines: manager-org-screen-parity.contract.test.ts:107 (a test double's thrown message), :232 (a describe title), :294 (an assertion message), stranded-request-inspection.test.ts:519 and :642 (an it and a describe title). All are string tokens, left as stages 1 to 6 left theirs. No operator string, generated header or quoted ruling in this package carries a dead number; the two verbatim quotations of the 2026-08-15 ruling carry no number and are untouched.
  • The gate-invisible spellings — right. At the head in this package: one #N-word line (record-reader-visibility.test.ts:342, #3266-era; #3266 resolves), one #A/#B line (approval-vocabularies.test.ts:66, #8543/#8580; both resolve), and no option #N with three or more digits. The one #A/#B whose second number was dead (approval-node.test.ts:462, #8287/#8778) is rewritten. NON_CITATION_HEADS does carry the row for the word option (「option ordinals inside a ruling」, scripts/check-issue-citations.mjs:466), so 「the option Does a DEACTIVATED sys_position still receive sharing-rule shares? expandPositionUsers never reads the catalogue row, so today it does #8710 rejected」 at approval-service.ts:2329 was never extracted — the body's account is exact. Repo-wide under packages/**/src at the head, the only one-line option #N spelling left is packages/objectql/src/validation/rule-validator.ts:2202 (option #14088, resolves), as the body says.
  • Form — consistent with the landed stages 1 to 6 (422db788a, b80ab579d, 4d04b6be3, 9a4b2bb38, 0e9ad74fb, 9b384f63a): the word commit plus the abbreviated sha in the position where the number stood, the decision carried in the sentence. The six reused anchors (aa4b90d9a, 04d03c3a0, 7901b2dd2, 1cba33f16, aa5994e17, 09b4f4e4e) are the ones this card's thread gave the same numbers.
  • Check-runs on the head, the gate verdicts, read 2026-09-29T21:15Z: 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. Every one of the seven required contexts is success: Lint & Repo Gates (which carries check:issue-citations and check:doc-authoring, the two gates this diff answers to; it was the last to finish), TypeScript Type Check, Test Core (all six shards and the aggregate), Dogfood Regression Gate (all three shards and the aggregate), 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 and No other open PR may claim the same issue are success too. An earlier read during this review (2026-09-29T21:11Z) caught Lint & Repo Gates still in_progress; nothing was awaited, the read was simply repeated at the end. Nothing was built, run or re-run locally.

② Semver level

  • .changeset/20596-plugin-approvals-provenance-anchors.md declares '@objectstack/plugin-approvals': patch — matches what the diff publishes. The package is released and all four dist entry files carry the rewritten docblocks, 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 6's landed body with only the package name changed (diffed against origin/main's copy: identical after the swap), and the filename carries the card number. plugin-approvals sits in the fixed group beside the six 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 (5897866351) 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/**, docs/NORTH-STAR.md, .claude/**, skills/**, AGENTS.md, CLAUDE.md); 92 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 (Part-of PR must not also close its card is success), so the card stays open for the remaining stages.

③ Boundary flags

The dev report (5898794524) has open_questions: []. Its nine deviations and two out-of-scope findings, each answered:

  1. 15 test-comment sites beyond the census's 24 — answered, in scope. The claim's surface is comment and docblock prose under plugin-approvals/src/**; test comments are that, and stages 1 to 6 rewrote theirs. The head grep above confirms the residue is strings only.
  2. Two sites neither instrument extracts — the slash-joined second number and 「the option Does a DEACTIVATED sys_position still receive sharing-rule shares? expandPositionUsers never reads the catalogue row, so today it does #8710 rejected」 — answered, right. Both are dead numbers in comment prose inside the claimed file surface, which is the boundary; the claim's named grep classes (#N-word, #A/#B) are instruments, not the boundary, so a third spelling found by the raw scan inside the surface is in scope, not a breach. Verified at the head that no dead site of either shape remains outside a kept string.
  3. Three changed lines with no dead site (1 reflow, 2 lost referents) — answered, right (① above). Checked line by line; every file keeps its line count.
  4. The supplementary and raw instruments judged against the before census's board reading plus single-number reads, with no board-dump script written — answered, immaterial. The census instrument itself ran unchanged, and this record's own board read confirms the 14 dead and the 4 live numbers independently.
  5. The after census's board moved during its run — answered, right. Its frontier equals the newest number at the run's end, which is the criterion A1 states, and the whole-repo drop of exactly 24 is the control.
  6. The harness attribution reminder versus AGENTS.md's trailer pair — answered, right. Both head commits end with the model-free trailer pair AGENTS.md prescribes (the session-URL trailer and the co-author line that names no model), no model identifier appears in either message, and the PR body's footer is the session-URL form that surface keeps.
  7. Labels — answered. documentation, size/s, tests, tooling are the labeler's; no skip-changeset, which is right.
  8. The report posted from the shared checkout after the worktree was removed — answered, immaterial. A process note; the report says nothing there was edited, and this PR's head is what this record reads.
  9. Stale prose naming the retired tenancy.organizationField key left beside two rewritten lines — answered, right to leave. Correcting it reaches past the dead citations, and the two fixture declarations are code tokens the guard would flag; recorded in the Acceptance notes and as out-of-scope 2 below.
  10. Out-of-scope 1, NON_CITATION_HEADS excuses option #N as an ordinal — answered, carrier stands. Verified at source (scripts/check-issue-citations.mjs:466). Class a with a named producer, the same family as the hyphen and slash spellings, so it folds into 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 under the fold rule; the seat's ACCEPT (5898832340) says the pointer went there in the same act, and this record reads no other card. No instrument change in a stage PR of this card is right. After this PR no dead option #N site remains under packages/**/src.
  11. Out-of-scope 2, comments and two inert test fixtures still naming the retired key — answered, a note is the right filing. Runtime behaviour is right (limb 0 reads PLATFORM_STAMP_ORGANIZATION_COLUMNS by object name), authoring the key is refused loudly with a tombstone, so this is drift, not a metadata-authoring trap; Prime Directive 10 puts it in the Acceptance notes, where it is. Observation for whoever next edits these files: the five comment lines now stand beside freshly re-anchored ones and read as current, so the next touch should correct them.

Nothing is escalated. Every check on the head was complete and green at this record's final read, so the landing waits on nothing this record can see; the owning seat lands it through the queue as the lane's earlier stages landed.

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

VERDICT: PASS


Generated by Claude Code

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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants