Skip to content

docs(cli): re-anchor the dead tracker citations in packages/cli/src to the commits that decided them, and the source-hashes header at its producer - #20656

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20594-cli-dead-citations
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20594-cli-dead-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20594
Clause-②: no

What changed

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

That is 313 comment sites on 304 lines in 64 files, covering 63 numbers: the census's 170 rewritable sites (of its 174), 142 more in test comments (which the census defers), and one site whose dead number is the second half of a slash-joined pair the citation grammar does not read (serve.ts:1173, #10943/#11157). Each rewritten line cites one of 62 distinct commits, except the six #15041 sites, which cite ADR-0104's 2026-09-05 addendum: that ADR records the maintainer ruling the lines describe, and the ruling allows the ADR to be cited instead of a commit.

Only comments changed in packages/cli/src, apart from the two string literals this stage declares (next section). Every touched file keeps its line count (319 lines out, 319 in, over 65 files), so no line citation into these files moves. Thirteen of the 319 lines held no dead site; each is the other half of a sentence that had to change: create.ts:326, doctor-organizations-message-spelling.test.ts:11, environments.test.ts:162, generate.ts:153, serve-cluster-host-resolution.test.ts:816, serve-host-fallback-base.test.ts:5, validate.ts:336, validate.ts:813, validate.ts:815, hook-body-lowering.test.ts:10, format.ts:1132, format.ts:1435 and i18n-extract.ts:34 (mostly 「that card」 to 「that commit」 once the antecedent became a commit).

No citation number is added. Every tracker number on an added line was already on the line it replaces. One PR number stands on an added line, and it was there before: doctor-organizations-message-spelling.test.ts:9 read 「PR #12463 (#12151) single-sourced」 and now reads 「Commit 27b6902 (PR #12463) single-sourced」, keeping the live PR as a convenience link beside the commit, as the ruling allows (#12463 is that commit's own PR). No code token moves (see the guard below).

Four dead comment sites are left on purpose, listed under "The sites left". Two more files outside packages/cli: a patch changeset for @objectstack/cli, and the shrink-only check:doc-authoring prose-id ledger (see Deviations).

The source-hashes producer and its 27 generated companions

The claim declares these as the only strings this stage moves, and the regeneration of the files they write.

Held files

packages/cli/src/utils/sdui-manifest.ts and sdui-manifest.test.ts stay at their base blobs (41c4549ffe and 3a242b54e0, equal at base and head), because PR #20589 edits them. They carry four citations (#4409 once, #20113 three times), and all four answer 200, so no dead site is held and there is no anchor to list for a follow-up.

Census: packages/cli, before and after

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

reading tree board whole-repo allocated-but-absent cli sites lines files numbers
before base 04b202e5cb, run 2026-09-29T12:35:39Z to 12:43:03Z enumerated, 185 pages, frontier #20642, 18,469 numbers 1,707 174 167 30 50
after head 6bb4d3b531, run 13:46:09Z to 13:54:25Z enumerated, 185 pages, frontier #20652, 18,479 numbers 1,510 4 4 3 2

The before count equals the card's 174 at f11b5f20a2. The 4 left are the deliberate sites below. The whole-repo drop is 197: this diff's 170 cli sites plus the 27 generated headers (3 in each of the nine packages; nothing else moved in any of them). The two merges of origin/main moved no count. An earlier after-run at d1e09a7eed (13:19:18Z to 13:29:41Z, frontier #20649) read the same 4 and 1,510; packages/cli and the 27 companions are byte-identical between the two heads. One more attempt at 6bb4d3b531 (13:35:15Z) exited 3, PREREQUISITE NOT MET, on a malformed board page, and measured nothing; the run in the table is its retry.

Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported extractCitations (whole-file and comment-prose projections) and classifyCitation over every .ts/.tsx file under packages/cli/src (298 files), against a board enumerated through the gate's own enumerateBoard. The lit controls #20594, #19123 and #20632 answered 200 and are on both boards; the dead controls #11671, #10514 and #14828 answered 404 and are on neither.

reading tree board citations dead src comment test comment src string test string
before, 12:43Z 04b202e5cb 185 pages, frontier #20642 3,029 368 174 142 4 48
after, 13:39Z 6bb4d3b531 185 pages, frontier #20650 2,715 54 4 0 2 48

Its src-comment column equals the census's 174 and 4, which is the control on the second instrument. The resolving citations (1,468 and 755 in comments, 50 and 53 as pull requests, 32 cross-repo) are the same in both readings, so no live citation was lost; the drop of 314 is exactly the dead sites removed (312 grammar-read comment sites and the two #11671 strings). The one slash-joined dead number the grammar never reads (#11157 in #10943/#11157) is gone too: a separate scan for dead #N tokens the grammar skips answers 1 before and 0 after.

Per-number table

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

number comment sites / files rewritten left strings kept anchor
#6217 12/8 (1 test) 12 0 0 2b641ddd4
#6238 2/1 (2 test) 2 0 2 c8d6f6e08
#6265 1/1 (1 test) 1 0 0 cfb549db8
#6268 6/2 (2 test) 6 0 1 68f5eccb1
#6293 2/2 (1 test) 2 0 0 c39a911ae
#6344 2/2 (1 test) 2 0 0 cfb549db8
#6345 21/6 (11 test) 21 0 4 e2798fab7
#6535 1/1 1 0 0 a92b1793c
#8692 5/2 (3 test) 5 0 3 712e185db
#10326 1/1 1 0 0 675ab574e
#10359 2/2 2 0 0 15b63e85a
#10398 1/1 (1 test) 1 0 0 0681a76b8
#10485 1/1 1 0 0 35ad101bc
#10499 1/1 1 0 0 6d441e41f
#10504 8/1 8 0 0 ff5733e03, 0d4bd93e7
#10514 16/2 (16 test) 16 0 2 5359a9b4c
#10763 1/1 (1 test) 1 0 0 c2b97c2a1
#10769 9/2 (6 test) 9 0 0 3d7deb700
#10908 10/3 (6 test) 10 0 5 9cc6777d3
#10909 2/1 2 0 0 5a90c56d1
#10917 1/1 1 0 0 7940de5e0
#10926 1/1 1 0 0 d173125fb
#10943 5/3 (2 test) 5 0 0 46d34ab7c
#10944 9/3 (6 test) 9 0 3 e598b1cbc
#10952 5/1 5 0 0 0d4bd93e7, ff5733e03
#10953 1/1 (1 test) 1 0 0 be7262e72
#10967 6/2 (6 test) 6 0 1 e4a71d418
#11022 1/1 (1 test) 1 0 0 21756b325
#11025 3/2 3 0 0 1c3a46f87
#11048 1/1 0 1 0 —
#11071 3/2 3 0 0 50fb191dc
#11157 15/4 (10 test) (1 slash-joined) 15 0 2 a4cb7817f
#11172 5/1 5 0 0 05181e8cc
#11174 2/1 (2 test) 2 0 1 ab23c67ab
#11221 3/1 (3 test) 3 0 1 e278a2970
#11331 3/2 0 3 0 —
#11671 1/1 1 0 0 (2 moved) 09b4f4e4e
#12125 11/3 11 0 0 79cf692b0
#12151 3/2 (3 test) 3 0 3 27b690272
#12162 2/1 (2 test) 2 0 0 c0f5e8f21
#12181 4/2 (3 test) 4 0 0 cf71d73f8
#12297 3/1 3 0 0 9fd45a952
#12943 2/1 (2 test) 2 0 0 090f2302e
#12961 1/1 1 0 0 901355c3b
#13109 2/1 2 0 0 8b236c826
#13193 6/2 (3 test) 6 0 0 faff497fd
#13218 1/1 1 0 0 c45d8e6b4
#13347 3/3 (2 test) 3 0 3 098a08ffa
#13651 12/7 (4 test) 12 0 0 ada3834ad
#14192 2/2 2 0 0 4d0d9445a
#14336 4/1 4 0 0 79c71d29d
#14397 1/1 1 0 1 957f7bb45
#14657 20/2 (7 test) 20 0 1 431979e67
#14667 1/1 1 0 0 dc7c226b9
#14824 3/1 3 0 0 cf6b67164
#14828 22/3 (7 test) 22 0 3 08706f0e0
#14829 9/3 (6 test) 9 0 3 ee370d318
#14902 1/1 1 0 0 61821e54c
#15040 6/3 (3 test) 6 0 2 8644d1d33
#15041 6/5 (4 test) 6 0 2 ADR-0104 (2026-09-05 addendum, landed as 932acc3)
#15045 2/2 (1 test) 2 0 0 288fe9c34
#16887 2/1 (2 test) 2 0 0 9cdffbe36
#17080 1/1 (1 test) 1 0 0 8b4890343
#17081 8/3 (4 test) 8 0 3 f721ef0ff, 24d622b94
#17883 10/3 (5 test) 10 0 3 b06b2db5c
total 317 313 4 49 62 distinct commits + ADR-0104

Every cited sha matches exactly one object (git rev-parse --disambiguate, count 1 for each of the 62), is a commit, has one parent, and is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 62). The checkout is not shallow (--is-shallow-repository false); the control leg 13a6cb4ad exits 0 and the negative control (this branch's own 47241dd80e, not on main) exits 1. ADR-0104's 2026-09-05 addendum landed as 932acc3df, which passes the same four checks. Where an earlier stage gave a number an anchor and the cli site describes the same decision, the same anchor is reused (17 numbers, #17081 for its application half only; for example e2798fab7 for #6345, 68f5eccb1 for #6268, 35ad101bc for #10485, 09b4f4e4e for #11671 and 61821e54c for #14902), so each number carries one anchor across the tree. Several numbers are the PR number of their own anchor commit (#6344, #10398, #14667, #16887), so the sha is the same object the number named.

Numbers with more than one anchor, by site:

Wordings to check, each true of its commit:

The sites left

No deciding commit (4 sites, all visible to the census):

String sites kept as tokens (49). 48 are test titles and test-code strings in 22 files. One is a non-test string: the os meta resync skip explanation at commands/meta/resync.ts:71, 「on installs from before #8692, the platform's own seeded defaults carry that same stamp」, which an operator reads (see Acceptance notes).

Mechanical guard: no code token moves, and exactly two string literals do

The check compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file at base 04b202e5cb against the working tree, over all 65 touched files in packages/cli/src, and lists EVERY differing token, not only the first. Controls mutate the head text in memory only, so nothing on disk moved for them.

  • Real run: 156,633 base tokens, token counts equal in every file, exactly 2 differing tokens, both StringLiteral: commands/i18n/extract.ts:227 (the help text) and utils/i18n-extract.ts:2294 (the header line). No other token in any file differs.
  • Comment-insertion control (serve.ts): still exactly those 2 (exit 1, no new difference).
  • Code-insertion positive control (a declaration in serve.ts): the count differs (17,815 to 17,820) and a third difference appears at token 0.
  • String positive control (one character added inside the first string literal past offset 2000 of serve.ts): a third difference appears, a StringLiteral at token 145.
  • The 27 generated companions: 2,940 base tokens, 0 differing tokens (their only change is a JSDoc line).

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

Changeset

This change ships bytes, so a patch changeset for @objectstack/cli is included, in PR #20632's form and level. Unlike stage 2's, it names the two strings, because 「Comments only」 would not be true here.

Measured on the built package: files[] is dist, README.md and CHANGELOG.md. After the build, the rewritten docblocks reach dist: 142 commit SHA citations in 39 of its .js / .d.ts files. For example storage-driver.ts:91's rewritten line 「(commit 68f5ecc). These are the」 is in both dist/utils/storage-driver.d.ts and .js, beside the unchanged next line of the same docblock 「runtime's declarations, not copies of them — in particular」 (the positive control); a negative control phrase appears nowhere. The new help text is in dist/commands/i18n/extract.js, the header literal with 09b4f4e4e is in dist/utils/i18n-extract.js, and #11671 appears nowhere in the package's dist.

Gates (head 6bb4d3b531)

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

os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 110s (1m50s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 12s · declare it in the PR body · pnpm --filter @objectstack/cli typecheck
os-verify-lock: VERDICT command-exit 1 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 150s (2m30s) · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 5s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/published-subpath-console.pin.test.ts test/published-subpath-hook-body.pin.test.ts
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 src/commands/generate-declared-column-default.pin.test.ts src/commands/generate-string-family-width.pin.test.ts src/commands/meta/delete-reset-carriers.test.ts 
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 30s · declare it in the PR body · pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/commands/validate-json-strict-exit.e2e.test.ts

The regeneration ran earlier against the same unlocked lock, on a closure build at 47241dd80e:

os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/cli...' --filter '@objectstack/platform-objects...' --filter '@objectstack/plugin-approvals...' --filter '@objectstack/plugin-audit...' --filter '@objectstack/plugin-security...' --filter '@objectstack/plugin-sharing...' --filter '@objectstack/plugin-webhooks...' --filter '@objectstack/service-messaging...' --filter '@objectstack/service-realtime...' --filter '@objectstack/service-storage...' build

The branch merged origin/main twice, as the dispatch orders (d1e09a7eed merging cd901d7a5f, and 6bb4d3b531 merging 0cb72cfc72); neither touched packages/cli, a translations directory or the prose-id ledger. After each merge: pnpm install --frozen-lockfile, then the whole workspace (turbo run build --filter='./packages/*' --filter='./packages/*/*', 71 tasks, 71 successful).

  • Tests (unit tier, then every touched file outside it):
    • vitest run --project unit: 234 files, 3,342 tests; 232 files and 3,337 tests pass, 5 tests in 2 untouched files fail on this host: test/published-subpath-console.pin.test.ts and test/published-subpath-hook-body.pin.test.ts compare a path under os.tmpdir() with the realpath the resolver answers, and on macOS /var is a symlink to /private/var. With TMPDIR set to its realpath the same two files pass, 29 of 29 (the fourth line above). Neither file is in this diff; the cli change is comments and two strings.
    • The unit tier holds 32 of the 36 touched test files. The other four ran by name: the three integration-tier files (--project integration: 3 files, 60 tests pass) and the nightly-tier validate-json-strict-exit.e2e.test.ts (OS_TEST_TIERS=nightly: 1 file, 7 tests pass). So every touched test file ran.
    • The producer's own tests and the companions' readers: test/i18n-extract-source-hashes.test.ts, test/i18n-extract-companion-orphan.test.ts and test/i18n-extract-generated-apps-leaf-provenance.test.ts (3 files, 25 tests); @objectstack/platform-objects's src/apps/translations (24 files, 430 tests); @objectstack/plugin-sharing's src/translations (3 files, 13 tests). All pass, at d71ab0e27e, whose packages/cli and companions are byte-identical to this head.
  • Typecheck: pnpm --filter @objectstack/cli typecheck exits 0. tsc --listFiles counts 298 src files under tsconfig.json, all 158 src test files among them, so every touched test file is type-checked; check:test-typecheck holds its ledger (3 files, 28 errors, 6 pinned signatures).
  • Lint: the repo-wide pnpm lint (eslint . --no-inline-config) exits 0 at 6bb4d3b531 (2026-09-29T13:44:40Z to 13:45:11Z). Not narrowed.
  • Citation judging: node scripts/check-issue-citations.mjs --base origin/main exits 0 after the second merge: 67 citations judged across 56 files (66 resolve, 1 cross-repo). These are the live numbers that stay on rewritten lines, 54 of them the [Decision] 把 #8765 的 source-hash sidecar 扩展到生成的 i18n bundle —— #9672 写明的升级条件已满足 #12069 and i18n: a source-string edit still leaves zh-CN / ja-JP / es-ES silently stale — and pinning en to the source makes the asymmetry sharper, not smaller (needs a maintainer decision) #8765 pair in the 27 headers. It defers *.test.ts, so the added-minus-removed count over the whole diff covers the rest: 0 numbers added.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at 6bb4d3b531 derived 76 families (68 before the prose-id ledger commit put a scripts/ path in the change set). All 76 ran, and --ran over a record carrying each exit code reads 「76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero).
    • 75 exit 0. One exits 1 for this host, not for this diff: pnpm check:bash32-floor runs its self-test first, and 7 of its 179 cases assert that each bash-4 probe is shell THIS host can parse; the only bash here is /bin/bash 3.2.57, which cannot. The gate's real-tree half, run alone (node scripts/check-bash32-floor.mjs), exits 0: 32 tracked shell files, 0 findings. The self-test half is NOT MEASURED here, reason: no bash 4+ on this host; CI's bash measures it. This diff touches no shell file.
    • Among them: check:doc-authoring (809 pinned sibling prose-id sites, no growth, no unrecorded burn-down), check:i18n (9 packages in sync), check:i18n-coverage (13 configs, none new), check:i18n-stale-fill (27 of 27 companions served, 0 stale), check:i18n-walk-parity, check:nul-bytes (9,283 files, no raw control bytes), check:published-files, check:cli-command-ids and check:issue-citations (self-test).
  • Artifact rosters: 35 of the 38 non-self-test roster rows exit 0 at 6bb4d3b531, check-changeset-fixed (its roster sits under .changeset/), check:error-code-casing, check:filter-alias-parity and check:authz-resolver (the four whose rosters share a directory with this diff) among them. The other three, check-closing-target-claim, check-partof-closing-keyword and check-single-claim-paths, need a pull request's context; they are run against this PR once it exists and reported on the card. The 17 checker-health-only rows were not run.

Hypotheses (measured first)

  • H0 holds. The filtered census answers 174 dead sites at 04b202e5cb (167 lines, 30 files, 50 numbers), equal to the card's count at f11b5f20a2: no drift.
  • H1 holds, with the listed exceptions. After the rewrite the filtered census answers 4, all for an unfound anchor: #11048 (an open support decision) and #11331 three times (an enforce leg never built). No site is held for an open PR: the two held files carry no dead citation. The claim's read and this stage's two reads of the open PRs' file lists (12:38:15Z, 7 open PRs; 13:58:56Z, 9 open PRs) found only PR fix(cli)!: the JSX page gate's console manifest fallback resolves through @objectstack/console/package.json, so a project without its own manifest gets full component checking #20589 in packages/cli/src and none touching a companion or the prose-id ledger. PR fix(platform-objects): re-translate the object leaves that contradict their current en source #20652, opened after the claim, edits packages/platform-objects's three LOCALE.objects.generated.ts bundles beside the companions: no file overlap.
  • H2 holds, by the token guard. A comment-stripped comparison of every touched file (the parser's leaf tokens, JSDoc excluded, every difference listed) finds exactly the two declared StringLiteral tokens and nothing else, and its code and string controls each add a difference. The emitted dist is not byte-identical, because docblocks and the two strings ship, which is why the changeset is patch.
  • H3 holds. git grep -n "#11671" -- '*.source-hashes.generated.ts': 0 hits at head, 27 at 04b202e5cb.

Acceptance notes

  • Form D, not touched here. 49 dead numbers stand inside string literals: 48 in test titles and test-code strings (22 files, 21 numbers), and one an operator reads: the os meta resync skip explanation at commands/meta/resync.ts:71, 「on installs from before [finding] os meta resync may silently skip every platform default permission set — the seeder never writes managed_by, and the tests stub the field default away #8692, the platform's own seeded defaults carry that same stamp」. The comments beside it (resync.ts:55 and :96) now cite 712e185db. Ruling D (no number, the lesson in words) is a string change outside this stage's two declared strings; the card already carries a form-D stage for the lane (ACCEPT 5888034755), and this string is its author-shown first.
  • #11671 outside this stage's surface. The number still stands in other lanes' files: the nine scripts/i18n-extract.config.ts docstrings (outside the census surface), six src/translations/index.ts files (plugin-approvals, plugin-audit, plugin-security, plugin-webhooks, service-realtime, service-storage), six sites in packages/platform-objects/src (source-hash.ts three times, setup.translation.ts, metadata-translations/index.ts, source-hash.test.ts), packages/cli/test/i18n-extract-source-hashes.test.ts:3, and 13 in scripts/** and .github/workflows/lint.yml. The anchor for all of them is 09b4f4e4e. Noted for those lanes' stages, not touched.
  • Outside the scope and the census surface. packages/cli outside src/** holds 242 dead citations: test/ 185, scripts/ 27, bin/ 10, vitest.config.ts 15, vitest-tiers.ts 2, vitest-tiers.fixtures.ts, tsconfig.test.json and test-typecheck-debt.json 1 each (whole-file projection, the before board). They stay for a later stage of this card.
  • A host-dependent pin. test/published-subpath-console.pin.test.ts and test/published-subpath-hook-body.pin.test.ts fail 5 tests on macOS, where os.tmpdir() is a symlink, and pass with a realpath TMPDIR. CI's Linux runners do not see it. Noted, not filed.
  • A hex colour in the whole-file reading. serve.ts:5621 holds the CSS colour #141417 in a string. The census blanks strings, so it never sees it; the supplementary whole-file projection reads it as a citation beyond the frontier (never-issued). It is not a citation; it is the second of the two src string sites left in the supplementary table, beside #8692.
  • The slash-joined grammar gap, again. CITATION_RE refuses a # preceded by /, so the second number of #A/#B is never judged. In packages/cli/src one such dead number stood (serve.ts:1173, rewritten here). The same shape PR docs(runtime): re-anchor the dead tracker citations in packages/runtime/src to the commits that decided them #20624 and PR docs(rest): re-anchor the dead tracker citations in packages/rest/src to the commits that decided them #20632 reported, for the grammar family PR docs(spec): re-anchor the dead tracker citations in data/ to the commits that decided them (stage 3) #20533 names.

Deviations

  • One file outside the claim's surface. scripts/doc-authoring-prose-id.baseline.json, the shrink-only ledger of check:doc-authoring's sibling-package prose-id leg, pinned the two #11671 string sites this stage removes, so the gate went red (「the prose-id baseline is STALE — pinned entries exceed the tree」) and prescribed regenerating it in the same PR. It was regenerated with its own command (node scripts/check-doc-authoring.mjs --census-ledger, which refuses to grow the ledger): 4 lines removed, the two #11671 pairs and nothing else. The claim's file surface did not name this file; it is the gate's own remedy for the two strings the claim does name.
  • One site beyond the census's read grammar (the slash-joined #11157) is rewritten, and thirteen more lines are the other half of a rewritten sentence (listed under What changed).
  • validate.ts:813-815 moved to the past tense, because the claim they carried was false before this change (see Wordings to check).
  • Anchor research for 64 of the 65 numbers ran in four read-only research subagents; every proposal was checked here against the commit's message or diff and every changed line was reviewed, and eight were reworded by hand (the four lines two subagents shared, 「that commit's to」, the kept PR link, and two companions).
  • Commit trailers are AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), and the pre-push trailer check passed on every push; the harness's attribution reminder asked for a model-named trailer, which AGENTS.md overrides. The two merge commits carry git's default message.

Generated by Claude Code

hotlong and others added 7 commits September 29, 2026 20:48
…cer, and state the flag's lesson in words

The generated-header line in renderSourceHashModule and the comment beside
previousSourceHashes cite commit 09b4f4e, which extended the source-hash
mechanism to the generated bundles, in place of a tracker number that no
longer resolves. The --source-hashes help text says what the companion
records instead of citing a number.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
…nchored header

Regenerated with node scripts/check-i18n-bundles.mjs --write, which runs each
package's documented os i18n extract command. Only line 8 of each file moves
(the provenance header now cites commit 09b4f4e); every hash entry is
byte-identical, and no other file in the nine packages changes.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
…o the commits that decided them

Every comment and docblock site in packages/cli/src (tests included) that
cited a tracker number now answering 404 cites the commit in this
repository's history that decided what the line describes, or ADR-0104's
2026-09-05 addendum where that record holds the decision, and says in its
own words what was decided. Four sites with no deciding commit stay as they
were. Comments only: 316 lines out, 316 in, over 64 files, and no code token
moves.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
…ce comments

The rewritten docblocks and the two strings ship in dist, so the package
publishes changed bytes.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
…prose-id baseline

The --source-hashes help text and the generated-header string no longer carry
a tracker number, so check:doc-authoring's shrink-only ledger over-pinned the
tree by those two pairs. Regenerated with its own command
(node scripts/check-doc-authoring.mjs --census-ledger); nothing else moves.

Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 10 package(s): @objectstack/cli, @objectstack/platform-objects, @objectstack/plugin-approvals, @objectstack/plugin-audit, @objectstack/plugin-security, @objectstack/plugin-sharing, @objectstack/plugin-webhooks, @objectstack/service-messaging, @objectstack/service-realtime, @objectstack/service-storage, touching 37 documentable anchor(s). ⚠️ 33 changed file(s) yielded no anchor (packages/cli/src/hook-body.ts, packages/cli/src/utils/artifact-packages.ts, packages/cli/src/utils/database-driver-flag.ts, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

42 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json c6b37cd08d0eb622d4f59b79effdc954f86dad9f.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 33 changed file(s) yielded no anchor (packages/cli/src/hook-body.ts, packages/cli/src/utils/artifact-packages.ts, packages/cli/src/utils/database-driver-flag.ts, …) — pages documenting those are invisible to this run
  • 2 anchor(s) matched too much of the corpus to be a work list: os lint (command, 30 pages), os validate (command, 53 pages)
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 51 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 c6b37cd08d0eb622d4f59b79effdc954f86dad9f → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json c6b37cd08d0eb622d4f59b79effdc954f86dad9f

⚠️ 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 c6b37cd08d0eb622d4f59b79effdc954f86dad9f → 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: 6bb4d3b53169c395bce11b0faf5ffc6e346656bb
Local-runs: none

Reviewed against card #20594 (body and all 11 comments, the stage-3 claim 5890374281 and the os-dev-report 5891928869 included), ruling C+D (5749154545 on #19123), PR #20656's body, its file list (one page of 94, pages 2 and 3 empty) and its net diff against main (94 diff --git sections, +360 / −350), and the check-runs on this head. Every GitHub read went through gh api (REST). Nothing was built, run or re-run; the only worktree reads were docs/adr/0104-field-runtime-value-shape-contract.md and scripts/check-doc-authoring.mjs as text.

Check-runs on this head, final read 2026-09-29T14:43:58Z: 35 names, newest run per name: 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke opt-in), 0 in progress, 0 failure. An earlier read at about 14:1xZ had 6 still running (Lint & Repo Gates, Type Check · workspace, Test Core 1/2/4/5 of 6); all six have since completed success. Part-of PR must not also close its card, Check Changeset, Lint & Repo Gates (which carries check:doc-authoring, check:i18n, check:i18n-stale-fill and check:issue-citations) are green.

① Derived judgments

Accept sets and public surface. No accept set moves: no schema, flag, exit code, error code, export, type or route changes anywhere in the diff. What reaches consumers is prose: the rewritten docblocks of @objectstack/cli (they ship in dist), the os i18n extract --source-hashes help text (author-shown), and line 8 of 27 generated LOCALE.source-hashes.generated.ts headers in nine other packages (a comment). Judged right, each named below.

(1) Comment-only apart from the two declared strings — YES. My own pairing of every removed line with its added line over the 92 changed source and generated files gives 346 pairs (the file list's 350 deletions less the 4 baseline-JSON lines; the 360 additions are those 346 plus the 14-line changeset). 343 pairs are lines that begin with // or * on both sides; 1 pair is a trailing comment whose code part is byte-identical (serve-cluster-host-resolution.test.ts:893, the '@objectstack/service-i18n', row); 2 pairs are the declared StringLiteral moves (commands/i18n/extract.ts:227, utils/i18n-extract.ts:2294). No identifier, error message, test assertion, describe title or code token moves; every describe(...) title that names a dead number stays as context, unchanged. The four named files were read whole: validate.ts 10 pairs, all // comment lines (six [#12125] markers to [commit 79cf692b0], the three ManifestSchema lines to the past tense); serve.ts 23 pairs, all comment lines (docblock and //); utils/i18n-extract.ts 8 pairs, 7 comment lines plus the producer literal; commands/i18n/extract.ts 1 pair, the help string. Because generate.ts, create.ts and init.ts are template-literal-heavy, their *-prefixed changed lines were checked against their hunk context: each sits in a source docblock (description: / defaultDir: / /** context lines, or module-level docblocks), none inside a backtick template, so no scaffold output changes.

(2) Anchors — all 62 shas plus the ADR addendum's 932acc3df exist in this repository, have one parent, and compare/SHA...main answers ahead with behind_by 0 for all 63 (62 in one batch; cf71d73f8's first compare died on a proxy connection reset, and its retry answers ahead=3479 behind=0). No sha stands on a removed line. Wording sample, each read against the commit's own subject and message body (49 sites, 30 files, 15 of them test files; the brief asked for 12 in 6):

No wrong or unsupported anchor found.

(3) Numbers. Over every removed and added line of the 92 source and generated files (the #N grammar plus the /#N slash-joined half): 63 numbers stand on removed lines and on no added line, and every one of the 63 answers 404 by gh api issues/N; 18 numbers stand on both sides (#3276 #5728 #5820 #6860 #8765 #10645 #11391 #11643 #11772 #12047 #12069 #12463 #13871 #14412 #15479 #15989 #17046 #17556), every one answers 200, each with equal removed and added counts, and per line pair no added line carries a number its removed line did not. No number answering 200 was removed or rewritten, and no number was added. The slash-joined #11157 at serve.ts:1173 is among the 63 removed.

(4) Line counts. From the file list: all 92 modified files have additions equal to deletions (65 under packages/cli/src, 319/319; 27 generated companions, 1/1 each); the changeset is a new 14-line file; the baseline JSON is 0/−4. Right.

(7) The declared step past comment-only. The two StringLiteral moves are the only non-comment changes in packages/cli/src (my pairing finds no third). Each wording is true of what it cites: 09b4f4e4e's message reads "Extends the #8765 Option B source-hash mechanism to the generated bundles, per maintainer ruling #12069 Option A" and its diff added the producer, the flag and the first three companions, so the header "bundles (commit 09b4f4e, maintainer ruling #12069 Option A, extending #8765 Option B)" names the deciding commit and keeps two live numbers (#12069 and #8765 both 200); the help text now says the companion "records which source revision each generated leaf is still a copy of, so a stale fill can be told from a translation", which is the commit's own description ("per leaf, the digest of the source revision that leaf is still a byte copy of"), with no number (form D). Right.

(8) The 27 companions. Every one of the 27 diffs is a single hunk @@ -5,7 +5,7 @@ with exactly one removed and one added line, and the removed line and the added line are the same string in all 27. With three context lines before the change, the changed line is line 8, confirmed by reading lines 1 to 12 of three files at this head (platform-objects/.../zh-CN, service-storage/.../es-ES, plugin-audit/.../ja-JP): line 8 is * bundles (commit 09b4f4e4e, maintainer ruling #12069 Option A, extending #8765 Option B)., byte-identical to the producer's literal at utils/i18n-extract.ts:2294 at this head. Every hash entry and every other line is untouched by construction (one-line hunks), and the file list shows no other file in the nine packages. The new header is the producer's output. Right.

(9) The prose-id baseline. The 4 removed lines are the whole "packages/cli/src/commands/i18n/extract.ts": { "#11671": 1 } block (3 lines) and the "#11671": 1, line under "packages/cli/src/utils/i18n-extract.ts"; nothing is added. scripts/check-doc-authoring.mjs (read as text) says a shrink is a stale baseline to regenerate with --census-ledger "in the same PR", and --census-ledger refuses to print a baseline that grows any pair. Carrying the shrink here is right: a separate PR would leave main red between the two merges, and the gate's own instruction is to carry it. Lint & Repo Gates is green on this head.

② Semver level

.changeset/cli-provenance-anchors.md: '@objectstack/cli': patch, naming the docblock re-anchoring and the two moved strings, and stating no command, flag, exit code, error code, type, export or runtime behaviour changes. Right. The package ships bytes (docblocks reach dist; the help string and the header literal are runtime strings), which is why patch rather than skip-changeset, in the form stages 1 and 2 used; no accept set moves, so nothing higher. The nine packages whose generated headers moved carry no changeset: the header is a leading comment block in a generated .ts and the dev's own build measurement finds none of the nine dists holding it; even if a bundler kept it, a comment byte with no consumer surface would not owe a bump, and the precedent stages of #20596 left these copies alone without one. Clause-②: line: Clause-②: no, present on the PR body's second line, and right: prose only, no accept set widens or narrows.

③ Boundary flags

  • Dev flags / open_questions: the report's open_questions is []. Its deviations are each answered: the baseline JSON outside the claim's surface (right, ①(9)); the two held files sdui-manifest.ts / sdui-manifest.test.ts (they carry only 200-answering numbers, #4409 and #20113; PR fix(cli)!: the JSX page gate's console manifest fallback resolves through @objectstack/console/package.json, so a project without its own manifest gets full component checking #20589 merged at 14:02:45Z, after this head, so nothing is owed and no anchor list is needed); the slash-joined site and the 13 companion lines (comment prose, counted in the 319); validate.ts:813-815 to the past tense (right, ①(2)); check:bash32-floor's self-test NOT MEASURED on the dev's host (measured by CI's Lint & Repo Gates, green).
  • (6) Part of #20594, no closing keyword: right. The PR body's first line is Part of #20594; the card stays open for types and the rest of the lane and its form-D stage; the Part-of PR must not also close its card gate is green.
  • (6) The four sites left as they are. init.ts:267 ([Decision] With packages: [] shipped, the engines.pnpm >=10.15 floor is now what refuses pnpm 10.0–10.4 — measured: they install with a lower floor, but never read the scaffold's build allowlist #11048) and plugin/publish.ts:118, utils/osplugin.ts:21, utils/osplugin.ts:49 (manifest.integrity declares per-file artifact digests the spec says the runtime re-verifies at unpack — nothing computes them and nothing checks them #11331) read at this head still cite their numbers, both 404. REST commit search: 11048 is named by exactly one commit, 568de194e, which files it "unassigned" as a support decision; 11331 by b60f48b52 and f89812e4d, which both state the unpack-time enforce leg is not built. No deciding commit exists for either, so form C has nothing to cite, and leaving them matches this lane's own accepted stages (ACCEPT 5888034755 and 5890033713 left #8641, #14365, #14026 and three runtime sites for the same reason). Escalated, not blocking: the spec lane's stage 1 (21ab41041, the tree the card names as the form to follow) treated #11331 differently — it dropped the pointer at both manifest.zod.ts sites and wrote "The enforce leg is unbuilt" / "is unbuilt, and belongs to that future runtime loader" (its per-number table: "none: an open tracked-on pointer, and the enforce leg is unbuilt. The lines now say so — dropped"). So the three cli #11331 sites and init.ts:267 could be reworded number-free the same way, which is the card's form-D stage for this lane to take, or the seat's to record as the lane's convention. The PR body lists all four sites, so nothing is hidden.
  • Out-of-scope findings (carried, not blocking): 49 dead numbers inside string literals in packages/cli/src stay as tokens, one of them operator-read (meta/resync.ts:71, #8692) — the card's form-D stage; #11671 outside this stage's surface (other lanes' i18n-extract.config.ts, translations/index.ts, platform-objects/src, scripts/**) with 09b4f4e4e as its anchor; packages/cli outside src/** (242 sites) for a later stage; the macOS os.tmpdir() symlink pins are not in this diff. The Docs Drift Check comment is advisory and names no page this prose-only change falsifies.
  • Sampling stated: all 94 file-list rows read; the whole diff scanned mechanically for line pairing, numbers and shas; the four named files' diffs read whole; generate.ts, create.ts, init.ts hunks read in context; 49 rewritten sites in 30 files read against their anchors' subjects and message bodies (17 anchors also by their file lists); three generated companions and the producer read at the head; 63 removed and 18 kept numbers probed; 63 shas probed for existence, parent count and compare...main.

Implemented-by: claude/issue-20594-cli-dead-citations
Reviewed-by: local_1d2a197c-c20e-4e90-9be8-413d4d432289

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 14:51
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 6bff748 Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20594-cli-dead-citations branch September 29, 2026 15:24
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…rc to the commits that decided them (objectstack-ai#20673)

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

## What changed

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

That is **83 comment sites on 83 lines in 17 files, covering 12
numbers**: the census's 52 (all of them) and 31 more in test comments,
which the census defers. Each rewritten line cites one of **12 distinct
commits**. No ADR or ruling-record file records any of these twelve
decisions, so every anchor is a commit.

Only comments changed. Every touched file keeps its line count (94 lines
out, 94 in, over 17 files), so no line citation into these files moves.
Eleven of the 94 lines held no dead site; each is the other half of a
sentence that had to change: `thrown-http-error.ts:316-319`,
`node.ts:1103`, `:1429`, `:1447`, `:1452`, `:1476`, and
`node.test.ts:449`, `:2420` (see "Wordings to check").

**No citation number is added.** Over the 94 line pairs, every tracker
number on an added line was already on the line it replaces (per-pair
check: 0 added), and no PR number stands on an added line. No code token
moves (see the guard below). No site was left: no dead comment site in
scope lacked a deciding commit, and no open PR touches
`packages/types/src`.

One file outside `packages/types/src`: a `patch` changeset for
`@objectstack/types`, in PR objectstack-ai#20632's form and level.

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

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

| reading | tree | board | whole-repo `allocated-but-absent` | types
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `6bff748bbd`, run 2026-09-29T15:31:59Z to 15:41:36Z |
enumerated, 185 pages, frontier objectstack-ai#20663, 18,490 numbers | 1,510 | **52**
| 52 | 7 | 11 |
| after | head `686a4c60cb`, run 2026-09-29T16:04:17Z to 16:12:39Z |
enumerated, 185 pages, frontier objectstack-ai#20671, 18,498 numbers | 1,458 | **0** |
0 | 0 | 0 |

The before count equals the card's 52 at `f11b5f20a2`: no drift. The
whole-repo drop is 52, exactly this diff's 52 sites, and the whole-repo
resolving count rises by one (32,882 to 32,883): the live objectstack-ai#12751 that
`index.ts:4` now spells so the grammar reads it. Both runs read this
worktree, the base and then the base plus this one commit, so no other
change entered either count.

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

| reading | tree | board | citations | dead | src comment | test comment
| src string | test string |
|---|---|---|---|---|---|---|---|---|
| before, 15:34Z | `6bff748bbd` | probed, frontier objectstack-ai#20661 | 622 |
**101** | 52 | 31 | 1 | 17 |
| after, 16:04Z | `686a4c60cb` | probed, frontier objectstack-ai#20668 | 540 | **18**
| 0 | 0 | 1 | 17 |

Its src-comment column equals the census's 52, site for site (the two
site lists are identical), which is the control on the second
instrument. The drop of 82 citations is the 83 dead sites removed plus
one live number the grammar now reads: `index.ts:4` spelled
`[objectstack-ai#11343/objectstack-ai#12751]`, whose second half the grammar skips after a slash,
and now reads `[commit c0714eb / objectstack-ai#12751]` like its module doc, so the
live objectstack-ai#12751 is judged (resolving src comments 273 to 274). Resolving
pull requests (20), cross-repo citations (14) and the other resolving
counts are unchanged. A separate scan for slash-joined pairs in
`packages/types/src` found six (`objectstack-ai#11343/objectstack-ai#12751`, `objectstack-ai#3878/objectstack-ai#3899`,
`objectstack-ai#7525/objectstack-ai#8016`, `objectstack-ai#4728/objectstack-ai#4825`, `objectstack-ai#8621/objectstack-ai#8622`, `objectstack-ai#5352/objectstack-ai#5367`); every
second half answers 200, so no dead number hid behind a slash here.

## Per-number table

Sites and files are the dead comment sites in scope at the base, test
sites counted in brackets. `strings kept` counts string-literal sites,
which are tokens and stay as they were. Every anchor was read in its
message or its diff, not only its subject: it is the commit that made
the change the line describes.

| number | comment sites / files | rewritten | strings kept | anchor |
|---|---|---|---|---|
| `objectstack-ai#8824` | 1/1 (1 test) | 1 | 0 | `8ac232306` |
| `objectstack-ai#9934` | 5/4 (2 test) | 5 | 2 | `79c46da90` |
| `objectstack-ai#10943` | 10/2 (4 test) | 10 | 2 | `46d34ab7c` |
| `objectstack-ai#10944` | 1/1 | 1 | 0 | `e598b1cbc` |
| `objectstack-ai#11343` | 3/3 (1 test) | 3 | 1 | `c0714eb5d` |
| `objectstack-ai#12281` | 1/1 | 1 | 0 | `0783d7b80` |
| `objectstack-ai#13197` | 5/2 (2 test) | 5 | 2 | `56c093c4d` |
| `objectstack-ai#13279` | 8/5 (2 test) | 8 | 0 | `6a180e42d` |
| `objectstack-ai#13324` | 15/3 (7 test) | 15 | 5 | `4cda78c9b` |
| `objectstack-ai#15044` | 8/2 (3 test) | 8 | 1 | `088f761e5` |
| `objectstack-ai#15045` | 21/2 (8 test) | 21 | 1 | `288fe9c34` |
| `objectstack-ai#16657` | 5/2 (1 test) | 5 | 4 | `5a95b0e93` |
| **total** | **83** | **83** | **18** | **12 distinct commits** |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 12), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 12). The checkout is not shallow (`--is-shallow-repository`
false); the control leg `f5a9bc2f3` (2026-08-10, older than the oldest
anchor, `8ac232306` of 2026-08-15) exits 0 and the negative control
(this branch's own `686a4c60cb`, not on `main`) exits 1.

**Anchors reused from earlier stages**, so each number carries one
anchor across the tree: `79c46da90` for objectstack-ai#9934 (stages 1 and 2, the spec
lane), `46d34ab7c` for objectstack-ai#10943, `e598b1cbc` for objectstack-ai#10944 and `288fe9c34`
for objectstack-ai#15045 (stage 3), `0783d7b80` for objectstack-ai#12281 (stage 1), `56c093c4d` for
objectstack-ai#13197 (stage 2, the spec lane), `6a180e42d` for objectstack-ai#13279 (stages 1 and 2,
`plugin-sharing`) and `c0714eb5d` for objectstack-ai#11343 (`plugin-auth`).

**New anchors, and how each was found:**
- `objectstack-ai#8824` → `8ac232306`: objectstack-ai#8824 is that commit's own PR number (its
subject ends `(objectstack-ai#8824)`), so the sha is the object the number named.
`error-leak.test.ts:180` read 「PR objectstack-ai#8824 corrected the」 and now reads
「Commit 8ac2323 corrected the」.
- `objectstack-ai#13324` → `4cda78c9b`: its subject does not name the number, but its
changeset heading does (「require a missing-table error to name the table
that was READ (objectstack-ai#13324)」), and its diff adds `readObject` and every
`[objectstack-ai#13324]` marker this module carries. It landed in
`packages/metadata/src/utils/schema-sync-errors.ts`, the file
`6a180e42d` then moved here (a rename at 86 percent similarity).
- `objectstack-ai#15044` → `088f761e5`: 「Part of objectstack-ai#15044」, the only commit whose
message names the number; it made the objectstack-ai#13330 succeeding leg recognise
the package root by the name the declaration promises, and added the
`BOUNDARY` pin at `node.test.ts:1863` that `:2175` and `:2419` point at.
- `objectstack-ai#16657` → `5a95b0e93`: it added `operatorFacingErrorText`,
`DECLARED_DATABASE_FAULT_CODE` and the raw-path fragment, and its
message calls itself the fourth prose round on objectstack-ai#16657.

## Wordings to check

- **A stale future tense, corrected by its anchor.**
`thrown-http-error.ts:315-320` said objectstack-ai#12281 「is a separate card with its
own measurement-first step, so nothing here applies it; this function is
the shape it will read」. `a81aa9dd5` wrote that on 2026-08-29;
`0783d7b80` landed the next day and its message says 「the door now reads
`serverFaultProvenance`」. Citing the commit in the future tense would
contradict itself, so the six lines now read 「Commit 0783d7b — the
prose axis of the same 2026-08-27 ruling — reads the `'declared'` limb
of this same function … It landed separately, after its own
measurement-first step, so nothing here applies it; this function is the
shape it reads rather than a second copy it would have had to grow.」
- **An open question named by a number that had already landed.**
`node.ts:1447-1452` called where a relative specifier should resolve
from 「an open policy question owned by objectstack-ai#10944」 and ended with 「Answering
half of another card's undecided question」. `e598b1cbc` (objectstack-ai#10944's
landing) had merged 40 minutes before `46d34ab7c` wrote those lines, and
it refuses the relative spelling. The lines now read 「the policy
question commit e598b1c settled for `serve` (it refuses a relative
`plugins: [...]` entry rather than silently re-basing it)」 and
「Answering half of another change's question」.
- **「the card」 once the antecedent became a commit.**
`node.ts:1102-1103` 「objectstack-ai#15045 is the card about telling an operator which
one was measured」 now reads 「commit 288fe9c is the change that tells
an operator which one was measured」. `node.ts:1476` 「the second
verification axis the card holds open」 now reads 「the second
verification axis that commit left unbuilt」, which is what `288fe9c34`'s
message says (「deliberately not built here」). `node.test.ts:449` 「The
card's own 4-row matrix」 now reads 「The 4-row matrix behind that
commit」.
- **Headings that named a defect by its number now say so.**
`node.test.ts:1566` reads 「Fixed by commit 088f761: the SUCCEEDING leg
recognised the package by the DECLARATION KEY」 and `:1881` reads
「Reworded by commit 288fe9c: the location sub-case REFUSES correctly
and EXPLAINED itself wrongly」 (`288fe9c34` changed the wording and kept
the refusal). The dash-rule headings trim trailing dashes:
`node.ts:1381`, `node.test.ts:1566`.
- **A quoted triage.** `node.test.ts:2160` quoted 「objectstack-ai#15045's triage」; it
now reads 「quoted from the triage commit 288fe9c landed」. That
commit's changeset records the same decision in its own words: the key
stays the expectation 「because widening it would accept any directory
sitting at the key and trade a wrong REMEDY for a wrong LOAD」.
`node.test.ts:2053` said objectstack-ai#15045 「asked for this sentence」; it now says
`288fe9c34` 「wrote this sentence」, and that commit's own test comment
says the card asked for it.
- **A defect that proved a point.**
`driver-error-classification.callers.test.ts:24-25` said the omission is
the shape 「objectstack-ai#13324 existed to close」 and that prose 「is exactly what
objectstack-ai#13324 proved insufficient」; it now reads 「the … shape commit 4cda78c
closed」 and 「prose is exactly what that commit's defect proved
insufficient」.
- **「pre-#N」 spellings** (the card's control sites
`driver-error-classification.ts:608` and `node.ts:1428`, plus
`callers.test.ts:20` and `node.test.ts:594`) now say 「before commit X」.

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file
at base `6bff748bbd` against the working tree at `686a4c60cb`, over all
17 touched files, and lists EVERY differing token, not only the first.
Controls mutate the head text in memory only, so nothing on disk moved
for them.

- Real run: 31,911 base tokens, token counts equal in every file, **0
differing tokens** (exit 0).
- Comment-insertion control (a new line comment in `node.ts`): 0
differing tokens (exit 0).
- Code-insertion positive control (a declaration prepended to
`node.ts`): the count differs and a difference appears at token 0 (exit
1).
- String positive control (one character changed inside the kept
`undeclaredMessage` literal at `node.ts:383`): exactly 1 differing
token, a `StringLiteral` at token 672 (exit 1).

So H2 holds by the token guard. The emitted `dist` is not
byte-identical, because the docblocks ship, which is why the changeset
is `patch`. Line balance: every touched file is +N/−N and every line
count is equal at base and head (17 files). A raw scan of the 18 changed
files for control bytes finds none (its positive control, a scratch file
holding a U+0001 byte, matches).

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/types`
is included, in PR objectstack-ai#20632's form and level: 「Comments only: no error
code, refusal text, type, export or runtime behaviour changes.」

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After the build, the rewritten docblocks reach `dist`:
`0783d7b80`, `79c46da90`, `5a95b0e93` and `c0714eb5d` are in
`dist/index.d.ts` and `index.d.mts`, `4cda78c9b` in all four `index`
files, `6a180e42d` in `index.js` and `index.mjs`, and `46d34ab7c` and
`288fe9c34` in `dist/node.d.ts` and `node.d.mts`. The positive control,
the unchanged sentence 「sanitisation REGIME is the condition, not one of
its two outcomes」 of the `0783d7b80` docblock, is in `dist/index.d.ts`
beside it; a negative control phrase appears nowhere. Of the twelve dead
numbers, only objectstack-ai#10943 remains in `dist`, twice, and both are the kept
operator-facing string at `node.ts:383` (see Acceptance notes).

## Gates (head `686a4c60cb`)

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

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

`origin/main` did not move after the branch was cut: `git merge
origin/main` answered 「Already up to date」 at `6bff748bbd`, so the base
is the merge base and nothing needed rebuilding. `origin/main` has since
moved to `6c11ef9ecb` (PR objectstack-ai#20663: two pages under
`content/docs/automation`, read 16:13Z). It touches nothing this diff or
its gates read, so the branch was not merged again and every reading
here stays at `686a4c60cb`.

- **Build:** the dependency closure (`@objectstack/types...`: `spec`
then `types`) and then the whole workspace (71 tasks, 71 successful).
`check-dts-emitted` finds 2 of 2 declared declaration files. The build
left the tree clean.
- **Tests:** `--project local`: 22 files, 685 tests pass. `--project
repo`: 1 file (`driver-error-classification.callers.test.ts`, touched
here), 7 tests pass. 22 + 1 is all 23 test files in the package, so
every touched test file ran.
- **Typecheck:** `pnpm --filter @objectstack/types typecheck` exits 0.
`tsc --listFiles` counts 42 `src` files under `tsconfig.json`, all 23
test files among them, so every touched test file is type-checked.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `686a4c60cb` (2026-09-29T16:01:23Z to 16:02:09Z). Not
narrowed.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0: 5 citations judged across 7 files (4 resolve, 1
cross-repo). These are the live numbers that stay on rewritten non-test
lines. It defers `*.test.ts`, so the per-pair count over the whole diff
covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `686a4c60cb` derived 61
families. All 61 ran and exit 0, and `--ran` over a record carrying each
exit code reads 「61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived
zero). Among them: `check:doc-authoring`, `check:nul-bytes`,
`check:issue-citations` (self-test), `check:published-files`,
`check:dts-closure`, `check:dual-build-cjs-loads`,
`check:type-check-coverage` and `check:type-check-debt`.
- **Artifact rosters:** all 36 non-self-test roster rows that run
without a pull request exit 0 at `686a4c60cb`, the four whose rosters
share a directory with this diff among them (`check-changeset-fixed`,
`check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`). The other three,
`check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths`, need a pull request's context; they are run
against this PR once it exists and reported on the card. The 18
checker-health-only rows were not run.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 52 dead sites at
`6bff748bbd` (52 lines, 7 files, 11 numbers), equal to the card's count
at `f11b5f20a2`: no drift.
- **H1 holds, with no exceptions.** After the rewrite the filtered
census answers 0 dead sites for `packages/types/`. No site is left for
an open PR or an unfound anchor: the claim's read and this stage's read
of the open PRs' file lists (15:40:08Z, 7 open PRs) found none touching
`packages/types/src` (the Version Packages PR touches only
`packages/types/CHANGELOG.md` and `package.json`). A second read before
this PR was opened (16:13:18Z, 11 open PRs) found the same.
- **H2 holds, by the token guard** above: 0 differing parser leaf tokens
over the 17 touched files, with the comment control at 0 and the code
and string controls each turning red.

## Acceptance notes

- **Form D, not touched here.** 18 dead numbers stand inside string
literals: 17 in test titles and test-code strings (8 files, 8 numbers),
and one an operator reads. That one is the `undeclaredMessage` note at
`node.ts:383`, 「a caller that needs its own resolution passes `{
fallbackImport: (s) => import(s) }`, objectstack-ai#10943)」, printed when the host
importer's undeclared fallback fails without a caller base. It is also
the only dead number left in `dist`. The comments around it
(`node.ts:368`, `:1381`, `:1428`) now cite `46d34ab7c`. Ruling D (no
number, the lesson in words) is a string change outside this
comment-only stage; the card already carries a form-D stage for the lane
(ACCEPT 5888034755), and this string is its author-shown first in
`packages/types`. A second one is a remedy an author reads: the `REMEDY`
text at `callers.test.ts:281`, which that gate test prints for any call
site that omits `readObject` (「Without it the predicate returns the
pre-objectstack-ai#13324 WIDE verdict」).
- **Outside the scope and the census surface.** `packages/types` outside
`src/**` holds one dead citation: `vitest.config.ts:25` cites objectstack-ai#17853
(404), the same number PR objectstack-ai#20624 and PR objectstack-ai#20632 reported in their
packages' `vitest.config.ts`. The six other citations outside `src/**`
(`CHANGELOG.md` excluded) resolve. It stays for a later stage of this
card.

## Deviations

- Eleven lines beyond the dead sites are the other half of a rewritten
sentence (listed under What changed), and the six lines at
`thrown-http-error.ts:315-320` move from the future tense to the
present, because the claim they carried stopped being true when
`0783d7b80` landed (see Wordings to check).
- The anchors were researched in this session, not delegated; every one
was checked against its commit's message or diff.
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push; the harness's attribution reminder asked for a model-named
trailer and a different PR footer, which AGENTS.md overrides.

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

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant