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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…kages/plugins/plugin-hono-server/src to the commits that decided them (objectstack-ai#20741)

Part of objectstack-ai#20594
Clause-②: no

## What changed

This is stage 10 of the `domain:cli` lane of the dead-citation sweep:
`packages/plugins/plugin-hono-server/src`. Every comment site there that
cited a tracker number answering 404 now cites, in ruling C+D's form C
(comment 5749154545 on objectstack-ai#19123), the commit in this repository's history
that decided what the line describes, and keeps saying in its own words
what that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 9 of
this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR
objectstack-ai#20703, PR objectstack-ai#20713, PR objectstack-ai#20723, PR objectstack-ai#20735) are the precedents. The card
stays open for the lane's remaining packages, so this PR says `Part of`.

That is **24 sites on 24 lines in 5 files, covering 5 numbers**,
rewritten to **5 distinct commits**:
- the census's **5 sites**: `src/adapter.ts` 4,
`src/current-user-endpoints.ts` 1 (4 numbers);
- **19 test-file comment sites** in 3 test files (the census defers
`*.test.ts`; stages 1 to 9 took test comments too):
`ui-plugin-auto-discovery.pin.test.ts` 16,
`handler-throw-declared-envelope.test.ts` 2,
`current-user-endpoints-localization.test.ts` 1.

Only comments changed: **24 lines out, 24 in**, every one of them a site
(no companion line), and every touched file keeps its line count (1,660
/ 1,020 / 335 / 403 / 697), so no line citation into these files moves.
**No citation number is added**: over the 24 line pairs, the added
numbers are a subset of the removed ones (`objectstack-ai#16599` x2, `objectstack-ai#9864`,
`objectstack-ai#16334`, all answering 200, stay where they stood), and no PR number
stands on an added line. No ADR or ruling-record file in `docs/adr/` or
`scripts/adr-anchors/` records any of these 5 decisions (a grep for the
5 numbers there reads 0 hits; the control number `objectstack-ai#7329` reads 1 in the
same tree), so every anchor is a commit.

A **`patch` changeset** for `@objectstack/plugin-hono-server` rides
along, because one rewritten comment reaches `dist` (measured below).
That is stage 6's and stage 9's case (PR objectstack-ai#20703, PR objectstack-ai#20735), not stages
5 and 7's.

## Census: `packages/plugins/plugin-hono-server`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run under `with-fleet.sh
--read` for the token. The count is its `allocated-but-absent` findings
under `packages/plugins/plugin-hono-server/`. Both runs enumerated the
whole board.

| reading | tree | board | whole-repo `allocated-but-absent` | package
sites | lines | numbers | files |
|---|---|---|---|---|---|---|---|
| before | base `f927864ea0`, run 2026-09-29T23:42:29Z to 23:46:56Z |
enumerated, 186 pages, frontier objectstack-ai#20735, 18,562 numbers | 1,105 | **5** |
5 | 4 | 2 |
| after | `91ce7e5e8f`, run 23:58:22Z to 2026-09-30T00:02:19Z |
enumerated, 186 pages, frontier objectstack-ai#20737, 18,564 numbers | 1,100 | **0** |
0 | 0 | 0 |

The whole-repo drop of 5 is exactly these sites: a site-by-site diff of
the two JSON outputs has 5 findings gone (`adapter.ts:225`, `:227`,
`:308`, `:349`; `current-user-endpoints.ts:448`) and none added. The
other three tallies (`resolves` 33,003, `resolves-as-pull-request`
1,984, `cross-repo-unjudged` 995) are equal in both runs.
`packages/plugins/plugin-hono-server` is byte-identical at `91ce7e5e8f`
and at the head.

**Supplementary scan (test files, strings and files outside `src/`
included).** The gate's exported `extractCitations` and
`classifyCitation` over all 41 tracked files of the package
(`CHANGELOG.md` excluded), comment-prose and whole-file projections,
with the board from the gate's own `probeBoard`: 347 citations and 32
dead before, 323 and 8 after. Under `src/`: comments 5 dead to 0, test
comments 19 to 0, strings 0 and test strings 2 unchanged. Its before
list of `src/` comment sites equals the census's. The 8 left are 2 test
strings and 6 sites outside `src/` (see Acceptance notes).

## Per-site table

`git blame` at the base ties each line to the commit that wrote it, and
each anchor was read in its message, changeset or diff, not only its
subject.

| number | sites (base line) | anchor: what it decided |
|---|---|---|
| `objectstack-ai#13279` | `adapter.ts:225`, `:227`;
`handler-throw-declared-envelope.test.ts:84`;
`current-user-endpoints-localization.test.ts:90` | `6a180e42d`:
`tryFind` in `resolveAuthzContext` raises `AuthzStoreUnavailableError`
(503 `SERVICE_UNAVAILABLE`) when a permission-store read is issued and
throws, instead of answering it as an empty read, and each fail-closed
transport `catch` (`requireDatasourceAdmin` in `service-datasource`
among them) re-raises it: an unreadable store licenses no verdict, the
maintainer's 2026-08-30 ruling recorded in its message. The first three
lines blame to `cefe068702` (the declared-envelope rendering, PR objectstack-ai#17412)
and the fourth to `5f7fa1de0`; both wrote them citing this ruling. PR
objectstack-ai#13475 (200) names objectstack-ai#13279. Stages 1, 2, 4 and 9 gave the number this
anchor. |
| `objectstack-ai#9934` | `adapter.ts:308`;
`handler-throw-declared-envelope.test.ts:152` | `79c46da90`: the
producer-side user-facing marking for hook refusals, the `userMessage`
channel, a text field a producer sets at throw time and every classified
envelope carries. Both lines blame to `cefe068702`. The PR that landed
it (PR objectstack-ai#9992) answers 404 too. Stages 1, 2, 4 and 6 gave the number this
anchor. |
| `objectstack-ai#6307` | `adapter.ts:349` | `293476148`: refuse a repeated
`?version=` on `GET`/`DELETE /packages/:id` rather than pick one value,
through `readSingleQueryValue`, which it introduces. The line blames to
`7cdbcbb306` (surface repeated query parameters as arrays, PR objectstack-ai#7396),
which says it follows that direction. Stages 2 and 8 gave the number
this anchor. |
| `objectstack-ai#6216` | `current-user-endpoints.ts:448` | `f586f1a89`: one
`ExecutionContext` assembler with two named anonymous entries,
`assembleExecutionContext` the default, fail-closed one and
`assembleExecutionContextOrGuest` the explicit guest one, the
maintainer's 2026-08-08 Option A recorded in its docblock. The line
blames to `6615a024c3` (the current-user faces adopt the shared
assembler). Stages 1, 2 and 7 gave the number this anchor. |
| `objectstack-ai#16721` | `ui-plugin-auto-discovery.pin.test.ts:27`, `:37`, `:42`,
`:180`, `:211`, `:217`, `:360`, `:363`, `:495`, `:498`, `:594`, `:596`,
`:604`, `:608`, `:631`, `:646` | `51ae73123`: `LiteKernel.use()` runs
the same `assertPluginContract` as `ObjectKernel.use()` and refuses the
same plugin objects with the same envelope, the maintainer's 2026-09-08
option A (the kernels converge) recorded in its changeset. 15 lines
blame to it; `:180` blames to `3c48234b3`, which re-wrapped that
sentence and keeps its fact. Its `lite-kernel.ts` docblock records the
measurement taken before converging, which `:604` describes ("before
commit 51ae731"). `:363`, `:498` and `:631` said the `hono-plugin.ts`
question was "noted on" the dead number; that note is the text this
commit wrote into this file, so they now say "raised with" / "recorded
with" it. New anchor; no other package has re-anchored this number yet.
|

**Anchor checks.** Every cited sha matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 5), is a commit, has
one parent, and is an ancestor of `main` (`merge-base --is-ancestor`
against `f927864ea0`, exit 0 for all 5). The checkout is not shallow.
The control leg `2672f855fa` (2026-08-09, the parent of the oldest
anchor `293476148`) exits 0 against `origin/main`, and the negative
control, this branch's own head, exits 1. Four anchors reuse the landed
stages' (`6a180e42d`, `79c46da90`, `293476148`, `f586f1a89`), so each
number carries one anchor across the tree; one is new (`51ae73123`).

**Numbers.** All 5 dropped numbers answer 404 by REST (probed
2026-09-30T00:14:17Z). The numbers kept on or beside the changed lines
answer 200: `objectstack-ai#16599`, `objectstack-ai#9864`, `objectstack-ai#16334`, `objectstack-ai#16363`, `objectstack-ai#16049`, `objectstack-ai#6878`,
`objectstack-ai#3867`, `objectstack-ai#8086`, `objectstack-ai#16545`, `objectstack-ai#15999`, `objectstack-ai#5090`.
`packages/plugins/plugin-hono-server/src` has no slash-joined `#A/#B`
without spaces; the spaced pairs (`objectstack-ai#3867 / objectstack-ai#8086`, `objectstack-ai#2408 / objectstack-ai#3361`) are
read by the grammar and every half answers 200.

## Mechanical guard: no code token moves

**H2 holds on the parser-token reading; the emitted `dist` is NOT
byte-identical, because one rewritten `//` comment sits inside a
returned object literal and the bundler keeps it.**

**Token guard.** It compares the TypeScript parser's leaf tokens
(TypeScript 6.0.3, `getChildren` walk, JSDoc nodes excluded) of the 5
touched files at base `f927864ea0` and at the head. Controls mutate the
head text in memory only.
- Real run: 15,054 base tokens (4,831 / 2,134 / 3,559 / 2,363 / 2,167),
0 differing, exit 0.
- Comment-insertion control: 0 differing, exit 0.
- Code-insertion control: all 5 files differ, exit 1.
- String control (the first character of the first import specifier
flipped in each file): exactly 1 differing `StringLiteral` per file,
exit 1.

All 48 changed lines (24 out, 24 in) are `//` or `*` comment lines.

**Emitted `dist`.** `pnpm --filter @objectstack/plugin-hono-server
build` at the head, then at base (the base blobs of the 5 touched files
restored in place under a trap-armed restore; an on-disk probe read
`objectstack-ai#9934` 1 and `commit 79c46da` 0 in `adapter.ts` before that build;
afterwards every touched blob equals its HEAD blob, `git diff HEAD` is
empty and the status is clean), with the same dependency builds:
- `index.js` and `index.mjs` differ, in one line each: the
`adapter.ts:308` comment, `refusal text (objectstack-ai#9934)` at base and `refusal
text (commit 79c46da)` at head. `index.d.ts`, `index.d.mts` and both
`.map` files are equal. No docblock of this diff reaches the declaration
files.
- The same parser comparison over the two differing files reads
identical tokens (10,818 in `index.js`, 10,521 in `index.mjs`), so the
whole `dist` delta is comment text. Its code control (a code line
appended) reads COUNT/TOKENS DIFFER in each.
- Code-mutation control (`scripts/ablation-replace.mjs`, wrap mode,
anchor `message: 'No response from handler' }` hit 1 to 0, planted
marker 0 to 1, blob `6a0c10f76282` to `d5223196afeb`;
`scripts/ablation-dist-preflight.mjs` found the marker in `index.js` and
`index.mjs`): `index.js`, `index.mjs` and both `.map` files differ from
the head build. The blob was restored to HEAD `6a0c10f76282` with `git
diff HEAD` empty, `dist` was rebuilt, its six sha256 values equal the
first head build, and the preflight in `--absent` mode reads the marker
absent from all 6 files with a clean tree.

A raw scan of the 6 changed files for ASCII control bytes finds none,
and `check:nul-bytes` exits 0.

## Changeset

**`patch` for `@objectstack/plugin-hono-server`**
(`.changeset/plugin-hono-server-provenance-anchors.md`), in PR objectstack-ai#20632's
form. The package's `files[]` is `dist`, `README.md` and `CHANGELOG.md`,
and the build above emits different `index.js` / `index.mjs` at base and
head, so this diff publishes. `check-changeset-no-major`,
`check-empty-changeset`, `check-adr-0087-registration` and
`check-changeset-fixed` all exit 0.

## Gates (head `03e5f4c0fd`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its official wording, verbatim (printed by every run; the
command line differs per run and is listed in the verdicts below):

> **Declared narrowing — verification ran UNLOCKED.**
`scripts/pm/os-verify-lock.sh`
> could not take the shared verify lock on this host: no usable `flock`.
The shared
> verify lock is declared Linux-only (`flock` is util-linux, and a stock
macOS does
> not ship it), so the command below was run directly, without the lock
—
> a declared narrowing, not a silent one. No serialization guarantee
held for this
> run, nor for any sibling agent in this container while it ran.

Its verdict line from each run (the closure build and the three `dist`
builds at `feaf0c9b73`, whose `packages/plugins/plugin-hono-server` is
byte-identical to this head; the first whole-workspace build, tests and
typecheck at `91ce7e5e8f`; the second whole-workspace build, tests and
typecheck at this head after the merge; the scratch-script paths
shortened to `SCRATCH`):

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 27s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-hono-server...' build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · bash SCRATCH/base-build.sh SCRATCH
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · node scripts/ablation-replace.mjs --file packages/plugins/plugin-hono-server/src/adapter.ts --anchor "message: 'No response from handler' }" --replacement "message: 'No response from handler ABLMARK20594S10' }" -- bash SCRATCH/mut-inner.sh SCRATCH
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 12s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 17s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 11s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server exec vitest run --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 16s · declare it in the PR body · pnpm --filter @objectstack/plugin-hono-server typecheck
```

- **Build:** `@objectstack/plugin-hono-server` with its closure (7 of 81
workspace projects), then the whole workspace, `turbo run build
--filter='./packages/*' --filter='./packages/*/*' --concurrency=2`, 71
of 71 tasks, before and again after the merge. The tree was clean after
each, and the package's six `dist` files after each whole build equal
the first head build by sha256.
- **Tests:** `vitest run --maxWorkers=2`: 27 files, 324 tests passed
(every `*.test.ts` under `src/`), at this head and at `91ce7e5e8f`.
- **Typecheck:** `pnpm --filter @objectstack/plugin-hono-server
typecheck` exits 0 at this head and at `91ce7e5e8f` (`tsc --noEmit`,
`tsc --noEmit -p tsconfig.typecheck.json`, and `check:test-typecheck` OK
with 0 files / 0 errors / 0 pinned signatures). `--listFiles`:
`tsconfig.json` and `tsconfig.test.json` each compile 33 `src/` files
including all 27 tests and all 5 touched files.
- **Spec artifacts:** `origin/main` brought a `packages/spec` change, so
`pnpm --filter @objectstack/spec check:generated` ran after the rebuild:
"All 15 generated artifacts are up to date" (exit 0).
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at this head (2026-09-30T00:25:09Z to 00:25:36Z), and at
`91ce7e5e8f`.
- **Citation judging:** after merging `origin/main` (`fbec216e2d`),
`node scripts/check-issue-citations.mjs --base origin/main` reports "no
issue citations added against fbec216 (2 file(s) read)" (exit 0).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 63 families, the same
list at `91ce7e5e8f` and at this head. All 63 exit 0 at this head in one
pass, and `--ran` with the exit-coded record reads "63 derived, 63 run,
0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them:
`check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`,
`check:published-files`, `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:type-check-debt`,
`check-adr-0087-registration`, `check-empty-changeset`.
- **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0 at
this head, including the four the derivation marks as keeping their
roster under one of this diff's paths (`check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three need a pull request's
context; they are run against this PR once it exists and reported on the
card. The 18 self-test-only rows grade their checkers' fixtures and
cannot judge this diff.

## Hypotheses (measured first)

- **H0 holds.** At base `f927864ea0` the filtered census answers 5 sites
on 5 lines, 4 numbers, 2 files, as on the seat's `0be898499f`. The
whole-repo count is 1,105.
- **H1 holds.** After the rewrite, the filtered census answers 0 for
`packages/plugins/plugin-hono-server`. No site was left for an open PR
(the file lists of all open PRs were read at 2026-09-29T23:48:36Z, 6
PRs, and again at 2026-09-30T00:15:14Z, 9 PRs: only the Version Packages
PR objectstack-ai#20639 touches the package, in `CHANGELOG.md` and `package.json`) or
for an unfound anchor.
- **H2 holds on the token reading, not on the `dist` reading.** The
parser leaf-token diff of all 5 touched files is empty with its controls
firing. The emitted `dist` differs in one comment line of `index.js` and
of `index.mjs`, token-identical with a code control. That is why the
changeset ships.

## Acceptance notes

- **Strings, the form-D stage.** One dead number remains in a string
literal in `packages/plugins/plugin-hono-server/src`: `objectstack-ai#16721` at the
end of the group-F `describe` title of
`ui-plugin-auto-discovery.pin.test.ts` (`:635`, a test title, no
assertion text). It stays on the card for its form-D stage; no string
moved here. The supplementary scan's second test-string hit, `:111`
(`'.os-pin{color:#123456}'`), is a CSS hex colour in a fixture, not a
citation: the whole-file projection reads it as a six-digit number,
while the census blanks strings and defers test files.
- **Outside `src/**`, a later stage of the card:**
`objectstack.config.ts:19` (`objectstack-ai#11332`) and `:26` (`objectstack-ai#10724`),
`tsconfig.test.json:3` and `:61` (`objectstack-ai#13176`),
`tsconfig.typecheck.json:12` (`objectstack-ai#11332` and `objectstack-ai#10724`; `objectstack-ai#4914` in the same
group answers 200). The other citations in the package outside `src/**`
(`CHANGELOG.md` excluded) answer 200: `tsconfig.test.json` (`objectstack-ai#14062`,
`objectstack-ai#5286`, `objectstack-ai#5449`, `objectstack-ai#12542`), `tsconfig.typecheck.json` (`objectstack-ai#13284`,
`objectstack-ai#5475`, `objectstack-ai#10756`), `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#9457`, `objectstack-ai#7378`;
`objectstack-ai#8129` resolves as a pull request). `README.md` carries none.
- **An open question now lives only in this file.** `hono-plugin.ts:521`
and `:523` still carry the `&& plugin.staticPath` conjunct and the
`plugin.slug || plugin.name.split('/').pop()` derivation that, since
`51ae73123`, neither published kernel's `use()` lets an input reach. The
pin file says so and leaves the call to `hono-plugin.ts`; the tracker
note it pointed at is gone, so this file's text (and commit
`3c48234b3`'s message) are the record. Unreachable defensive code, not a
defect: noted, not filed.
- **Card-word residue, cited nowhere.**
`handler-throw-declared-envelope.test.ts` still says "before this card"
(`:85`) and "the card" (`:19`, `:32`) around its rewritten lines. They
cite no dead number, so they were left, as the landed stages left
theirs.
- **The moving `origin/main`.** The branch merged `origin/main` once
(`03e5f4c0fd`, merging `fbec216e2d`: the ADR-0087 migration chain moves
to `@objectstack/spec/migrations`). `packages/spec` is in this package's
dependency closure, so the workspace was rebuilt and the package's
tests, typecheck and every gate above were rerun at the merge head;
nothing in `packages/plugins/plugin-hono-server` changed.

## Deviations

- **Three derived gates first read NOT MEASURED.**
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` exited 3 (PREREQUISITE NOT MET: built output
absent) in the first pass, before the whole-workspace build. Rerun after
it, each exits 0, and all 63 exit 0 in the single pass at this head.
- **The first `check:generated` run was void.** This host's global
`pnpm` is a v11 front end that rejects the `-s` each sub-gate passes, so
all 15 rows read "unexpected argument '-s'" (exit 1, nothing measured).
Rerun with the real pnpm 10.31 binary first on `PATH`, it reads all 15
up to date (exit 0).
- **Commit trailers** are AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The harness's attribution reminder asked for a model-named
trailer and a different PR footer, and AGENTS.md overrides it. The merge
commit carries git's default message.

---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…mmits that decided them (objectstack-ai#20742)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the tenth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/services/service-package/src/**` and nothing else. By the
seat's census at the claim (`5901757839`), 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 objectstack-ai#19123), by the method of stages 1 to 9 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as
`9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR
objectstack-ai#20737 as `4dfff176b`). That is **18 sites on 17 lines in 5 files,
covering 5 numbers**:

- 13 census sites (every census site this package has);
- 5 sites in test comments, which the census defers;
- no site the gate's grammar cannot see (the package has none, see
Acceptance notes).

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: **4 distinct shas**. No number in this package has an ADR or
ruling record of its own in the repository (a grep of `docs/adr/` and
`scripts/adr-anchors/` for all 5 finds none, and nothing else under
`docs/` names them), 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
(17 lines out, 17 in, over 5 files), so no line citation into these
files moves. Every one of the 17 changed lines carried a dead citation;
there is no reflow line. No code token moves (see the guard below).

**No citation number is added.** The one tracker number on an added
line, `objectstack-ai#10677`, was already on the line it replaces (`index.ts:234`) and
resolves. Over the whole diff, added minus removed is 0 for `objectstack-ai#10677` and
negative for the five dead numbers, and no number is new to the diff. No
PR number is the citation on an added line: the three `PR #N` spellings
in scope became their pull request's squash commit.

4 dead sites are left on purpose, all of them `describe` titles (see the
list below).

One more file: a `patch` changeset for `@objectstack/service-package`,
because one rewritten docblock ships (see Changeset below).

## Census: `service-package`, 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/services/service-package/`. 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` |
service-package sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `4dfff176b`, run 2026-09-30T00:41:15Z to 00:44:24Z |
enumerated, 186 pages, frontier objectstack-ai#20741 (newest objectstack-ai#20741 before and after),
18,568 numbers | 1,082 | **13** | 12 | 1 | 4 |
| after | head `34ba921e6`, run 00:49:33Z to 00:52:49Z | enumerated, 186
pages, frontier objectstack-ai#20741 (newest objectstack-ai#20741 before and after), 18,568 numbers
| 1,069 | **0** | 0 | 0 | 0 |

The before count matches the seat's census and A1 (13 sites). The
whole-repo drop is 13, exactly this diff's census sites. The `resolves`
tally is 33,003 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 `34ba921e6`; the head `ffd2f1ed2` 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 `service-package/src` (6 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,065 rows) and did not report
it. The one number the census never saw, because it stands only in test
files here, was read on its own: `objectstack-ai#16650` answers 404 on the issues
endpoint and on the pulls endpoint.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `4dfff176b` | 82 | **22** | 13 | 5 | 0 | 4 |
| after, `34ba921e6` | 64 | **4** | 0 | 0 | 0 | 4 |

Its src-comment column equals the census's 13, which is the control on
the second instrument. The 60 live citations are the same in both
readings (no cross-repo citation stands in this package), and the drop
of 18 citations is exactly the rewritten sites. A third, raw reading
(every `#` followed by 2 to 6 digits, whatever surrounds it) finds 82
occurrences and 22 dead before, 64 and 4 after: the same as the gate's
grammar, so nothing here sits beyond it, and it has no unjudged token.

## 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 17 line and anchor
pairs).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#10965` | 17/3 | 13/4 | `ab47f6974` (PR objectstack-ai#11064): `get()` and `list()`
refuse a storage seam that accepted the query and returned no result
set, with a declared ADR-0112 envelope (`SERVICE_UNAVAILABLE` / 503),
and the skipped boot rehydration is logged at warn; a seam that answers
with zero rows is unchanged. Its body says `Part of objectstack-ai#10965` three times,
and it is the only commit that wrote the seam guard (`git log -S
packageSeamUnreadableError`). The `runtime` stage's anchor for the same
number |
| `objectstack-ai#10788` | 1/1 | 1/0 | `3a7ec2d3b`: `os migrate duplicates` holds a
raw-SQL seam that cannot answer to be absent, not empty. The squash
commit of the pull request that was `objectstack-ai#10788` (404 on the pulls endpoint
too); `objectstack-ai#10677`, the card it answers, stays beside it. New to the sweep |
| `objectstack-ai#10789` | 1/1 | 1/0 | `38bc74ed1`: `backfillSeedTenancy`'s read
probes hold a seam that cannot answer to be absent, not empty. Its
subject names `objectstack-ai#10789`. The `runtime` stage's anchor for the same number
|
| `objectstack-ai#10964` | 1/1 | 1/0 | `38bc74ed1`: the same commit, the squash commit
of the pull request that was `objectstack-ai#10964` (404 on the pulls endpoint too),
so the pair `objectstack-ai#10789 / PR objectstack-ai#10964` became one sha |
| `objectstack-ai#16650` | 2/2 | 2/0 | `001a83b04`: `SqlDriver.execute()` declares a
backend refusal as `DATABASE_ERROR` / 500. The squash commit of the pull
request that was `objectstack-ai#16650`; its review round (「pin the package-door code
flip」) wrote the two `[objectstack-ai#16019]` blocks whose closing sentence these
lines are. The `rest` stage's anchor for the same sentence in
`package-door-16019-raw-statement-fault-code.test.ts` |

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

## Wordings to check

- **Bracket tags.** `[objectstack-ai#10965]` became `[commit ab47f69]` on 9 lines of
`index.ts` (`:208`, `:286`, `:304`, `:327`, `:451`, `:476`, `:502`,
`:517`, `:626`).
- **`index.ts:223`**, a section heading: 「(objectstack-ai#10965)」 became 「(commit
ab47f69)」, and its trailing rule was shortened from 11 characters to 2
so the line stays near its old width.
- **`index.ts:234-235`**, the two siblings of the seam guard: 「(objectstack-ai#10677 /
PR objectstack-ai#10788 for / `os migrate duplicates`, objectstack-ai#10789 / PR objectstack-ai#10964 for
`backfillSeedTenancy`)」 became 「(objectstack-ai#10677 / commit 3a7ec2d for / `os
migrate duplicates`, commit 38bc74e for `backfillSeedTenancy`)」. The
live `objectstack-ai#10677` stays beside its fix; the dead issue and its dead pull
request became their one squash commit.
- **`mysql2-tuple.test.ts:26` and `:196`.** 「objectstack-ai#10965's guard」 and
「(objectstack-ai#10965's leg」 became 「commit ab47f69's guard」 and 「(commit
ab47f69's leg」.
- **`null-seam.test.ts:4`**, the file's title line: 「objectstack-ai#10965 — `get()` /
`list()` answered over a driver they never queried.」 became 「The card
behind commit ab47f69 — …」, so line 8's 「The card established the
conflation by READING」 keeps its referent.
- **`delete-driver-fault.test.ts:319` and
`publish-driver-fault.test.ts:357`.** 「The reviewer of PR objectstack-ai#16650
required the flip」 became 「The reviewer of commit 001a83b required the
flip」, the `rest` stage's form for the same sentence.

## The 4 sites left

- **Test strings, 4 sites**, all `objectstack-ai#10965`, all `describe` titles in
`null-seam.test.ts` (`:140`, `:184`, `:229`, `:284`), left as stages 1
to 9 left theirs.
- No source string, operator log string, assertion message, quoted
maintainer ruling or generated file in this package carries a dead
number.

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited), base
`4dfff176b` against head. String and template literals are therefore
read in full. It ran over all 5 touched `.ts` files.

- Real run: 3,323 base leaf tokens, **0 files with a token change**
(exit 0).
- Comment control in `index.ts` (「Is this the seam refusal above?」 to 「…
named above?」): 0 files changed, as expected (exit 0).
- Positive control, a code token added in `index.ts`
(`isResultSet(result)` given `as any` in `get()`): DIFFER (exit 1).
- Positive control, one digit changed inside a kept test title
(`null-seam.test.ts:140`, `objectstack-ai#10965` to `objectstack-ai#10966`): 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 (`2555410dd0a7`, `0c5bf5e7e190`), with
`git diff HEAD` empty and a clean tree afterwards.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/service-package`
(`.changeset/20596-service-package-provenance-anchors.md`) is included.
Its body is stage 9'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, `ab47f6974` appears once in each of
`dist/index.d.ts` and `dist/index.d.cts`: the rewritten docblock sits on
the exported `PACKAGE_SEAM_UNREADABLE_MESSAGE`. The other rewritten
comments do not reach `dist` (0 for `3a7ec2d3b`, `38bc74ed1` and
`001a83b04`, and 0 for `ab47f6974` in `index.js` and `index.cjs`).
Positive control: the unchanged line 「Like {@link
PACKAGE_PUBLISH_DRIVER_FAULT_MESSAGE}, a CONSTANT that」, in the same
docblock, is found once in each declaration file. A never-written
negative phrase appears nowhere in `dist`. None of the 5 dead numbers is
left in `dist`.

## Gates (head `ffd2f1ed2`)

- **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 1
citation in 1 file and found it on the board: `objectstack-ai#10677`, which already
stood on its line.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0; the
sibling-package prose-id baseline holds (808 pinned sites, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `ffd2f1ed2` derived 62 commands:
all 56 derived at dispatch, plus `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 62 exit 0. `--ran`, fed each command with its exit code,
reports 62 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/service-package test`: 5 files pass and 79
tests pass. `vitest list --filesOnly` names 5 files, all the tracked
test files, the 4 touched ones included.
- `pnpm --filter @objectstack/service-package typecheck` exits 0. `tsc
--listFiles` holds all 6 files under `src/`, all 5 touched files
included.
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 5 touched `.ts` files gives 5 files, 0 errors and 0
warnings. All 5 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 6 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 `#`
(objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word
「option」. In this package: `#N-word` none, `#A/#B` none, `option #N`
none, at the base and at the head. The raw scan agrees: nothing sits
beyond the gate's grammar here.
- **「This card」 phrases are left.** 14 comment lines in 5 files of this
package speak of 「this card」, 「the card」 or 「The card」. They carry no
number and neither instrument sees them. One title line was worded so
that its neighbour keeps a referent (`null-seam.test.ts:4`, above); the
rest are unchanged, as in stages 8 and 9.
- **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: `objectstack-ai#10788` →
`3a7ec2d3b`; `objectstack-ai#10964` → `38bc74ed1`. The other three reuse sibling
stages' anchors: `objectstack-ai#10965` → `ab47f6974` and `objectstack-ai#10789` → `38bc74ed1` (the
`runtime` stage), `objectstack-ai#16650` → `001a83b04` (the `rest` stage).
- **Base.** The branch is on `main` at `4dfff176b`. `main` has since
moved four commits (`03cdb9a5c`, `b785c3b11`, `5a23096ca`, `01e78dcee`).
Their 40 files touch nothing under `service-package`, nor
`scripts/check-issue-citations.mjs` or `.changeset/config.json`; the
`doc-authoring-prose-id` baseline they shrink has no `service-package`
row. So no merge was taken; the merge queue rebuilds on the merged
generation.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…s to the commits that decided them (stage 10) (objectstack-ai#20750)

Part of objectstack-ai#20234

Clause-②: no

Stage 10 of the dead-citation sweep: the migration registry's
hand-written entries. Every tracker number in
`packages/spec/src/migrations/entries/**` that no longer exists on the
board now cites the commit that decided it, in ruling C+D form C (ruling
`5749154545` on objectstack-ai#19123). Where the number alone carried the meaning, the
line now says what was decided. That is 131 sites over 21 numbers. All
of them are comments; the entries' string literals carry no dead number.
`migrations/registry.ts` moves only by `gen:migration-registry`. No
entry id, literal, order, `conversionIds` or code token moves, and no
live citation is removed.

## Boundary

- **In:** all of `entries/**`. The gate's census reads 117 sites there,
under the claim's ~120 slicing threshold, so there is no slice. My raw
walk finds 14 more comment sites that the census does not count (see
*Census* below), which gives 131 in total. They are the same dead card
on the line after a counted site, plus one README line, so they are
rewritten with it.
- **Regenerated only:** `migrations/registry.ts`, 128 lines.
`spec-changes.json` and `docs/protocol-upgrade-guide.md` do not move,
because entry comments are never projected into them. Both `check:`
scripts pass with no regeneration.
- **Out, per the claim:** `registry.ts`'s hand-written parts. They still
hold 13 dead sites, listed under Acceptance notes. The six
`18.*-unit-in-key.ts` entries that PR objectstack-ai#20706 edits carry no dead site,
and I did not touch them. I did not touch `conversions/` or other lanes'
sites.

## The anchors (one per number, reused from earlier stages where they
anchored the same number)

| number | sites | anchor | what the rewritten line says it decided |
|---|---|---|---|
| objectstack-ai#13135 | 26 (13 are `re-charter objectstack-ai#13135`) | commit 9e0ba21 | retires
the paper metadata-customization protocol; re-charter of objectstack-ai#12057, which
stays |
| objectstack-ai#8495 | 23 | commit 4bfe1a5 (PR objectstack-ai#8666 kept) | the precedent: the
first 17.x-line narrowing registered under protocol 18 (its own second
commit says so) |
| objectstack-ai#8715 | 16 | commit 2c86fe3 | the ApiKeySchema retirement; its
message names the "route 3" kit (no carrier key, no tombstone, no D2) |
| objectstack-ai#14691 | 11 | commit b3a63d3 | retires the ten inert
`RestServerConfig` keys |
| objectstack-ai#10724 | 11 | commit be21955 | retires the nine dead `contributes`
members |
| objectstack-ai#14369 | 10 | commit a3d5724 | the liveness census that recorded the
15 `dead` rows |
| objectstack-ai#10485 | 6 | commit 35ad101 | retires the `themes` carrier and
`ThemeSchema` |
| objectstack-ai#11846 | 5 | commit 0c2334f | retires preview mode; its own text
carries the ruling record (objectstack-ai#12428 kept) |
| objectstack-ai#14676 | 5 | commit 13c48c2 | retires `connector.errorMapping` |
| objectstack-ai#11332 | 3 | commit dce5cd4 | retires the manifest's three dead
containers |
| objectstack-ai#6361 | 2 | commit 90bbf25 | retires the notification-list `cursor`
on both halves |
| objectstack-ai#10627 | 2 | commit be21955 | that commit records the controlled
monorepo census |
| objectstack-ai#14365 | 2 | commit f60ab90 | the open `z.partialRecord` proposal
that commit's changeset recorded; the retirement leaves no record to
reshape |
| objectstack-ai#14526 | 2 | commit db16b94 | the landing of the client envelope
convergence (anchor block, and the README's measured case) |
| objectstack-ai#6363 | 1 | commit 17d0954 | "ruled jointly with the `unreadCount`
fix" |
| objectstack-ai#6239 | 1 | commit f549a0d | the ViewProtocol retirement sweep |
| objectstack-ai#10726 | 1 | commit bc56e18 | retires `contributes.routes` (Option
B) |
| objectstack-ai#10812 | 1 | commit be21955 | "the cloud census", whose 2026-08-24
reading @5b5925a that commit's message records |
| objectstack-ai#9041 | 1 | commit d491625 | the url-branch refinement (objectstack-ai#9147, live,
stays) |
| objectstack-ai#14996 | 1 | commit db16b94 | the ADR-0087 registration, which
landed in the same squash (its body: "Registration requested on objectstack-ai#14996")
|
| objectstack-ai#14312 | 1 | commit e944fdb | the `oauth.*` binding, which left
`applications.delete` out as a behaviour change (PR objectstack-ai#15445 kept) |

Two lines change without a number, so the sentence still reads:
`patterns`' follow-on line and the `oauth` anchor block's continuation
line. In total 133 lines are removed and 133 added in 86 files, and
every file is balanced.

## Verification record (final head `1ee5841c09`; base `fbec216e2d`)

**Census** (the gate's own `check-issue-citations.mjs --census --json`,
board enumerated, 186 pages):

| subtree | base (00:21Z, frontier objectstack-ai#20740) | head (01:18Z, frontier
objectstack-ai#20743) |
|---|---|---|
| `entries/retired-keys` | 73 | 0 |
| `entries/retired-defs` | 40 | 0 |
| `entries/semantic` | 4 | 0 |
| `registry.ts`, generated regions | 114 | 0 |
| `registry.ts`, hand-written parts | 2 | 2 |
| `chain.ts`, `types.ts`, `index.ts`, `spec-changes.ts`, tests | 0 | 0 |
| **`migrations/`** | **233** | **2** |
| `packages/spec/src` | 235 | 4 |

- **Site-set difference:** 254 sites are only at base. 231 are this
diff's (117 plus 114 copies). The other 23 are `plugin-audit`'s, from
`main`'s objectstack-ai#20737. 0 sites are only at head.
- **Kind** (every census site is a comment): a TypeScript 6.0.3 walker
classes all 131 entry sites as comments. It also finds 13 dead string
sites, all in `registry.ts`'s hand-written rationale (see Acceptance
notes).
- **Probe:** REST `issues/N` without following redirects, over all 302
distinct in-repo numbers of 100 or more in `migrations/`. 277 answer 200
and 25 answer 404. Controls were read at start, every 50 and end: lit
objectstack-ai#20234 200 (8/8), dead objectstack-ai#8710 404 (8/8).
- **Blind spots:** the census does not count 14 of the 131 entry sites.
- 13 are `re-charter objectstack-ai#13135`, because `NON_CITATION_HEADS` reads
`re-charter #N` as an ordinal.
- 1 is in `entries/README.md`, which is outside the gate's
`packages/**/src/**/*.ts` surface.
- **Numbers:** no tracker number is added. The only numbers on added
lines are the live ones kept from the same lines: objectstack-ai#8666, objectstack-ai#12057, objectstack-ai#8586,
objectstack-ai#12428, objectstack-ai#9147 and objectstack-ai#15445. The diff-scoped gate judges them: "every
citation this change adds resolves".

**Residue** (scratch walker over the TypeScript parser, per file, base
vs head, 86 `.ts` files):
- The file with every comment range cut out, everything else byte for
byte, is IDENTICAL.
- The leaf-token stream is IDENTICAL: 38,417 tokens, aggregate
`67c1f6db944e93ed`.
- **Controls** mutate the head text in memory only, 259 of 259 as
expected:
  - Expected identical: a comment insertion in every file.
- Expected to differ: a string-literal edit, a template-literal edit, a
regex-literal edit (where the file has one) and an appended declaration.

**Regeneration** (`gen:migration-registry`):
- `check:migration-registry` exits 1 before and 0 after.
- The registry diff is 128 lines out and 128 in. As (old, new) pairs
they equal the entry diff's pairs, re-indented by four spaces.
- 0 changed lines fall outside the generated regions.
- 5 entry pairs are deliberately not carried: the README line, and the
semantic "Anchors" header lines, which sit above a blank line and so are
not in the carried comment run.
- `check:spec-changes` and `check:upgrade-guide` exit 0 with no
regeneration.

**Build and tests** (under `os-verify-lock`):
- Build: `turbo run build` over `./packages/*` and `./packages/*/*`, 71
of 71, at `845e90fea4` and again at `1ee5841c09`.
- `check:generated`: all 15 artifacts up to date, at both heads.
- spec `local` project: 576 files, 16,991 passed and 1 todo, at both
heads.
- spec `repo` project: 41 of its 45 files, 666 passed, at both heads.
- spec `typecheck`: exit 0; 53 files / 251 errors / 138 pinned
signatures held.

**Gates:** `dispatch-gates --commands` derives 82 gates, and the
derivation is identical before and after the second merge. At
`1ee5841c09` all 82 exit 0. `--ran` reconciles: 82 derived, 82 run, 0
NOT-MEASURED, a derived zero.

**Lint** (a proven narrowing):
- `eslint --no-inline-config --format json` over the 86 touched `.ts`
files: 86 files, 0 errors, 0 warnings.
- `isPathIgnored` is false for all 86.
- `eslint.config.mjs:327-328` states that type-aware linting is never
enabled, so a comment edit cannot move an untouched file's verdict.

**Changeset:** `patch`.
- In the built `dist`, the new wording (for example "the precedent of
commit 4bfe1a5, PR objectstack-ai#8666") appears in `dist/migrations/index.js` and
`.mjs`, and the old wording appears in 0 files.
- Control: an unchanged phrase appears in the same 2 files.
- So the published `@objectstack/spec/migrations` entry carries these
comments.

**Merges:** `origin/main` was merged twice through
`scripts/pm/os-regen-merge.sh`. Neither merge left a regeneration to
commit.
- Since then, `main` has advanced to `97005aed04`, and none of those
commits touches `packages/spec`.
- Driver-free `merge-tree` probes (from a bare `--shared` scratch clone
with no `merge.*` config) exit 0 against that `main` and against PR
objectstack-ai#20706's head `d8bac3877d`.

## Acceptance notes

- **The next stage for this card: 13 dead sites left in `registry.ts`'s
hand-written parts.** They are outside this claim, which says
`registry.ts` is regenerated only.
  - **Comments (form C), 2:**
- `:5940` objectstack-ai#8495, the step-18 doc comment on the persistence placeholder
refusal.
    - `:22819` objectstack-ai#6239, the `RETIRED_DEFS_BY_MAJOR[17]` doc comment.
  - **Author-shown rationale strings (form D), 11:**
- Step 17's `:344` objectstack-ai#6345. It projects into
`docs/protocol-upgrade-guide.md:68`, so that stage regenerates the
guide.
- Step 18's fragments: `:5121` objectstack-ai#14676, `:5455` objectstack-ai#12868, `:5526` objectstack-ai#10329,
`:5544` objectstack-ai#8495, `:5555` objectstack-ai#13135, `:5742` objectstack-ai#10724, `:5745` objectstack-ai#10627, `:5752`
objectstack-ai#10726, `:5799` objectstack-ai#10485, `:5816` objectstack-ai#10926. These are not projected into
either artifact yet.
- **Census blind spots, measured here and not filed** (a coverage
boundary, not one of the three filing classes; new gates default to no):
- `NON_CITATION_HEADS` skips `re-charter #N` even where N is a tracker
card.
  - `.md` files under `packages/**/src` are outside the surface.
- **NOT MEASURED locally, left to CI:**
- `scripts/build-schemas-check-mode.test.ts` hit the foreground cap
alone (exit 124 after 560 s, no output). It parses `registry.ts`'s
source; the residue check above shows that source's code and string
tokens are unchanged.
- The other three heavy `repo`-project files (`def-key-collisions`,
`publish-smoke-boot-failure`, `publish-smoke-port-collision`) were not
run, as in stages 8 and 9.
- **Hypothesis 6's baseline half is falsified by construction.**
`scripts/doc-authoring-prose-id.baseline.json` has no `packages/spec`
row, because its leg excludes `packages/spec`. `check:doc-authoring`
reads strings only, so a comment-only diff cannot move it. It read 808
sites across 229 files at `845e90fea4` and 794 across 227 at
`1ee5841c09`. The difference is `main`'s own re-anchor commits; this
diff touches no row.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ts that decided them (objectstack-ai#20757)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the eleventh stage of the `domain:services` lane of the
dead-citation sweep. It covers `packages/plugins/plugin-email/src/**`
and nothing else. By the seat's census at the claim (`5902547086`), 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 objectstack-ai#19123), by the method of stages 1 to 10 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as
`9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR
objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`). That is **16 sites on
16 lines in 8 files, covering 4 numbers**:

- 7 census sites (every census site this package has);
- 9 sites in test comments, which the census defers. Three of them carry
`objectstack-ai#13190`, a dead number that stands only in test files here, so the
census never judged it; it was read on its own (404);
- no site the gate's grammar cannot see (the package has none that is
dead, see Acceptance notes).

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: **4 distinct shas**. No number in this package has an ADR or
ruling record of its own (a grep of `docs/adr/` and
`scripts/adr-anchors/` finds only ADR-0131 naming `objectstack-ai#11741`, as evidence
in its D7, not as the record of that decision; nothing else under
`docs/` names the four), 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
(16 lines out, 16 in, over 8 files), so no line citation into these
files moves. Every one of the 16 changed lines carried a dead citation;
there is no reflow line. No code token moves (see the guard below).

**No citation number is added.** The added lines carry no tracker number
at all. Over the whole diff, added minus removed is negative for the
four dead numbers and zero for every other number, and no number is new
to the diff. No PR number is the citation on an added line: the two `PR
objectstack-ai#8675` spellings became that pull request's squash commit.

10 dead sites are left on purpose, all of them `describe` / `it` titles
(see the list below).

One more file: a `patch` changeset for `@objectstack/plugin-email`,
because the rewritten prose ships (see Changeset below).

## Census: `plugin-email`, 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-email/`. Each run counts as a reading only
because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
plugin-email sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `97005aed0`, run 2026-09-30T02:00:45Z to 02:04:02Z |
enumerated, 186 pages, frontier objectstack-ai#20748 (newest objectstack-ai#20747 before, objectstack-ai#20748
after: a pull request opened at 02:03:20Z, inside the run) | 1,064 |
**7** | 7 | 4 | 3 |
| after | head `15a7d69a7`, run 02:11:19Z to 02:14:30Z | enumerated, 186
pages, frontier objectstack-ai#20753 (newest objectstack-ai#20753 before and after) | 1,057 | **0**
| 0 | 0 | 0 |

The before count matches the seat's census and A1 (7 sites: `objectstack-ai#13189` ×4,
`objectstack-ai#11741` ×2, `objectstack-ai#8675` ×1). The before run's board moved during the run;
its frontier equals the newest number at the run's end, which is A1's
criterion (stage 7's precedent). The whole-repo drop is 7, exactly this
diff's census sites. The `resolves` tally is 33,029 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 `15a7d69a7`; the head
`23283d394` 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-email/src` (50 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,072 rows) and did not report
it. The eleven numbers the census never saw, because they stand only in
test files or as the second half of a slash pair here, were read one by
one on the issues endpoint: `objectstack-ai#13190` answers 404; `objectstack-ai#5169`, `objectstack-ai#5286`,
`objectstack-ai#10619`, `objectstack-ai#16506`, `objectstack-ai#20374`, `objectstack-ai#5197` answer 200 as issues, and `objectstack-ai#8348`,
`objectstack-ai#5191`, `objectstack-ai#5211`, `objectstack-ai#5232` as pull requests.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `97005aed0` | 360 | **26** | 7 | 9 | 0 | 10 |
| after, `15a7d69a7` | 344 | **10** | 0 | 0 | 0 | 10 |

Its src-comment column equals the census's 7, which is the control on
the second instrument. The 323 live citations are the same in both
readings, and the drop of 16 citations is exactly the rewritten sites.
11 extracted tokens are not tracker references at all and are not
judged: the HTML entity `&objectstack-ai#39;` (6 sites in the template engine and its
tests) and the fixture subjects `Invoice objectstack-ai#42` to `Invoice objectstack-ai#45` (5
sites). A third, raw reading (every `#` followed by 2 to 6 digits,
whatever surrounds it) finds 371 occurrences and 26 dead before, 355 and
10 after. Beyond the gate's grammar it sees 11 tokens, none dead: the
nine second numbers of the `#A/#B` lines (all live), the excused `Prime
Directive objectstack-ai#12`, and the CSS colour `#2563eb`.

## 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 16 line and anchor
pairs).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#13189` | 13/4 | 8/5 | `33fbd3566` (PR objectstack-ai#13375): the SMTP port guard
tests integrality (`Number.isInteger`), so a fractional port such as
`587.5` is refused at construction, and the generated refusal sentence
reads `(expected an integer 1-65535)`, the range still rendered from the
constants. Its changeset headline names `objectstack-ai#13189`; its diff writes the
integrality docblocks the rewritten lines sit in. New to the sweep |
| `objectstack-ai#13190` | 5/1 | 3/2 | `56c5b1dbe` (PR objectstack-ai#13316):
`smtpOptionsFromMailSettings` passes a present-but-unreadable
`smtp_port` through to the guard instead of omitting it (which had
silently fallen back to 587); absent and `''` still mean "not set", and
no second refusal was added. Its changeset headline names `objectstack-ai#13190`; its
diff writes the `objectstack-ai#13190` comment block itself. New to the sweep |
| `objectstack-ai#11741` | 6/3 | 3/3 | `b706af987` (PR objectstack-ai#11839): `SendEmailInput` /
`SendTemplateInput` gain an optional `organizationId`, which
`plugin-email`'s writer stamps verbatim onto `sys_email.organization_id`
(pass-through only, no resolution or fabrication), and `sendTemplate`
forwards it as a producer of `send()`. Its message names `objectstack-ai#11741` as the
card that commit closed; `git blame` puts all three rewritten lines in
it. The `plugin-auth` stage's anchor for the same number |
| `objectstack-ai#8675` | 2/2 | 2/0 | `c9f595083`: the squash commit of the pull
request that was `objectstack-ai#8675` (its subject ends `(objectstack-ai#7987) (objectstack-ai#8675)`):
`sys_account`'s OAuth token columns are declared `internal: true`. Its
diff records the trap both lines describe: those columns are `required:
false`, so inferring "key missing, therefore the strip ran" broke
ordinary sign-in (16 red tests), which is why the readback carries the
`absenceProvesStrip` discriminator. New to the sweep |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 4), and every one is an
ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 4;
control leg: stage 1's landing `422db788a` exit 0; the history is
complete, `--is-shallow-repository` false, 15,155 commits). Each of the
4 numbers answers 404 on the issues endpoint, which serves pull requests
too. Independently, the package's own shipped `CHANGELOG.md` pairs
`b706af9`, `33fbd35` and `56c5b1d` with the same three decisions.

## Wordings to check

- **Tag swaps in parentheses.** 「(objectstack-ai#13189)」 became 「(commit 33fbd35)」
at `transports/smtp-port-contract.ts:87` (a section heading), `:134` and
`transports/smtp.ts:68`.
- **Line openers.** 「objectstack-ai#11741 —」 became 「Commit b706af9 —」 at
`email-service.ts:742` and `:1439`; 「objectstack-ai#13190 —」 became 「Commit 56c5b1d
—」 at `transports/smtp.test.ts:221`; 「## objectstack-ai#13189 —」 became 「## Commit
33fbd35 —」 at `transports/smtp-port-contract.test.ts:34`.
- **`email-service.test.ts:342`**, a section rule: 「── objectstack-ai#11741 —」 became
「── Commit b706af9 —」, and its trailing rule was shortened by 10
characters so the line keeps its width exactly.
- **`internal-header-readback.ts:37`.** 「(PR objectstack-ai#8675 hit exactly this on
`sys_account`'s optional」 became 「(Commit c9f5950 records exactly this
on `sys_account`'s optional」: a commit does not "hit" a trap, it records
one, and that commit's own diff is where the 16 red tests are recorded.
- **`email-headers-internal.integration.test.ts:251`.** 「The regression
PR objectstack-ai#8675 measured on a sibling card」 became 「The regression commit
c9f5950 records from a sibling card」, the same reading.
- **`transports/smtp-port-contract.test.ts:228`.** 「objectstack-ai#13189 is the card
that SPENDS that」 became 「Commit 33fbd35 is the change that SPENDS
that」, so the noun matches the anchor.
- **`transports/smtp.ts:127`, `transports/smtp.test.ts:272`, `:276`,
`:281`, `:283`.** The number became 「commit SHA」 in place (「until commit
33fbd35:」, 「The bucket commit 56c5b1d never had to name」, 「Commit
33fbd35 made the guard test」, 「Commit 56c5b1d's rule is that」,
「commit 33fbd35 changed which numbers」).

## The 10 sites left

- **Test strings, 10 sites on 9 lines**, all `describe` / `it` titles,
left as stages 1 to 10 left theirs: `email-service.test.ts:349` and
`send-template.test.ts:63`, `:88` (`objectstack-ai#11741`);
`transports/smtp-port-contract.test.ts:225`, `:309`, `:340` (`objectstack-ai#13189`);
`transports/smtp.test.ts:230` (`objectstack-ai#13190`), `:271` (`objectstack-ai#13189`), `:293`
(`objectstack-ai#13190` and `objectstack-ai#13189`).
- No source string, operator log string, assertion message, quoted
maintainer ruling or generated file in this package carries a dead
number.
- Outside `src`, the package's `CHANGELOG.md` names three of these
numbers on 5 lines. It is release-owned and deliberately not edited here
(see Acceptance notes).

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited), base
`97005aed0` against head. String and template literals are therefore
read in full. It ran over all 8 touched `.ts` files.

- Real run: 7,035 base leaf tokens, **0 files with a token change**
(exit 0).
- Comment control in `email-service.ts` (「no resolution, no default, no
fabrication」 to 「… no default and no fabrication」): 0 files changed, as
expected (exit 0).
- Positive control, a code token added in `transports/smtp.ts`
(`isValidSmtpPort(port)` given `as number`): DIFFER, 587 to 588 leaf
tokens (exit 1).
- Positive control, one digit changed inside a kept test title
(`transports/smtp.test.ts:293`, `objectstack-ai#13189` to `objectstack-ai#13188`): DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blob (`1e99bd5e2bcb`, `46c13267611b`, `da5314910bc4`), with
`git diff HEAD` empty and a clean tree afterwards.

## Changeset

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

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`, and the package is not private. After the build,
`b706af987` appears twice in each of `dist/index.js` and
`dist/index.mjs` (the two inline comments in `email-service.ts`, which
the bundle keeps). `c9f595083` appears once in each of `dist/index.d.ts`
and `dist/index.d.mts` (the `internal-header-readback.ts` docblock), and
so does `33fbd3566` (the docblock on `SmtpTransportOptions.port`).
`56c5b1dbe` reaches nothing (test files only). Positive controls, one
unchanged line beside each shipped rewrite, land exactly where their
neighbours do: 「context, so the input's organization is the one fact it
may stamp:」 and 「caller's organization so the sys_email row it persists
is stamped.」 once in each JS file; 「token columns: inheriting」 and the
unchanged line just above the rewritten one in the `port` docblock once
in each declaration file. A never-written negative phrase appears
nowhere in `dist`. None of the 4 dead numbers is left in `dist`.

## Gates (head `23283d394`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
exits 0. `node scripts/check-issue-citations.mjs` exits 0: the
diff-scoped run found no citation added against `97005aed0` (4 files
read; test files are a deferred surface).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `23283d394` derived 61 commands:
all 55 derived at dispatch, plus `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 61 exit 0. `--ran`, fed each command with its exit code,
reports 61 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-email test`: 31 files pass and 510
tests pass. `vitest list --filesOnly` names 31 files, all the tracked
test files, the 4 touched ones included.
- `pnpm --filter @objectstack/plugin-email typecheck` exits 0 (`tsc` on
`tsconfig.json`, then `check:test-typecheck` on `tsconfig.test.json`: 0
files and 0 errors in its debt ledger). `tsc --listFiles` holds all 8
touched files in both programs, and the test program holds all 50 files
under `src/`.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 8 touched `.ts` files, gives 8 files, 0 errors and 0 warnings.
All 8 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 9 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 `#`
(objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word
「option」. In this package: `#N-word` none, `#A/#B` 9 lines, `option #N`
none, at the base and at the head, which is the claim's 0 / 9 / 0. Every
second number on the 9 slash lines answers 200 (`objectstack-ai#5197` ×2, `objectstack-ai#5191`,
`objectstack-ai#5211`, `objectstack-ai#5232` ×2, `objectstack-ai#5177`, `objectstack-ai#4251`, `objectstack-ai#5094`), so nothing there needed
rewriting.
- **ADR-0131 names `objectstack-ai#11741`.** Its D7 cites `objectstack-ai#11741` as the writer fact
that keeps `sys_email` tenant data. That is evidence inside a later
record, not the record of what `objectstack-ai#11741` decided, so it is not this
stage's anchor, and `docs/adr/**` is a governed Tier H surface outside
this card's stages. It joins the ADR-tree residue the seat already
carries (ADR-0131's `objectstack-ai#14484`, stage 2).
- **`CHANGELOG.md` is left.**
`packages/plugins/plugin-email/CHANGELOG.md` names `objectstack-ai#11741`, `objectstack-ai#13189`,
`objectstack-ai#13190` and `objectstack-ai#8675` on 5 lines. It is release-owned (AGENTS.md,
Documentation Guardrails), a deferred surface of the citation gate, and
⛔ not part of this stage.
- **「This card」 phrases are left.** 20 comment lines in 8 files of this
package speak of 「this card」, 「the card」 or 「the two cards」. They carry
no number and neither instrument sees them. Inside the `objectstack-ai#13189` test
block, they still have the kept `(objectstack-ai#13189)` title as their referent; the
one rewritten line that said 「the card」 now says 「the change」 (above).
The rest are unchanged, as in stages 8 to 10.
- **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: `objectstack-ai#13189` →
`33fbd3566`; `objectstack-ai#13190` → `56c5b1dbe`; `objectstack-ai#8675` → `c9f595083`. `objectstack-ai#11741` →
`b706af987` reuses the `plugin-auth` stage's anchor.
- **Base.** The branch is on `main` at `97005aed0`. `main` has since
moved two commits (`9c8f113c6`, `a6866da0c`). Their 14 files touch
nothing under `plugin-email`, nor `scripts/check-issue-citations.mjs`,
`.changeset/config.json` or the `doc-authoring-prose-id` baseline, and
the three console-injection scripts they change are not among this
diff's 61 derived families. So no merge was taken; the merge queue
rebuilds on the merged generation.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ommits that decided them (objectstack-ai#20775)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the twelfth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/triggers/trigger-schedule/src/**` and nothing else. By the
seat's claim (`5903462246`), it is the largest package in the lane that
no in-flight work holds, now that objectstack-ai#20599's PR objectstack-ai#20746 (which edited
`time-relative-trigger.ts`) has 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 11 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as
`9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR
objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`, PR objectstack-ai#20757 as
`cba417a8f`). That is **32 sites on 32 lines in 6 files, covering 2
numbers**:

- 18 census sites (every census site this package has);
- 14 sites in test comments, which the census defers;
- no site the gate's grammar cannot see (the package has none, see
Acceptance notes).

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: **2 distinct shas**. Neither number has an ADR or ruling record
of its own (a grep of `docs/adr/`, `scripts/adr-anchors/` and the rest
of `docs/` for both numbers finds nothing, and no ADR records the
acting-organization decision or the driver-memory per-call refusal), so
both anchors are commits, per ruling C's order. No number was dropped.

Only comments changed. Every touched source file keeps its line count
(32 lines out, 32 in, over 6 files), so no line citation into these
files moves. Every one of the 32 changed lines carried a dead citation;
there is no reflow line. No code token moves (see the guard below).

**No citation number is added.** The only tracker number on an added
line is the live `objectstack-ai#8844`, on the line it already stood on. Added minus
removed is negative for the two dead numbers and zero for every other
number, and no number is new to the diff. No PR number is the citation
on an added line.

4 dead sites are left on purpose: three `describe` titles, and one
comment that quotes one of those titles verbatim (see the list below).

One more file: a `patch` changeset for `@objectstack/trigger-schedule`,
because the rewritten prose ships (see Changeset below).

## Census: `trigger-schedule`, 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/triggers/trigger-schedule/`. Each run counts as a reading only
because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
trigger-schedule sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `cba417a8f`, run 2026-09-30T03:30:14Z to 03:33:24Z |
enumerated, 186 pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after)
| 823 | **18** | 18 | 2 | 2 |
| after | head `226be8050`, run 03:37:59Z to 03:41:09Z | enumerated, 186
pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after) | 805 | **0** |
0 | 0 | 0 |

The before count matches the seat's census and A1 (18 sites: `objectstack-ai#16659`
×17 and `objectstack-ai#16589` ×1, in `schedule-trigger.ts` ×5 and
`time-relative-trigger.ts` ×13). A1 noted that PR objectstack-ai#20746 edited
`time-relative-trigger.ts` today; the before count above is taken on the
base that already holds that edit. The whole-repo drop is 18, exactly
this diff's census sites. The `resolves` tally is 33,038 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 `226be8050`; the head
`14314f49c` 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 `trigger-schedule/src` (14 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, 36,840 rows) and did not report
it. Every number this package cites is covered by one or the other, so
no number needed a separate read to be judged; the two dead numbers were
also read one by one on the issues endpoint, and each answers 404.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `cba417a8f` | 159 | **36** | 18 | 15 | 0 | 3 |
| after, `226be8050` | 127 | **4** | 0 | 1 | 0 | 3 |

Its src-comment column equals the census's 18, which is the control on
the second instrument. The 123 live citations are the same in both
readings, and the drop of 32 citations is exactly the rewritten sites. A
third, raw reading (every `#` followed by 2 to 6 digits, whatever
surrounds it) finds 162 occurrences and 36 dead before, 130 and 4 after.
Beyond the gate's grammar it sees 3 tokens, none a tracker reference:
the maintainer decision-batch ordinals `batch objectstack-ai#13`, `objectstack-ai#116` and `objectstack-ai#118`,
which the gate's `NON_CITATION_HEADS` excuses by design.

## 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 (21 lines blame to the anchor itself; for the other 11,
`merge-base --is-ancestor` of anchor and blamed commit exits 0).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#16659` | 34/6 | 30/4 | `ecdfc9411` (PR objectstack-ai#17334): a time-triggered
flow (`schedule` or `time_relative`) declares its acting organization on
its start node as `config.organization`; the engine lifts it onto the
binding; both time triggers refuse to bind a flow that declares none,
naming it at `error` and THROWING so the engine records the refusal
instead of reporting the flow bound; the run carries the declared
organization as `tenantId`; and the time-relative sweep's own query
carries it too, so the sweep SELECTS inside that organization (the
review finding F2 its diff names), with a store that cannot honour the
scope reported at `error` and an object the engine exempts from scoping
disclosed at bind. Its body names `objectstack-ai#16659` twice (the three consequences
pinned on both drivers, and the proof registered), and its diff names it
on 65 added lines. The anchor the spec stage (`0f6dcac5e`) and the lint
stage (`f29c83db1`) already give the same number |
| `objectstack-ai#16589` | 2/2 | 2/0 | `555a89cbd` (PR objectstack-ai#17005): `driver-memory` gains
a third seam, `assertCallNotTenantScoped`, called first in every driver
door that accepts `DriverOptions`, which REFUSES a call the engine
tenant-scoped instead of discarding the scope and answering every
organization's rows; row-level isolation is deliberately not
implemented. Its message does not carry the number, but its own diff
writes the mechanism the two lines describe and names `objectstack-ai#16589` 30 times
(the `[objectstack-ai#16589] Seam 3` markers and the guard's docblock), so it is the
commit that decided it. New to the sweep |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 2), and both are ancestors of
the base (`merge-base --is-ancestor`, exit 0 for both; control leg:
stage 1's landing `422db788a` exit 0; reverse leg, base against
`ecdfc9411`, exit 1; the history is complete, `--is-shallow-repository`
false, 15,160 commits; each anchor lies deeper than the control, 1,616
and 1,814 commits behind the base). Each of the 2 numbers answers 404 on
the issues endpoint, which serves pull requests too. Independently, the
package's own shipped `CHANGELOG.md` pairs `ecdfc94` with `objectstack-ai#16659` (line
149) and `assertCallNotTenantScoped` with `objectstack-ai#16589` (line 238).

## Wordings to check

- **Tag swaps in brackets or parentheses.** 「[objectstack-ai#16659]」 became 「[commit
ecdfc94]」 on 18 lines, 「(objectstack-ai#16659)」 became 「(commit ecdfc94)」 at
`schedule-trigger.ts:251`, `:372` and `time-relative-trigger.ts:50`, and
「(objectstack-ai#16589)」 became 「(commit 555a89c)」 at `time-relative-trigger.ts:615`
and `time-relative-trigger.test.ts:988`.
- **Section rules.** `schedule-trigger.test.ts:327`,
`time-relative-trigger.test.ts:794` and `:862`: the 16-character phrase
replaces the 6-character number and the trailing rule loses 10
characters, so each line keeps its width exactly. The `:862` heading
keeps 「F2」 beside the sha; F2 is the selection finding `ecdfc9411`'s own
diff names.
- **「before objectstack-ai#16659」** at `time-relative-trigger.ts:365` and `:543`
became 「before commit ecdfc94」: before that commit the sweep queried
with `isSystem` alone, which is the unscoped selection both sentences
describe.
- **「the objectstack-ai#16659 defect」** at `time-relative-trigger.ts:561` and
`time-relative-trigger.test.ts:1371` became 「the defect commit ecdfc94
fixed」: a commit fixes a defect, it is not one, and the widening both
sentences name is the selection half that commit closed.
- **`schedule-trigger.test.ts:512`.** 「the exact defect objectstack-ai#16659's own
refusal was shaped to avoid」 became 「the exact defect commit ecdfc94's
own refusal was shaped to avoid」: the defect is a refusal that logs and
arms anyway, and that commit is where the refusal became a throw so the
engine records it.
- **`schedule-trigger.test.ts:588`.** 「(the objectstack-ai#16659 suite above)」 became
「(commit ecdfc94's refusal suite above)」, so the pointer still lands
on the refusal suite at `:337`.

## The 4 sites left

- **Test strings, 3 sites on 3 lines**, all `describe` titles, left as
stages 1 to 11 left theirs: `schedule-trigger.test.ts:337` and `:462`,
`time-relative-trigger.test.ts:805` (all `objectstack-ai#16659`).
- **One comment that quotes a kept title verbatim:**
`schedule-trigger.test.ts:71` points the reader at 「`ScheduleTrigger —
the acting-organization refusal (objectstack-ai#16659)` below」, the exact text of the
`describe` title at `:337`. The number there belongs to the quotation,
so it stays with the title it quotes: rewriting it would point at a
title that does not exist. It moves when the title does.
- No source string, operator log string, assertion message, quoted
maintainer ruling or generated file in this package carries a dead
number.
- Outside `src`, the package's `CHANGELOG.md` names `objectstack-ai#16659` on 6 lines
and `objectstack-ai#16589` on 2 (lines 149, 153, 238, 273, 297, 300, 302, 304). It is
release-owned and deliberately not edited here (see Acceptance notes).
The package `README.md`, which also ships, names neither number.

## Mechanical guard: no code token moves

The guard compares, base `cba417a8f` against head, over all 6 touched
`.ts` files:

- **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited). String
and template literals are therefore read in full.
- **Reading 2**, the full token stream in parser context (a
`getChildren` walk, so punctuation and keywords are included; JSDoc
nodes skipped).

Results:

- Real run: 10,192 base leaf tokens, **0 files with a token change** on
either reading (exit 0).
- Comment control in `schedule-trigger.ts` (「the same way `schedule`
is.」 to 「the same way as `schedule`.」): 0 files changed, as expected
(exit 0).
- Positive control, a code token added in `time-relative-trigger.ts`
(`resolveBindingOrganization(binding)` given `as FlowTriggerBinding`):
DIFFER, 1,215 to 1,216 leaf tokens and 2,760 to 2,762 full tokens (exit
1).
- Positive control, one digit changed inside a kept test title
(`schedule-trigger.test.ts:462`, `objectstack-ai#16659` to `objectstack-ai#16658`): DIFFER on the
string literal (exit 1).

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blob (`651170483856`, `c85aadbd168d`, `78a5dea4a463`), with
`git diff HEAD` empty and a clean tree afterwards.

A first version of reading 2 used TypeScript's context-free scanner and
was discarded before any control ran: it opened template tokens on
backticks it could not place and swallowed comment text into them, so it
reported comment edits as token changes (4 files) while reading 1 read
0. The parser-context stream replaced it, and every figure above is from
the replacement.

## Changeset

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

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`, and the package is not private. After the build:

- `ecdfc9411` appears 3 times in each of `dist/index.js` and
`dist/index.mjs`: the inline comments at `schedule-trigger.ts:777` and
`time-relative-trigger.ts:585` and `:702`, which the bundle keeps.
- It appears twice in each of `dist/index.d.ts` and `dist/index.d.mts`:
the `FlowTriggerBinding.organization` docblock
(`schedule-trigger.ts:32`) and the sweep-context docblock
(`time-relative-trigger.ts:50`).
- `555a89cbd` appears once in each JS entry
(`time-relative-trigger.ts:615`).
- Positive controls, one unchanged line beside each shipped rewrite,
land exactly where their neighbours do: four neighbours once in each JS
file and 0 in the declaration files, and two once in each declaration
file and 0 in the JS files.
- A never-written negative phrase appears nowhere in `dist`.
- Neither dead number is left in `dist`.

## Gates (head `14314f49c`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
exits 0. `node scripts/check-issue-citations.mjs` exits 0: the
diff-scoped run judged 1 added citation across 2 files, the live
`objectstack-ai#8844`, and it resolves.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `14314f49c` (after a fresh fetch)
derived 59 commands. They are all 53 derived at dispatch, plus
`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 59 exit
0.
- `--ran`, fed each command with its exit code, reports 59 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/trigger-schedule test`: 8 files pass and
170 tests pass. `vitest list --filesOnly` names 8 files, all the tracked
test files, the 4 touched ones included.
- `pnpm --filter @objectstack/trigger-schedule typecheck` exits 0, and
`tsc --listFiles` holds all 14 files under `src/`, the 6 touched ones
included.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 6 touched `.ts` files, gives 6 files, 0 errors and 0 warnings
(its `--format json` output). All 6 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 7 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 `#`
(objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word
「option」. In this package: `#N-word` none, `#A/#B` none, `option #N`
none, at the base and at the head, which is the claim's 0 / 0 / 0. The
two `pre-objectstack-ai#10220` spellings in `time-relative-trigger.test.ts` are
extracted by the gate as this repository's `objectstack-ai#10220`, which the census
judges live.
- **`CHANGELOG.md` is left.**
`packages/triggers/trigger-schedule/CHANGELOG.md` names `objectstack-ai#16659` and
`objectstack-ai#16589` on 8 lines. It is release-owned (AGENTS.md, Documentation
Guardrails), a deferred surface of the citation gate, and ⛔ not part of
this stage.
- **「The card」 phrases are left.** 8 comment lines in 5 files of this
package speak of 「this card」, 「that card」 or 「the card」. They carry no
number and neither instrument sees them. The one beside a rewritten
line, `schedule-trigger.test.ts:329` (「the card's consequence (3)」),
sits under the heading `:327` that now names `ecdfc9411`, whose own
message pins those three consequences, so it keeps a referent. The rest
are unchanged, as in stages 8 to 11.
- **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: `objectstack-ai#16589` →
`555a89cbd` is new to the sweep; `driver-memory`'s own `src` still names
`objectstack-ai#16589` on 29 lines in 4 files (26 of them comments; corrected by the
seat from the dev report, which measured it), all outside this lane's
stage surface. `objectstack-ai#16659` → `ecdfc9411` reuses the spec and lint stages'
anchor.
- **Base.** The branch is on `main` at `cba417a8f`. `main` has since
moved three commits (`0d9349fea`, `7053333e1`, `f284ab26d`). Their 9
files are one changeset, ADR-0053, and sources and tests under
`service-analytics` and `service-automation`. They touch nothing under
`trigger-schedule`, nor `scripts/check-issue-citations.mjs`,
`.changeset/config.json` or the `doc-authoring-prose-id` baseline.
`service-automation` is a dev dependency of this package, but this diff
moves no code token, so nothing here can interact with it. No merge was
taken; the merge queue rebuilds on the merged generation.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…the commits that decided them (objectstack-ai#20789)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the thirteenth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/triggers/trigger-record-change/src/**` and nothing else. By
the seat's claim (`5904332626`), it is the largest package in the lane
that no in-flight work holds, while `service-automation` stays held
behind objectstack-ai#20726. 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 12 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as
`9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR
objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`, PR objectstack-ai#20757 as
`cba417a8f`, PR objectstack-ai#20775 as `91e8fa194`). That is **29 sites on 29 lines
in 5 files, covering 3 numbers**:

- 6 census sites (every census site this package has, all `objectstack-ai#14744`);
- 23 sites in test comments, which the census defers: 17 more of
`objectstack-ai#14744`, 1 of `objectstack-ai#13657`, and 5 of `objectstack-ai#11081`. `objectstack-ai#11081` stands only in a
test file here, so the census never judged it; it was read on its own
and answers 404.

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: **4 distinct shas**. None of the three numbers has an ADR or
ruling record of its own, so every anchor is a commit, per ruling C's
order (see the per-number table). No number was dropped.

Only comments changed. Every touched source file keeps its line count
(30 lines out, 30 in, over 5 files), so no line citation into these
files moves. 29 of the 30 changed lines carried a dead citation; the
thirtieth keeps a referent the rewrite would otherwise have removed (see
Wordings). No code token moves (see the guard below).

**No citation number is added.** The only tracker numbers on added lines
are the live `objectstack-ai#15356` (3 times) and `objectstack-ai#8738` (once), each on the line it
already stood on. Added minus removed is negative for the three dead
numbers and zero for every other number, and no number is new to the
diff. No PR number is the citation on an added line.

4 dead sites are left on purpose, all test titles (see the list below).

One more file: a `patch` changeset for
`@objectstack/trigger-record-change`, because the rewritten prose ships
(see Changeset below).

## Census: `trigger-record-change`, 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/triggers/trigger-record-change/`. Each run counts as a reading
only because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.
In all three runs a new number was opened while the run was enumerating;
each frontier equals the newest number at the run's end, which is the
criterion (stages 7 and 11 met the same shape).

| reading | tree | board | whole-repo `allocated-but-absent` |
trigger-record-change sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `91e8fa194`, run 2026-09-30T04:58:08Z to 05:01:28Z |
enumerated, 187 pages, frontier objectstack-ai#20779 (newest objectstack-ai#20778 before, objectstack-ai#20779
after) | 802 | **6** | 6 | 2 | 1 |
| after | `bb9d39a87` (the comments commit), run 05:07:54Z to 05:11:48Z
| enumerated, 187 pages, frontier objectstack-ai#20780 (newest objectstack-ai#20779 before, objectstack-ai#20780
after) | 796 | **0** | 0 | 0 | 0 |
| after, final head | head `bbfe7cb24`, run 05:39:10Z to 05:42:26Z |
enumerated, 187 pages, frontier objectstack-ai#20784 (newest objectstack-ai#20783 before, objectstack-ai#20784
after) | 796 | **0** | 0 | 0 | 0 |

The before count matches the seat's census and A1 (6 sites, all
`objectstack-ai#14744`: `decouple-flow-record.ts` ×1 and `record-change-trigger.ts`
×5). The whole-repo drop is 6, exactly this diff's census sites. The
`resolves` tally is 33,055 in all three runs, and
`resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did
not move either. No run was truncated or discarded: all three
enumerations read 187 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 `trigger-record-change/src` (14 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 the gate's own
census-scope extraction (36,836 citations over 2,617 files) judged it
and the census did not report it. Five numbers are covered by neither,
because they stand only in test files: each was read on its own.
`objectstack-ai#11081` answers 404; `objectstack-ai#5715` and `objectstack-ai#17982` answer 200 as pull requests;
`objectstack-ai#5785` and `objectstack-ai#17985` answer 200 as issues. The three dead numbers were
also read one by one, and each answers 404.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `91e8fa194` | 186 | **32** | 6 | 23 | 0 | 3 |
| after, `bbfe7cb24` | 157 | **3** | 0 | 0 | 0 | 3 |

Its src-comment column equals the census's 6, which is the control on
the second instrument. The 154 live citations are the same in both
readings, and the drop of 29 citations is exactly the rewritten sites. A
third, raw reading (every `#` followed by 2 to 6 digits, whatever
surrounds it) finds 195 occurrences before and 166 after. Beyond the
gate's grammar it sees 9 tokens, the same at base and head: the second
number of five `#A/#B` pairs (only one is dead, the kept title at
`before-update-flow-payload-reach.test.ts:872`), two `/objectstack-ai#3457/` regex
literals in assertions (live), and two `PD objectstack-ai#12` ordinals.

## 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.

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#14744` | 27/4 | 22/4 | `4f85e4d11` (PR objectstack-ai#15475): the flow-facing
`record` (and its `params` alias) and `previous` are decoupled from the
engine's own objects before a flow runs (`decoupleFromEngineState`:
arrays, plain objects, `Date`, `RegExp`, `Map` and `Set` are copied,
primitives, functions and other class instances shared), so a flow
mutating a nested value in place no longer writes the batch payload that
ADR-0058 Addendum II D3 shares across every row of a `multi: true`
update. A COPY rather than a FREEZE, because `expandDeclaredLookups`
writes into the record it is handed. The engine's write shape is
unchanged, and the same-key per-row-value residue is deliberately left
unguarded. Its changeset records the maintainer's option-A ruling on
`objectstack-ai#14744` in its own words, its diff names `objectstack-ai#14744` on 29 added lines,
and it created `decouple-flow-record.ts` and both of this package's pin
files. `git blame` at the base puts every one of the 22 lines in this
commit. New to the sweep |
| `objectstack-ai#14744` (the census line) | (in the row above) | 1/0 | `03c1b0f6f`
(PR objectstack-ai#15301): the census of same-key / per-row-VALUE `beforeUpdate`
rewrites, which found ZERO across 23 production registration sites and
recorded the `buildContext` overlay conclusion as a source reading, not
a measurement. Its message names `objectstack-ai#14744` four times and states that
result word for word. `before-update-flow-payload-reach.test.ts:29`
describes this census, not the fix, so it cites the census commit, by
the per-arm precedent of stages 5 and 9. The line was written by
`4f85e4d11`, which descends from `03c1b0f6f` (`merge-base --is-ancestor`
exit 0). New to the sweep |
| `objectstack-ai#13657` | 1/1 | 1/0 | `b003cf2e8` (PR objectstack-ai#13864): the post-hook half of
the declared-field door, which refuses an undeclared field a before-hook
writes, with one envelope on every driver. Its message names `objectstack-ai#13657`
seven times. The runtime and lint stages' anchor for the same number.
The line was written by `4f85e4d11`, which descends from it (exit 0) |
| `objectstack-ai#11081` | 5/1 | 5/0 | `c28e4cfae` (PR objectstack-ai#11570): the two
SqlDriver-backed fixtures stop blanket-silencing their kernel and carry
`@objectstack/runtime`'s shared expected-noise capture, which withholds
only a declared table's own `no such table` line, forwards every other
driver fault, and lets `afterAll` assert each channel fired. Its message
names `objectstack-ai#11081`, and its diff writes the five `[objectstack-ai#11081]` tags in this
very file; `git blame` at the base puts all five lines in it. Stage 7's
anchor for the same number |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 4), and all 4 are ancestors of
the base (`merge-base --is-ancestor`, exit 0 for each; reverse leg, base
against each anchor, exit 1 for each; control legs exit 0: stage 1's
landing `422db788a`, and the repository's root commit, which lies deeper
than every anchor; the history is complete, `--is-shallow-repository`
false, 15,167 commits; the anchors lie 2,516, 2,585, 3,082 and 4,207
commits behind the base). Each of the 3 numbers answers 404 on the
issues endpoint, which serves pull requests too.

No ADR, `scripts/adr-anchors/` file or other `docs/` page records any of
the three as its decision.
`docs/audits/2026-09-multi-update-per-row-value-census.md` names
`objectstack-ai#14744`, but it states that it is "measurement only — ships nothing …
implements no guard", the input to a decision rather than its record, so
the census line cites the commit that landed it.

## Wordings to check

- **Tag swaps in brackets or parentheses.** 「[objectstack-ai#14744]」 became 「[commit
4f85e4d]」 at `decouple-flow-record.test.ts:4` and
`before-update-flow-payload-reach.test.ts:805`. 「[objectstack-ai#11081]」 became
「[commit c28e4cf]」 on 5 lines. 「(objectstack-ai#14744, measured by objectstack-ai#15356)」 became
「(commit 4f85e4d, measured by objectstack-ai#15356)」 at `decouple-flow-record.ts:5`.
「(objectstack-ai#14744)」 became 「(commit 4f85e4d)」 at
`record-change-trigger.ts:340`. 「(objectstack-ai#8738 pre-hook / objectstack-ai#13657 post-hook)」
became 「(objectstack-ai#8738 pre-hook / commit b003cf2 post-hook)」.
- **Headings `:4` and `:859`.** 「[objectstack-ai#15356 measured, objectstack-ai#14744 closed]」 and
「[objectstack-ai#15356 measured it, objectstack-ai#14744 closed it]」 keep the live `objectstack-ai#15356` and put
the sha where the dead number stood.
- **`before-update-flow-payload-reach.test.ts:10`.** 「objectstack-ai#14744 then ruled
the door closed」 became 「The option-A ruling (commit 4f85e4d) then
closed the door」: the ruling is named in words beside the commit that
carried it, whose changeset records it, the form stages 2, 6 and 7 used
for a ruling.
- **`:22` and `:87`.** 「the objectstack-ai#14744 residue shape」 and 「the objectstack-ai#14744 pinned
residue shape」 became 「the residue shape commit 4f85e4d pins」: the
positive control that pins it is in that commit's diff.
- **`:23`.** 「because objectstack-ai#14744's fix is about aliasing」 became 「because
commit 4f85e4d fixes aliasing」: a commit fixes something, it does not
have a fix.
- **`:29` and `:34`, the census paragraph.** 「objectstack-ai#14744's census found」
became 「The census in commit 03c1b0f found」. That removed the referent
of 「The conclusion recorded on that card」 five lines down, so `:34`
became 「The conclusion recorded in that census」. This is the one changed
line that carried no dead number. It is true as written: the census
record `03c1b0f6f` landed carries that very conclusion, "On a source
reading, `buildContext` materialises a *new* record object by overlay …
a reading, not a measurement"
(`docs/audits/2026-09-multi-update-per-row-value-census.md:308-311`).
- **`:455`.** 「that is precisely the blind spot objectstack-ai#14744 is weighing」
became 「… the blind spot commit 4f85e4d left unguarded」. The present
tense described a card still being weighed; that commit's changeset says
the key-set refusal "is untouched and is not widened — a hook that
assigns the same key with per-row values still passes it".
- **「Before objectstack-ai#14744」 / 「before objectstack-ai#14744」** at `:686`, `:705`, `:738`,
`:924` (the word 「Before」 sits at the end of the line above at `:685`
and `:704`) became 「before commit 4f85e4d」: before that commit the
flow-facing record shared its nested values with the payload, which is
the reading each sentence quotes.
- **「objectstack-ai#14744 made」, 「objectstack-ai#14744 carries the fix」, 「objectstack-ai#14744 closed the door」**
at `:47`, `:95`, `:642`, 「Until objectstack-ai#14744」 at
`record-change-trigger.ts:341`, 「and objectstack-ai#14744.」 at `:124`, 「objectstack-ai#14744 —
DECOUPLE」 at `:453`, 「(unchanged by objectstack-ai#14744 —」 at `:496`: the number
became the commit, and each sentence already states what the commit did.

## The 4 sites left

- **Test strings, 4 sites on 4 lines**, all `describe` / `it` titles
carrying `objectstack-ai#14744`, left as stages 1 to 12 left theirs:
`before-update-flow-payload-reach.test.ts:825` and `:872` (the second
number of `[objectstack-ai#15356/objectstack-ai#14744]`, a spelling the gate's grammar cannot see),
`decouple-flow-record.test.ts:78` and `:136`.
- No source string, operator log string, assertion message, quoted
maintainer ruling or generated file in this package carries a dead
number.
- Outside `src`, the package's `CHANGELOG.md` names `objectstack-ai#14744` on 2 lines
(467, 478). It is release-owned and deliberately not edited here (see
Acceptance notes). The package `README.md`, which also ships, names none
of the three.

## Mechanical guard: no code token moves

The guard compares, base `91e8fa194` against head, over all 5 touched
`.ts` files:

- **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild`
walk, so comments are trivia and JSDoc nodes are never visited). String
and template literals are therefore read in full.
- **Reading 2**, the full token stream in parser context (a
`getChildren` walk, so punctuation and keywords are included; JSDoc
nodes skipped).

Results:

- Real run at the final head `bbfe7cb24`: 6,110 base leaf tokens, **0
files with a token change** on either reading (exit 0).
- Comment control in `record-change-trigger.ts` (「reach nothing outside
its own run.」 to 「reach nothing beyond its own run.」): 0 files changed,
as expected (exit 0).
- Positive control, a code token added in `record-change-trigger.ts`
(`params: isolatedRecord,` given `as typeof isolatedRecord`): DIFFER,
953 to 954 leaf tokens and 2,130 to 2,133 full tokens (exit 1).
- Positive control, one digit changed inside a kept test title
(`decouple-flow-record.test.ts:78`, `objectstack-ai#14744` to `objectstack-ai#14745`): DIFFER on the
string literal (exit 1).

Every mutation went through `scripts/ablation-replace.mjs` (wrap mode)
under a shell trap that restores by absolute path, and each landed
(anchor 1 to 0, blob changed). Each restore was proven byte-identical to
the HEAD blob (`f3235a962fc5`, `9a8bf70abbcc`), with `git diff HEAD`
empty and a clean tree afterwards.

## Changeset

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

Measured on the built package (A3), after a full workspace build in
which this package was a cache miss: `files[]` is `dist`, `README.md`
and `CHANGELOG.md`, and the package is not private.

- `4f85e4d11` appears 3 times in each of `dist/index.js` and
`dist/index.mjs`: the `buildContext` docblock
(`record-change-trigger.ts:340` and `:341`) and the inline comment at
`:496`, which the bundle keeps.
- It appears twice in each of `dist/index.d.ts` and `dist/index.d.mts`:
the same `buildContext` docblock.
- The other three anchors appear nowhere in `dist`: their lines are in
test files. The rewrites at `record-change-trigger.ts:124` and `:453`
and `decouple-flow-record.ts:5` are stripped by the bundle.
- Positive controls, one unchanged line beside each rewrite, land
exactly where their neighbours do: the line after `:341` once in all
four files, the line before `:496` once in each JS file and 0 in the
declaration files, and the neighbours of the three stripped rewrites 0
everywhere.
- A never-written negative phrase appears nowhere in `dist`.
- None of the three dead numbers is left in `dist`.

## Gates (final head `bbfe7cb24`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
exits 0 (self-test, 114 cases, 8 batteries). `node
scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1
added citation across 2 files, the live `objectstack-ai#15356` at
`decouple-flow-record.ts:5`, and it resolves.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `bbfe7cb24` (after a fresh fetch)
derived 59 commands. They are all 53 derived at dispatch, plus
`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 59 exit
0; none exited 3.
- `--ran`, fed each command with its exit code, reports 59 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, at `bbfe7cb24`:**
- `pnpm --filter @objectstack/trigger-record-change test`: 10 files pass
and 101 tests pass. `vitest list --filesOnly` names 10 files, all the
tracked test files, the 3 touched ones included.
- `pnpm --filter @objectstack/trigger-record-change typecheck` exits 0.
`tsc --listFiles` on `tsconfig.test.json` holds all 14 files under
`src/`, and on `tsconfig.json` the 4 non-test files, so all 5 touched
files are compiled.
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the 5 touched `.ts` files, gives 5 files, 0 errors and 0 warnings
(its `--format json` output). All 5 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 6 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
`#`, `NON_CITATION_HEADS` excuses a number after the word 「option」, and
a URL-spelled link carries no `#` at all (objectstack-ai#20636). In this package, at
the base and at the head: `#N-word` none, `#A/#B` 5 lines, `option #N`
none, URL-spelled none, which is the claim's 0 / 5 / 0 / 0. Of the five
`#A/#B` second numbers (`objectstack-ai#4251` twice, `objectstack-ai#5038`, `objectstack-ai#4649`, `objectstack-ai#14744`), only
`objectstack-ai#14744` is dead, and it stands in a kept test title.
- **`CHANGELOG.md` is left.**
`packages/triggers/trigger-record-change/CHANGELOG.md` names `objectstack-ai#14744` on
2 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a
deferred surface of the citation gate, and ⛔ not part of this stage.
- **A live number in a runtime string, left for its lane.**
`record-change-trigger.ts:239`'s operator `warn` for an array-form
trigger event ends with the live `objectstack-ai#3457`, and two tests assert the
message carries it. That is form D, not this card's comment-only form C,
and the shrink-only `doc-authoring-prose-id` baseline already holds it
(`record-change-trigger.ts`: `objectstack-ai#3457: 1`), so `check:doc-authoring` sees
no growth.
- **「The card」 phrases are left.** 3 other comment lines in 2 files of
this package speak of 「the card」. They carry no number, neither
instrument sees them, and none of them lost a referent in this diff.
They are unchanged, as in stages 8 to 12.
- **The census instrument did not truncate in this stage.** All three
enumerations read 187 pages at the newest frontier.
- **Anchors the next stages can reuse**, each checked here: `objectstack-ai#14744` →
`4f85e4d11` (the decoupling) or `03c1b0f6f` (its census), both new to
the sweep; `objectstack-ai#13657` → `b003cf2e8` and `objectstack-ai#11081` → `c28e4cfae` reuse the
runtime and lint stages' anchor and stage 7's.
- **Base.** The branch is on `main` at `91e8fa194`. `main` has since
moved six commits (`cd6d8a5ff`, `1bcba27d2`, `a3d7588b5`, `9ad654487`,
`274e16271`, `085ca6bc1`). Their 50 files touch nothing under
`trigger-record-change`, nor `scripts/check-issue-citations.mjs`,
`.changeset/config.json` or the `doc-authoring-prose-id` baseline, and
none is a path in this diff. Three of them are gate inputs
(`scripts/engine-double-contract.pinned.json`,
`scripts/objectql-double-limit.baseline.json`,
`scripts/sdui-manifest.record.json`), so those families ran here against
the base's copies; this diff moves no code token, so nothing here can
interact with them. No merge was taken; the merge queue rebuilds on the
merged generation.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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