Skip to content

docs(lint): re-anchor the dead tracker citations in packages/lint/src to the commits that decided them - #20612

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20597-lint-dead-citations
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20597-lint-dead-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20597
Clause-②: no

The packages/lint stage of the dead-citation sweep: the domain:spec lane's only package (census 5884031174 on #20556, claim 5885046469). Every comment or docblock line in 22 of the 23 claimed packages/lint/src/ files that cited a tracker number answering 404 now cites, in ruling C+D's form C, the commit in this repository's history that decided what the line describes, and says in its own words what was decided. Comments only: 81 lines out, 81 in, across 22 files. No code token, string literal, rule message, hint or rule id moves.

authoring-rules.ts (5 sites) is excluded and left at its base blob: PR #20593 (#20553) edits it and was still open at the last read (2026-09-29T07:34Z). So this PR says Part of: those 5 sites stay for a follow-up once that PR lands, with their anchors already verified (see Acceptance notes).

The census is the gate's own node scripts/check-issue-citations.mjs --census --json, filtered to packages/lint/. Before: base 7a1faf1a5d, 2026-09-29T06:42:03Z to 06:45:24Z, board enumerated (185 pages, frontier #20606). After: head 0c7b847f18, 07:28:22Z to 07:31:52Z (185 pages, frontier #20611).

Measurement

file (under packages/lint/src/) dead before after numbers, then anchor
lint-flow-patterns.ts 9 0 #13681 ×9 to 8ed9c54b4 (#14394 stays, 200)
validate-hook-body-writes.ts 9 0 #8663 ×6 to 192213f66; #13657 ×3 to b003cf2e8
runtime-gate.ts 8 0 #10064 ×5 to def0d3e63; #19370 ×2 to a227afa41 (#19143 stays); #9798 to c7655d472 (#9261 stays)
validate-searchable-fields.ts 7 0 #8404 ×4 to b849e6911, the pre-#8404 control at :312 included; #10001 ×3 to f1b5ad39a
authoring-rules.ts 5 5 excluded: PR #20593 holds the file
validate-expressions.ts 5 0 #6290 ×5 to e9b526597 (#6584, that commit's own PR, stays)
validate-react-page-props.ts 5 0 #11284 ×4 to 5383fa670; #8404 to b849e6911
validate-sortable-fields.ts 5 0 #10001 ×4 to f1b5ad39a; #8404 to b849e6911
validate-flow-node-writes.ts 4 0 #8663 ×4 to 192213f66
validate-page-field-bindings.ts 4 0 #6629 ×2 to cd584d559 (plus the slash-joined :208, see Deviations); #8664 ×2 to 8798cd2a6
validate-translation-references.ts 4 0 #14700 ×3 to de3c52beb (#14253 stays); #6124 to b3c1f3cd5
lint-liveness-properties.ts 3 0 #10262 ×3 to 2aca1bc4c
validate-action-body-writes.ts 3 0 #8663 ×3 to 192213f66
validate-security-posture.ts 3 0 #19370 ×3 to a227afa41 (#8310 stays)
flow-template-grammar.ts 2 0 #11060 ×2 to 815585513
data-model-rules.ts 1 0 #10064 to def0d3e63
reference-integrity-suite.ts 1 0 #13653 to 36d287803
validate-component-types.ts 1 0 #12950 to 225e7690f (#12183 stays)
validate-empty-combinators.ts 1 0 #6528 to 3510e4a25 (#5659 stays)
validate-list-view-field-refs.ts 1 0 #10001 to f1b5ad39a
validate-readonly-action-writes.ts 1 0 #13653 to 36d287803
validate-readonly-flow-writes.ts 1 0 #13653 to 36d287803
validate-readonly-hook-writes.ts 1 0 #13653 to 36d287803
23 files 84 5 21 numbers; 19 removed from the 22 edited files, to 19 distinct shas

Per-file counts at base equal the claim's (census at f11b5f20a2) in all 23 files. A second instrument agrees site for site: every #N in the 23 files, classified by the TypeScript parser as comment, string or code, and each of 360 distinct numbers probed by REST issues/N without following redirects. At base it found 1,323 sites (1,259 comment, 64 string, 0 code); 339 numbers answer 200 and 21 answer 404, the census's 21. Its dead comment sites are the census's 84 plus one slash-joined #5775/#6629 the grammar does not read, and it found one dead string: validate-react-page-props.ts:1198 (see Acceptance notes). At head: 1,243 sites and 344 numbers, the same 339 answer 200, and 5 answer 404, all in authoring-rules.ts comments or that one string. Lit controls #16862, #16847 and #17698 answered 200, and dead controls #16714, #16715 and #16697 answered 404, at every checkpoint (5 at base, 5 at head).

Why each anchor decides its line

Each sha resolves uniquely, is an ancestor of origin/main and of the base, has one parent, and names the number it replaces in its own message (15 of 19) or its own diff (17 of 19); every one does at least one. Each was read for the rule its line states.

Rung: no ADR, docs/NORTH-STAR.md or scripts/adr-anchors/ file records any of these 19 decisions (the one lint anchor file, data-model-rules.ts, pins ADR-0120, which none of these lines cites), so the commit rung is the right one, as in #20234's stages.

Mechanical proof

Tests and gates (at head 0c7b847f18)

  • pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/* under os-verify-lock: Tasks 71 successful, 71 total, VERDICT command-exit 0.
  • pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 under the lock: Test Files 115 passed (115), Tests 5363 passed (5363); then pnpm --filter @objectstack/lint typecheck: exit 0, check:test-typecheck OK (2 files / 6 errors / 2 pinned signatures held). VERDICT command-exit 0. The same two runs passed with the same counts on the pre-merge head 2ce32f6b48.
  • Lint, a proven narrowing: eslint --no-inline-config --format json over the 22 touched .ts files gives 22 files, 0 errors, 0 warnings. isPathIgnored is false for all 22, read through eslint's API. eslint.config.mjs:327-328 says type-aware linting is never enabled, so a comment edit cannot move an untouched file's verdict. The repo-wide pnpm lint is CI's.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 54 families derived, all run, every one exit 0. --ran reads "54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero (every line carries its exit code). Among them:
    • node scripts/check-issue-citations.mjs (the live diff-scoped run) judged 18 citations across 22 files: 17 answer as issues and 1 answers as a pull request, the kept fix(formula,lint): current_user 收进 SCOPE_ROOTS,字段级拒绝改为按面的真规则 (#6290) #6584; pnpm check:issue-citations (self-test, 114 cases in 8 batteries) passes.
    • pnpm check:doc-authoring: the sibling prose-id baseline holds, 810 pinned sites across 230 files, no growth.
    • pnpm check:nul-bytes: OK over 9,239 tracked text files; a control-byte scan of the 23 changed files finds none.
  • No generated page carries a lint docblock: no page under content/docs/references/ names any of the 19 numbers, and no generator reads packages/lint/src, so nothing was regenerated.
  • Changeset: patch for @objectstack/lint. files[] ships dist, and the rewritten comments reach it: 12 of the 19 shas appear in the built dist (for example 8ed9c54b4 and def0d3e63 in index.d.ts, b849e6911 in index.js and index.d.ts); the positive control, the unchanged sentence "the near-miss shape: a try_catch that declares no catch" of an exported docblock, is in index.d.ts. Hence patch, not skip-changeset.
  • Merge probe: a no-driver merge-tree of the head onto origin/main 0f6dcac5e9, from a bare shared clone with no merge.* config, exits 0. The two commits main gained after the merge touch none of the 23 files.
  • No ablation or reverse verification: the change is comment-only, so there is no behaviour to invert.

Hypotheses (measured first)

  1. Holds. 84 dead sites, 21 numbers, 23 files at the tip 7a1faf1a5d, equal per file to the claim.
  2. Holds. Only comment and docblock lines moved. The one dead number inside a string (validate-react-page-props.ts:1198, a finding message) stays byte-identical; no test or script reads a rewritten line by literal.
  3. Holds, and conditions the card. PR feat(lint): os validate refuses an api flow with no per-flow secret #20593 was still open at 07:34Z, so authoring-rules.ts stays at its base blob. At that read, the 9 open PRs' full file lists and the newest Claim: on all 11 pm:dispatched cards name none of the other 23 paths.
  4. Holds. validate-searchable-fields.ts:312 pre-#8404 is listed dead before and is gone after.
  5. Holds, with nothing to regenerate. No lint docblock projects into a generated page; no release page is touched.

Deviations

  • Two changed lines beyond the census's sites. validate-page-field-bindings.ts:208 carried #5775/#6629, a slash-joined dead number the citation grammar does not read; it now reads "the same SDUI props 声明与 renderer 不一致:6 处「renderer 兑现但 ComponentPropsMap 未声明」+ 2 处「声明了没人读」(#5068 error 升级的 spec 侧前置) #5775 residue class (commit cd584d5)", stage 5's precedent for the slash-joined #9972. runtime-gate.ts:780 is the other half of the rewritten :779 sentence and held no number.
  • origin/main was merged once (0c7b847f18, merging c96beb2707): the first derivation read STALE TREE because scripts/sdui-manifest.record.json changed on main. The merge was clean, no driver-routed path and no lockfile change, and it touches none of the 23 files; the build, tests and gates above ran after it.
  • Commit trailers follow AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude); the pre-push trailer check passed on every push.

Acceptance notes

What stays for this card (why it says Part of): authoring-rules.ts, 5 sites, excluded while PR #20593 holds it. Anchors, verified the same way, for whoever takes it after that PR lands: :198 #10064 to def0d3e63; :1117 #16659 to ecdfc9411 (it added flow-schedule-organization-missing to the registry); :1687 "(PR #8546)" to ba5e957ef, that PR's own squash commit; :1713 and :1733 #19370 to a227afa41.

Form D, not touched: validate-react-page-props.ts:1198 is the react-prop-deprecated finding message, which ends "...is removed after the deprecation window (#11284)." An author sees it, so it takes ruling D (no number), which is a string change and outside this comment-only scope. scripts/doc-authoring-prose-id.baseline.json pins it (#11284: 1 for this file). It needs a form-D carrier.

Outside the census's surface, which blanks strings and defers test files (noted, not swept here):

Wording, each true of its commit.


Generated by Claude Code

…the commits that decided them

Comment and docblock lines in 22 packages/lint/src files that cited a tracker
number answering 404 now cite, in ruling C+D's form C, the commit in this
repository's history that decided what the line describes, and say in words
what was decided. Comments only: 81 lines out, 81 in; no code token, string,
message or hint moves. authoring-rules.ts is left at its base blob while an
open PR holds it.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 20 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/lint/src/data-model-rules.ts, packages/lint/src/flow-template-grammar.ts, packages/lint/src/lint-liveness-properties.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/permissions/authorization.mdx (via validateSecurityPosture (symbol, a top-level function))

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

  • content/docs/releases/v13.mdx (via validateSecurityPosture (symbol, a top-level function))
  • content/docs/releases/v16.mdx (via validateStackExpressions (symbol, a top-level function))

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
  • 8 changed file(s) yielded no anchor (packages/lint/src/data-model-rules.ts, packages/lint/src/flow-template-grammar.ts, packages/lint/src/lint-liveness-properties.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 — 4 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 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2309ce232c26867a1869f0df5665e3d49ac6f7e9 — the merge of head 0c7b847f187ff983d52137ef61bb83a27616df23 into base 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f, 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 2309ce232c26867a1869f0df5665e3d49ac6f7e9 && git checkout 2309ce232c26867a1869f0df5665e3d49ac6f7e9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f 0c7b847f187ff983d52137ef61bb83a27616df23 && git checkout -B drift-repro 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f && git merge --no-ff 0c7b847f187ff983d52137ef61bb83a27616df23

node scripts/docs-audit/affected-docs.mjs --json 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f

⚠️ 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 0f6dcac5e99d0c6211f0d8a0e150a112d78a776f → 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: 0c7b847f187ff983d52137ef61bb83a27616df23
Local-runs: none

Read (this act, 2026-09-29T07:45Z to 07:57Z): card #20597 (body; every comment: the claim 5885046469 and the os-dev-report 5885887024), #20556 (body; every comment, in particular the census split 5884031174, the close-out 5884580132 and the cursor-exhaustion pointer 5885710792), ruling C+D 5749154545 on #19123 and its pointer 5856637615 on #20234, stage 6's record 5885205776 on PR #20606 as the method precedent, PR #20612 (body, its one comment 5885856143, the 23-file list, the three commits and the net diff against the merge base c96beb2707, read from the local ref at the head above with -U0 and the base and head blobs of every changed file), the 19 cited commits (identity, message and stat for all; their own diffs of the files they now anchor wherever the subject does not name the number), packages/formula/src/cel-engine.ts at the head for the quoted docblock, docs/adr/**, docs/NORTH-STAR.md, scripts/adr-anchors/ and content/docs/references/** at the head for the 19 numbers, packages/lint/package.json files[] and tsup.config.ts, scripts/check-issue-citations.mjs (declared and deferred surfaces, CITATION_RE), scripts/doc-authoring-prose-id.baseline.json, the lint tests that read their rule's source as text, lint.yml and ci.yml for the job roster, PR #20593 (state, events, file list), cards #20594 to #20596 and the pm-dispatch lane table for packages/formula. Nothing was built, run or re-run: the blobs were compared with a string-, template- and regex-aware comment stripper in scratch (a read of the diff, not a gate), the numbers were probed with REST GET against this repository (a GitHub read), and the check-runs were read for judgment once, at 2026-09-29T07:56:56Z.

① Derived judgments

(a) Scope and file surface: right. 23 files: the 22 packages/lint/src/ files of the claim's 23-file surface, each .ts file on the claim's list, plus the new .changeset/lint-provenance-anchors.md (+11). The 23rd, authoring-rules.ts, is absent from the diff, and rightly: the claim conditions it on PR #20593 having landed before the push, and PR #20593 is still open at every read (events: added_to_merge_queue 06:28:53Z, removed_from_merge_queue 06:59:13Z, nothing after; merged: false at 07:5xZ), its file list holding authoring-rules.ts and index.ts. No other package, no test file, no release page, no gate file, no governed surface is touched. Head repository is the base repository; draft; assignee os-tesla; no auto-merge armed; no reviews; mergeable: true, mergeable_state: blocked (draft); 173 changed lines. First line Part of #20597, no closing keyword, is right: 5 of the card's 84 sites stay on the card by the claim's own condition, and Part-of PR must not also close its card is success. Clause-②: no on the body's second line. Three commits: the rewrite, the changeset, and one two-parent merge of origin/main (c96beb2707); the two authored commits carry the model-free Claude-Session plus Co-authored-by: Claude pair. origin/main (now eb4b17c346) has moved since the merge base; the files it moved (spec sources, plugin-audit, rest, two generated pages, three changesets) intersect the 23 in nothing, so the net diff against current main is the diff judged here.

(b) Comment-only, no code token, string literal, rule message, hint, rule id or test title moves: right. git diff -U0 over the 22 .ts files: 81 lines removed, 81 added, every one of the 162 beginning with //, * or /**. Enclosure and residue, read on the base and head blobs: every removed line lies inside a comment in the base blob and every added line inside a comment in the head blob, 22 of 22 files; the non-comment residue (code, string, template and regex literals kept, comments dropped) is byte-identical base to head in all 22, with every line count equal and every file +N/-N. Controls, mutated in memory only: a comment insertion into validate-react-page-props.ts and into runtime-gate.ts leaves the residue equal; a code edit and a string-literal edit each make it differ. So no message, hint, rule-id constant, export or code token moved in any of the 22 files. validate-react-page-props.ts:1198, the react-prop-deprecated finding message ending "is removed after the deprecation window (#11284)", is byte-identical base to head, the only (#11284) left in the file. No test file is in the diff, so no it/describe title moved; of the nine lint tests that read a rule's source as text, the two that read a touched file (validate-security-posture.test.ts, .runtime-surface.test.ts) strip comments first or slice validateSecurityRoleWord's body for stack.X tokens, which no added line carries, and validate-expressions.test.ts strips comments before matching. The one changed line holding no number, runtime-gate.ts:780, is the second half of the :779 sentence.

(c) Numbers: right. Over the 22 files, added-minus-removed tracker numbers is empty in every file; the changeset carries none; net-removed is exactly 19 numbers on 80 sites (the census's 79 in these files plus the slash-joined #6629 at validate-page-field-bindings.ts:208): #6124, #6290, #6528, #6629, #8404, #8663, #8664, #9798, #10001, #10064, #10262, #11060, #11284, #12950, #13653, #13657, #13681, #14700, #19370. Per file the diff's counts equal the claim's census (lint-flow-patterns 9, hook-body-writes 9, runtime-gate 8 plus the :780 half, searchable-fields 7 with the pre-#8404 control at :312 gone, expressions 5, react-page-props 5, sortable-fields 5, flow-node-writes 4, page-field-bindings 4 plus :208, translation-references 4, liveness-properties 3, action-body-writes 3, security-posture 3, template-grammar 2, and 1 each in the other eight). No PR #N stands on any added line. REST issues/N without redirects, read at 2026-09-29T07:53Z: all 19 removed numbers answer 404; the 12 numbers standing on added lines answer 200 (#5659, #5775, #8310, #8340, #9261, #9313, #12183, #13390, #14253, #14394, #19143 as issues; #6584 as a pull request, the anchor commit's own, allowed beside it as ruling C's convenience link); controls #16862 200, #16714 404, #20597 200, #20612 200. On the slash-joined :208: CITATION_RE's look-behind class excludes /, so #6629 after #5775/ was never read by the gate, which is why the census counts 79 here and the dev's raw instrument 80; the rewrite keeps the live #5775 and names the commit. The dev's census over 360 numbers is otherwise not re-run.

(d) Anchor truth: right, 19 of 19. 19 distinct 9-hex shas stand on added lines and none on removed lines. git rev-parse --disambiguate answers exactly one object for each; git merge-base --is-ancestor exits 0 for all 19 against origin/main; every one is a single-parent commit; the repository is not shallow. Nine name the replaced number in their own subject (a227afa41 #19370, b849e6911 #8404, f1b5ad39a #10001, e9b526597 #6290 and #6584, cd584d559 #6629 and #5775, 8798cd2a6 #8664, b3c1f3cd5 #6124, 2aca1bc4c #10262, 3510e4a25 #5659 and #6528); the other ten are tied by their body or their own diff, and every anchor was read against the sentence it now supports:

(e) Form C over the whole diff: right. Each rewrite leads with commit 9-hex (or [commit 9-hex] / (commit 9-hex) where the marker form stood) and says in words what was decided; no tracker number and no PR #N is added. Nothing author-shown is touched, so form D does not arise in this diff. Read end to end for sense: runtime-gate.ts:362-363 and :776-781 (dense, true, one sentence each), lint-flow-patterns.ts:58, :343, :1333, :1468, validate-expressions.ts:568-572 and :768-772, flow-template-grammar.ts:33-35 and :102-104, validate-translation-references.ts:547-551 and :983, validate-hook-body-writes.ts:12-18, lint-liveness-properties.ts:458, validate-empty-combinators.ts:10. The citation gate's declared surface is packages/**/src/**/*.ts with string literals blanked and test files deferred, so all 22 files are in the diff-scoped run's population; that run judged the 18 kept-number sites on added lines and is success (see the CI read).

(f) Generated pages: nothing to regenerate, right. No page under content/docs/references/ at the head names any of the 19 numbers, and none of the scripts that mention packages/lint/src is a page generator (they are gates and the docs-drift advisory). The claim's "any generated page those docblocks project into" resolves to none.

② Semver level

patch for @objectstack/lint is right and Clause-②: no is right. The package ships bytes from this diff: files[] is dist, README.md, CHANGELOG.md, and tsup.config.ts emits dts (unless OS_SKIP_DTS), so the docblocks on exported declarations reach dist/index.d.ts and dist/runtime.d.ts (LintIssue at data-model-rules.ts:64, FLOW_TEMPLATE_VALUE_FUNCTIONS at flow-template-grammar.ts:102, the rule-id constants in lint-flow-patterns.ts, buildReadonlyIndex at validate-readonly-flow-writes.ts:118, getNested's seam, among them). (b) shows the non-comment residue identical base to head, so no export, type, value, message or behaviour moves and nothing widens or narrows: a change that moves no public surface stays at patch, the level #20234's stages 1 to 6 took for the same act, and skip-changeset would be wrong because the tarball changes. The changeset names one package, describes only the comment re-anchoring, carries no tracker number and no model identifier; Check Changeset is success at the read.

③ Boundary flags

Blocking: none.

Dev deviations, each answered:

  1. Part of #20597, not Fixes, with authoring-rules.ts excluded at its base blob: right, judged in (a); PR feat(lint): os validate refuses an api flow with no per-flow secret #20593 open and out of the queue at the push and at this read.
  2. Two changed lines beyond the census's sites (validate-page-field-bindings.ts:208, runtime-gate.ts:780): right, judged in (b) and (c); both files are on the claim's surface and the lines are the dead-number line's own sentence.
  3. validate-expressions.ts:770 quotation closing before the number, :571 keeping fix(formula,lint): current_user 收进 SCOPE_ROOTS,字段级拒绝改为按面的真规则 (#6290) #6584 beside the commit: right, judged in (d).
  4. One merge of origin/main (c96beb2707): confirmed, the third commit is a two-parent merge; the files main brought intersect the 23 in nothing; no driver-routed path or lockfile change is in the diff.
  5. Commit trailers model-free: confirmed on the two authored commits; the merge commit carries git's default message and no trailers, which the pre-push hook is the authority on and which a squash landing does not keep. Non-blocking.
  6. The empty probe push, no force-push, worktree cleanup: not observable from here and not this diff's; the PR shows exactly three commits.
    Open questions: the report lists none.

Out-of-scope findings, each judged:

Non-blocking observations: (1) the docs-drift comment 5885856143 lists content/docs/permissions/authorization.mdx via validateSecurityPosture and two release pages read-only, a symbol-anchored advisory on a comment-only diff; Flag docs affected by code changes is success. (2) runtime-gate.ts:362 packs the fail-open, #9261 and ADR-0110 D3 into one parenthesis; true, if dense.

Escalated: none.

CI at this head, the judging read at 2026-09-29T07:56:56Z: 33 check-runs, 28 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), path-filtered or opt-in, not this diff's), 0 failure, 2 in progress: Test Core (1/6) and Test Core (5/6), with the Test Core aggregate not yet created. So six of the seven required contexts are success: Lint & Repo Gates (07:55:05Z; it runs the diff-scoped node scripts/check-issue-citations.mjs verdict with pnpm check:issue-citations, check:doc-authoring, pnpm lint and the repo check:* roster), TypeScript Type Check, Build Core, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard; the seventh, Test Core (the lint vitest suite rides its shards; 2/6, 3/6, 4/6 and 6/6 are success), is NOT presumed green here and must be read concluded before the PR is armed. Also success: Check Changeset, Type Check · source gates, · workspace, · consumer gates, · debt ledger, Dogfood Verify CLI, Check PR Size, Check Documentation Links, Flag docs affected by code changes, the three claim and closing guards, Auto Label and filter. No red run exists to attribute. Of the dev's 54 derived families, the head's runs answer the citation pass, doc-authoring, lint, changeset, typecheck, build, dogfood, temporal and governed-surface families now; the test family is the open run above. PR is a draft; mergeable: true, mergeable_state: blocked (draft); assignee os-tesla; no auto-merge armed.

Implemented-by: claude/issue-20597-lint-dead-citations
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 08:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit aa23e2c Sep 29, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20597-lint-dead-citations branch September 29, 2026 08:30
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…me/src to the commits that decided them (objectstack-ai#20624)

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

## What changed

This is stage 1 of the `domain:cli` lane of the dead-citation sweep:
`packages/runtime/src/**`, the lane's largest package. Every comment or
docblock site in scope that cited a tracker number answering 404 now
cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit
in this repository's history that decided what the line describes, and
says in its own words what that commit decided. PR objectstack-ai#20533 is the method
and PR objectstack-ai#20609 the closest sibling. Later stages cover `rest`, `cli`,
`types` and the rest of the lane, so this PR says `Part of` and the card
stays open.

That is **513 comment sites on 508 lines in 118 files, covering 96
numbers**: 194 of the census's 217 sites, and 319 more in test comments,
which the census defers. Three more sites carried a slash-joined dead
number the citation grammar does not read (`objectstack-ai#10629/objectstack-ai#10630`,
`objectstack-ai#5811/objectstack-ai#12281`, `objectstack-ai#8421/objectstack-ai#12194`), and they are rewritten too. Each
rewritten line cites one of **95 distinct commits**.

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of these numbers. ADR-0126 and ADR-0131
name objectstack-ai#10243 only as the incident, ADR-0126 names objectstack-ai#11513 only for the
flow-clone half, and ADR-0112 names objectstack-ai#12281 only as another card. So
every anchor is a commit. The anchors the landed stages already gave the
same numbers are reused (24 numbers, for example `f19475c0a` for objectstack-ai#14143,
`e2798fab7` for objectstack-ai#6345 and `79c46da90` for objectstack-ai#9934), so each number carries
one anchor across the tree.

Only comments changed. Every touched file keeps its line count (508
lines out, 508 in, over 118 files), so no line citation into these files
moves. Seven of the 508 lines held no census site. Five are the other
half of a sentence that had to change:
`action-governance-scope-divergence.test.ts:6` (「the card names」 to
「that diverged」, because line 4 no longer names the card),
`action-record-load-denied.test.ts:560`,
`dispatcher-5xx-demoted-code-withhold.test.ts:45` (「that card's change」
to 「that commit's change」),
`hook-input-writeback-readonly-provenance.integration.test.ts:380`
(「that card」 to 「that commit」) and
`standalone-stack-seeder-declaration-copy.test.ts:88` (a trailing 「PR」
whose number wrapped onto line 89). Two carry only a slash-joined
number: `dispatcher-plugin.ts:688` and
`meta-compound-arity-mint-door.test.ts:4`. 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. No PR number stands on an added
line, and none of the 95 shas is on a removed line.

Twenty-eight dead comment sites are left on purpose:
- **20 in `domains/meta.ts`.** PR objectstack-ai#20615 (objectstack-ai#20590's) opened at
2026-09-29T08:12:40Z, after this stage's claim and first read, and edits
that file. So the file went back to its base blob (`b4ddb362cc`) in
`a5cdfd8a46`, as PR objectstack-ai#20612 did with `authoring-rules.ts`. The anchors
are verified and listed below for the follow-up.
- **8 with no deciding commit, or with a literal reader.** See "The
sites left" below.

One more file: a `patch` changeset for `@objectstack/runtime`, because
the rewritten docblocks ship (see Changeset below).

## Census: `packages/runtime`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run with the fleet token. Its
surface is comment prose in `packages/**/src/**/*.ts` with string
literals blanked, and it defers `*.test.ts`. The count is its
`allocated-but-absent` findings under `packages/runtime/`. Both runs
enumerated the whole board (185 pages), so neither read a truncated
board.

| reading | tree | board | whole-repo `allocated-but-absent` | runtime
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `eb4b17c346`, run 2026-09-29T07:55:51Z to 08:05:47Z |
enumerated, 185 pages, frontier objectstack-ai#20614, 18,441 numbers | 2,397 | **217**
| 216 | 29 | 59 |
| after | head `a5cdfd8a46`, run 09:08:23Z to 09:14:11Z | enumerated,
185 pages, frontier objectstack-ai#20623, 18,450 numbers | 2,027 | **23** | 23 | 4 |
12 |

The before count equals the card's 217 at `f11b5f20a2`. The 23 left are
the 20 held `domains/meta.ts` sites and 3 deliberate ones
(`api-exposure.ts:108`, `domains/mcp.ts:360`, `route-ledger.ts:300`).
The whole-repo drop is 370: this diff's 194, plus the 97 and 79 of PR
objectstack-ai#20609 and PR objectstack-ai#20612, which landed on `main` in between and came in with
the merge.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `classifyCitation` over
every `.ts` file under `packages/runtime/src` (373 files), against a
board probed by REST for every number cited there. The lit controls
objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 and the dead controls objectstack-ai#16714,
objectstack-ai#16715 and objectstack-ai#16697 answered 404 in both runs.

| reading | tree | citations | dead | src comment | test comment | src
string | test string |
|---|---|---|---|---|---|---|---|
| before, 08:17:55Z | `eb4b17c346` | 5,364 | **641** | 217 | 324 | 5 |
95 |
| after, 09:24:24Z | `a5cdfd8a46` | 4,851 | **128** | 23 | 5 | 5 | 95 |

Its src-comment column equals the census's 217 and 23, which is the
control on the second instrument. The 4,499 resolving citations, the 195
that resolve as pull requests and the 29 cross-repo ones are the same in
both readings. The drop is 513, exactly this diff's grammar-read sites.

## Per-number table

Sites and files are the dead comment sites in scope at the base, tests
included. `held` is `domains/meta.ts` (see above) and `left` is a site
with no deciding commit or with a literal reader. `strings kept` counts
string-literal sites, which are tokens and stay as they were. Every
anchor was read in its message or its diff, not only in its subject: it
is the commit that made the change the line describes, and its own
message or diff names the number it replaces.

| number | comment sites / files | rewritten | held | left | strings
kept | anchor |
|---|---|---|---|---|---|---|
| `objectstack-ai#6065` | 1/1 | 1 | 0 | 0 | 0 | `026101660` |
| `objectstack-ai#6123` | 1/1 | 1 | 0 | 0 | 0 | `59d1933f9` |
| `objectstack-ai#6206` | 5/2 | 5 | 0 | 0 | 0 | `8e13ca876` |
| `objectstack-ai#6216` | 2/1 | 2 | 0 | 0 | 1 | `f586f1a89` |
| `objectstack-ai#6220` | 1/1 | 1 | 0 | 0 | 0 | `83df2fd73` |
| `objectstack-ai#6238` | 2/2 | 2 | 0 | 0 | 2 | `c8d6f6e08` |
| `objectstack-ai#6259` | 4/2 | 3 | 0 | 1 | 1 | `6968885ef` |
| `objectstack-ai#6265` | 12/2 | 12 | 0 | 0 | 4 | `cfb549db8` |
| `objectstack-ai#6268` | 9/3 | 9 | 0 | 0 | 0 | `68f5eccb1` |
| `objectstack-ai#6287` | 1/1 | 1 | 0 | 0 | 0 | `84c86fb45` |
| `objectstack-ai#6307` | 1/1 | 1 | 0 | 0 | 0 | `293476148` |
| `objectstack-ai#6316` | 6/3 | 6 | 0 | 0 | 0 | `448ac9565` |
| `objectstack-ai#6345` | 10/3 | 10 | 0 | 0 | 0 | `e2798fab7` |
| `objectstack-ai#6361` | 4/2 | 4 | 0 | 0 | 6 | `90bbf2510` |
| `objectstack-ai#6363` | 6/2 | 6 | 0 | 0 | 2 | `17d095413` |
| `objectstack-ai#6483` | 3/2 | 3 | 0 | 0 | 0 | `ee58392e1` |
| `objectstack-ai#8722` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#8724` | 1/1 | 1 | 0 | 0 | 0 | `ff4ba6a06` |
| `objectstack-ai#8726` | 8/4 | 7 | 1 | 0 | 1 | `e783e163d` |
| `objectstack-ai#8796` | 13/3 | 13 | 0 | 0 | 4 | `a4331227b` |
| `objectstack-ai#8848` | 3/2 | 1 | 2 | 0 | 1 | `4fc4a3c0b` |
| `objectstack-ai#8919` | 1/1 | 0 | 1 | 0 | 0 | `b5378550e` (held file) |
| `objectstack-ai#9934` | 17/7 | 17 | 0 | 0 | 4 | `79c46da90` |
| `objectstack-ai#9967` | 1/1 | 1 | 0 | 0 | 0 | `8f266f1cd` |
| `objectstack-ai#10179` | 1/1 | 0 | 0 | 1 | 2 | — |
| `objectstack-ai#10243` | 40/13 | 40 | 0 | 0 | 9 | `266436a7f`, `02b41232d` |
| `objectstack-ai#10293` | 3/3 | 3 | 0 | 0 | 0 | `92a69d813` |
| `objectstack-ai#10338` | 1/1 | 1 | 0 | 0 | 0 | `d2619fd0c` |
| `objectstack-ai#10340` | 3/2 | 2 | 1 | 0 | 1 | `26f3588fb` |
| `objectstack-ai#10380` | 12/2 | 12 | 0 | 0 | 0 | `dd8172ee2` |
| `objectstack-ai#10485` | 3/3 | 3 | 0 | 0 | 0 | `35ad101bc` |
| `objectstack-ai#10503` | 8/2 | 3 | 5 | 0 | 1 | `67ceb9aef` |
| `objectstack-ai#10537` | 2/1 | 2 | 0 | 0 | 0 | `e634ecf6a` |
| `objectstack-ai#10554` | 1/1 | 1 | 0 | 0 | 0 | `6abc4df03` |
| `objectstack-ai#10629` | 75/23 | 75 | 0 | 0 | 0 | `13a6cb4ad` |
| `objectstack-ai#10630` | 4/1 | 4 | 0 | 0 | 0 | `dd8172ee2` |
| `objectstack-ai#10789` | 2/1 | 2 | 0 | 0 | 1 | `38bc74ed1` |
| `objectstack-ai#10886` | 1/1 | 1 | 0 | 0 | 1 | `809e61221` |
| `objectstack-ai#10888` | 3/3 | 2 | 1 | 0 | 1 | `d806081dd` |
| `objectstack-ai#10961` | 5/3 | 5 | 0 | 0 | 3 | `222d06fc1` |
| `objectstack-ai#10965` | 2/1 | 2 | 0 | 0 | 1 | `ab47f6974` |
| `objectstack-ai#10978` | 1/1 | 1 | 0 | 0 | 0 | `4c9780c7a` |
| `objectstack-ai#10983` | 3/2 | 3 | 0 | 0 | 0 | `6a4e929f5` |
| `objectstack-ai#11006` | 4/4 | 3 | 1 | 0 | 0 | `cccbe51bf` |
| `objectstack-ai#11015` | 3/1 | 3 | 0 | 0 | 0 | `82cb6e849` |
| `objectstack-ai#11166` | 8/3 | 8 | 0 | 0 | 4 | `735f5c709` |
| `objectstack-ai#11333` | 1/1 | 1 | 0 | 0 | 0 | `ea4d16420` |
| `objectstack-ai#11504` | 3/2 | 3 | 0 | 0 | 0 | `f90e82024` |
| `objectstack-ai#11513` | 2/2 | 2 | 0 | 0 | 0 | `e170b0ae5` |
| `objectstack-ai#11703` | 8/3 | 8 | 0 | 0 | 1 | `5cb62d88b` |
| `objectstack-ai#12010` | 1/1 | 1 | 0 | 0 | 0 | `77b91bdb4` |
| `objectstack-ai#12176` | 5/5 | 5 | 0 | 0 | 0 | `7986d973f` |
| `objectstack-ai#12194` | 11/4 | 8 | 3 | 0 | 0 | `311433f6b` |
| `objectstack-ai#12195` | 9/4 | 4 | 5 | 0 | 10 | `7986d973f` |
| `objectstack-ai#12281` | 20/5 | 20 | 0 | 0 | 5 | `0783d7b80` |
| `objectstack-ai#12943` | 7/3 | 7 | 0 | 0 | 0 | `090f2302e` |
| `objectstack-ai#13037` | 8/2 | 8 | 0 | 0 | 5 | `e7dfb1d69` |
| `objectstack-ai#13233` | 5/1 | 5 | 0 | 0 | 0 | `3800e4293` |
| `objectstack-ai#13241` | 5/4 | 5 | 0 | 0 | 1 | `a21d2a9cf` |
| `objectstack-ai#13273` | 11/3 | 11 | 0 | 0 | 0 | `3a86a65e7` |
| `objectstack-ai#13279` | 3/2 | 3 | 0 | 0 | 0 | `6a180e42d` |
| `objectstack-ai#13325` | 3/1 | 3 | 0 | 0 | 0 | `2e0b7b18f` |
| `objectstack-ai#13644` | 5/4 | 5 | 0 | 0 | 1 | `34ce8e7db` |
| `objectstack-ai#13657` | 13/1 | 13 | 0 | 0 | 1 | `b003cf2e8` |
| `objectstack-ai#14143` | 26/8 | 26 | 0 | 0 | 4 | `f19475c0a` |
| `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | 0 | `9d7f7259f` |
| `objectstack-ai#14398` | 3/1 | 3 | 0 | 0 | 0 | `317132495` |
| `objectstack-ai#14403` | 6/1 | 6 | 0 | 0 | 0 | `93d2d679b` |
| `objectstack-ai#14421` | 2/1 | 2 | 0 | 0 | 0 | `bd8795ea1` |
| `objectstack-ai#14422` | 4/2 | 4 | 0 | 0 | 4 | `dc7c226b9` |
| `objectstack-ai#14423` | 3/1 | 3 | 0 | 0 | 1 | `a56baa2bd` |
| `objectstack-ai#14474` | 1/1 | 1 | 0 | 0 | 0 | `df657d9df` |
| `objectstack-ai#14667` | 2/1 | 2 | 0 | 0 | 0 | `dc7c226b9` |
| `objectstack-ai#14678` | 2/1 | 2 | 0 | 0 | 2 | `73ad0bba7` |
| `objectstack-ai#14683` | 2/2 | 2 | 0 | 0 | 0 | `96326040f` |
| `objectstack-ai#14723` | 1/1 | 1 | 0 | 0 | 0 | `65846bc46` |
| `objectstack-ai#14745` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#14748` | 1/1 | 1 | 0 | 0 | 1 | `92b5d7f00` |
| `objectstack-ai#14758` | 15/5 | 15 | 0 | 0 | 1 | `84199cb87` |
| `objectstack-ai#14760` | 6/2 | 6 | 0 | 0 | 2 | `ee32e1cb8` |
| `objectstack-ai#14864` | 3/3 | 3 | 0 | 0 | 3 | `066dd3bd0` |
| `objectstack-ai#14878` | 2/1 | 2 | 0 | 0 | 1 | `29db3cd2a` |
| `objectstack-ai#14908` | 3/2 | 3 | 0 | 0 | 0 | `d5cbb44f3` |
| `objectstack-ai#14921` | 2/1 | 2 | 0 | 0 | 0 | `c1d274de7` |
| `objectstack-ai#15063` | 2/1 | 2 | 0 | 0 | 0 | `ad35745e8` |
| `objectstack-ai#15068` | 2/2 | 2 | 0 | 0 | 4 | `8744de9e9` |
| `objectstack-ai#15071` | 5/2 | 5 | 0 | 0 | 0 | `cf6e0a193` |
| `objectstack-ai#16610` | 3/1 | 3 | 0 | 0 | 0 | `316a20fc5` |
| `objectstack-ai#16649` | 4/1 | 4 | 0 | 0 | 0 | `44c917a47`, `613bfbd3d` |
| `objectstack-ai#16755` | 1/1 | 1 | 0 | 0 | 0 | `44c849c7d` |
| `objectstack-ai#16758` | 1/1 | 1 | 0 | 0 | 0 | `6e9bee640` |
| `objectstack-ai#16783` | 1/1 | 1 | 0 | 0 | 0 | `854639b31` |
| `objectstack-ai#16919` | 1/1 | 1 | 0 | 0 | 0 | `2cd4c548e` |
| `objectstack-ai#17038` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#17039` | 1/1 | 1 | 0 | 0 | 0 | `edf59e359` |
| `objectstack-ai#17041` | 2/2 | 0 | 0 | 2 | 0 | — |
| `objectstack-ai#17114` | 2/2 | 2 | 0 | 0 | 2 | `4af758d47` |
| `objectstack-ai#17147` | 1/1 | 1 | 0 | 0 | 0 | `aaacf1d5c` |
| `objectstack-ai#17148` | 1/1 | 0 | 0 | 1 | 0 | — |
| `objectstack-ai#17195` | 1/1 | 1 | 0 | 0 | 0 | `d2c1d1980` |
| `objectstack-ai#17219` | 1/1 | 1 | 0 | 0 | 0 | `706ad0fcc` |
| `objectstack-ai#19364` | 2/2 | 2 | 0 | 0 | 0 | `ada701220` |
| `objectstack-ai#19394` | 5/2 | 5 | 0 | 0 | 0 | `0862063ba` |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 95), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 95). The checkout is not shallow (`--is-shallow-repository`
false), and the control leg `13a6cb4ad` exits 0 too.

**Numbers with more than one anchor, by site:**
- `objectstack-ai#10243` (40 sites): `266436a7f` for the 26 sites that describe the
2026-08-23 ruling it implements (the enablement door joins the
`manage_metadata` write set, with the `trigger` exclusion), and
`02b41232d` for the 14 that name the leak itself (「the leak commit
02b4123 measured」). That commit recorded the measurement over HTTP and
says it is part of that card.
- `objectstack-ai#16649` (4 sites): `613bfbd3d` for the first half (the fourteen
remaining `boot-refusal` rows registered) and `44c917a47` for the second
(the face refusal widened to every published package, and `boot-refusal`
retired).
- `objectstack-ai#12176`, `objectstack-ai#12194`, `objectstack-ai#12195`: the stages of one ruled retirement.
`311433f6b` is stage 1 (the item-name grammar refused at the publish
door) and `7986d973f` is stage 3 (the compound arities un-mounted).
These are the anchors the spec stages gave.

**Wordings to check, each true of its commit:**
- `objectstack-ai#10293` (3 sites) cited the p1 flake whose signature had the
expected-noise lines lifted into it. They now read 「(a vitest teardown
race, fixed by commit 92a69d8)」. `92a69d813` names that number in its
subject and fixed the flake by disarming vitest's console-forwarding
teardown race, which is why the noise pointed the dispatch at the wrong
mechanism.
- `objectstack-ai#16755` and `objectstack-ai#16783` each cited an open PR that held a file at the
time. They now read 「the change that landed as commit 44c849c held
that file」 and 「then held by the change that landed as commit
854639b」. Each commit's diff edits the named file
(`domains/automation.ts`, `seed-loader.test.ts`).
- Quoted rulings keep their words.
`dispatcher-plugin.declared-5xx-prose-withhold.test.ts:13` and
`dispatcher-plugin.declared-user-message.test.ts:35` quote the
2026-08-27 ruling, and
`dispatcher-5xx-demoted-code-withhold.test.ts:281` quotes an older note.
There the commit stands in an editorial bracket (`[commit 79c46da]`,
`[commit 0783d7b]`) in place of the number.
- `objectstack-ai#9934`'s 「second constraint」 and 「third constraint」 now read 「the
ruling's second constraint, commit 79c46da」. That commit's own diff
calls status-agnosticism 「the ruling's second constraint」.
- `domains/packages.ts:841` read 「declares, since objectstack-ai#19364:」 above the
`enabled` line, but that line predates `ada701220` (objectstack-ai#19364's commit). It
now reads 「declares — a key commit ada7012 kept rather than retired:」.
- `route-ledger.ts:288`: 「objectstack-ai#16758 filed the second kind」 now reads
「Commit 6e9bee6 gated the second kind」, because that commit added the
row census after the index-slice incident the sentence goes on to
describe.
- `flow-clone.ts:7` and `domains/automation.ts:2403` cite `e170b0ae5`
for objectstack-ai#11513: the commit that landed 「lock package-declared permission
sets at the save door; clone to customize」, whose changeset names the
number.

## The sites left

**No deciding commit, or a literal reader (8 sites):**
- `api-exposure.ts:108` (objectstack-ai#6259): `api-exposure.test.ts:152` splits this
`@param` block on the literal `'objectstack-ai#6259'`, so rewriting the comment would
change what the test measures. Its deciding commit is `6968885ef`, which
the three test-comment sites of the same number now cite.
- `domains/mcp.ts:360` (objectstack-ai#8722): a wider contract change 「archived
unscheduled」. It never landed, so no commit decided it.
- `domains/meta-state-plural-tolerance.test.ts:130` (objectstack-ai#10179): an untaken
option on a tracking card. The only commit naming the card, `53a48c93f`,
recorded the opposite state.
- `package-door-namespace-conflict-code.test.ts:30` (objectstack-ai#14745): a residue
item on a review card. The only commit carrying the token is the one
that added this file.
- `route-ledger.conformance.test.ts:33` (objectstack-ai#17038): an ablation measured
on a PR whose squash commit, `6a7910abb`, neither records nor performs
it.
- `route-ledger.conformance.test.ts:38` and `route-ledger.ts:300`
(objectstack-ai#17041): a maintainer decision the lines call open.
- `security/artifact-granted-permissions.test.ts:291` (objectstack-ai#17148): a
question the line itself says is unsettled.

**Held with `domains/meta.ts` (20 sites), anchors verified for the
follow-up:** `objectstack-ai#8726` `:116` to `e783e163d`; `objectstack-ai#8848` `:200`, `:1398` to
`4fc4a3c0b`; `objectstack-ai#8919` `:1247` to `b5378550e`; `objectstack-ai#10340` `:1309` to
`26f3588fb`; `objectstack-ai#10503` `:14`, `:1143`, `:1159`, `:1250`, `:1308` to
`67ceb9aef`; `objectstack-ai#10888` `:1337` to `d806081dd`; `objectstack-ai#11006` `:103` to
`cccbe51bf`; `objectstack-ai#12194` `:831`, `:1050`, `:1173` to `311433f6b`; `objectstack-ai#12195`
`:819`, `:827`, `:1046`, `:1167`, `:1960` to `7986d973f`. PR objectstack-ai#20615's
one hunk there is at `:1874`, disjoint from these lines, but the rule is
file-level.

**String sites kept as tokens (100).** 95 are test titles and test-code
strings in 43 files. Five are non-test strings: the `route-ledger.ts`
`note` fields at `:435`, `:441` and `:505`, a string at
`dispatcher-error-vocabulary.ts:349`, and the enablement door's refusal
text at `domains/activation-gate.ts:279`, which ends 「(objectstack-ai#10243).」 (see
Acceptance notes).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, so template literals are read in context)
of each touched file at base `eb4b17c346` against the working tree at
`a5cdfd8a46`, over all 118 touched `.ts` files. Controls mutate the head
text in memory only, so nothing on disk moved for them.

- Real run: 301,081 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`domains/activation-gate.ts`): 0 files
changed (exit 0).
- Code-insertion positive control (a declaration in the same file):
DIFFER at token 34 (exit 1).
- String positive control (`(objectstack-ai#10243)` to `(objectstack-ai#10244)` inside the kept
refusal string): DIFFER at token 339 (exit 1).

Line balance: every touched file is +N/−N (508/508), and every line
count is equal at base and head. A raw scan of the 119 changed files for
control bytes finds none.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/runtime` is included, in PR objectstack-ai#20609's form and level. It
says only that the provenance comments were re-anchored.

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/runtime build`, the
rewritten docblocks reach `dist`: for example `e2798fab7` appears 3
times and `68f5eccb1` 6 times in `dist/index.d.ts`, and `f19475c0a` 4
times in `dist/index.js`. The positive control, the unchanged sentence
「drags `@libsql/client` (native bindings included)」 of the same
`turso-driver-factory.ts` docblock, is in `dist/index.d.ts`, and a
negative control phrase appears nowhere. The only dead number left in
`dist` is the kept refusal string's `objectstack-ai#10243`.

## Gates (head `a5cdfd8a46`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each locked run at this
head:

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/runtime 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 99s (1m39s) · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 6s · declare it in the PR body · pnpm --filter @objectstack/runtime exec vitest run --project repo --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 8s · declare it in the PR body · pnpm --filter @objectstack/runtime typecheck
```

The dependency closure and the whole workspace were built first, at the
merge head `ca6d13d6ab`, the same way: `turbo run build
--filter='@objectstack/runtime...'` (30 tasks, exit 0) and `turbo run
build --filter='./packages/*' --filter='./packages/*/*'` (71 tasks, exit
0). `a5cdfd8a46` differs from that head only in `domains/meta.ts`, which
went back to base bytes, and `@objectstack/runtime` was rebuilt at
`a5cdfd8a46`.

- **Tests:** `vitest run --project local`: 288 files, 4,190 tests
passed, 1 skipped. `--project repo` (which holds the touched
`action-owner-key-single-source.test.ts`): 3 files, 727 tests passed.
Together they cover every touched test file.
- **Typecheck:** `pnpm --filter @objectstack/runtime typecheck` exits 0.
`tsc --listFiles` counts 82 `src` files (no tests) under `tsconfig.json`
and all 291 test files under `tsconfig.test.json`, which
`check:test-typecheck` judges: 27 files, 190 errors, 68 pinned
signatures held.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `a5cdfd8a46` (2026-09-29T09:23:06Z to 09:23:36Z). A narrowed
run over the 118 touched `.ts` files through eslint's API agrees: 118
linted, 0 ignored, 0 errors, 0 warnings.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0. The diff-scoped run judged 23 citations across 28
files, and all 23 resolve. These are the live numbers that stay on
rewritten lines. It defers `*.test.ts`, so the added-minus-removed count
over the whole diff covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `a5cdfd8a46` derived 67
families, the same set as at the merge head. All 67 exit 0. `--ran`
reads 「67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN」.
- At the merge head, `check:dual-build-cjs-loads` and
`check:type-check-debt` first exited 3 (PREREQUISITE NOT MET) on a
partly built workspace. After the whole-workspace build both exited 0,
and both exit 0 at the final head.
- Among them: `check:doc-authoring` (the sibling prose-id baseline
holds, 810 pinned sites, no growth), `check:nul-bytes` (9,250 files, no
raw control bytes), `check:route-ledger-census`,
`check:dispatcher-error-vocabulary` and `check:issue-citations`
(self-test, 114 cases in 8 batteries).
- **Artifact rosters:** 38 of the 41 non-self-test roster rows exit 0 at
the merge head. The other three, `check-closing-target-claim`,
`check-partof-closing-keyword` and `check-single-claim-paths`, answer
「NOT WIRED」 (exit 2) without a pull request's context, and are run
against this PR and reported on the card.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 217 dead sites at
`eb4b17c346` (29 files, 59 numbers), equal to the card's count at
`f11b5f20a2`: no drift.
- **H1 holds, with the listed exceptions.** After the rewrite the
filtered census answers 23: the 20 sites held with `domains/meta.ts` for
an open PR, and 3 deliberate ones (a literal reader, a card never
landed, an open decision). The supplementary reading adds 5 test-comment
sites of the same two kinds.
- **H2 holds, by the token guard.** A comment-stripped comparison of
every touched file (the parser's leaf tokens, JSDoc excluded) is empty,
and its controls fire. The emitted `dist` is not byte-identical, because
the docblocks ship, which is why the changeset is `patch`.

## Acceptance notes

- **The held file.** The claim's read (07:51Z) and this stage's first
read of the open PRs' file lists (08:06:32Z, 8 open PRs) found none
touching `packages/runtime/src`. PR objectstack-ai#20615 opened at 08:12:40Z and edits
`domains/meta.ts`. The re-read at 09:07:08Z (7 open PRs) found it, and
it is the only open PR touching the package. The file went back to its
base blob in `a5cdfd8a46`, and `git hash-object` equals `b4ddb362cc`,
the blob at the base and at `origin/main`. The 20 anchors above are
ready for the follow-up once that PR lands.
- **Form D, not touched here.** `domains/activation-gate.ts:279` is part
of the enablement door's refusal message and ends 「(objectstack-ai#10243).」. An author
sees it, so it is ruling D's (no number, the lesson in words), a string
change outside this comment-only scope. It needs a form-D carrier. The
other four non-test string sites are ledger `note` data and a gate's own
string.
- **The grammar does not read a slash-joined number.** `CITATION_RE`
refuses a `#` preceded by `/`, so the second number of `#A/#B` is never
judged. In `packages/runtime/src`, 3 such dead numbers exist (`objectstack-ai#10630`,
`objectstack-ai#12281`, `objectstack-ai#12194`), and all 3 are rewritten here. The other 36 distinct
slash-joined numbers there were probed by REST and answer 200. One more
dead one, `objectstack-ai#17219`, stands slash-joined inside a test title, a string,
and is kept. This is the same shape as PR objectstack-ai#20612's slash-joined
`objectstack-ai#5775/objectstack-ai#6629`. It is noted, not filed.
- **Outside the scope and the census surface.**
`packages/runtime/vitest.config.ts:54` cites `objectstack-ai#17853`, which answers
404. The file is outside `src/**`, so it is left for whoever owns the
package's config. The other numbers there, and those in `tsup.config.ts`
and `README.md`, answer 200.
- **Base.** The branch merged `origin/main` once (`ca6d13d6ab`, merging
`c1d8051e0a`) before the `--base origin/main` run, as the dispatch
orders. That merge brought PR objectstack-ai#20609's and PR objectstack-ai#20612's landed stages and
touched none of this diff's files. `origin/main` has since moved to
`ed6f7348f9`, one commit that touches only `packages/cli`, so there was
no second merge.
- **Anchors shared with the landed stages.** 24 numbers keep the anchor
the spec, lint or service-messaging stages already gave them, for
example `f19475c0a` (objectstack-ai#14143), `b003cf2e8` (objectstack-ai#13657), `311433f6b`
(objectstack-ai#12194), `8e13ca876` (objectstack-ai#6206) and `17d095413` (objectstack-ai#6363).

## Deviations

- Three changed lines hold only a slash-joined dead number, beyond the
census's sites (see Acceptance notes). Five more are the other half of a
rewritten sentence (listed under What changed).
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The merge commit carries git's default message.

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

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…les.ts to the commits that decided them (objectstack-ai#20631)

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

Stage 2 of the `packages/lint` dead-citation sweep (claim `5888191846`).
Stage 1 (PR objectstack-ai#20612) left `packages/lint/src/authoring-rules.ts` at its
base blob while PR objectstack-ai#20593 held the file. That PR has landed
(`e651556e2d`). Each of the file's five comment and docblock lines that
cited a tracker number answering 404 now cites, in ruling C+D's form C,
the commit in this repository's history that decided what the line
states. Each line still says what was decided. Comments only: 5 lines
out, 5 in, in one file, plus one `@objectstack/lint` `patch` changeset.

This PR says `Part of`: the form-D finding-message string at
`validate-react-page-props.ts:1198` (`(objectstack-ai#11284)`, shown to authors) stays
on the card as a separate decision. It is byte-identical here (see
Acceptance notes).

## Measurement

The instrument is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to `packages/lint/`.

- **Before:** base `1322cc72c9`, 2026-09-29T10:18:50Z to 10:22:33Z,
board enumerated (185 pages, frontier objectstack-ai#20627). Repo-wide
`allocated-but-absent` 2,209.
- **After:** head `2e1955b492`, 11:13:21Z to 11:16:47Z (185 pages,
frontier objectstack-ai#20630). Repo-wide `allocated-but-absent` 2,204, exactly 5
fewer. No finding at head is absent at base.

| site in `authoring-rules.ts` | before | after | anchor |
|---|---|---|---|
| `:201` (the `AuthoringFinding.path` docblock) | objectstack-ai#10064 | commit |
`def0d3e63` |
| `:1123` | objectstack-ai#16659 | commit | `ecdfc9411` |
| `:1722` | `(PR objectstack-ai#8546)` | commit | `ba5e957ef` |
| `:1748` | `[objectstack-ai#19370]` | commit | `a227afa41` |
| `:1768` | `[ADR-0090 D3 / objectstack-ai#8310 → objectstack-ai#19370]` | commit | `a227afa41`
(ADR-0090 D3 and objectstack-ai#8310 stay) |
| **`packages/lint` total** | **5** | **0** | 4 numbers, to 4 distinct
shas |

The five lines on `origin/main` `e651556e2d` are the same lines at the
base: `packages/lint` is byte-identical between `e651556e2d` and
`1322cc72c9`.

## Why each anchor decides its line

Each sha resolves uniquely (`rev-parse --disambiguate` gives 1 object).
Each is single-parent. `merge-base --is-ancestor` exits 0 against
`origin/main` and against the base, and the repository is not shallow.
Each commit's own diff was read for the rule its line states.

- **objectstack-ai#10064 to `def0d3e63`**: "key collection-resident publish-gate
finding paths by name, not the private snapshot index". Its body names
objectstack-ai#10064 as the card it lands, "(maintainer ruling 2026-08-20: Option A)".
Its own diff wrote this very docblock: the positional-as-rules-emit-it
sentence, the `objects.acme_invoice.sharingModel` example and the
pointer to `nameKeyFindingPath`, which the same commit introduced in
`runtime-gate.ts`.
- **objectstack-ai#16659 to `ecdfc9411`**: the squash commit that declares a
time-triggered flow's acting organization. Its diff adds
`FLOW_SCHEDULE_ORGANIZATION_MISSING`
(`flow-schedule-organization-missing`, at `warning`) to
`validate-flow-trigger-readiness.ts`. It also wrote the
`authoring-rules.ts` sentence "... added a sixth id,
`flow-schedule-organization-missing`, at `warning`" that this line
opens, and its sub-commits name objectstack-ai#16659. The live objectstack-ai#17396 retirement
beside it stays.
- **`(PR objectstack-ai#8546)` to `ba5e957ef`**: PR objectstack-ai#8546's own squash commit,
"permission/book cross the runtime publish gate; object measured dirty
stays behind". Its `authoring-rules.ts` diff changes `runtimeTypes:
['seed']` to `['seed', 'permission', 'book']`, which is objectstack-ai#8310 slice 1 as
the line states. The live objectstack-ai#8310 stays.
- **objectstack-ai#19370 to `a227afa41`** (two sites): "`security-role-word` crosses
to the runtime publish gate, whole". Its body names objectstack-ai#19370 as the card
it lands. Its own `authoring-rules.ts` diff wrote both lines: "[objectstack-ai#19370]
It has since crossed, also whole, on its own entry" and the `[ADR-0090
D3 / objectstack-ai#8310 → objectstack-ai#19370]` marker. Stage 1 cited the same commit for the same
number in `runtime-gate.ts` and `validate-security-posture.ts`.

Rung: no file under `docs/adr/**`, `docs/NORTH-STAR.md` or
`scripts/adr-anchors/` names any of the four numbers. So the commit rung
is right, as in stage 1. ADR-0090 D3 already stands on `:1768` and is
kept.

## Mechanical proof

- **Token and residue guard.** A scratch instrument on the TypeScript
6.0.3 parser compares the base blob with the head blob at two levels.
The first is leaf AST tokens, with JSDoc nodes excluded. The second is
the non-comment residue: every comment range dropped, everything else
compared byte for byte. The controls mutate the head text in memory
only.
- Real run: 3,543 tokens at base and at head, tokens EQUAL, residue
EQUAL (exit 0).
- Dark control, a whole comment line inserted: tokens EQUAL, residue
EQUAL, comment ranges 957 to 958 (exit 0).
- Lit control, a code statement inserted: DIFFER at token 0, residue
DIFFER (exit 1).
- Lit control, one character inserted into a parser-located string
literal: DIFFER at token 5, residue DIFFER (exit 1). The first string
control was a no-op and is void: it searched by text and landed in a
comment, reading EQUAL. It was re-anchored on a parser-located literal
and re-run. The mutation was confirmed landed.
- **Line balance**: +5/−5, and every changed line is comment-shaped. The
file has 2,011 lines at base and at head.
- **Tracker numbers**: removed objectstack-ai#10064, objectstack-ai#16659, objectstack-ai#8546 and objectstack-ai#19370 ×2. The
added lines carry only the live objectstack-ai#8310 ×2, which stands on both the
removed and the added side of `:1722` and `:1768`. So added-not-removed
is empty, and no `PR #N` stands on an added line. There are 215 `#N`
tokens at base and 210 at head.
- **Shas**: 4 distinct on added lines (`a227afa41` ×2), none on removed
lines.
- **Literal readers**: `scripts/doc-authoring-prose-id.baseline.json`
pins this file's string sites as objectstack-ai#4463, objectstack-ai#4716, objectstack-ai#4717, objectstack-ai#7220, objectstack-ai#8309 and
objectstack-ai#9698, none of them these four numbers. `check-docs-transcript-drift`
loads the registry module, not its comments.

## Tests and gates (at head `2e1955b492`)

- Build under `os-verify-lock`: `pnpm exec turbo run build
--concurrency=2 --filter=./packages/* --filter=./packages/*/*`. The last
run printed Tasks 71 successful, 71 total, and VERDICT command-exit 0.
It took three attempts inside a 270 s timeout on a shared box. The first
two were cut off at 39 of 46 and 66 of 68 tasks, and turbo's cache
carried their finished tasks forward.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` under
the lock: Test Files 115 passed (115), Tests 5379 passed (5379), VERDICT
command-exit 0.
- `pnpm --filter @objectstack/lint typecheck` under the lock: exit 0.
`check:test-typecheck` OK (2 files, 6 errors, 2 pinned signatures held).
VERDICT command-exit 0.
- Lint, as a proven narrowing: `eslint --no-inline-config --format json
packages/lint/src/authoring-rules.ts` reports 1 file, 0 errors, 0
warnings. `isPathIgnored` is false, read through eslint's API.
`eslint.config.mjs:327-328` says type-aware linting is never enabled, so
a comment edit cannot move an untouched file's verdict. The repo-wide
`pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 54 families, and all 54 ran with exit 0. `--ran`
reads "54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero.
Among them:
- `node scripts/check-issue-citations.mjs`, the live diff-scoped run,
judged 2 citations in 1 file, the kept objectstack-ai#8310 ×2, and both answer as
issues. `pnpm check:issue-citations` passes its self-test (114 cases in
8 batteries).
- `pnpm check:doc-authoring`: the sibling prose-id baseline holds, 810
pinned sites across 230 files, no growth.
- `pnpm check:nul-bytes`: OK over 9,253 tracked text files. A
control-byte scan of both changed files finds none.
- Generated pages: none to regenerate. No page under
`content/docs/references/` names the four numbers, `AuthoringFinding` or
`nameKeyFindingPath`, and no generator reads `packages/lint/src`.
- Changeset: `patch` for `@objectstack/lint`, a new file (stage 1's
`lint-provenance-anchors.md` is untouched). `files[]` ships `dist`, and
the rewritten comments reach it:
- `commit def0d3e` is in the `AuthoringFinding` docblock of
`dist/runtime-*.d.ts`, beside the unchanged "Positional as RULES emit
it", the positive control.
- `commit ba5e957` (cited only here) and `commit a227afa` are in
`dist/index.js` and `dist/index.cjs`.
  - None of the four numbers remains in `dist`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`542670da6d`, from a bare shared clone with no `merge.*` config, exits
0. None of the three commits `main` gained since the base touches
`packages/lint`.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** At the base the census reads exactly 5 dead sites in
`packages/lint`, all in `authoring-rules.ts`, and after the change it
reads 0. The card's sixth site, the `(objectstack-ai#11284)` string at
`validate-react-page-props.ts:1198`, is outside the census because the
census blanks string literals. It was read directly: still present, and
the file is byte-identical from base to head.
2. **Holds.** The five sites read `:201` objectstack-ai#10064, `:1123` objectstack-ai#16659, `:1722`
`(PR objectstack-ai#8546)`, `:1748` and `:1768` objectstack-ai#19370, on `e651556e2d` and at the
base alike. All four anchors were re-verified above from their own
diffs, not copied.
3. **Holds.** PR objectstack-ai#20593's new lines cite objectstack-ai#20553, objectstack-ai#20611 and objectstack-ai#20552 (and
ADR-0041). All three answer 200, and the census finds no dead site on
them. None of the five lines' sentences changed in meaning. The one
adjacency is described under Acceptance notes.

## Deviations

- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`), not the model-named trailer the harness
reminder suggested. The pre-push trailer check passed on both pushes.
- The first lit string control was a no-op: it searched by text and
landed in a comment. It is reported void above and was re-run on a
parser-located literal.

## Acceptance notes

**Form D, not touched (why this PR says `Part of`):**
`validate-react-page-props.ts:1198` is the `react-prop-deprecated`
finding `message`. It ends "...is removed after the deprecation window
(objectstack-ai#11284)." An author sees it, so it takes ruling D (no number). That is
a string change, outside this comment-only claim.
`scripts/doc-authoring-prose-id.baseline.json` pins it (`objectstack-ai#11284: 1` for
that file), and that baseline is shrink-only.

**An ordinal beside PR objectstack-ai#20593's insertion, kept verbatim:** the
paragraph above `:1123` now ends "objectstack-ai#20553 made it five", counting the
rules that emit `error`. `:1123` reads "Commit ecdfc94 added a sixth
id", an ordinal that commit wrote itself. The two count different
things: rules that emit `error`, and ids in the rule file. The ordinal
is also imprecise on its own terms, because
`validate-flow-trigger-readiness.ts` exported six ids before
`ecdfc9411`, so the new one was its seventh. This PR moves only the
tracker number, so the word stays as written.

**Outside the census's surface (noted, not swept):** stage 1 notes that
lint test titles and hand-written docs still cite these numbers.
`content/docs/deployment/validating-metadata.mdx` cites objectstack-ai#19370 at
`:472`, `:483` and `:515`.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… to the commits that decided them (objectstack-ai#20632)

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

## What changed

This is stage 2 of the `domain:cli` lane of the dead-citation sweep:
`packages/rest/src/**`. Every comment or docblock site in scope that
cited a tracker number answering 404 now cites, in ruling C+D's form C
(comment 5749154545 on objectstack-ai#19123), the commit in this repository's history
that decided what the line describes, and says in its own words what
that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20624 (stage 1,
`packages/runtime`) the precedent this follows line for line. Later
stages cover `cli`, `types` and the rest of the lane, so this PR says
`Part of` and the card stays open.

That is **457 comment sites on 445 lines in 85 files, covering 74
numbers**: the census's 191 sites, 256 more in test comments (which the
census defers), and 10 sites whose dead number is the second half of a
slash-joined pair the citation grammar does not read (`objectstack-ai#3984/objectstack-ai#6241`,
`objectstack-ai#9901/objectstack-ai#10255` four times, `objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292`, `objectstack-ai#11235/objectstack-ai#11242`
twice, `objectstack-ai#10993/objectstack-ai#11242`, `objectstack-ai#7543/objectstack-ai#15071`). Each rewritten line cites one
of **70 distinct commits**.

ADR-0076 D11 is the only ADR that records any of these numbers, and it
records objectstack-ai#8850 only as the extraction it names as landed in `8664a2c99`,
so that commit is the anchor there. No other ADR or ruling-record file
in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any
of these numbers, so every anchor is a commit. The anchors the landed
stages already gave the same numbers are reused where the rest sites
describe the same decision (30 numbers, for example `79c46da90` for
objectstack-ai#9934, `7986d973f` / `311433f6b` for the compound-name retirement,
`6a180e42d` for objectstack-ai#13279 and `cf6e0a193` for objectstack-ai#15071), so each number
carries one anchor across the tree.

Only comments changed. Every touched file keeps its line count (451
lines out, 451 in, over 85 files), so no line citation into these files
moves. Six of the 451 lines held no dead site; each is the other half of
a sentence that had to change:
- `discovery-schema-conformance.test.ts:343` (「(reaffirmed by」 to
「(which commits」, because line 344 now names the two commits that landed
the ruling),
- `package-door-16019-raw-statement-fault-code.test.ts:51` and
`error-response.ts:1485` (a trailing 「PR」 whose number wrapped onto the
next line),
- `error-response-structured-arm-door-parity.test.ts:463` (「That card
added the limb」 to 「That commit」, because line 459's tag now names the
commit),
- `rest-hook-script-fault-envelope.test.ts:331` (「both sides of that
card」 to 「that fix」),
- `rest-server.ts:908` (「(objectstack-ai#14409, landed」 to 「(landed as commit」, the
sha `3ecb7dc1a` already standing on line 909).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. No PR number stands on an added
line. One of the 70 shas is on a removed line, and it was there before:
`rest-14078-invalid-date-total-arm.test.ts:19` read 「PR objectstack-ai#14409 (landed
`3ecb7dc1a`)」 and now reads 「Commit 3ecb7dc drove」. No code token
moves (see the guard below).

Three dead comment sites are left on purpose, listed under "The sites
left". One more file: a `patch` changeset for `@objectstack/rest`,
because the rewritten docblocks ship (see Changeset below).

## Census: `packages/rest`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run with the fleet token. Its
surface is comment prose in `packages/**/src/**/*.ts` with string
literals blanked, and it defers `*.test.ts`. The count is its
`allocated-but-absent` findings under `packages/rest/`. Both runs
enumerated the whole board (185 pages), so neither read a truncated
board.

| reading | tree | board | whole-repo `allocated-but-absent` | rest
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `a186aea996`, run 2026-09-29T10:28:18Z to 10:36:06Z |
enumerated, 185 pages, frontier objectstack-ai#20628, 18,455 numbers | 2,015 | **191**
| 186 | 14 | 51 |
| after | head `93e4d69ba6`, run 11:11:30Z to 11:17:37Z | enumerated,
185 pages, frontier objectstack-ai#20630, 18,457 numbers | 1,764 | **0** | 0 | 0 | 0 |

The before count equals the card's 191 at `f11b5f20a2`. The whole-repo
drop is 251: this diff's 191, plus the 60 of PR objectstack-ai#20626
(`packages/plugins/plugin-sharing`, 63 to 3), which landed on `main` in
between and came in with the merge. No other package moved.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `classifyCitation` over
every `.ts` file under `packages/rest/src` (256 files), against the
board enumerated through the gate's own `enumerateBoard`. The lit
controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20624 answered 200 and are on both boards;
the dead controls objectstack-ai#13214, objectstack-ai#14541 and objectstack-ai#15071 answered 404 and are on
neither.

| reading | tree | board | citations | dead | src comment | test comment
| src string | test string |
|---|---|---|---|---|---|---|---|---|
| before, 10:29Z | `a186aea996` | 185 pages, frontier objectstack-ai#20628 | 4,620 |
**577** | 191 | 259 | 1 | 126 |
| after, 11:21Z | `93e4d69ba6` | 185 pages, frontier objectstack-ai#20631 | 4,174 |
**130** | 0 | 3 | 1 | 126 |

Its src-comment column equals the census's 191 and 0, which is the
control on the second instrument, and a site-by-site comparison of the
two before-readings is identical. Resolving comment citations move by
one (1,364 to 1,365 in src): `(objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292)` became `(objectstack-ai#10993,
commit 376c70f, objectstack-ai#11292)`, so the grammar now reads the live `objectstack-ai#11292`
that the slash hid. The drop is 447 grammar-read sites; the other 10
rewritten sites are the slash-joined ones the grammar never read.

Separately, every one of the 77 numbers was probed on its web endpoint:
76 answer 404 (deleted) and one, #14026, answers 302 to
objectstack-ai/objectui#10102 (transferred), which is why it is left
(see below).

## Per-number table

Sites and files are the dead comment sites in scope at the base, tests
and slash-joined halves included. `left` is a site with no deciding
commit (see below). `strings kept` counts string-literal sites, which
are tokens and stay as they were. Every anchor was read in its message
or its diff, not only in its subject: it is the commit that made the
change the line describes, and its own message or diff names the number
it replaces or adds the citation the line carries.

| number | comment sites / files | rewritten | left | strings kept |
anchor |
|---|---|---|---|---|---|
| `objectstack-ai#6037` | 5/3 | 5 | 0 | 0 | `18189983d` |
| `objectstack-ai#6122` | 2/2 | 2 | 0 | 0 | `64cd01082` |
| `objectstack-ai#6206` | 1/1 | 1 | 0 | 0 | `8e13ca876` |
| `objectstack-ai#6216` | 6/2 | 6 | 0 | 2 | `f586f1a89` |
| `objectstack-ai#6241` | 10/3 (1 slash-joined) | 10 | 0 | 1 | `83a3b1f2e` |
| `objectstack-ai#6259` | 2/1 | 2 | 0 | 0 | `6968885ef` |
| `objectstack-ai#6303` | 1/1 | 1 | 0 | 0 | `465c5fc14` |
| `objectstack-ai#6306` | 9/5 | 9 | 0 | 3 | `fec784863` |
| `objectstack-ai#6307` | 4/2 | 4 | 0 | 0 | `293476148` |
| `objectstack-ai#6349` | 4/2 | 4 | 0 | 4 | `2443bb4c4` |
| `objectstack-ai#6474` | 1/1 | 1 | 0 | 0 | `18189983d` |
| `objectstack-ai#6535` | 3/2 | 3 | 0 | 0 | `a92b1793c` |
| `objectstack-ai#6640` | 1/1 | 1 | 0 | 1 | `2ab1257c9` |
| `objectstack-ai#6704` | 5/1 | 5 | 0 | 1 | `c3f491626` |
| `objectstack-ai#8641` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#8850` | 3/3 | 3 | 0 | 0 | `8664a2c99` |
| `objectstack-ai#8885` | 6/3 | 6 | 0 | 3 | `30b1c636a` |
| `objectstack-ai#8919` | 7/3 | 7 | 0 | 7 | `b5378550e` |
| `objectstack-ai#9741` | 12/1 | 12 | 0 | 0 | `2a29caa53` |
| `objectstack-ai#9805` | 1/1 | 1 | 0 | 0 | `45862a53d` |
| `objectstack-ai#9934` | 19/10 | 19 | 0 | 4 | `79c46da90` |
| `objectstack-ai#9967` | 2/2 | 2 | 0 | 4 | `8f266f1cd` |
| `objectstack-ai#10063` | 2/2 | 2 | 0 | 1 | `9e04c3e35` |
| `objectstack-ai#10178` | 1/1 | 1 | 0 | 0 | `38cf397ea` |
| `objectstack-ai#10179` | 0/0 | 0 | 0 | 1 |  |
| `objectstack-ai#10255` | 18/4 (4 slash-joined) | 18 | 0 | 2 | `6ce58a735` |
| `objectstack-ai#10340` | 13/3 | 13 | 0 | 2 | `26f3588fb` |
| `objectstack-ai#10345` | 13/6 | 13 | 0 | 6 | `cad8b42f0` |
| `objectstack-ai#10350` | 1/1 | 1 | 0 | 0 | `490879ad0` |
| `objectstack-ai#10485` | 2/1 | 2 | 0 | 1 | `35ad101bc` |
| `objectstack-ai#10537` | 9/3 | 9 | 0 | 1 | `e634ecf6a` |
| `objectstack-ai#10888` | 2/2 | 2 | 0 | 0 | `d806081dd` |
| `objectstack-ai#11006` | 3/1 | 3 | 0 | 0 | `cccbe51bf` |
| `objectstack-ai#11130` | 1/1 | 1 | 0 | 0 | `851909530` |
| `objectstack-ai#11235` | 4/2 (1 slash-joined) | 4 | 0 | 0 | `376c70f98` |
| `objectstack-ai#11242` | 3/2 (3 slash-joined) | 3 | 0 | 0 | `98ea3443f` |
| `objectstack-ai#12144` | 1/1 | 1 | 0 | 0 | `3a04b0125` |
| `objectstack-ai#12176` | 11/7 | 11 | 0 | 2 | `7986d973f` |
| `objectstack-ai#12194` | 15/5 | 15 | 0 | 4 | `311433f6b` |
| `objectstack-ai#12195` | 35/16 | 35 | 0 | 7 | `7986d973f` |
| `objectstack-ai#13182` | 2/2 | 2 | 0 | 0 | `5b3ff63cc` |
| `objectstack-ai#13197` | 1/1 | 1 | 0 | 0 | `56c093c4d` |
| `objectstack-ai#13213` | 2/1 | 2 | 0 | 0 | `4801296e7` |
| `objectstack-ai#13214` | 18/6 | 18 | 0 | 14 | `cc837dbfe`, `889ec5b42`, `3d10755f0`
|
| `objectstack-ai#13244` | 5/2 | 5 | 0 | 1 | `889ec5b42` |
| `objectstack-ai#13255` | 4/1 | 4 | 0 | 6 | `43028a8f8` |
| `objectstack-ai#13258` | 1/1 | 1 | 0 | 0 | `3d10755f0` |
| `objectstack-ai#13279` | 23/5 | 23 | 0 | 5 | `6a180e42d` |
| `objectstack-ai#13280` | 13/4 | 13 | 0 | 2 | `add6a1b1c` |
| `objectstack-ai#13282` | 1/1 | 1 | 0 | 0 | `43028a8f8` |
| `objectstack-ai#13377` | 3/2 | 3 | 0 | 0 | `e10cf3444` |
| `objectstack-ai#13378` | 2/1 | 2 | 0 | 0 | `82faea03f` |
| `objectstack-ai#13454` | 1/1 | 1 | 0 | 0 | `7ad57e17a` |
| `#14026` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14365` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14366` | 14/4 | 14 | 0 | 2 | `53cbad9f7` |
| `objectstack-ai#14369` | 3/2 | 3 | 0 | 0 | `a3d5724c8`, `53cbad9f7` |
| `objectstack-ai#14389` | 7/3 | 7 | 0 | 7 | `10220a7bf` |
| `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | `9d7f7259f` |
| `objectstack-ai#14409` | 2/2 | 2 | 0 | 0 | `3ecb7dc1a` |
| `objectstack-ai#14541` | 27/4 | 27 | 0 | 5 | `6d178a408` |
| `objectstack-ai#14613` | 2/2 | 2 | 0 | 0 | `81208086a` |
| `objectstack-ai#14677` | 1/1 | 1 | 0 | 0 | `a4e4d2d78` |
| `objectstack-ai#14683` | 8/2 | 8 | 0 | 0 | `96326040f` |
| `objectstack-ai#14691` | 15/2 | 15 | 0 | 2 | `b3a63d32c` |
| `objectstack-ai#14704` | 9/3 | 9 | 0 | 2 | `1c7adc73d` |
| `objectstack-ai#14723` | 7/4 | 7 | 0 | 4 | `65846bc46` |
| `objectstack-ai#14725` | 3/3 | 3 | 0 | 2 | `f5cc78b63` |
| `objectstack-ai#14849` | 3/1 | 3 | 0 | 0 | `226e72443` |
| `objectstack-ai#14907` | 1/1 | 1 | 0 | 0 | `e1d4f9e3f` |
| `objectstack-ai#14908` | 1/1 | 1 | 0 | 0 | `d5cbb44f3` |
| `objectstack-ai#15021` | 2/1 | 2 | 0 | 8 | `cc238db8b` |
| `objectstack-ai#15034` | 6/2 | 6 | 0 | 0 | `abf9101f1` |
| `objectstack-ai#15065` | 1/1 | 1 | 0 | 0 | `1c7adc73d` |
| `objectstack-ai#15071` | 23/4 (1 slash-joined) | 23 | 0 | 3 | `cf6e0a193` |
| `objectstack-ai#16650` | 1/1 | 1 | 0 | 0 | `001a83b04` |
| `objectstack-ai#17058` | 3/1 | 3 | 0 | 4 | `94c930248` |
| `objectstack-ai#18546` | 3/2 | 3 | 0 | 3 | `58f60e37e` |
| **total** | **460** | **457** | **3** | **127** | **70 distinct
commits** |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 70), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 70). The checkout is not shallow (`--is-shallow-repository`
false); the control leg `13a6cb4ad` exits 0 and the negative control
(this branch's first WIP commit, not on `main`) exits 1. Several numbers
are the PR number of their own anchor commit (objectstack-ai#6122, objectstack-ai#6303, objectstack-ai#6474,
objectstack-ai#11242, objectstack-ai#13213, objectstack-ai#13244, objectstack-ai#13258, objectstack-ai#13282, objectstack-ai#14409, objectstack-ai#14677, objectstack-ai#14908, objectstack-ai#15065,
objectstack-ai#16650), so the sha is the same object the number named.

**Numbers with more than one anchor, by site:**
- `objectstack-ai#13214` (18 sites) was one card with three commits. `cc837dbfe` (the
ownership gate, the 2026-08-30 ruling) for the 11 sites that describe
the gate; `889ec5b42` for the 5 in
`ui-view-route-identity.measurement.test.ts`, the identity measurement
it created; `3d10755f0` for the tenancy file's header, the measurement
it created; and `rest-server.ts:2247`, 「Driven and reported on objectstack-ai#13214
(PRs objectstack-ai#13244, objectstack-ai#13258)」, now reads 「Measured in commits 889ec5b
(identity) and 3d10755 (tenancy)」: those PRs are exactly those two
commits.
- `objectstack-ai#14369` (3 sites): `a3d5724c8` (the liveness census it recorded) for
`rest-server.ts:1172` and `rest-sub-config-parse-not-cast.test.ts:48`.
`rest-server.ts:4092` said the zero read sites of `api.documentation` /
`api.responseFormat` came from 「the objectstack-ai#14369 census」, but `a3d5724c8`
explicitly left `api` out of that census; the zero was measured by
`53cbad9f7` (its changeset: no other read site for either key), which is
the anchor there.
- `objectstack-ai#11235` / `objectstack-ai#11242` / `objectstack-ai#10993`: `376c70f98` derives the discovery
`version` in metadata-protocol (objectstack-ai#11235), and `98ea3443f` is objectstack-ai#11242's own
squash, which landed the objectstack-ai#10993 ruling on `/health` and the dispatcher's
`/discovery`. So 「the objectstack-ai#10993 ruling … reaffirmed by objectstack-ai#11235/objectstack-ai#11242」 now
reads 「the objectstack-ai#10993 ruling, landed by commits 98ea344 and 376c70f」
(`rest-server.ts:4528`, `discovery-schema-conformance.test.ts:343-344`).
`objectstack-ai#10993`, `objectstack-ai#11292` and `objectstack-ai#11297` answer 200 and stay.
- `objectstack-ai#6037` / `objectstack-ai#6474`: one commit, `18189983d` (objectstack-ai#6474 is its PR number),
so 「(objectstack-ai#6037 / PR objectstack-ai#6474)」 became 「(commit 1818998)」.

**Wordings to check, each true of its commit:**
- A commit does not rule. Where a line said a number ruled, it now says
what the commit did with the ruling: 「the ruling commit 79c46da landed
says it does」, 「the ruling commit cf6e0a1 implemented fences it」, 「the
ruling commit 10220a7 implemented」, 「the 2026-08-20 ruling, landed as
commit 6ce58a7」, 「recorded in commit 6ce58a7's message (option A)」
(its message reads 「Ruled on objectstack-ai#10255 (2026-08-20, option A)」), and
「question was ruled on 2026-08-20 and landed as commit 6ce58a7」 where
the line said 「filed as objectstack-ai#10255」.
- `objectstack-ai#14541`'s contract review: 「the objectstack-ai#14541 contract review (condition N)」
now reads 「the contract review of commit 6d178a4 (condition N)」; that
commit's message lists the conditions it carries. 「objectstack-ai#14541's §4」 and
「objectstack-ai#14541 §5」 in
`error-response-generic-passthrough-object-parity.test.ts` are sections
of `error-response-structured-arm-door-parity.test.ts` (the file
`6d178a408` created), so they now name that file. 「measured on the
objectstack-ai#14541 branch」 reads 「on the branch that landed as commit 6d178a4」.
- A line that named a DEFECT by its number now says so: 「Before commit
9e04c3e the draft→active promotion door could not…」, 「Before commit
26f3588 the `/meta` doors decided ORGANIZATION SCOPE from the RAW
url」, 「the defect commit 2443bb4 fixed」 and 「would be the defect
commit 26f3588 fixed」.
- `objectstack-ai#13255`: 「As written for objectstack-ai#13255 this file repaired nothing」 reads 「As
first written (commit 43028a8)」, the commit that created the file and
answered the measurement; 「CONTEXT-LOST family (objectstack-ai#13255), still unruled」
reads 「first measured by commit 43028a8」 (the ruling on that family
never landed, which the line still says).
- `objectstack-ai#13214` in the identity file: 「the half objectstack-ai#13214 marks UNMEASURED」
reads 「the half left UNMEASURED until commit 889ec5b」, and 「objectstack-ai#13214
asks for an INDEPENDENT reproduction」 reads 「commit 889ec5b is an
INDEPENDENT reproduction」.
- 「the objectstack-ai#8885 sweep」 reads 「the sweep behind commit 30b1c63」, the
commit that registered the 9 codes the sweep found; 「objectstack-ai#14849 predicted」
reads 「The card behind commit 226e724 predicted」; 「the hazard objectstack-ai#13377
names」 reads 「the hazard commit e10cf34 was written to remove」; 「The
concrete harm objectstack-ai#6704 names」 reads 「removed」.
- Quoted ruling: `error-response-sandbox-arm-message.test.ts:340` sits
inside a verbatim ruling quote, so the commit stands in an editorial
bracket (「not from [commit 1c7adc7]'s list」), as PR objectstack-ai#20624 did.
- Two markdown tables in comments
(`meta-state-route-engine-outage.test.ts:76`,
`objectql-slot-consumer-census.test.ts:43`): the rewritten cell is wider
than its column, and its padding is reduced rather than widening the
four sibling rows.

## The sites left

**No deciding commit (3 sites, all in test files, so the census does not
see them):**
- `meta-object-owd-gate.test.ts:516` (objectstack-ai#8641): 「whether it should stay is
objectstack-ai#8641's question」, an open decision. The commit that added the citation
calls it a pointer to the open decision card, and no commit decides it.
- `rest-sub-config-parse-not-cast.test.ts:321` (objectstack-ai#14365): the
`z.partialRecord` question 「deferred to objectstack-ai#14365」 was never taken (`git
log -S partialRecord`); `b3a63d32c` made it moot by retiring the record,
which the other half of the same line now cites.
- `import-integration.test.ts:1043` (#14026): not deleted, TRANSFERRED.
The web endpoint answers 302 to objectstack-ai/objectui#10102, the REST
read follows the redirect, and the board enumeration does not list it,
so the census and the supplementary reading both class it
`allocated-but-absent`. The line says how an issue was raised; no commit
decides that, so form C has nothing to cite.

**String sites kept as tokens (127).** 126 are test titles and test-code
strings in 41 files. One is a non-test string: the `note` field of the
REST route ledger's `GET /api/v1/meta/object/:name/state/:field` row at
`rest-route-ledger.ts:290`, which ends 「(objectstack-ai#10179)」 (see Acceptance
notes).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file
at base `a186aea996` against the working tree at `93e4d69ba6`, over all
85 touched `.ts` files. Controls mutate the head text in memory only, so
nothing on disk moved for them.

- Real run: 272,653 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`error-response.ts`): 0 files changed (exit
0).
- Code-insertion positive control (a declaration in the same file):
DIFFER at token 0 (exit 1).
- String positive control (the first string literal past offset 2000 of
the same file, one character added inside it): DIFFER at token 26 (exit
1).

Line balance: every touched file is +N/−N (451/451), and every line
count is equal at base and head. A raw scan of the 86 changed files for
control bytes finds none (its positive control on a scratch file with a
U+0001 byte matches).

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/rest`
is included, in PR objectstack-ai#20624's form and level. It says only that the
provenance comments were re-anchored.

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/rest build`, the
rewritten docblocks reach `dist`: for example `53cbad9f7` appears 4
times in `dist/index.d.ts`, and `26f3588fb` 8 times and `b3a63d32c` 5
times in `dist/index.js`. The positive control, the unchanged sentence
「It was VALIDATE-ONLY from objectstack-ai#11637」 of the same `rest-server.ts` docblock
whose first line now reads 「[commit 53cbad9] The parsed output is
CONSUMED」, is in `dist/index.d.ts` beside it; a negative control phrase
appears nowhere.

## Gates (head `93e4d69ba6`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each locked run at this
head:

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 47s · declare it in the PR body · pnpm --filter '@objectstack/rest...' build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 76s (1m16s) · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/rest typecheck
```

The branch merged `origin/main` once (`93e4d69ba6`, merging
`542670da6d`) before these runs, as the dispatch orders; `origin/main`
has not moved since (read at 11:19Z). The merge brought PR objectstack-ai#20626 and PR
objectstack-ai#20587 and touched none of this diff's files. The dependency closure was
built first (`pnpm --filter '@objectstack/rest...' build`, 26 packages),
then the whole workspace (`turbo run build --filter='./packages/*'
--filter='./packages/*/*'`, 71 tasks, 71 successful).

- **Tests:** `vitest run --project local`: 227 files, 4,382 tests
passed, 50 skipped. `--project repo` (which holds the touched
`meta-state-route-doc-spelling.test.ts`): 1 file, 8 tests passed.
Together they are all 228 test files of the package, so every touched
test file ran.
- **Typecheck:** `pnpm --filter @objectstack/rest typecheck` exits 0.
`tsc --listFiles` counts 28 `src` files (no tests) under `tsconfig.json`
and all 228 test files under `tsconfig.test.json`, which
`check:test-typecheck` judges: 0 files, 0 errors, 0 pinned signatures in
the ledger.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `93e4d69ba6` (2026-09-29T11:19:30Z to 11:20:00Z). Not
narrowed.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0: 19 citations judged across 14 files (18 resolve, 1
resolves as a pull request). These are the live numbers that stay on
rewritten lines. It defers `*.test.ts`, so the added-minus-removed count
over the whole diff covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `93e4d69ba6` derived 68
families. All 68 exit 0, and `--ran` over a record carrying each exit
code reads 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived
zero).
- `check:dual-build-cjs-loads` and `check:type-check-debt` first exited
3 (PREREQUISITE NOT MET, nothing measured) on the closure-only build;
after the whole-workspace build both exited 0.
- Among them: `check:doc-authoring`, `check:nul-bytes`,
`check:rest-log-declared`, `check:route-envelope`,
`check:system-context-census` (106 elevation read sites, the page's 102
symbols held) and `check:issue-citations` (self-test).
- **Artifact rosters:** 33 of the 36 non-self-test roster rows exit 0 at
`93e4d69ba6`, `check-changeset-fixed` (the one whose roster sits under
`.changeset/`) and `check:route-ledger-census` among them. The other
three, `check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull
request's context; they are run against this PR once it exists and
reported on the card.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 191 dead sites at
`a186aea996` (186 lines, 14 files, 51 numbers), equal to the card's
count at `f11b5f20a2`: no net drift, although PR objectstack-ai#20601 (merged as
`eb4b17c346`, before this base) touches four files in `packages/rest`.
- **H1 holds.** After the rewrite the filtered census answers 0. The
supplementary reading leaves 3 test-comment sites, the three listed
above: an open decision, an untaken option and a transferred issue, none
with a deciding commit. No site was held for an open PR: the claim's
read and this stage's two reads of the open PRs' file lists (10:27:35Z,
7 open PRs; 11:30:34Z, 8 open PRs) found none touching `packages/rest`.
- **H2 holds, by the token guard.** A comment-stripped comparison of
every touched file (the parser's leaf tokens, JSDoc excluded) is empty,
and its code and string controls fire. The emitted `dist` is not
byte-identical, because the docblocks ship, which is why the changeset
is `patch`.

## Acceptance notes

- **Form D, not touched here.** 127 dead numbers stand inside string
literals: 126 in test titles and test-code strings, and one in the
`note` of the REST route ledger's legal-next-state row
(`rest-route-ledger.ts:290`, 「(objectstack-ai#10179)」), which is ledger data, not an
author-shown refusal. Ruling D (no number, the lesson in words) is a
string change outside this comment-only scope; the card already carries
a form-D stage for the lane.
- **A transferred issue among the 404s.** #14026 answers 302 to
objectstack-ai/objectui#10102 on its web endpoint. The census classes it
`allocated-but-absent` (deleted and transferred are only told apart
under `--probe-cause`), and `scripts/check-issue-citations.mjs`'s header
says the `transferred` arm has no positive specimen on this tree; this
is one. Noted, not filed.
- **The grammar does not read a slash-joined number.** `CITATION_RE`
refuses a `#` preceded by `/`, so the second number of `#A/#B` is never
judged. In `packages/rest/src` six such dead numbers stood at 10 comment
sites, all rewritten here; one more, `objectstack-ai#14389` in `objectstack-ai#14095/objectstack-ai#14389`, stands
inside a string
(`error-response-structured-arm-door-parity.test.ts:187`) and is kept.
The same shape PR objectstack-ai#20624 and PR objectstack-ai#20612 reported. Noted, not filed.
- **Outside the scope and the census surface.**
`packages/rest/vitest.config.ts:21` cites objectstack-ai#17853, which answers 404;
`packages/rest/test-typecheck-debt.json`, written by
`gen:test-typecheck-debt`, carries objectstack-ai#13470, objectstack-ai#13454, objectstack-ai#13377 and objectstack-ai#13378 in
its prose, all 404. Neither is under `src/**`. The other numbers in
`vitest.config.ts`, `tsconfig.json` and `tsconfig.test.json` answer 200.
- **Two comments stale on their own, not touched.** The anchor research
found `rest-server.ts`'s `api` docblock near `:1115` and the 「zero read
sites」 sentence at `:4092` both overtaken by `80153f5a4`, whose own
acceptance notes record it. This PR re-anchors their citations and
leaves their claims alone.
- **An attribution corrected by the anchor.** `rest-server.ts:4092`
credited its zero-read-site count to 「the objectstack-ai#14369 census」, which
(`a3d5724c8`) excluded `api`; it now cites `53cbad9f7`, the commit that
measured it.
- **Base.** One merge of `origin/main` (`93e4d69ba6`) before the `--base
origin/main` run, as the dispatch orders.

## Deviations

- Ten sites beyond the census's read grammar carry a slash-joined dead
number and are rewritten; six more lines are the other half of a
rewritten sentence (listed under What changed).
- The whole-workspace build ran with `--concurrency=4`, not 2, to stay
inside the ten-minute foreground cap on this host; it took 1m42s.
- Anchor research for 33 of the 77 numbers ran in three read-only
research subagents; every proposal was verified here against the
commit's message or diff, and the wording of each changed line was
reviewed and corrected by hand in a second pass.
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The merge commit carries git's default message.

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

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…ds, not a tracker number (objectstack-ai#20641)

Fixes objectstack-ai#20597

Clause-②: no

The last stage of objectstack-ai#20597, and the card's only author-shown site: the
`react-prop-deprecated` finding message in
`packages/lint/src/validate-react-page-props.ts`. It ended by citing
objectstack-ai#11284, which answers 404. Ruling C+D (`5749154545`) takes author-shown
text in form **D**: the lesson in words, with no number to look up.

## What changed

Three files, +15 / -2, against base `f29c83db13`.

- `packages/lint/src/validate-react-page-props.ts:1198`: the message's
tail changes. The tag, prop and `dep.replacedBy` interpolations are
byte-identical, and so are the rule id, the `warning` severity and the
hint (`dep.note`). In placeholder spelling (TAG, PROP and REPLACED_BY
stand for the three interpolations):
- before: `TAG prop "PROP" is the deprecated spelling of the
metadata-tier "REPLACED_BY" and is removed after the deprecation window
(objectstack-ai#11284).`
- after: `TAG prop "PROP" is the deprecated spelling of the
metadata-tier "REPLACED_BY": the react tier converges on the
metadata-tier vocabulary, so this spelling keeps working through the
deprecation window and is removed after it.`
- `scripts/doc-authoring-prose-id.baseline.json`: the `objectstack-ai#11284` pin for
this file is removed (one line). The other pin for the same file
(`objectstack-ai#5583`) stays. The file was regenerated with the gate's own `node
scripts/check-doc-authoring.mjs --census-ledger`, and its diff against
the checked-in baseline is exactly that one line.
- `.changeset/20597-react-prop-deprecated-message-words.md`: one
`@objectstack/lint` `patch`.

**Where the words come from.** objectstack-ai#11284 answers 404, so its decision was
read from the record that survives. The `@objectstack/lint` CHANGELOG
entry for commit `5383fa6` records it (maintainer ruling 2026-08-23):
the react tier converges on the metadata-tier spelling, deprecate-first.
The deprecated spelling stays published and accepted for the whole
deprecation window, every use warns, and removal comes after the window.
The same deciding commit wrote this message line. The
`REACT_PROP_DEPRECATED` docblock and the
`ReactInteractionProp.deprecated` docblock in
`packages/spec/src/ui/react-blocks.ts` still state the same contract.
The new sentence says that decision and adds nothing else.

## Verification record (head `6c463cec86`)

**Premise.** `GET /repos/objectstack-ai/issues/11284`
answers 404. The control `issues/5583` answers 200.

**The ratchet moves down by exactly this site.**
- `pnpm check:doc-authoring` exits 0. Its sibling-package leg reads 809
pinned sites across 230 files, one fewer than the 810 it read at stage 1
and stage 2. The file count holds, because this file keeps its `objectstack-ai#5583`
pin.
- Stale-arm control, working-tree only, restored after: the base
baseline over the new message gives exit 1, `STALE`, listing exactly one
pair, `packages/lint/src/validate-react-page-props.ts objectstack-ai#11284 (1 pinned,
0 measured)`. The restore was proven by blob equality with HEAD and an
empty `git diff HEAD`.
- Growth-arm control, through `scripts/ablation-replace.mjs`: the old
tail was put back over the new baseline (anchor x1 to x0, replacement x0
to x1). The mutated blob `f5684f0774` equals the base blob. The gate
exits 1 with `validate-react-page-props.ts objectstack-ai#11284 (0 pinned, 1
measured)` at `:1198`. The tool proved the restore (blob equals HEAD
`55de478256`, `git diff HEAD` empty).
- No tracker number is added. The only number on a changed line is the
removed `objectstack-ai#11284`.

**Pin sweep (whole repo, one round).**
- `git grep` for `deprecation window (objectstack-ai#11284)`, `removed after the
deprecation window` and `deprecated spelling of the metadata-tier` at
the base finds only the source line itself, so the grep is not blind. At
head the first two find nothing.
- `REACT_PROP_DEPRECATED` / `react-prop-deprecated` appear in the
source, the barrel (`index.ts`), two release-owned CHANGELOGs, and
`validate-react-page-props.test.ts`. The test asserts the rule id only:
the `pastDeprecation` filter, and `toEqual([])` at `:116` and `:204`. No
test, snapshot, doc or skill asserts the message text, so there is no
pin to flip, and none was added. Wording is not pinned unless a consumer
parses it, and none does. The sibling `objectui` checkout (at `b120b66`)
has 0 hits for the rule id, the fragment and the number.

**Build, tests, typecheck.** All three ran under
`scripts/pm/os-verify-lock.sh` (slot `issue-20597-s3`), which printed
`VERDICT command-exit 0`.
- `turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*`: 71 successful of 71.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`: Test
Files 115 passed (115), Tests 5379 passed (5379).
- `pnpm --filter @objectstack/lint typecheck`: exit 0, and
`check:test-typecheck` OK.
- No consumer sweep is owed. No export, type or rule id moves. The only
change is the text of one `warning` message, and nothing parses it.

**Gates.**
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 62 commands. All 62 ran with exit 0, and `--ran`
reconciles "62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN".
- The six build-reading gates ran after the build:
`check:docs-transcript-drift`, `check:dts-closure`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:published-files` and `check:sourcemap-no-sources-content`.
- `check:doc-authoring` and `check:issue-citations` are among the 62,
and both exit 0 (`check:issue-citations` reads "no issue citations added
against f29c83d").
- These roster families keep their roster in a directory this diff
touches, so they ran too, all exit 0: `check-changeset-fixed`,
`check-published-list-mirrors` (plus its `--self-test`),
`check-dts-references --self-test`, `check:authz-resolver`,
`check:console-injection`, `check:engine-double-contract`,
`check:error-code-casing`, `check:filter-alias-parity`,
`check:i18n-stale-fill` and `check:published-readme-exports`. The last
one first exited 3 (prerequisite, before the build) and then 0 after the
build.

**Lint, as a proven narrowing.** `pnpm exec eslint --no-inline-config
--format json packages/lint/src/validate-react-page-props.ts` reports 1
file, 0 errors and 0 warnings. `isPathIgnored` is false through eslint's
API, and the resolved config has no `parserOptions.project` or
`projectService`. `eslint.config.mjs:327` says type-aware linting is
never enabled, so a string edit here cannot move any other file's
verdict. The repo-wide `pnpm lint` is CI's to run.

**Changeset.** `files[]` ships `dist`. The new sentence is in
`dist/index.js`, `index.cjs`, `runtime.js` and `runtime.cjs`, and the
old `deprecation window (objectstack-ai#11284)` is in none of them. The positive
control `Likely a typo of a contract prop. Fix it or remove it.` is in
all four. Hence `patch`.

**Merge.** A driver-free `merge-tree` of `6c463cec86` onto `origin/main`
`f1e921ab8e`, run from a bare shared clone with no `merge.*` config,
exits 0. The four commits `main` gained since the base touch none of the
three paths.

No ablation of behaviour is owed, since no behaviour changes. The two
gate-arm controls above are the one-time proof that this site is the
gate's to see. No test file was left behind.

## Acceptance notes

- **The message is dormant today (noted, not filed).** The built
`REACT_BLOCKS` contract carries 0 props with `deprecated` (4 blocks, 37
interactions). The only deprecated spellings there ever were, ListView's
`objectName` / `viewType`, now sit in `REACT_RETIRED_OVERLAY_PROPS` and
report `react-prop-retired`. So `react-prop-deprecated` cannot fire
until a new deprecation is declared. The mechanism is kept, and the new
text states its contract. Carrier: none.
- **Stale test comment (noted, not filed).** The header comment of
`validate-react-page-props.test.ts` (about `:32`–`:36`) still says every
`objectName` ListView fixture "carries exactly one deprecation warning".
The assertions at `:116` and `:204` say zero. The claim puts test
comments out of this card's scope, so it is untouched. Carrier: none
(承接者:无).
- **The same file's other author-shown id (noted, not filed).** `:458`
is the hint of the `REACT_CHART_AGGREGATE_INVALID` warning, and one
sentence of it begins `objectstack#5583 ruled that …`. objectstack-ai#5583 is live
(200), so it is not a dead citation and not on this card. It stays
pinned in the prose-id baseline with the rest of the ledger's 809
adjudicated sites. Carrier: none.
- With this PR, all of objectstack-ai#20597's items are done: stage 1 (objectstack-ai#20612, 22
files), stage 2 (objectstack-ai#20631, `authoring-rules.ts`) and this form-D string.

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

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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants