Skip to content

docs(spec): re-anchor the dead tracker citations in the packages/spec/src remainder to the commits and ADRs that decided them (stage 6) - #20606

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20234
Clause-②: no

Stage 6 of the staged sweep: the packages/spec/src remainder outside migrations/ and the held files. Its claim is 5884233505, with 22 files named there. Every comment or docblock line in those files that cited a tracker number answering 404 now cites what decided its rule, in ruling C+D's form C. That is the commit on main that decided the rule, or, for one number, the ADR amendment that records the ruling. Each line says in its own words what was decided. Comments only: 66 lines out, 66 in, across 21 files. No code token, string literal, describe() text or message-catalog string moves. Two dead sites stay byte-identical, because a test reads each one by literal.

The census is the gate's own node scripts/check-issue-citations.mjs --census --json, filtered to the 22 paths. Before: base c876a7426d, board enumerated (185 pages, frontier #20590). After: head 9d63cb6548, frontier #20604.

Measurement

file (under packages/spec/src/) dead before after numbers, then anchor
api/rest-server.zod.ts 11 0 #14691 ×9 to b3a63d32c; #14369 ×2 to a3d5724c8
system/i18n-resolver.ts 14 0 #12961 ×6 to 901355c3b; #13218 ×4 to c45d8e6b4; #8460 ×2 to ADR-0029 D9.2a; #10926 to d173125fb; #13109 to 8b236c826
system/operation-message.ts 4 0 #12493 ×4 to aa5994e17 (docblock lines only)
system/translation.zod.ts 2 0 #10926 ×2 to d173125fb
system/core-services.zod.ts 1 0 #6604 to d127ff002
system/dev-login.zod.ts 1 0 #17081 to 24d622b94 (#17556 stays, 200)
system/environment-artifact.zod.ts 1 0 #11333 to e58ea8b38 (#14865 and #13457 stay, 200)
shared/identifiers.zod.ts 7 0 #12245 ×2 to c41b42e8d; #12144 ×2 to 3a04b0125; #12194 and #12176 (:140-141) to 311433f6b; #12176 (:189) to 7986d973f
shared/metadata-collection.zod.ts 1 0 #10485 to 35ad101bc
index.ts (package root) 6 0 #11350 ×5 to ece4dad31 (#11709 stays, 200); #10485 to 35ad101bc
automation/control-flow.zod.ts 1 0 #14419 to c5a7448d5 (#14954 stays, 200)
automation/execution.zod.ts 1 0 #13681 to 18d816a50
automation/index.ts 1 0 #16659 to ecdfc9411
automation/schedule-organization.zod.ts 1 0 #16659 to ecdfc9411
ai/index.ts 1 0 #11350 to ece4dad31
identity/identity.zod.ts 1 1 #8715 kept at :230 (a test reads it); 2c86fe3ea added on :231
security/explain.zod.ts 1 0 #8714 to 42b05af89
security/public-form.ts 1 0 #6640 to 2ab1257c9
data/api-derivation.ts 1 1 #6259 kept at :163 (two tests read it); 6968885ef already on :164; file untouched
data/driver/turso.zod.ts 2 0 #6345 ×2 to e2798fab7
conversions/walk.ts 1 0 #13031 to b799ac553
meta-spelling/metadata-url-spelling.ts 2 0 #10485 ×2 to 35ad101bc
22 files 62 2 26 numbers, 24 removed: 24 distinct shas and 1 ADR

Per-file counts at base equal the claim's (census 5884031174 at f11b5f20a2) in all 22 files. A second instrument agrees site for site: every #N in the 22 files, classified by the TypeScript parser, and each of 201 distinct numbers probed by REST issues/N without redirects. It found 484 sites, all in comments and none in a string, 26 dead numbers and 62 dead sites. Its string-class positive control found 19 string sites in api/rest-server.test.ts. Head: 424 sites and 177 numbers, 175 answer 200 (the same 175), and 2 answer 404 (the two kept sites). Lit controls #16862, #16847 and #17698 answered 200 at every checkpoint (4 at base, 3 at head); dead controls #16714, #16715 and #16697 answered 404 at every checkpoint.

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 or diff. Each was read for the rule its line states.

Mechanical proof

  • Token guard (my tokcmp.mjs: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). Base c876a7426d against the head, 21 files, 37,539 base tokens:
    • Real run: 0 files with a token change (exit 0).
    • Comment-insertion control (ai/index.ts): 0 (exit 0).
    • Code-insertion positive control (system/i18n-resolver.ts): DIFFER at token 14452 (exit 1).
    • String positive control (a real StringLiteral in system/operation-message.ts, found by the parser): DIFFER at token 5 (exit 1).
    • The first string-control attempt matched a quoted fragment inside a comment and did not fire. It was a vacuous control, not a measurement, and the parser-located control above replaced it.
  • Line balance: every file is +N/−N (66/66 across 21 files); every line count is equal at base and head.
  • Tracker numbers: added-not-removed is empty in every file, and no PR #N is on an added line. Net-removed: 60 sites, 24 numbers.
  • Shas: 24 distinct on added lines, 0 on removed lines.
    • rev-parse --disambiguate answers 1 object for each.
    • merge-base --is-ancestor exits 0 against origin/main e666636fd9 and against the base.
    • Each is single-parent; the repository is not shallow; the control leg e9584681a4 exits 0.
    • Each commit's own message (17 of 24) or diff (all 24) names the number it replaces.
  • Literal readers: every string literal in the repository that carries one of the 26 numbers was matched against the 22 files' text. Three hits read these files:
    • data/api-derivation.test.ts:236 splits on [#6259];
    • packages/runtime/src/api-exposure.test.ts:152 splits on #6259;
    • identity/api-key-retirement.test.ts:118 asserts are NOT declared here (#8715.
    • Those lines are the two kept sites. No test or script matches any rewritten line by pattern.

Tests and gates (at head 9d63cb6548)

Hypotheses (measured first)

  1. Holds. The population is exactly the claim's 22 files: 62 dead sites at the tip, equal per file to the census at f11b5f20a2.
  2. Holds, with nothing to respell. No sibling-qualified pair occurs among the 62 sites; no pre-#N spelling is dead here.
  3. Holds. Re-read at 2026-09-29T06:23Z, after the last push and before this PR was opened: all 14 open PRs' full file lists (1,116 files in the version PR alone), and the newest Claim: on all 15 pm:dispatched cards. None names any of this PR's 24 paths. The five exclusions stay excluded.
  4. Holds. The two projected pages were regenerated by the generator, never by hand. No other page under content/docs/references/ carries any of the 26 numbers.
  5. Holds. No #N in the 22 files is inside a string; the two literal-read sites stay as tokens. In system/i18n-resolver.ts and system/operation-message.ts only docblock and line-comment lines moved.

Deviations

  • The file surface is 21 of the 22 named files. data/api-derivation.ts is untouched, because its one dead site is read by literal and its commit already stands on the next line (stage 3's disposition).
  • Six changed lines held no dead number. Each is the other half of a rewritten sentence: rest-server.zod.ts:756, identifiers.zod.ts:19, index.ts:133, environment-artifact.zod.ts:137, schedule-organization.zod.ts:82, and identity.zod.ts:231 (the commit placed beside the kept :230).
  • 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 later stages. At the tip 7a1faf1a5d with this PR applied, packages/spec/src holds 260 dead sites (34 numbers). This is the gate's census on this head, with the four spec sources main changed since the base re-extracted and re-probed at the tip. By area:

Carried from earlier stages, outside the gate's census (which blanks strings and defers test files): the dead-number test-title strings, the two why strings, the PROVENANCE_WAIVERS reason, the two AGGREGATION_CASES notes, and the liveness/** notes.

Outside packages/spec/src (#20556's lane): packages/spec/scripts/check-entry-nameability.ts cites #11350 in its header (:14) and PRINTS "recorded on #11350" in its failure text (:727); packages/spec/scripts/root-entry-type-nameability.pin.test.ts cites it too. Commit ece4dad31 is the anchor, already verified here.

Rung. Five of the anchored retirements also have ADR-0087 D3/D2 entries: identity-api-key-schema-retired, metadata-item-name-grammar-enforced, rest-server-config-dead-keys-retired, stack-themes-carrier-retired and translation-component-submit-label-retired. This PR takes the commit rung, as stages 1–5 did. The D3 id is the more durable in-repo record if the ruling's first rung is later read to include those entries.

Wording, each true of its commit.

Observation, not filed (a pre-existing live citation, not a tracker number; carrier: none): environment-artifact.zod.ts:137 and packages/runtime/src/security/artifact-granted-permissions.ts:6 cite "ADR-0025 §3.5 step 2" for the granted set. At the tip, §3.5's numbered step 2 is "Compatibility" and "Permission consent" is step 3.


Generated by Claude Code

…/src remainder to the commits and ADRs that decided them (stage 6)

Comment and docblock lines only, 66 out and 66 in across 21 files; no
code token, string literal or describe() text moves. Each dead number is
replaced by the commit on main that decided what its line describes, or
by the ADR that records the ruling (ADR-0029 D9.2a).

Two dead sites stay byte-identical because a test reads them by literal:
api-derivation.ts:163's [#6259] marker, and identity.zod.ts:230, whose
deciding commit now sits on the next line.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…cks project into

check:generated proved exactly one artifact stale (check:docs,
content/docs/references/**) and --fix regenerated it: two pages, three
lines, each the same substitution as its source docblock line.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
13 of the 21 touched sources are src/**/*.zod.ts, which files[] ships
verbatim, and the rewritten docblocks reach dist .d.ts and .js, so the
change publishes bytes and takes a patch.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 14 changed file(s) yielded no anchor (packages/spec/src/ai/index.ts, packages/spec/src/automation/control-flow.zod.ts, packages/spec/src/automation/execution.zod.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/concepts/north-star.mdx (via EnvironmentArtifactSchema (symbol, a top-level const))

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

  • content/docs/releases/v17/17-3.mdx (via EnvironmentArtifactSchema (symbol, a top-level const))

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
  • 14 changed file(s) yielded no anchor (packages/spec/src/ai/index.ts, packages/spec/src/automation/control-flow.zod.ts, packages/spec/src/automation/execution.zod.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 — 137 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 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5

⚠️ 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 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5 → 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: 9d63cb65484db46a7657a435938b86222693c87e
Local-runs: none

Read (this act, 2026-09-29T06:51Z): card #20234 (body; every comment: triage 5856637615 and the ruling it points at, 5749154545 on #19123; the seat-2 pointer 5858331362; stages 1 to 5's claims, reports, records, ACCEPTs and landings, in particular stage 5's claim 5882723856, report 5883647445, record 5883799044 on PR #20576, ACCEPT 5883838119 and landing 5884188565; the stage-6 claim 5884233505 and the report 5884983160), PR #20606 (body, its one comment 5884956320, the 24-file list, the three commits and the net diff against the merge base c876a7426d, read from the local ref at the head above with -U0 and the base and head blobs of every changed file), the 24 cited commits (message and stat for all; their own diffs of the files they now anchor wherever the message does not name the number or the line claims more than the subject), docs/adr/0029-*.md D9.2a and docs/adr/0025-*.md section 3.5, docs/adr/**, docs/NORTH-STAR.md and scripts/adr-anchors/ for the 24 numbers, the two test readers in packages/spec and the one in packages/runtime, packages/spec/package.json files[], lint.yml's job roster, pr-automation.yml's WHICH LEVEL, scripts/check-issue-citations.mjs's declared and deferred surfaces, content/docs/references/** at the head for the removed numbers, and the state of #20556, #19124, #20233 and the four stage cards #20594 to #20597. Nothing was built, run or re-run: the diff and the anchors were read with git, the blobs compared with a comment-stripping residue scan in scratch (a read of the diff, not a gate), the numbers probed with REST GET against this repository (a GitHub read). The check-runs were read for judgment once, at 2026-09-29T06:38Z.

① Derived judgments

(a) Scope and file surface: right. 24 files: 21 .ts files, every one on the claim's 22-file surface; the two generated pages content/docs/references/automation/schedule-organization.mdx (+2/-2) and content/docs/references/data/driver-turso.mdx (+1/-1); the new .changeset/spec-remainder-provenance-anchors.md (+14). The 22nd file, data/api-derivation.ts, is untouched, and rightly: its one dead site [#6259] at :163 is read by literal (judged in (e)) and commit 6968885ef already stands on :164 from stage 3. The five exclusions (conversions/registry.ts, data/analytics.zod.ts, stack.zod.ts, integration/connector.zod.ts, migrations/**) are absent from the diff. No governed surface is touched. origin/main (7a1faf1a5d) is three commits past the merge base and the set of files those three move is disjoint from the 24 (intersection empty), so the net diff against current main is the diff judged here; GitHub reads mergeable: true, mergeable_state: blocked (draft). Head repository is the base repository; first line Part of #20234, no closing keyword; Clause-②: no on the body's second line; draft; assignee os-tesla; no auto-merge armed; no reviews. 152 changed lines.

(b) Comment-only, no code token, string, test title or catalog string moves: right. git diff -U0 over the 21 .ts files: 66 lines added, 66 removed, every one of the 132 begins with //, * or /**. Enclosure and residue, read on the base and head blobs with a string-, template- and regex-aware comment stripper: every changed line falls inside a comment in both blobs, and the non-comment residue of each file is byte-identical base to head, 21 of 21, with every line count equal. Controls, mutated in memory only: a code insertion into i18n-resolver.ts and a string-literal edit in operation-message.ts (its ./i18n-resolver import specifier) each make the residue differ; a comment insertion does not. So in system/i18n-resolver.ts and system/operation-message.ts, the two files carrying author- and user-shown strings, no message-catalog string, .describe() text, exported string or code token moved, only docblock and line-comment bytes. No test file is in the diff, so no it/describe title is touched. The six changed lines that held no dead number (rest-server.zod.ts:756, identifiers.zod.ts:19, index.ts:133, environment-artifact.zod.ts:137, schedule-organization.zod.ts:82, identity.zod.ts:231) are each the other half of a rewritten sentence, inside the 66-for-66 balance. The dev's parser-level token comparison is not re-run and not needed for that verdict.

(c) Numbers: right. Over the 21 .ts files, added-minus-removed tracker numbers is empty for every file; net-removed is exactly 24 numbers on 60 sites: #6345 x2, #6604, #6640, #8460 x2, #8714, #10485 x4, #10926 x3, #11333, #11350 x6, #12144 x2, #12176 x2, #12194, #12245 x2, #12493 x4, #12961 x6, #13031, #13109, #13218 x4, #13681, #14369 x2, #14419, #14691 x9, #16659 x2, #17081. Per file that is the claim's census in all 22 files (60 rewritten plus the 2 kept equals the claim's 62). No PR #N stands on any added line. The two mdx pages lose #16659 and #6345 and gain no number. REST issues/N without redirects, read at 2026-09-29T06:41Z: all 24 removed numbers answer 404; the six numbers standing on added lines answer 200 (#9249, #11709, #13457, #14865, #14954, #17556); lit controls #16862, #16847, #17698 answer 200 and dead controls #16714, #16715, #16697 answer 404, re-read after the run (#16862 200, #16714 404). The dev's census over 201 numbers is otherwise not re-run.

(d) Anchor truth: right, 24 of 24. 24 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 24 against origin/main 7a1faf1a5d; every one is a single-parent commit; the repository is not shallow. Seventeen name the replaced number in their own message; the seven that do not are tied by their own diff or changeset, and every anchor was read against the sentence it now supports:

(e) The two kept sites: right. data/api-derivation.ts:163 keeps [#6259]: api-derivation.test.ts:236 does split('[#6259]') and packages/runtime/src/api-exposure.test.ts:152 does split('#6259'), both at the head; the file is not in the diff. identity/identity.zod.ts:230 keeps (#8715, byte-identical: identity/api-key-retirement.test.ts:118 asserts toContain('are NOT declared here (#8715') as its anti-vacuity literal, and the head's :230 still carries that text. Adding commit 2c86fe3ea on :231 is within the surface (the file is on the claim's list, the line is the second half of the same sentence, and the claim's rule is the dead-number lines of those files) and true of the commit (judged in (d)). Removing either kept number would be a test-string change, form D, outside this comment-only claim; the census reads 2 left for that reason.

(f) The generated pages: right. schedule-organization.mdx:83-84 and driver-turso.mdx:24 are the schedule-organization.zod.ts:81-82 and turso.zod.ts:36 lines verbatim with the * prefix stripped; the surrounding paragraphs agree line for line, and each page carries the AUTO-GENERATED header. No other page under content/docs/references/ at the head carries any of the 26 numbers. check:docs runs in Type Check · source gates (lint.yml, beside check:authorable-surface and check:generated --reconcile-only), success at the read, which is the generator's own verdict that the committed pages equal its output at this head.

(g) 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; the one ADR site keeps the ADR; no tracker number or PR #N is added. The citation gate judges packages/**/src/**/*.ts and defers test files, so all 21 files are in the diff-scoped run's population. Nothing author-shown is touched, so form D does not arise. Spot-read end to end for sense: rest-server.zod.ts:328-336, :365, :652-653, :756-757; identifiers.zod.ts:18-19, :69, :72, :128, :140-142, :189; i18n-resolver.ts:1580, :1619, :1631, :1672, :1696, :1700, :1749, :1930, :1942, :1971, :1996, :2003, :2517-2520; index.ts:132-144, :148, :158-160, :207. Each reads as one sentence about the act its anchor performed.

② Semver level

patch for @objectstack/spec is right and Clause-②: no is right. The package ships bytes from this diff: files[] carries dist and src/**/*.zod.ts, so the 13 touched .zod.ts sources ship verbatim with their rewritten comments, and the other docblocks reach dist. (b) shows the non-comment residue identical base to head, so no export, key, value or type moves and nothing widens or narrows: WHICH LEVEL (pr-automation.yml, the 2026-09-04 ruling on #15294) keeps a change that moves no public surface at patch, the level stages 1 (21ab410417), 2 (5cf58eb164), 3 (03b19d9cfd), 4 (6154165484) and 5 (2123fcca3b) took for the same act. The changeset names one package, describes only the comment re-anchoring, carries no tracker number and no model identifier, and Check Changeset is success at the read.

③ Boundary flags

Blocking: none.

Dev deviations, each answered:

  1. 21 of the 22 named files, data/api-derivation.ts untouched: right, judged in (a) and (e).
  2. identity.zod.ts:230 kept, 2c86fe3ea on :231: right, judged in (e).
  3. Six changed lines with no dead number: right, judged in (b).
  4. Two generated pages beyond the claim's source lines: right, judged in (f); the claim names the class.
  5. check:generated exit 1 (stale check:docs) then --fix then exit 0: a dev-side sequence, not a property of the diff; the head's Type Check · source gates is the verdict.
  6. No merge of origin/main, three commits behind: confirmed, the three touch none of the 24 files.
  7. Commit trailers: all three commits carry Claude-Session plus Co-authored-by: Claude, no model identifier; the PR body ends with the session-URL footer.
  8. The empty probe push and three commits, no force-push: the PR shows exactly three commits; the rest is not observable from here and not this diff's.
  9. Worktree cleanup: not observable, not this diff's.
    Open questions: the report lists none.

Out-of-scope findings, each judged:

Non-blocking observations: (1) the docs-drift comment 5884956320 lists north-star.mdx via EnvironmentArtifactSchema, a symbol-anchored advisory on a comment-only diff; Flag docs affected by code changes is success. (2) i18n-resolver.ts:2519-2520 names "the 2026-08-13 ruling" twice, once for the catalog layer and once for D9.2a; both are dated 2026-08-13 and the sentence is true, if dense.

Escalated: none.

CI at this head, the judging read at 2026-09-29T06:38Z: 32 check-runs, 15 success, 2 skipped (Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failure, 15 not concluded. In progress: Lint & Repo Gates, Build Core, Temporal Conformance (live PG + MySQL), Test Core (1/6) through (6/6), Dogfood Regression Gate (1/3) and (2/3), Dogfood Verify CLI, Type Check · workspace, Type Check · consumer gates and Type Check · debt ledger; the Test Core, Dogfood Regression Gate and TypeScript Type Check aggregates had not yet been created. So six of the seven required contexts are NOT presumed green here and must be read concluded before the PR is armed: Lint & Repo Gates (the diff-scoped citation verdict node scripts/check-issue-citations.mjs, check:doc-authoring, pnpm lint and the repo check:* steps), TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core and Temporal Conformance (live PG + MySQL). Success at the read: Governed Surface Queue Guard (the seventh), Type Check · source gates (the spec artifact gates: check:docs, check:authorable-surface, check:generated --reconcile-only and siblings), Check Changeset, Spec property liveness, Build Docs, Dogfood Regression Gate (3/3), 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 108 families the dev derived, the head's runs answer the changeset, generated-artifact, liveness, docs-build and governed-surface families now; the citation pass, doc-authoring, lint, typecheck, build, test, dogfood and temporal families are the runs still open above. PR is a draft; mergeable: true, mergeable_state: blocked (draft); assignee os-tesla; no auto-merge armed.

Implemented-by: claude/issue-20234-dead-citations-remainder
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 36533810671 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (2/6) — 失败步骤: Run this shard's tests

    @objectstack/metadata-protocol:test:  FAIL  src/protocol.metadata-redaction.test.ts > #20552 — the protocol serves the projection and executes the stored body > GET → edit → PUT keeps the stored secre
      ↳ 失败原因: @objectstack/metadata-protocol:test: Error: flow/inbound_hook failed author-time validation: 1 issue — flows[0].nodes[0].config.secret [flow-api-trigger-secret-missing]
    @objectstack/metadata-protocol:test:  FAIL  src/protocol.metadata-redaction.test.ts > #20552 — first save of a registry-only (code-authored) flow keeps its secret > the served body saved straight back
      ↳ 失败原因: @objectstack/metadata-protocol:test: Error: flow/inbound_hook failed author-time validation: 1 issue — flows[0].nodes[0].config.secret [flow-api-trigger-secret-missing]
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 9 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 0f6dcac Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20234-dead-citations-remainder branch September 29, 2026 07:20
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…commits that decided them (objectstack-ai#20609)

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

## What changed

This is the first stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/services/service-messaging/src/**` and nothing else, the
largest package in the lane that no open PR or in-flight claim holds
(the claim, `5884863234`, gives the order). Later stages cover the other
packages, so this PR says `Part of` and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), the way the landed `packages/spec/src` stages
apply it (PR objectstack-ai#20533 is the method). That is **127 sites on 109 lines in
28 files, covering 14 numbers**: the 97 census sites outside the
generated headers, and 30 sites in test comments, which the census
defers. Each rewritten line now cites the commit in `origin/main`
history that decided what the line describes, and it says in its own
words what that commit decided.

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of the 14 numbers, so every anchor is a
commit: **13 distinct shas**. No number was dropped.

Only comments changed. Every touched source file keeps its line count
(116 lines out, 116 in, over 28 files), so no line citation into these
files moves. Seven of those 116 lines held no dead citation: they are
the other half of a sentence that had to be reflowed
(`inbox-caller.ts:87`, `:88`, `messaging-service.test.ts:972`,
`notification-keyed-text-bounds.test.ts:83`,
`notification-subscription.object.ts:81`, `:82`), or a pointer that lost
its referent (`sql-outbox.ts:281`, 「the race the card describes」 to 「the
race that commit describes」, because line 278 now names the commit). 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 (added-minus-removed over the whole
diff: 0). No PR number stands on an added line.

Fifteen dead sites are left on purpose: 12 string literals and 3
generated file headers (see the list below).

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

## Census: `service-messaging`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only,
unchanged. Its surface is comment prose in `packages/**/src/**/*.ts`
with string literals blanked, and it defers `*.test.ts`. The count below
is its `allocated-but-absent` findings under
`packages/services/service-messaging/`.

| reading | tree | board | whole-repo `allocated-but-absent` |
service-messaging sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `7a1faf1a5`, run 2026-09-29T06:31:54Z to 06:35:25Z |
enumerated, 185 pages, frontier objectstack-ai#20606, 18,433 numbers | 2,457 | **100**
| 82 | 22 | 13 |
| after | head `685200760`, run 06:48:33Z to 06:52:17Z | enumerated, 185
pages, frontier objectstack-ai#20606, 18,433 numbers | 2,360 | **3** | 3 | 3 | 1 |

The before count matches the 100 that census `5884031174` read at
`f11b5f20`. The whole-repo drop is 97, exactly this diff's census sites,
and the `resolves` tally is 32,744 in both runs. The 3 left are the
generated headers below. `267c11562`, the final head, adds only the
changeset, which is outside the census surface.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes both. So a second
reading runs the gate's own exported `extractCitations` (whole-file and
comment-prose projections) and `classifyCitation` over every `.ts` file
under `service-messaging/src` (87 files), against the same enumerated
board.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `7a1faf1a5` | 613 | **142** | 100 | 30 | 3 | 9 |
| after, `685200760` | 486 | **15** | 3 | 0 | 3 | 9 |

Its src-comment column equals the census's 100, which is the control on
the second instrument. The 450 resolving citations and 21 pull-request
citations are the same in both readings.

## Per-number table

Sites and files are all dead sites in scope at the base (comments and
strings, tests included). `rewritten / left` counts comment sites
rewritten and sites left. Every anchor was read in its diff or message,
not only in its subject: it is the commit that made the change the line
describes, and its own diff or message names the number it replaces.

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#6206` | 1/1 | 1/0 | `8e13ca876`: the share-link routes pass the
whole authz envelope into enforcement instead of a four-field trim. The
line lists it as one member of the defect family behind
`assembleExecutionContext` |
| `objectstack-ai#6363` | 14/2 | 13/1 | `17d095413`: `listInbox`'s `unreadCount`
counts the total unread, not the fetched window (maintainer ruling
2026-08-07, Option A: make the declaration true); it adds
`countUnreadTotal`. The same anchor the spec stages gave this number |
| `objectstack-ai#9722` | 1/1 | 1/0 | `2074b2651`: corrects the
`sys_notification_subscription` index note — `role:` and `team:` resolve
against `sys_member` and `sys_team_member` |
| `objectstack-ai#9807` | 4/3 | 4/0 | `44738f7af`: marks the subscription-to-recipient
expansion NOT WIRED and aligns `principal` with the forms
`RecipientResolver.resolveOne()` accepts, email kept verbatim |
| `objectstack-ai#11374` | 17/6 | 16/1 | route A of the maintainer's 2026-08-24
ruling: a keyed text column declares a `maxLength` sourced from its
producer. Written as 「route A, ruling 2026-08-24」 beside `e4902d2b9`,
the commit that applied it here. `scripts/check-keyed-text-bounds.mjs`'s
header states route A in words |
| `objectstack-ai#11452` | 6/3 | 5/1 | `3b5f0360c`: the plugin-facing
`listInboxAsCaller`, scoped to the authenticated caller |
| `objectstack-ai#11453` | 26/13 | 23/3 | `1a47a5368`: `ack()` refuses a row that is
not `in_flight` (`NotificationAckError`, `DELIVERY_NOT_ELIGIBLE`), as a
compare-and-set in the SQL outbox. The same anchor stage 2 gave it |
| `objectstack-ai#11671` | 4/4 | 1/3 | `09b4f4e4e`: `os i18n extract --source-hashes`
writes the per-locale provenance companion (maintainer ruling objectstack-ai#12069
Option A, which stays cited) |
| `objectstack-ai#11741` | 6/2 | 5/1 | `b706af987`: `SendEmailInput` gains
`organizationId`, and the email channel threads it on both arms. The
same anchor stage 1 gave it |
| `objectstack-ai#11859` | 29/13 | 27/2 | `d9cf78eaa`: `ack()` takes the claimed
record back and binds its claim credential in the compare-and-set. The
same anchor stage 2 gave it |
| `objectstack-ai#12144` | 2/2 | 2/0 | `3a04b0125`: identifier ceilings are
storage-owned (`sys_metadata.name` is 255) |
| `objectstack-ai#12147` | 1/1 | 1/0 | `945e91a13`: the class-level
`check-keyed-text-bounds` gate |
| `objectstack-ai#12978` | 17/6 | 16/1 | `e4902d2b9`: declares the sourced `maxLength`
on all 15 keyed text columns of the `sys_notification_*` objects. No
commit message names the card; its diff is where every `[objectstack-ai#12978]` marker
entered the tree |
| `objectstack-ai#18424` | 14/4 | 12/2 | `879b51270`: an email or SMS channel with no
transport refuses with `transport_not_configured` instead of reporting
success |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each), and every one is an ancestor of the
base (`merge-base --is-ancestor`, exit 0 for all 13). The history was
unshallowed first (`git fetch --unshallow`, 15,062 commits), so no
anchor was read from a truncated log.

Wordings to check, each true of its commit:
- `inbox-caller.ts:86-88`: 「(objectstack-ai#6071, objectstack-ai#6551, and the share-link envelope
trim commit 8e13ca8 undid)」. `8e13ca876`'s message records the trim
(four fields kept, five dropped) and the whole-envelope fix.
- `outbox.ts:72`: 「the option-A shape commit d9cf78e's ruling
refused」. `d9cf78eaa`'s message: 「The caller never supplies an identity:
ownership is proven by round-tripping what claim() returned.」
- The fifteen `sys_notification_*` bound comments: `[commit e4902d2]
... (route A, ruling 2026-08-24)`. `e4902d2b9`'s message opens 「Every
bound names its producer in the declaration」, and `3954fb7df`'s records
the ruling's date and its A and C routes.
- `notification-keyed-text-bounds.test.ts:82-83`: the `objectstack-ai#9807` pointer
becomes 「Every other arm of the grammar commit 44738f7 documented is
narrower」, because `44738f7af` is where the email arm of the selector
grammar was written down.
- `outbox-ack-claim-ownership.integration.test.ts:40`: 「the objectstack-ai#11453 file
beside this one」 names the file itself,
`outbox-ack-precondition.integration.test.ts`.

## The 15 sites left

- **Non-test strings (3 sites, 2 lines), refusal text.** `outbox.ts:187`
(「see objectstack-ai#11453」) and `outbox.ts:210` (「(objectstack-ai#11453, objectstack-ai#11859)」) are inside
`notificationAckNotClaimedMessage` and
`notificationAckLostClaimMessage`, the messages `NotificationAckError`
carries. They are runtime strings, so they are form D, not form C. The
landed objectstack-ai#20234 stages left every string site as a token and rewrote no
refusal text, so these are left and listed, as PR objectstack-ai#20533 did. The form D
stages that did rewrite strings (objectstack-ai#20233's) cover `os migrate meta`
guidance, a different class.
- **Test titles (9 sites, 8 lines).** `describe` titles in
`email-channel.test.ts:90`, `:592`, `messaging-service.test.ts:809`,
`:1286`, `notification-keyed-text-bounds.test.ts:38` (2 numbers),
`outbox-ack-claim-ownership.integration.test.ts:109`,
`outbox-ack-precondition.integration.test.ts:110` and
`sms-channel.test.ts:90`. Tokens, left as they were.
- **Generated headers (3 sites).** Line 8 of `es-ES`, `ja-JP` and
`zh-CN` `.source-hashes.generated.ts` reads 「(objectstack-ai#11671, maintainer ruling
objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B)」. `os i18n extract` writes
that line from `packages/cli/src/utils/i18n-extract.ts:2294`, and 27
generated files across the repo carry it. A hand edit here would be
undone by the next extract, so the fix belongs at the producer in a
later stage, which regenerates every copy. The hand-written
`translations/index.ts:29` is rewritten here.

## Mechanical guard: no code token moves

The check compares leaf tokens with comments stripped, base `7a1faf1a5`
against head. It uses the TypeScript parser's leaf nodes, so template
literals are read in context, and it excludes JSDoc nodes. It ran over
all 28 touched `.ts` files.

- Real run: 52,337 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control, in `outbox.ts`: 0 files changed, as
expected (exit 0).
- Positive control, a declaration inserted into `outbox.ts`: DIFFER
(exit 1). The first attempt was a no-op: its anchor text was still
inside the replacement, so `scripts/ablation-replace.mjs` refused it
before the guard ran. It was redone with a hitting anchor.
- Positive control, one digit changed inside the kept `outbox.ts:210`
refusal string: DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs`, and each
restore was proven byte-identical to the HEAD blob (`80618f8711e2`) with
`git diff HEAD` empty.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/service-messaging` is included. It says only that the
provenance comments were re-anchored.

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/service-messaging
build`, the rewritten comments reach both halves of `dist`. `d9cf78eaa`
appears 8 times in `dist/index.d.ts`, `1a47a5368` 6 times and
`17d095413` 6 times, and `e4902d2b9` appears 15 times in
`dist/index.js`. The positive control, an unchanged
`notification-subscription.object.ts` docblock sentence, appears in
`dist/index.d.ts`, and a negative control phrase appears nowhere. The
only dead numbers left in `dist` are the two kept refusal strings.

## Gates (head `267c11562`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
(self-test, 114 cases in 8 batteries) exits 0, and `node
scripts/check-issue-citations.mjs` exits 0. The diff-scoped run judged 5
citations across 19 files, and all 5 resolve.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `267c11562` derived 64 families.
They include all 50 derived at dispatch, plus 14 more. All 64 exit 0.
`--ran` reports 64 run, 0 NOT MEASURED, 0 unrun, and exits 0.
- Three gates first exited 3 (PREREQUISITE NOT MET) because the
workspace was unbuilt: `check:dual-build-cjs-loads`, `check:i18n` and
`check:type-check-debt`. A full `turbo run build` of `./packages/*` and
`./packages/*/*` then ran under the shared verify lock (71 tasks, exit
0). The first two exited 0 on their rerun.
- `check:type-check-debt` exited 3 once more: `outbox.ts`'s mtime had
moved during the guard controls, although its bytes had not, so turbo's
cache hit left `dist` older than the source. A direct `pnpm --filter
@objectstack/service-messaging build` then let it exit 0 (4 ledger
entries re-measured, none above its number).
- **Tests and typecheck:**
- `pnpm --filter @objectstack/service-messaging test`: 46 files and 507
tests pass, covering every touched test file.
- `pnpm --filter @objectstack/service-messaging typecheck` exits 0. Its
`tsc` program lists all 46 test files and 87 files under `src/` in total
(`--listFiles`).
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 28 touched `.ts` files gives 28 files, 0 errors and 0
warnings. All 28 are in eslint's own population (`isPathIgnored` is
false for each). `eslint.config.mjs` never enables type-aware linting
(no `parserOptions.project`, as its own line 328 states), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the 28 files for control bytes finds none.

## Acceptance notes

- **The census instrument returned a truncated board once, at exit 0.**
The first `--census --json` run of this stage (06:25:55Z, base
`7a1faf1a5`) read `enumerated (85 pages)`, frontier objectstack-ai#8854, 8,444
numbers, when the newest number was above objectstack-ai#20600. The `Link` header of
its 85th page had carried no `rel="next"`, so `enumerateBoard` stopped
and classified 16,187 citations as `never-issued`. A reader counting
only `allocated-but-absent`, as this stage's count does, would have got
8 service-messaging sites instead of 100, silently. The next four
enumerations in this session read 185 pages and frontier objectstack-ai#20606, and the
counts above come from those. Nothing in `enumerateBoard` compares its
frontier with the newest issue number, which `probeBoard` does read.
Reported to the seat, not changed here: this stage makes no instrument
change.
- **What stays for later stages.**
- The 3 generated `objectstack-ai#11671` headers, whose producer is
`packages/cli/src/utils/i18n-extract.ts:2294`. That line is the
repo-wide carrier (27 generated files).
- The 3 refusal-string sites in `outbox.ts` (form D) and the 9
test-title sites.
- **Base.** The branch is 9 commits behind `origin/main` (`0f6dcac5e`,
read at 07:23Z). One of them, `8c87d26a5` (the version packages
release), touches `service-messaging`, but only its `CHANGELOG.md` and
`package.json`, and neither is in this diff. So there was no merge.
- **Anchors shared with the spec stages.** `17d095413` (objectstack-ai#6363),
`1a47a5368` (objectstack-ai#11453), `d9cf78eaa` (objectstack-ai#11859) and `b706af987` (objectstack-ai#11741) are
the anchors stages 1 and 2 already gave the same numbers in
`packages/spec/src`, so each number carries one anchor across the tree.

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

---------

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#20612)

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

The `packages/lint` stage of the dead-citation sweep: the `domain:spec`
lane's only package (census `5884031174` on objectstack-ai#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
objectstack-ai#20593 (objectstack-ai#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 objectstack-ai#20606). After: head `0c7b847f18`, 07:28:22Z to
07:31:52Z (185 pages, frontier objectstack-ai#20611).

## Measurement

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

- **objectstack-ai#13681 to `8ed9c54b4`**: lands the per-iteration containment rule
PAIR (`flow-loop-body-uncontained`, `flow-try-catch-without-catch`) and
its measured minimal `catch`; its diff wrote all nine lines, and its
changeset records the measurements the lines cite. objectstack-ai#14394 (the rule
card, 200) stays beside it.
- **objectstack-ai#8663 to `192213f66`**: "three write rules ask anchor provenance
before exempting a system column"; its body names objectstack-ai#8663 and its diff
wrote the `[objectstack-ai#8663]` lines in all three rule files.
- **objectstack-ai#13657 to `b003cf2e8`**: the post-hook half of the declared-field
door, one envelope on every driver; its diff wrote the three lines.
- **objectstack-ai#10064 to `def0d3e63`**: name-keys collection-resident publish-gate
finding paths; its body reads "maintainer ruling 2026-08-20: Option A"
for objectstack-ai#10064.
- **objectstack-ai#19370 to `a227afa41`**: `security-role-word` crosses to the runtime
publish gate, whole, per ruling batch objectstack-ai#203 item 3 letter B; it maps
`position` / `app` and writes the past-tense crossing lines.
- **objectstack-ai#9798 to `c7655d472`**: the change that carried objectstack-ai#9798 to done (its
body names it), restoring the sys_comment unscoped multi-delete refusal
that could not fire through the wired engine, the
declared-but-unenforced fail-open the line lists beside objectstack-ai#9261 and
ADR-0110 D3.
- **objectstack-ai#8404 to `b849e6911`**: warns when `searchableFields` declares an
unprovisioned injected anchor, adding the optional provenance index the
lines describe; the SORT twin line names it as the SEARCH wiring.
- **objectstack-ai#10001 to `f1b5ad39a`**: a standalone ViewItem record's nested
`config.sort` / `config.searchableFields` reach the runtime publish
gate, the RECORD rung.
- **objectstack-ai#6290 to `e9b526597`**: `current_user` joins `SCOPE_ROOTS`, the
field-level rejection becomes its own rule, and option-level
`visibleWhen` is walked for the first time. `:770` quotes `SCOPE_ROOTS`'
docblock in `packages/formula`; the quote now stops at "the last one
this list was missing", verbatim, with the commit outside the quotation.
- **objectstack-ai#11284 to `5383fa670`**: the ListView react-tier vocabulary
converges on the metadata-tier spelling, deprecate-first; its changeset
reads "(objectstack-ai#11284, maintainer ruling 2026-08-23)".
- **objectstack-ai#6629 to `cd584d559`**: drops the retired `displayField` /
`searchFields` from the record_picker entry and adds
`component-field-specs-liveness.test.ts`.
- **objectstack-ai#8664 to `8798cd2a6`**: names what actually guards the
`unprovisionedAnchors` wiring; its diff wrote both lines.
- **objectstack-ai#14700 to `de3c52beb`**: descends into `conditional` `then` /
`otherwise` when building the `_validations` universe; its diff wrote
all three lines.
- **objectstack-ai#6124 to `b3c1f3cd5`**: the squash commit of objectstack-ai#6124 itself, leg 1 of
the `_views` key ruling (the CLI i18n extractor keyed by the runtime
view identity).
- **objectstack-ai#10262 to `2aca1bc4c`**: adds the package-internal test seam for
`getNested`'s array fan-out; its diff wrote all three lines.
- **objectstack-ai#11060 to `815585513`**: its body records "Maintainer ruling on
objectstack-ai#11060 (2026-08-23): option A", the CEL-mirrored six with no second
semantics, which the lines quote.
- **objectstack-ai#13653 to `36d287803`**: gates a hook body's `ctx.api` write to a
readonly field, and shares `buildReadonlyIndex` from the flow rule, the
export `:118` describes.
- **objectstack-ai#12950 to `225e7690f`**: created `validate-component-types.ts`, the
author-time rejection for unknown component types in spec-reserved
namespaces (stage 5's anchor for the same number).
- **objectstack-ai#6528 to `3510e4a25`**: the squash commit of objectstack-ai#6528 itself, one
implementation of the filter identity reduction (maintainer ruling
2026-08-06, option 1). The line read `PR objectstack-ai#6528`; it now names the
commit.

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 objectstack-ai#20234's stages.

## Mechanical proof

- **Token guard** (scratch `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens,
JSDoc kinds excluded, controls mutate the head text in memory only). The
merge base `c96beb2707` against the head, 22 files, 54,508 base tokens
(the 22 files are byte-identical at `7a1faf1a5d` and at the merge base):
  - Real run: 0 files with a token change (exit 0).
  - Comment-insertion control (`runtime-gate.ts`): 0 (exit 0).
- Code-insertion positive control (`validate-hook-body-writes.ts`):
DIFFER at token 216 (exit 1).
- String positive control (a parser-located `StringLiteral` in
`validate-react-page-props.ts`): DIFFER at token 5 (exit 1).
- **Line balance**: every file is +N/−N (81/81 across 22 files), every
changed line is comment-shaped, and every line count is equal at base
and head.
- **Tracker numbers**: added-not-removed is empty in every file, and no
`PR #N` stands on an added line. Net-removed: 80 sites (the census's 79
in these files plus the slash-joined one), 19 numbers. The numbers kept
on added lines all answer 200: objectstack-ai#5659, objectstack-ai#5775, objectstack-ai#8310, objectstack-ai#8340, objectstack-ai#9261, objectstack-ai#9313,
objectstack-ai#12183, objectstack-ai#13390, objectstack-ai#14253, objectstack-ai#14394, objectstack-ai#19143, and objectstack-ai#6584 (a pull request, the
anchor commit's own PR).
- **Shas**: 19 distinct on added lines, 0 on removed lines. `rev-parse
--disambiguate` answers 1 object for each; `merge-base --is-ancestor`
exits 0 against `origin/main` and against the base; each is
single-parent; the repository is not shallow; the control leg
`e9584681a4` exits 0.
- **Literal readers**: every string or regex literal in the repository
that carries one of the 21 numbers (85 literals) was matched against the
23 files' text: no reader of any rewritten line. The lint tests that
read these sources as text stay green below. For example,
`validate-expressions.test.ts` strips comments before it matches, and
`validate-security-posture.runtime-surface.test.ts` collects the
`stack.X` reads inside `validateSecurityRoleWord`, which no added line
carries.

## 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 objectstack-ai#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 objectstack-ai#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-objectstack-ai#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 `objectstack-ai#5775/objectstack-ai#6629`, a
slash-joined dead number the citation grammar does not read; it now
reads "the same objectstack-ai#5775 residue class (commit cd584d5)", stage 5's
precedent for the slash-joined `objectstack-ai#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 objectstack-ai#20593 holds it.
Anchors, verified the same way, for whoever takes it after that PR
lands: `:198` objectstack-ai#10064 to `def0d3e63`; `:1117` objectstack-ai#16659 to `ecdfc9411` (it
added `flow-schedule-organization-missing` to the registry); `:1687`
"(PR objectstack-ai#8546)" to `ba5e957ef`, that PR's own squash commit; `:1713` and
`:1733` objectstack-ai#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 (objectstack-ai#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 (`objectstack-ai#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):
- `packages/lint/src/*.test.ts` titles and comments still cite several
of these dead numbers (objectstack-ai#6290, objectstack-ai#8404, objectstack-ai#8663, objectstack-ai#10001, objectstack-ai#10064, objectstack-ai#10262,
objectstack-ai#13681, objectstack-ai#19370 and others).
- Hand-written docs pages cite them too:
`content/docs/automation/hook-bodies.mdx` (objectstack-ai#8663, objectstack-ai#13657),
`content/docs/automation/flows.mdx` (objectstack-ai#11060) and
`content/docs/deployment/validating-metadata.mdx` (objectstack-ai#19370).
- `packages/formula/src/cel-engine.ts` cites objectstack-ai#6290 four times, including
the docblock `validate-expressions.ts:770` quotes. It is in the census,
in another lane's package.

**Wording, each true of its commit.**
- `validate-expressions.ts:571` keeps objectstack-ai#6584 beside `e9b526597`: objectstack-ai#6584 is
that commit's own PR, so "arrived in commit e9b5265, and needed that
same change (objectstack-ai#6584) to be noticed" states the one act both old numbers
named.
- `runtime-gate.ts:362` names the objectstack-ai#9798 shape in words, as the fail-open
that commit c7655d4 ended, next to objectstack-ai#9261 and ADR-0110 D3.
- `lint-flow-patterns.ts:343` reads "The measured case commit 8ed9c54
records, exactly: one row with a null owner killed the sweep"; that
commit wrote the sentence, and `c02f70e13` later fixed the same shape in
the showcase flow.

---
_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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants