Skip to content

docs(observability,verify): re-anchor the dead tracker citations in packages/observability/src and packages/verify/src to the commits that decided them - #20842

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20594-observability-verify-citations
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20594-observability-verify-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20594
Clause-②: no

What changed

This is stage 13 of the domain:cli lane of the dead-citation sweep: packages/observability/src and packages/verify/src in one PR. The maintainer ruled that in the handover 5903477632 on the card (「合成一个 PR (Recommended)」). The ruling changes the one-package-per-PR direction for these two packages only, so each package's before and after census counts are listed separately below.

Every comment site in these two trees that cited a tracker number answering 404 now cites the commit in this repository's history that made the decision the line describes. The form is ruling C+D's form C (comment 5749154545 on #19123), as stages 1 to 12 of this card applied it. The last stage was plugin-dev, landed as f7c6d65f5. The card stays open for its later stages, so this PR says Part of.

In total, 4 sites on 4 lines in 3 files, covering 3 numbers, now cite 3 distinct commits:

  • observability: 1 site, src/semconv.ts:49 (a census site);
  • verify: 3 sites:
    • src/harness.ts:580 (a census site);
    • src/erasure-transaction-authorization.test.ts:163 and :167. These are test-file comments, which the census defers. Stages 1 to 12 took test comments too.

Only comments changed: 4 lines out, 4 in. Every one of them is a site, with no reflow and no companion line. Every touched file keeps its line count (178 / 955 / 185 at base and head), so no line citation into these files moves.

No citation number is added. The only tracker numbers on added lines are #9650 and #9835 at semconv.ts:49. The removed line already carried both, and both answer 200. No PR number stands on an added line.

No ADR or ruling-record file records any of the three decisions. A grep of docs/adr/ and scripts/adr-anchors/ for the 3 numbers, the 3 shas, afterResponse and http_request_duration_ms reads 0 hits; the control number 7329 reads 1 file in the same tree. So all three anchors are commits.

One patch changeset, for @objectstack/observability only (.changeset/20594-observability-provenance-anchors.md). Its rewritten // line reaches the published dist. The verify rewrites leave verify's dist byte-identical, so verify takes no changeset. Both results are measured below.

Census, before and after, one package at a time

Instrument: the gate's own node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count is its allocated-but-absent findings under each package's path. Both runs enumerated the whole board (187 pages).

package before (base 22e584c9db) after (head 62e556317c)
packages/observability 1 (src/semconv.ts:49, #10004) 0
packages/verify 1 (src/harness.ts:580, #10943) 0
whole repository 752 750
run tree when (UTC) board
before base 22e584c9db 2026-09-30 10:33:12 to 10:37:06 frontier #20838, 18,665 numbers
after head 62e556317c 2026-09-30 10:49:43 to 10:53:16 frontier #20839, 18,666 numbers

The whole-repo drop of 2 is exactly these two sites. A site-by-site diff of the two JSON outputs has 2 findings gone (semconv.ts:49, harness.ts:580) and 0 added. The other three tallies are equal in both runs: 33,155 citations that answer 200, 1,985 that answer as pull requests, and 1,018 cross-repo.

Supplementary scan (census-invisible spellings, test files and files outside src/). The scan covers every #N token (two to six digits) in the two packages' 53 tracked files, CHANGELOG.md excluded, and probes each by REST (2026-09-30 10:34Z, re-probed 10:58Z).

  • It finds 90 distinct numbers: 12 in observability and 79 in verify, with 1 shared. 86 answer 200 and 4 answer 404: #10004, #10943, #11477 and #15145.
  • Dead occurrences in src/: 1 in observability and 3 in verify at base, 0 and 0 at head. None of them was in a string literal. At head all 232 #N tokens under the two src/ trees answer 200.
  • Spellings the census cannot see:
    • #N-word: 0.
    • #A/#B: 4 sites. Only semconv.ts:49 held a dead member.
    • option #N or clause #N: 0.
    • word-#N: 1, the pre-#11477 at :167, rewritten.
    • URL forms (issues/N, pull/N): 0. The only github.com strings are the two package.json repository/bugs URLs.
  • A re-grep of the four numbers with no word-boundary operator finds only verify/tsconfig.test.json:1 (see Acceptance notes). A control of the same shape, #9835, reads 2 lines of semconv.ts.

Per-number table

git blame at the base (on a full, unshallowed history) ties each line to the commit that wrote it. That commit's message and diff were read to decide the anchor.

number sites (file:line) anchor what that commit decided
#10004 observability/src/semconv.ts:49 1e050a5b1 http_request_duration_ms is emitted from the IHttpServer.afterResponse transport seam, so p95 latency sees every inbound surface. It is the squash of the pull request numbered #10004, and the line cites it beside the two live numbers for the same seam move. The line was written by 914c41302 (the http_request_errors_total retirement), whose changeset, now the released entry at observability/CHANGELOG.md:985, cites #10004 for this same seam move.
#10943 verify/src/harness.ts:580 46d34ab7c The host importer's undeclared fallback resolves from the caller's base, not from @objectstack/types, and bootStack hands in (s) => import(s). The line blames to this commit. Stages 3 and 4 used the same anchor for the same number in cli and types.
#11477 verify/src/erasure-transaction-authorization.test.ts:163, :167 6dd3e6968 /admin/remove-user gets the raw-mount shading whose gateAdmin runs before the break-glass guard (ruled option A, as its message records), so a plain member hears PERMISSION_DENIED. Both lines blame to this commit. Its squashed message includes the test(verify) step that rewrote this pin. The plugin-auth stage used the same anchor for the same number.

All three shas were checked the same way:

  • Each has exactly 1 match under rev-parse --disambiguate.
  • Each is a commit with one parent.
  • merge-base --is-ancestor of each against base 22e584c9db exits 0, on a history that is not shallow (--is-shallow-repository false).
  • The control legs: 1e050a5b1^ against the base exits 0, and base-as-ancestor-of-1e050a5b1 exits 1.

Wording per site

  • semconv.ts:49: (#9650 / #9835 / #10004) becomes (#9650 / #9835 / commit 1e050a5b1). Precedents for a mixed list: cli/src/commands/lint.ts:915, mcp/src/plugin.ts:8.
  • harness.ts:580: #10943: becomes Commit 46d34ab7c:. The neighbouring #4700:, #4719: and #17911: labels answer 200 and stay.
  • erasure-transaction-authorization.test.ts:163: #11477 (maintainer-ruled option A): becomes Commit 6dd3e6968 (maintainer-ruled option A):.
  • erasure-transaction-authorization.test.ts:167: the pre-#11477 route becomes the route before that commit, which refers back to :163 four lines up.

Does the rewrite reach dist? Measured per package

Both packages were built at base 22e584c9db (closure plus package, pnpm --workspace-concurrency=2 --filter '@objectstack/verify...' --filter '@objectstack/observability...' build, VERDICT 0) and again at head 5a144efc39 (VERDICT 0). All 6 dist files of each package were compared byte for byte.

  • observability: reaches dist.
    • index.js and index.cjs differ in exactly 1 line each: the semconv.ts:49 comment, which esbuild keeps inside the SEMCONV object literal.
    • index.d.ts, index.d.cts and both maps are equal.
    • The base dist carried #10004 in index.js and index.cjs.
    • ⇒ a patch changeset.
  • verify: does not reach dist. All 6 files are byte-equal at base and head, and the base dist carried none of the three numbers. ⇒ no changeset.
  • Code-mutation control for verify, which proves that "equal" was a measurement and not a stale build.
    • The mutation went through scripts/ablation-replace.mjs in wrap mode. The anchor const organizationsPkg = opts.organizationsPackage went 1 to 0, and the marker ABLMARK20594S13 went 0 to 1 in the source. The blob moved 47a921ad0b to 3f54cdebfe.
    • After a rebuild, ablation-dist-preflight found the marker in dist/index.js and dist/index.cjs. index.js, index.cjs and both maps differ from the head build, and both .d.ts files are equal.
    • The restore leg: the restored blob equals HEAD 47a921ad0b and git diff HEAD is empty. After a rebuild, preflight --absent reads the marker absent from all 6 files with a clean tree, and all 6 files are byte-equal to the head build.
    • The first control attempt used an anchor that was a prefix of its own replacement. The tool refused it (anchor count 1 to 1) and restored the file, so no build ran on it.
  • The whole workspace build, pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2, finished 71/71 (all cache hits). The dist of both packages is byte-equal to the head build.

Token guard

The guard compared TypeScript 6.0.3 parser leaf tokens (getChildren, JSDoc nodes excluded) of the 3 source files at base 22e584c9db and at 5a144efc39, over 3,523 base tokens:

  • the real diff: 0 files differ;
  • comment-insertion control: 0 differ;
  • code-insertion control: 3 of 3 differ;
  • string control (first character of the first string literal): 3 of 3 differ, first differing kind StringLiteral.

The script exited 0, and its controls ran in memory only. A first attempt with a bare scanner was discarded: it misaligns inside template literals and reported a false difference.

Tests, typecheck, lint and gates (head 62e556317c)

  • observability tests: pnpm --filter @objectstack/observability exec vitest run --maxWorkers=2 → Test Files 7 passed (7), Tests 85 passed (85).
  • verify tests: pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 → Test Files 16 passed (16), Tests 120 passed (120). The package has 16 test files, erasure-transaction-authorization.test.ts included.
  • verify typecheck: pnpm --filter @objectstack/verify typecheck passed, with VERDICT command-exit 0 (tsc --noEmit, then check:test-typecheck over tsconfig.test.json: 0 files / 0 errors). tsc --listFiles confirms that tsconfig.json reaches harness.ts and tsconfig.test.json reaches both edited verify files.
  • observability typecheck: the package has no typecheck script; it is a DEBT entry in check-type-check-coverage.mjs at 11 errors. tsc -p packages/observability/tsconfig.json --noEmit reads 11 errors, all in src/__tests__/, none in semconv.ts, and the program includes semconv.ts. pnpm check:type-check-coverage and pnpm check:type-check-debt pass (exit 0).
  • pnpm lint: the repo-wide eslint . --no-inline-config exits 0 (2026-09-30 10:54:08 to 10:57:25 UTC, at 62e556317c).
  • Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (run with no path) derived 60 commands, and all 60 were run with their exit codes recorded before any pipe.
    • 59 exited 0 on the first pass.
    • pnpm check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: no dist for 33 packages). After the whole-workspace build it was run again and exited 0.
    • --ran reconciliation: 「60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN」, exit 0.
    • The diff-scoped node scripts/check-issue-citations.mjs judged the 2 citations this change adds (#9650, #9835); both answer 200, exit 0.
    • pnpm check:nul-bytes passed, and a control-byte scan of the 4 changed files reads 0.
  • Main moved during the work: origin/main moved 8 commits past the base (to 1741c5dcb6). git diff --name-only over the two packages and the changeset path reads 0 files, so this branch was not merged forward. The merge ref CI builds covers the joint tree.

Acceptance notes

  • Left outside src/**, listed and not changed (a later stage of the card): packages/verify/tsconfig.test.json:1 cites #15145, which answers 404. The other 14 citations outside src/** in the two packages answer 200: observability/vitest.config.ts:11, verify/tsconfig.json:5 and :13, verify/tsconfig.test.json:1, :2, :32, :35, :43 and :60, and verify/vitest.config.ts:8, :14, :32, :63 and :70. README.md in either package carries no #N.
  • Release-owned, not a site: packages/observability/CHANGELOG.md:985 carries #10004 (with #9834) in a released entry. This PR does not edit it.
  • Open-PR overlap (read 2026-09-30 10:58Z): 13 open PRs. Only the Version Packages PR chore: version packages #20639 touches either package, and only in CHANGELOG.md and package.json.
  • Not governed: no path is on the governed-surface register. The diff changes 19 lines, far under 5,000.

Generated by Claude Code

…he commits that decided them

Four comment lines in packages/observability/src and packages/verify/src
cited tracker numbers that answer 404. Each now cites the commit in this
repository's history that decided what the line describes:

- observability semconv.ts: the transport-seam move of
  http_request_duration_ms -> commit 1e050a5
- verify harness.ts: the host importer's undeclared fallback resolving
  from the caller -> commit 46d34ab
- verify erasure-transaction-authorization.test.ts (two lines): the
  /admin/remove-user shading that runs gateAdmin before the break-glass
  guard -> commit 6dd3e69

Comments only; every touched file keeps its line count.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
…ored comment ships in dist

The rewritten SEMCONV comment reaches dist/index.js and dist/index.cjs
(one line each; both maps and both .d.ts unchanged). The verify rewrites
leave its dist byte-identical, so verify carries no changeset.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/observability, @objectstack/verify, touching 2 documentable anchor(s).

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

  • content/docs/releases/v17/17-0.mdx (via bootStack (symbol, a top-level function))
  • content/docs/releases/v17/17-2.mdx (via SEMCONV (symbol, a top-level const object))

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
  • 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 — 5 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 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 84d05fd1a27cc348671c2099a516054cbb82ae80 — the merge of head 62e556317c5aaeda8c4dce93d872a178551bf2aa into base 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8, 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 84d05fd1a27cc348671c2099a516054cbb82ae80 && git checkout 84d05fd1a27cc348671c2099a516054cbb82ae80
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8 62e556317c5aaeda8c4dce93d872a178551bf2aa && git checkout -B drift-repro 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8 && git merge --no-ff 62e556317c5aaeda8c4dce93d872a178551bf2aa

node scripts/docs-audit/affected-docs.mjs --json 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8

⚠️ 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 1741c5dcb62d11bc9b59702dd67080ef6a7ac9e8 → 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: 62e556317c5aaeda8c4dce93d872a178551bf2aa
Local-runs: none

Inputs read: card #20594 body and all 53 comments (the handover 5903477632 with the maintainer's 「合成一个 PR (Recommended)」 ruling, the claim 5909415031, the dev report 5910216119, the stage-12 dev report 5903397162 and the stage-12 release 5904216649 included); the stage-12 record 5903823584 on PR #20767; ruling 5749154545 on #19123 read at its source; PR #20842 body, file list and the net diff git diff 22e584c9db refs/review/pr-20842 (merge-base 22e584c9dbee155c9a2cfabf91059275448093c5, the branch fetched into a ref of my own); the check-runs on the head, polled in the background until every run completed; and the three cited commits 1e050a5b1, 46d34ab7c, 6dd3e6968 read in full with git show on the unshallowed history, plus 914c41302 (the commit that wrote the semconv.ts line). Nothing was built, run or re-run; the tracker numbers were probed by REST at review time.

Seat correction on adoption: the dev report reads origin/main as 8 commits past the base (to 1741c5dcb6). At review time it is 9 (to 30839063b5, a ci(test-core) shard-env change). The intersection of the PR's file list with git diff --name-only 22e584c9db origin/main is 0 files, and 0 under packages/observability, packages/verify and the changeset path. The verdict does not depend on the count.

① Derived judgments

Diff: 4 files, +15/-4, 2 commits. Three files under packages/observability/src/ and packages/verify/src/ with 4 comment lines out and 4 in; every touched file keeps its line count (178 / 955 / 185 at base and head, read by me). One new file, .changeset/20594-observability-provenance-anchors.md. Every hunk replaces a // comment line and nothing else: no code token, string literal, export, type, assertion or message moves, so the accept set of neither package moves. The describe title string #10792 in the erasure test is untouched (a string literal, the card's form-D stage; it answers 200 anyway). Not governed: no path is on the register, Governed Surface Queue Guard is success, 19 changed lines.

Site by site, each judged against the cited commit's own message and diff, with git blame at the base tying each line to its author commit:

  1. observability/src/semconv.ts:49: (#9650 / #9835 / #10004) becomes (#9650 / #9835 / commit 1e050a5b1). Right.
    • The line sits in the RETIRED block that 914c41302 wrote in place of httpRequestErrorsTotal; the sentence says the transport emits http_requests_total through the seam so it covers every inbound surface. 1e050a5b1 is the squash landing of the pull request numbered 10004: its subject is "emit http_request_duration_ms from the transport seam, so p95 latency sees every inbound surface", and its diff rewrites this same file's HTTP block to attribute each family to its emitter. Substituting the dead member of the list with the commit that landed it says truthfully what was decided.
    • #9650 and #9835 are kept, not added; both answer 200 at review time. #10004 answers 404.
    • Mixed-list precedent confirmed on origin/main: cli/src/commands/lint.ts:915 and mcp/src/plugin.ts:8.
  2. verify/src/harness.ts:580: #10943: becomes Commit 46d34ab7c:. Right.
    • The line blames to 46d34ab7c, whose diff on this very file adds both this comment block and the fallbackImport: (specifier) => import(specifier) argument the block explains. Its message is that decision: the host importer's undeclared fallback resolves from the caller, not from @objectstack/types. The neighbouring #4700:, #4719: and #17911: labels answer 200 and stay. #10943 answers 404.
    • Stages 3 and 4 of this card already anchor the same number to the same commit in packages/cli and packages/types (read on origin/main).
  3. verify/src/erasure-transaction-authorization.test.ts:163: #11477 (maintainer-ruled option A): becomes Commit 6dd3e6968 (maintainer-ruled option A):. Right.
  4. verify/src/erasure-transaction-authorization.test.ts:167: the pre-#11477 route becomes the route before that commit, referring to :163 four lines up. Right: before 6dd3e6968 the route was served unshaded, and the vendor literal was reached only after the guard had answered, exactly as the sentence says.

Anchor choice. docs/adr/** and scripts/adr-anchors/** at the base hold 0 hits for the three numbers, the three shas, afterResponse, http_request_duration_ms, createHostImporter, fallbackImport, remove-user and gateAdmin (control 7329: 2 hits). break-glass hits ADR-0090, ADR-0091 and ADR-0134, which govern grant-lifecycle break-glass and the SCIM last-administrator guard, not the /admin/remove-user ordering. So ruling C's order lands on the commit for all three, each cited by sha alone; no PR number stands on an added line.

Occurrence counts under the two src/ trees, base to head, read by me: #10004 1 to 0, #10943 1 to 0, #11477 2 to 0; the three shas 0 to 1 each; controls #9650 2 and 2, #9835 9 and 9, #10792 2 and 2. Test files are a DEFERRED surface of the gate (an exclusion glob, packages/**/*.test.ts), so the two erasure-test sites are census-invisible; rewriting them follows stages 1 to 12, as the stage-12 record judged three test-file sites.

Changeset ('@objectstack/observability': patch):

  • Accurate: the SEMCONV comment beside the retired http_request_errors_total entry is where the line sits (914c41302 removed the key and wrote the block); the cited number answers 404; 1e050a5b1 is the commit that moved http_request_duration_ms to the transport seam; comments only.
  • Carries no tracker number, no PR number and no model identifier in the body (scanned: no #N token, no bare 4-to-6-digit number). The filename carries 20594, the same form as 115 changesets on origin/main (the 20596-*-provenance-anchors series among them); the filename is consumed by changeset version and never ships, so this is a form observation, not a defect.
  • Matches what publishes: @objectstack/observability is public (files: dist, no private), and the dev's measurement is that the base dist already carried #10004 in index.js and index.cjs while the head dist differs in exactly that one line each. @objectstack/verify is public too, but the base dist carried none of #10943 or #11477, so the lines never shipped and their replacements cannot, under an unchanged build config; the dev's byte-equal reading with a code-mutation control (marker reached dist, restore leg byte-equal) is consistent with that. I ran no build; the record rests on the diff, the dev's measurement and that reasoning. No changeset for verify is right, and no skip-changeset is right since one package publishes.

Census (the card's instrument, the dev's runs at 22e584c9db and 62e556317c, whole board enumerated both times): packages/observability 1 to 0, packages/verify 1 to 0, whole repository 752 to 750 with a site-by-site diff of exactly these two sites gone and 0 added. The PR body lists each package's before and after count separately, in its own table row, as the handover ruling requires. The "before" of 1 per package agrees with my blame and REST probes: the only dead census-visible sites in these trees at base were semconv.ts:49 and harness.ts:580.

Check-runs on the head (converged 2026-09-30T11:45:18Z; the Test Core roll-up completed at 11:44:59Z):

  • 34 names, all completed: 31 success and 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)). 0 failed.
  • All seven required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard.
  • Check Changeset, Part-of PR must not also close its card, The card this PR closes must claim this branch and both single-claim guards are success.

② Semver level

  • patch for @objectstack/observability is right: a released package publishes changed bytes (one comment line in dist/index.js and dist/index.cjs) while no accept set, export, type or behaviour moves.
  • No changeset for @objectstack/verify is right: nothing it publishes changes.
  • Clause-②: no on the claim and on the PR body is right; the changeset is non-breaking, so no ADR-0087 marker is due.
  • PR shape: draft, base main, head repo equals base repo, assignee huangyiirene mirrors the card, first line Part of #20594, no closing keyword anywhere in the body, session-URL footer. Both commits carry the model-free trailer pair.

③ Boundary flags

The dev report 5910216119 has empty open_questions and premise_still_valid true. Each of its ten deviations is answered:

  1. main not merged forward (dev: 8 commits; seat: 9, to 30839063b5): 0 files overlap on the full file list and 0 under the PR's paths; the change is comment prose, so no joint-behaviour question exists; the PR's merge-ref CI is green on the joint tree and the merge queue rebuilds on current main. The skipped pull-main step is declared and costs nothing here. Accepted.
  2. git fetch --unshallow on the shared object store: additive only (objects and history; no ref, index or working tree moved, no stash); no rule in AGENTS.md forbids it, and blame and --is-ancestor needed it. It is also why this review could read the three anchors with git show. Declared, accepted.
  3. Closure build spelled as package-plus-dependencies: local only, no bearing on the diff.
  4. First code-mutation control refused as a no-op: nothing built on it; the second attempt is the measurement.
  5. Bare-scanner token guard discarded for the parser-leaf form: the four hunks are readable by eye as comment-only; no bearing.
  6. Seven gate families beyond the PM's stale lead list ran, exit 0: no bearing; Lint & Repo Gates is success.
  7. Artifact-roster and wide-population families not run locally: the head's check-runs are the verdict, all green.
  8. observability has no typecheck script (DEBT ledger at 11 errors): Type Check · debt ledger and Type Check · workspace are success, and semconv.ts moves no token.
  9. Model-free trailer pair and session-URL footer used over the harness reminder: right per AGENTS.md; verified on both commits.
  10. Worktree cleanup after posting: no bearing on the diff.

Both out_of_scope_findings are noted, not filed, and that is right:

  • packages/verify/tsconfig.test.json:1 cites #15145 (404 at review time): outside the census's declared surface, carried by item 3 of the card's release 5904216649 (each package's files outside src/**).
  • packages/observability/CHANGELOG.md:985 carries #10004 in a released entry: release-owned, a deferred surface of the gate, and the card's direction forbids edits to published release text; the entry was true when published, so it is not a factual error owed a docs-only PR. Listed in the PR's Acceptance notes.

Nothing needs escalation.

Implemented-by: claude/issue-20594-observability-verify-citations
Reviewed-by: session_01VvcEokUG1tvVxkceYfR5XB

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 11:51
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 820d3f4 Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20594-observability-verify-citations branch September 30, 2026 12:13
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…mit that decided it (objectstack-ai#20850)

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

## What changed

This is the sixteenth stage of the `domain:services` lane of the
dead-citation sweep. It covers `packages/plugins/plugin-webhooks/src/**`
and nothing else. By the seat's claim (`5909908590`), it is the next of
the three single-site packages left in the lane, taken one package per
stage. Later stages cover the other packages, so this PR says `Part of`
and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 15 (the latest is PR
objectstack-ai#20836, landed as `bbe03f406`). That is **1 site on 1 line in 1 file,
covering 1 number**:

- `src/translations/index.ts:26`, the census's one site in this package
at the base. No test comment, test string, source string or
gate-invisible spelling in this package carries a dead number (see the
supplementary reading below), so the census site is the whole
population.

The rewritten line now cites the commit in this repository that decided
what it describes: **1 commit sha**. No ADR or ruling record records
that decision (see the per-number table), so ruling C's commit rung
applies. No number was dropped.

Only a comment changed. The file keeps its line count (1 line out, 1
in), so no line citation into it moves. No code token moves (see the
guard below). **No citation number is added**: the only tracker number
on the added line is the live `objectstack-ai#12069`, which already stood on it.

The rewritten line is byte-identical to the line the six sibling
translation sets already carry (`plugin-approvals:26`,
`plugin-audit:28`, `plugin-security:26`, `plugin-sharing:26`,
`service-messaging:29`, `service-storage:29`), landed by stages 1, 2, 4,
6, 7 and 9.

**No changeset.** The rewritten docblock does not reach `dist`, and
`dist` is byte-identical with and without the rewrite (see Changeset
below), so this PR takes `skip-changeset`.

## Census: `plugin-webhooks`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/plugins/plugin-webhooks/`. Each run counts as a reading only
because its board frontier equals the newest issue or pull-request
number, read by a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
plugin-webhooks sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `bbe03f406`, run 2026-09-30T11:09:05Z to 11:12:38Z |
enumerated, 187 pages, frontier objectstack-ai#20841 (newest objectstack-ai#20841 before and after)
| 748 | **1** | 1 | 1 | 1 |
| after | head `01312906e`, run 11:26:38Z to 11:30:05Z | enumerated, 187
pages, frontier objectstack-ai#20843 (newest objectstack-ai#20842 before; objectstack-ai#20843 after, a pull
request opened 11:29:02Z inside the run, so the frontier equals the
newest at the run's end) | 747 | **0** | 0 | 0 | 0 |

The whole-repo drop is 1, exactly this diff's census site. The
`resolves` tally is 33,194 in both runs, and `resolves-as-pull-request`
(1,985) and `cross-repo-unjudged` (1,018) did not move either. Neither
run was truncated or discarded: both enumerations read 187 pages at the
newest frontier. The seat's census counted 1 here at `6bff748b`, and the
base agrees: `6bff748b` is an ancestor of the base, and the one commit
between them that touches this package's `src` (`7184436a4`, a
re-translation of generated object leaves) does not touch
`translations/index.ts:26`.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings. So a second reading runs the gate's own exported
`extractCitations` (whole-file and comment-prose projections) and
`namesThisRepository` over every `.ts` file under `plugin-webhooks/src`
(34 files). It takes its verdicts from the before census's own board
reading rather than from a second enumeration: a number is dead when
that census reported it `allocated-but-absent`, and alive when the
gate's own census-scope extraction judged it and the census did not
report it. 4 numbers are covered by neither, because they stand only in
test files. Each was read on its own through the read-only tools:
`objectstack-ai#10619` answers as an issue; `objectstack-ai#8556`, `objectstack-ai#13565` and `objectstack-ai#15687` answer as
pull requests. The probe's controls: `objectstack-ai#11671` and the known issue
`objectstack-ai#10619` answer 404 on the pull-request endpoint.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `bbe03f406` | 369 | **1** | 1 | 0 | 0 | 0 |
| after, `01312906e` | 368 | **0** | 0 | 0 | 0 | 0 |

Its src-comment column equals the census's 1, which is the control on
the second instrument. The 368 live citations are the same in both
readings (363 by the census's judgement, 5 by the single reads), none is
cross-repo, none is unjudged, and the drop of 1 citation is exactly the
rewritten site. A third, raw reading (every `#` followed by 2 to 6
digits, whatever surrounds it) finds 378 occurrences before and 377
after, the same drop of 1. The 9 tokens beyond the gate's grammar are
the same before and after, and none is dead (see Acceptance notes).

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e` (PR objectstack-ai#12557): the extract command's
`--source-hashes` option writes a per-locale provenance companion,
`LOCALE.source-hashes.generated.ts`, beside the generated translation
bundles, recording for each leaf the source revision it is still a byte
copy of, per maintainer ruling objectstack-ai#12069 Option A. That is exactly what
`:25-26` say. Its message names `objectstack-ai#12069` but not `objectstack-ai#11671`; its own diff
names `objectstack-ai#11671` on 16 added lines, the first of them its changeset's
headline. `git blame` puts `:26` in `30928a615` (PR objectstack-ai#12724, which wires
the serving-time read of that companion into all nine bundle sets), a
descendant of the anchor. Reused: stages 1, 2, 4, 6, 7 and 9 gave the
identical line in six sibling packages this anchor |

The sha matches exactly one commit (`git rev-parse --disambiguate`,
count 1) and is an ancestor of the base (`merge-base --is-ancestor`,
exit 0; reverse leg, base against the anchor, exit 1; control legs exit
0: stage 1's landing `422db788a`, and the repository's root commit,
15,200 commits deep against the anchor's 3,802; the history is complete,
`--is-shallow-repository` false, 15,201 commits). `objectstack-ai#11671` answers 404
on the issues endpoint and on the pull-request endpoint, read one by
one.

No ADR, `scripts/adr-anchors/` file or ruling record names `objectstack-ai#11671`,
`objectstack-ai#12069` or the source-hash companion (`git grep` over `docs/adr` and
`scripts/adr-anchors`: one unrelated `source-hash cache` hit in
ADR-0080), so ruling C's first rung is empty.

## Wordings to check

- **Tag swap in place.** 「(maintainer ruling objectstack-ai#12069 Option A, objectstack-ai#11671)」
became 「(maintainer ruling objectstack-ai#12069 Option A, commit 09b4f4e)」. It is
the form, and the byte-identical line, the six sibling translation sets
carry.

## Sites left

- **In `src`: none.** No test title, assertion message, operator log
string, runtime refusal, quoted maintainer ruling or generated file in
this package carries a dead number.
- **Outside `src`, listed and left, not edited in this stage:**
- `scripts/i18n-extract.config.ts:17` names the dead `objectstack-ai#11671` (beside
the live `objectstack-ai#12069`); the anchor for whoever next edits it is `09b4f4e4e`.
The file is outside `files[]` and outside the census surface.
- `tsconfig.scripts.json:1` names the dead `objectstack-ai#11351` (404 on the issue
and pull-request endpoints).
- `tsconfig.test.json:3` and `:56` name the dead `objectstack-ai#13176` (404 on both
endpoints; stage 4 measured it dead).
- The other numbers in those files and in `vitest.config.ts` are live:
`objectstack-ai#12069`, `objectstack-ai#12559`, `objectstack-ai#10868`, `objectstack-ai#10756`, `objectstack-ai#5475`, `objectstack-ai#14062`, `objectstack-ai#5286`,
`objectstack-ai#5449`, `objectstack-ai#12542`, `objectstack-ai#10772`, `objectstack-ai#10374`, `objectstack-ai#12642`.
  - The release-owned `CHANGELOG.md` names `objectstack-ai#11671` on 2 lines.
- `package.json`, `tsconfig.json` and `tsup.config.ts` name none.
`files[]` lists `README.md`, but the package has none.

## Mechanical guard: no code token moves

The guard compares, base `bbe03f406` against head, over the one touched
`.ts` file:

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

Results:

- Real run at the head `01312906e`: 50 base leaf tokens, **0 files with
a token change** on either reading (exit 0).
- Comment control (「Recording alone changes nothing a user sees」 to 「...
alters ...」): 0 files changed, as expected (exit 0).
- Positive control, a code token renamed (`undefined,
zhCNGeneratedSourceHashes)` to `...SourceHashesX)`): DIFFER on the
identifier in both readings (exit 1).
- Positive control, a string-literal key changed (`'es-ES'` to
`'es-EZ'`): DIFFER on the string literal in both readings (exit 1).

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

## Changeset: none (`skip-changeset`)

`files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is
not private. Measured on the built package (A3), after `pnpm --filter
'@objectstack/plugin-webhooks...' build` at `01312906e`, where the
package's own `tsup` ran and cleaned its output folder:

- The rewritten line, its whole docblock (`:22-55`, which sits on the
unexported `const enSource`) and the old number appear in no `dist` file
(0 in all 20, source maps included). Nor do its neighbours `:23`, `:25`,
`:27` and `:30`, or a never-written negative phrase.
- Positive controls, text that does ship: `WebhooksTranslations` from
this same file is in `index.js`, `index.cjs` and both translations
chunks (8), and `zhCNGeneratedSourceHashes` is in both translations
chunks (4). A docblock line of this package, `auto-enqueuer.ts:33`
(「Enqueue callback into the shared `service-messaging` HTTP outbox
(ADR-0018 M3).」), is in `index.d.ts` and `index.d.cts`.
- **Byte identity.** The package was rebuilt with the base line put back
(through `scripts/ablation-replace.mjs`, with the file proven equal to
the base blob `686646fac51d` during the leg), and again after the proven
restore. The sha256 of all 20 `dist` files is identical in all three
builds: JS entries, declaration files, chunks and source maps.

So nothing this PR rewrites is published, and a changeset would announce
a change no consumer can observe. No `.changeset/*` file is added or
touched.

## Gates (head `01312906e`)

- **Citation judging, as CI runs it:** `node
scripts/check-issue-citations.mjs` exits 0 (「every citation this change
adds resolves」). `pnpm check:issue-citations` exits 0 (self-test, 114
cases, 8 batteries).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the
sibling-package prose-id baseline holds, no growth).
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `01312906e` (change set derived
from git: 1 path against the merge base `bbe03f406`) derived 50
commands, all among the dispatch list's 65.
- All 65 of the dispatch list ran, each with its exit code captured
before any pipe, and all 65 exit 0; none exited 3.
- `--ran`, fed each command with its exit code, reports 50 derived, 50
run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0.
- A full `turbo run build` over `./packages/*` and `./packages/*/*` ran
first under the shared verify lock (71 of 71 tasks), so no gate hit an
unbuilt workspace.
- **Roster families the derivation lists outside its commands** (their
rosters sit in a directory this diff touches): `pnpm
check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm
check:filter-alias-parity`, plus `node
scripts/check-changeset-fixed.mjs`, which the dispatch named. Each exits
0.
- **Tests and typecheck, under the verify lock, at `01312906e`:**
- `pnpm --filter @objectstack/plugin-webhooks test`: 13 files pass and
160 tests pass, which is every tracked test file under `src/`.
- `pnpm --filter @objectstack/plugin-webhooks typecheck` exits 0, and
`tsc --noEmit --listFiles` puts the touched file in the program (the
config includes `src` and excludes no test file).
- `pnpm --filter @objectstack/plugin-webhooks check:test-typecheck`
exits 0 (0 files held in the package's debt ledger).
- **Lint, as a proven narrowing:** eslint with inline config disabled,
over the touched `.ts` file, gives 1 file, 0 errors and 0 warnings (its
`--format json` output). The file is in eslint's own population (not
reported ignored; `dist/index.js`, the control, reads ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the changed file for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636,
including the `clause #N` position). At the base and at the head,
`#N-word` is on 0 lines. `#A/#B` is on 8 lines (8 second numbers), and
every second number is live: `objectstack-ai#8022` (3 lines), `objectstack-ai#7986` (2) and `objectstack-ai#8043`
(2) by the census's own judgement, and `objectstack-ai#8565` read on its own as a pull
request. `option #N`, `clause #N` and URL-spelled links are on 0 lines.
So the claim's 0 / 8 / 0 / 0 / 0 hold, and nothing dead hides behind
them. The one other raw token beyond the grammar is `PD objectstack-ai#12` at
`auto-enqueuer.ts:919`, a Prime Directive ordinal the extractor excuses
on purpose.
- **Outside-`src` residue.** Three dead numbers in files outside `src`
and outside `files[]` (`objectstack-ai#11671`, `objectstack-ai#11351`, `objectstack-ai#13176`, listed above) are
left, as stages 13 to 15 left theirs. They ship nothing and move no
gate.
- **The census instrument did not truncate in this stage.** Both
enumerations read 187 pages at the newest frontier.
- **Anchor for the next stages:** `objectstack-ai#11671` → `09b4f4e4e`.
`service-realtime/src/translations/index.ts:27` still carries the dead
number in the identical sentence.
- **Base.** The branch is on `main` at `bbe03f406`. `main` has since
moved five commits (to `688ddef3c`). None touches
`packages/plugins/plugin-webhooks`, `scripts/check-issue-citations.mjs`,
`scripts/pm/dispatch-gates.mjs` or `.changeset/config.json`. No merge
was taken; the merge queue rebuilds on the merged generation.

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

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

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

## What changed

This is stage 14 of the `domain:cli` lane's dead-citation sweep. It
covers the lane packages' tracked files outside `src/**`, the card's one
remaining stage (claim `5911994187`): `README.md`, `tsconfig*.json`,
`vitest.config.*`, `tsup.config.*` and each package's `test/**`.

The census at the stage's base `660a9b247e` counts **270 dead comment
sites** on that surface. That is over the claim's 40-site line, so this
stage takes the largest package, **`packages/cli`**. Every other package
is listed below with its count.

In `packages/cli`, every comment site on that surface that cited a
tracker number answering 404 now cites a commit instead. Following
ruling C+D's form C (comment `5749154545` on objectstack-ai#19123), it is the commit
in this repository's history that decided what the line describes.
Stages 1 to 13 of this card are the precedents; the latest are PR objectstack-ai#20767
and PR objectstack-ai#20842.

- **154 sites on 149 lines in 56 files**, covering **51 numbers**, now
cite **50 distinct commits**. The 56 files are `vitest.config.ts`,
`tsconfig.test.json` and 54 files under `test/**`: 50 test files,
`helpers/serve-process.ts` and 3 fixtures.
- **Comments only.** 150 lines out and 150 in, and every file keeps its
line count.
- One of the 150 is a companion line: `generate-skill.e2e.test.ts:35`,
"both pass" becomes "both passed".
- Its anchor, commit `3c418c498`, rewrote the two `@example` blocks the
sentence is about. A present tense beside that citation would contradict
it.
- **5 sites have no deciding commit** and are left as they were (see
"The sites left").
- **No tracker number is added.** The 12 numbers still on changed lines
were already on the removed lines, and all 12 answer 200: objectstack-ai#5286, objectstack-ai#5728,
objectstack-ai#10678, objectstack-ai#11391, objectstack-ai#11643, objectstack-ai#11772, objectstack-ai#12047, objectstack-ai#13158, objectstack-ai#14554, objectstack-ai#16483, objectstack-ai#16888
(kept as a convenience link beside its commit) and objectstack-ai#17978.
- **No ADR or ruling record carries any of these decisions.** A grep of
`docs/adr` and `scripts/adr-anchors` finds 0 hits for the 54 numbers and
0 for the 50 shas. The control `7329` finds 1 file in the same tree. So
every anchor is a commit.
- Where an earlier stage gave a number an anchor and the line here
describes the same decision, the same anchor is reused. That covers 33
of the 51 numbers (for `objectstack-ai#13504`, the tier half only), read by pairing
the landed stages' removed and added lines. The other 18 anchors are
new.

## Census, per package

**Instrument.** The card's gate does not read these files. Its declared
surface is `packages/**/src/**`, and `surfaceFor` answers null for all
56 touched paths; the control `packages/cli/src/commands/init.ts`
answers `package-docblocks`. So the census is taken by hand, with the
gate's own grammar:
- **Files:** every tracked file of the 19 lane packages outside
`src/**`, `CHANGELOG.md` excluded. That is 534 files.
- **Extraction:** `extractCitations` from
`scripts/check-issue-citations.mjs`, whole-file. Its comment-prose
projection decides comment versus string.
- **Numbers kept:** only those naming this repository.
- **Probe:** each distinct number is probed by REST, `GET
/repos/objectstack-ai/objectstack/issues/N`.

**Probes:**
- **Before**, at base `660a9b247e`, 2026-09-30T13:15:23Z to 13:16:12Z:
889 numbers. 794 answer 200 and 95 answer 404.
- **After**, at head `fda0702246`, 15:00:12Z to 15:01:16Z: 864 numbers.
794 answer 200, 69 answer 404, and one answered 502. That one was
objectstack-ai#11267; re-probed twice at 15:01:23Z, it answers 404.
- No number present in both probes changed its answer.
- Citation sites went from 3,634 to 3,480, a drop of exactly the 154
rewritten sites.

Dead sites per package. "Comment" is this stage's surface. "String"
means a string, a `describe`/`it` title or a message on the same file
list; those belong to objectstack-ai#20752 (form D) and are untouched. "Off-list"
means files outside the claim's file list; they are untouched and listed
below.

| package | comment, before → after | string (unchanged) | off-list
(unchanged) |
|---|---|---|---|
| `cli` | **159 → 5** | 42 | 41 |
| `qa/dogfood` | 94 → 94 | 28 | 0 |
| `plugin-hono-server` | 4 → 4 | 0 | 2 |
| `plugin-dev` | 2 → 2 | 0 | 0 |
| `client` | 2 → 2 | 0 | 1 |
| `qa/vitest-filter-preflight` | 2 → 2 | 0 | 1 |
| `cloud-connection` | 1 → 1 | 0 | 0 |
| `mcp` | 1 → 1 | 0 | 1 |
| `qa/downstream-contract` | 1 → 1 | 0 | 0 |
| `rest` | 1 → 1 | 0 | 7 |
| `runtime` | 1 → 1 | 0 | 1 |
| `types` | 1 → 1 | 0 | 0 |
| `verify` | 1 → 1 | 0 | 0 |
| `qa/http-conformance` | 0 → 0 | 0 | 1 |
| `observability`, `client-react`, `create-objectstack`,
`adapters/hono`, `qa/refd-timer-testkit` | 0 → 0 | 0 | 0 |
| **total** | **270 → 116** | 70 | 55 |

- **`tsconfig*.json` string values: none.** Every dead site in a
`tsconfig*.json` sits in a `//` comment, and these files are JSONC.
  - **Taken here:** cli's `tsconfig.test.json:1`.
- **Still dead:** `verify/tsconfig.test.json:1` (objectstack-ai#15145),
`plugin-dev/tsconfig.test.json:3` and `:56`, and
`plugin-hono-server/tsconfig.test.json:3` and `:61` (objectstack-ai#13176),
`plugin-hono-server/tsconfig.typecheck.json:12` (objectstack-ai#11332, objectstack-ai#10724), and
`client/tsconfig.json:8` (objectstack-ai#12181).
- **No `README.md`** in any lane package cites a dead number.

## Per-number anchors

`sites` counts the rewritten sites for each number. Each anchor was
checked by blame, and in its message or its diff. The "what it decided"
column is what the lines describe.

| number | sites | anchor | what it decided |
|---|---|---|---|
| `objectstack-ai#6217` | 4 | `2b641ddd4` | `--json` reserves stdout for the payload
across the `bootSchemaStack` family (its message closes the card) |
| `objectstack-ai#10152` | 1 | `ad492e7fd` | wrote the measured suite-cost section
this heading opens (a later commit's message pairs the card with this
PR) |
| `objectstack-ai#10323` | 1 | `5a616d558` | `create-objectstack` derives "Created
files" from the finished project |
| `objectstack-ai#10324` | 3 | `ecd06f613` | self-contained starter comments, and
creates `starter-comments-self-contained.test.ts` |
| `objectstack-ai#10326` | 1 | `675ab574e` | the two benign peer skews declared inside
the scaffold (stage 3's anchor) |
| `objectstack-ai#10359` | 4 | `15b63e85a` | retires `os g agent` (stage 3's anchor) |
| `objectstack-ai#10366` | 1 | `bbe643c08` | gates plugin-auth's localhost
trusted-origin substitution to non-production (the plugin-auth stage's
anchor) |
| `objectstack-ai#10498`, `objectstack-ai#10499` | 1, 4 | `6d441e41f` | its squash carries both: the
measured pnpm boundary, and the gate between the two scaffold paths |
| `objectstack-ai#10504` | 5 | `ff5733e03` | `UI: 0 Apps` instead of a dropped row;
records the triage ruling |
| `objectstack-ai#10557` | 2 | `818e02700` | init's "Created files" summary after
install, and the `create-objectstack` alias |
| `objectstack-ai#10763` | 1 | `c2b97c2a1` | `os package publish` prints the server's
reason (stage 3's anchor) |
| `objectstack-ai#10917` | 7 | `7940de5e0` | retires the `@capabilities` hook-body
directive |
| `objectstack-ai#10926` | 1 | `d173125fb` | the ruling commit stage 3 and the spec
stages used for this number |
| `objectstack-ai#10931` | 2 | `afe1c4e0a` | declares the four `@better-auth/utils`
peer skews |
| `objectstack-ai#10943` | 1 | `46d34ab7c` | `fallbackImport` becomes a
caller-supplied parameter |
| `objectstack-ai#10952` | 6 | `0d4bd93e7` | every summary section prints its zero
state |
| `objectstack-ai#10953` | 2 | `be7262e72` | the four structural advisories in `os
validate --json` |
| `objectstack-ai#11022` | 2 | `21756b325` | adds the fifth `MONOREPO_ONLY` pattern |
| `objectstack-ai#11025` | 3 | `1c3a46f87` | `os g skill` writes `NAME.skill.ts` |
| `objectstack-ai#11026` | 2 | `3c418c498` | rewrites `skill.zod.ts`'s two `@example`
blocks off `triggerPhrases`; its changeset names the number |
| `objectstack-ai#11071` | 7 | `50fb191dc` | derives every `os generate` filename from
the registry (its message closes the card, and measures the loader) |
| `objectstack-ai#11157` | 4 | `a4cb7817f` | `serve` hands the host importer its own
base |
| `objectstack-ai#11172` | 2 | `05181e8cc` | keeps the `Runtime:` row and stops
counting an unrendered metric |
| `objectstack-ai#11174` | 1 | `ab23c67ab` | `os validate --json --strict` exits 1 |
| `objectstack-ai#11267` | 19 | `1ddda1d00` | introduces `childEnv()`, the measurement
table and `serve-process-child-env.e2e.test.ts`; its message names the
card twice |
| `objectstack-ai#11268` | 4 | `918988ad3` | the PR itself; its squash changes
`turbo.json` to `@objectstack/cli#test` `dependsOn: ["build"]` |
| `objectstack-ai#11671` | 1 | `09b4f4e4e` | the source-hashes provenance companion
(every earlier stage's anchor) |
| `objectstack-ai#12125`, `objectstack-ai#12285` | 7, 1 | `79cf692b0` | carries `conversions` on
every failure exit. `objectstack-ai#12285` is this commit's own PR. Its message
withholds the fold question, which is what 3 of the lines say |
| `objectstack-ai#12297` | 3 | `9fd45a952` | `os lint` surfaces conversion notices
(stage 3's anchor) |
| `objectstack-ai#12964` | 4 | `e6fd1caf7` | names the missing build output; its
changeset names the number, and it holds both halves |
| `objectstack-ai#13109` | 2 | `8b236c826` | matches i18n-extract's walk to
`translatePage`'s |
| `objectstack-ai#13112` | 1 | `e7191ce71` | per-condition `types` targets in the
dual-build packages; its changeset names the number |
| `objectstack-ai#13193` | 1 | `faff497fd` | `os serve` writes the state file before
it announces the port |
| `objectstack-ai#13218` | 1 | `c45d8e6b4` | exports the addressed-component walk; its
changeset says "ruled 2026-08-30" |
| `objectstack-ai#13504` | 4 | `55519d503` | the measurement half: attributes the
`import` term per file (`vitest.config.ts:82`, `:261`, `:263`, `:359`) |
| `objectstack-ai#13504` | 5 | `44813ba57` | the tier half: the named `unit` /
`integration` split and the partition pin (`:496`, `:541`, `:677`,
`:831` and `vitest-tiers-partition.test.ts:5`; the qa stage's anchor) |
| `objectstack-ai#13651` | 1 | `ada3834ad` | the silent hook-body downgrade becomes a
lint verdict (stage 3's anchor) |
| `objectstack-ai#14336` | 2 | `79c71d29d` | object / view / action / app scaffolds
`os validate` accepts |
| `objectstack-ai#14710` | 2 | `95fdf627b` | wires the test layer into
`check:test-typecheck` |
| `objectstack-ai#14715` | 1 | `accb9231c` | the PR itself; keeps the exit-2 assertion
for the never-read reader |
| `objectstack-ai#14811` | 1 | `8ad872ba3` | sweeps every `os explain` catalog entry;
its diff adds this heading |
| `objectstack-ai#14817` | 2 | `5529a374e` | puts the three platform record pages
under an i18n gate; its message records the harm |
| `objectstack-ai#14824` | 6 | `cf6b67164` | `os create` emits a project that installs
outside the monorepo (stage 3's anchor) |
| `objectstack-ai#14858` | 5 | `0c5e97368` | a closed stderr read end exits 2 |
| `objectstack-ai#15150` | 5 | `cc986c913` | the sixth `MONOREPO_ONLY` pattern (the
create-objectstack stage's anchor) |
| `objectstack-ai#16330` | 2 | `4998efa71` | the blank template's CI workflow, with
`lint` (the create-objectstack stage's anchor) |
| `objectstack-ai#16350` | 2 | `68aee4c99` | `os init` writes a `lint` script; its
diff adds the pin that cites the number (PR objectstack-ai#16888 kept beside it) |
| `objectstack-ai#16721` | 1 | `51ae73123` | `LiteKernel.use()` enforces the plugin
contract (the plugin-hono-server stage's anchor) |
| `objectstack-ai#17080` | 1 | `8b4890343` | the per-release `spec-changes.json`
section (its message closes the card) |
| `objectstack-ai#17853` | 2 | `08f5f0e5a` | a vitest filter that selects nothing says
so (the qa stage's anchor) |

**Anchor checks:**
- **Each sha is unambiguous:** it matches exactly one object (`git
rev-parse --disambiguate`, count 1 for each of the 50).
- **Each is a plain commit** with one parent.
- **Each is on the base:** `merge-base --is-ancestor` against
`660a9b247e` exits 0 for all 50.
- **The history is complete:** the checkout is not shallow.
- **Control legs:** the parent of the oldest anchor, `8e13ca8764` (the
parent of `2b641ddd4`, 2026-08-08), exits 0. The negative control, the
base as an ancestor of `2b641ddd4`, exits 1.

**Wordings to check, each true of its commit:**
- **A defect named by its number now says so:** "The defect commit
79cf692 fixed — ...".
- **A card's words stay the card's.** Where a line quotes what a card
said or asked, it now says "commit X's card". That is
`vitest.config.ts:263`, `:359` and `:541`, and
`published-subpath-hook-body.pin.test.ts:28`.
- **The objectstack-ai#12125 fold question:** "the same question commit 79cf692 left
open", "commit 79cf692 explicitly withheld an answer". The commit's
message reads: "`warnings` and `conversions` are deliberately NOT
folded: whether they should become one field is a live question the
ruling did not address."
- **Ruling dates:** `platform-page-i18n-parity.test.ts:163` keeps its
ruling date as "(the 2026-08-30 ruling)".
- **Headings with a dash rule** keep their width by trimming the rule.
The exception is `commands.test.ts:181`, which keeps a 2-character rule
and grows by 6.

## The sites left

**No deciding commit (5 sites).** The claim says to list them, not
guess.
- **`vitest.config.ts:221` (objectstack-ai#10149):** "the outer fan-out ... is a
property of the shard, decided in `ci.yml`".
- Only two commits name the number. `cc21aad8ed` is a measurement that
says "Part of" and leaves the decision to the maintainer. `d18bc32770`
cites the card's recorded reasoning.
- Neither decides it, and the commit that wrote the line (`ad492e7fd`)
only cites it.
- **`test/init.test.ts:189` (objectstack-ai#11048):** "Admitting that band is a
support decision".
- It names an open decision (pnpm 10.0 to 10.4), and the floor is still
pnpm 10.15 or later at the base.
- Stage 3 and the create-objectstack stage left the sibling sites for
the same reason.
- **`published-entry-stderr-error-listener.test.ts:11` and `:150`, and
`published-subpath-hook-body.pin.test.ts:35` (objectstack-ai#14874):** "npm packs a
`bin` target regardless of `files`".
- No commit message names the number. The commits that add it
(`95d5cbb31`, `b3ef687c0`) only cite it.
- `5023630b1`'s changeset records the same measurement but names no
number, so the link is not proven.

## Not in this stage

- **The other lane packages**, 116 dead comment sites in total: see the
census table.
- **String sites (70, form D, objectstack-ai#20752, not moved):**
- **`cli`, 42 sites in 24 test files.** Examples: `describe('objectstack-ai#12125 —
...')` in `build-json-failure-conversions.e2e.test.ts:300`, `:459` and
`:557`, and `describe('[objectstack-ai#11025] ...')` in
`generate-skill.e2e.test.ts:203`.
- **`qa/dogfood`, 28 sites in 13 test files**, including
`authz-conformance.matrix.ts:429` (3 numbers in one string).
- **Off-list files (55 sites):**
- **`cli`:** `bin/run-dev.js` 6 and `bin/run.js` 4 (comments;
`bin/run.js` ships). `scripts/check-app-nav-i18n.mjs` has 13 comment and
14 string sites. `vitest-tiers.ts` has 2 and `vitest-tiers.fixtures.ts`
1 (comments). `test-typecheck-debt.json` has 1 (string).
  - **`plugin-hono-server`:** `objectstack.config.ts` 2.
- **`test-typecheck-debt.json` strings:** `rest` 7, and 1 each in
`runtime`, `mcp`, `client` and `qa/http-conformance`.
- **`qa/vitest-filter-preflight`:** `package.json` 1 (the description
string).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens of the 56 changed
files at base `660a9b247e` against head `fda0702246`. It walks with
`getChildren`, excludes JSDoc nodes, and treats comments as trivia.
`tsconfig.test.json` is compared by its parsed JSONC value. Controls
mutate the head text in memory only.

| run | result | exit |
|---|---|---|
| real diff | 86,550 base tokens, **0 files differing** | 0 |
| comment-insertion control | 0 differing | 0 |
| code-insertion control | 56 of 56 differ | 1 |
| string control (one character flipped in each file's first real
`StringLiteral`) | 56 of 56 differ (55 first at a `StringLiteral`, plus
the JSON value) | 1 |

A raw scan of the 56 changed files for control bytes finds none. Its
positive control, a scratch file holding a U+0001 byte, matches 1.

## Changeset: none (`skip-changeset`)

`@objectstack/cli`'s published set is `files: ["dist", "README.md",
"CHANGELOG.md"]`, plus the `bin` target npm packs regardless of `files`.
No touched path is in it:
- `dist` is built from `tsconfig.build.json` (`rootDir: "src"`,
`include: ["src"]`).
- `bin/` is not touched.

Measured on the built package at head, 3 phrases the change adds occur
in 0 files of `dist`:
- "commit 55519d5's card"
- "the two templates commit cf6b671 repaired"
- "commit 1ddda1d"

The positive control, a `src` docblock phrase ("already drifted once
(closed by commit 6d441e4)"), occurs in `dist/commands/init.js`.

## Tests (head `fda0702246`, `os-verify-lock` with
`OS_VERIFY_LOCK_SLOT=issue-20594-outsidesrc`)

- **Closure build.** `pnpm --workspace-concurrency=2 --filter
'@objectstack/cli...' build` → VERDICT command-exit 0.
- **Unit tier.** `pnpm --filter @objectstack/cli exec vitest run
--project unit --maxWorkers=2` → VERDICT 0, Test Files 237 passed (237),
Tests 3370 passed (3370). The unit tier holds 23 of the 50 touched test
files.
- **Queue integration tier.** The one touched file ran by name:
`--project integration
test/published-entry-stderr-error-listener.test.ts` → 1 file, 6 tests
passed.
- **Nightly tier.** The other 26 touched test files ran by name under
`OS_TEST_TIERS=nightly`, in 4 runs: 9 files / 87 tests, 6 / 74, 6 / 22
and 5 / 22, each VERDICT 0 and all passed.
- **Coverage.** Every touched test file ran.
- **Typecheck.** `pnpm --filter @objectstack/cli typecheck` → VERDICT 0.
- It runs `tsc --noEmit` and then `check:test-typecheck`, which reports
"3 file(s) / 28 error(s) / 6 pinned signature(s) held in
test-typecheck-debt.json". That is the ledger as it stands at the base,
unchanged.
- **Whole workspace.** `pnpm exec turbo run build
--filter='./packages/*' --filter='./packages/*/*' --concurrency=2` →
VERDICT 0, 71/71 tasks. This was needed by the two gates that read built
output.
- **Reverse verification: none.** A comment-only change has no behaviour
to invert. The token guard's controls are the sensitivity proof.

## Gates (head `fda0702246`)

- **Derivation.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 49 families.
  - All 49 ran, and every recorded exit code is 0.
  - Three needed a second run, each for a reason outside the diff:
- `check:dual-build-cjs-loads` refused with exit 3 (PREREQUISITE NOT
MET, no `dist` for 9 packages). After the whole-workspace build it exits
0.
- `check:query-options-erasure` hit my 300 s cap on a contended box.
Rerun, it exits 0 in 363 s ("ratchet holds ... none new").
- `check:type-check-debt` refused with exit 3 (no built `objectql`).
After the build it exits 0 ("none above its recorded number").
- **Reconciliation.** `--ran`: 49 derived, 49 run, 0 NOT-MEASURED, 0
UNRUN.
- **Issue citations, diff mode.** `node
scripts/check-issue-citations.mjs` → 0 citations added.
- **Lint.** `pnpm lint` (`eslint . --no-inline-config`, repo-wide) →
exit 0, 2026-09-30T14:56:17Z to 15:00:01Z, at `fda0702246`.

## Acceptance notes

- **`origin/main` moved 14 commits past the base (to `9905e61ca2`), and
the branch was not merged forward.**
  - Of the files those commits touch, 0 are among this PR's 56.
- The derived family set on `9905e61ca2` with this diff applied (a
throwaway detached worktree, since removed) is the same 49 commands.
  - The merge-ref CI and the queue cover the joint tree.
- **Five nearby wordings still read as before, because they are not
sites:**
- `print-metadata-stats-zero-row.test.ts:101` says "That card" and
`:184` "that card's head". Both now follow a commit citation and mean
the card behind it.
- The `it` title at `generate-skill.e2e.test.ts:269` says both spec
`@example` blocks "still pass" `triggerPhrases`. That is stale since
`3c418c498`, but it is a test string and belongs to form D.
- **The objectstack-ai#14874 wording repeats in `bin/run.js:225` and three
`scripts/check-changeset-no-major.mjs` sites.** Both are outside this
surface; `scripts/**` is objectstack-ai#15809's lane.

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants