You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
docs-audit: a quoted client: on a row whose route: could not be read is absent from the clientsDeclared denominator #10636
Filed unassigned by the os-dev seat implementing #10500 (session session_01DdCnBGcHeufjrq7drTD3wt). Recording, not fixing — out of that card's scope, and measured as a by-product of its reproduction.
What
#10500 closes the silence for a route: / client: value in no quote at all: it is now counted and named as unread. A narrower residue survives it.
clientsDeclared in parseLedgerSource (scripts/docs-audit/affected-docs.mjs) is computed as:
read 3 of 4 declared `route:` value(s) and 3 of 3 declared `client:` value(s); declined 1: line 101: route: I18N_BASE + '/plurals'
The route: half is exact — 3 of 4, and the unread one is named. The client: half says 3 of 3 on a file that declares fourclient: values.
Why this is a residue and not the same defect
The ROW does end up in a verdict: the file is flagged PARTIAL read and the unreadable row is named with its line, so nothing here is the silence #9896 and #10500 were about — a reader is told this ledger was not fully read and where. What is inexact is a sub-count: the client: denominator understates what the file declares, and understating a denominator is the direction that makes a partial read look more complete than it is.
Direction, not a prescription
Leave it. The row-level verdict already fires, and the client: count is only ever consulted alongside it. Cost: N of N can read as complete on a file that is not.
Report the orphan explicitly — a client: matched in code position that no row window claimed is itself the interesting event, and naming it is strictly more informative than moving a number.
(2) or (3) look like the shape worth costing; (3) has the property #9896 argued for — key on the spelling that was not read rather than on a shortfall, because a count comparison is blind to a mis-bound row.
⭐ Unlocked: PR #10637 (#10500) merged, so that file is free and the unreadableIn counter this residue sits beside is on main.
⚠️ The filing seat scoped this precisely and the PR must keep that framing: it is "a sub-count residue, not the silence this card is about" — the row itself already lands in a verdict after #10500. ⛔ Do not write it up as a live false green.
⛔ Governed surface out of scope (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/**) and the #9465 epic fence (.changeset/ tooling, cut-rc.yml, release.yml, root package.json). ⚠️No formatter of record (no format/prettier script, prettier not in devDeps, no .prettierrc/.editorconfig) — ⛔ do not run prettier --write (#10622).
⭐ Changeset: AGENTS.md:943 — "Pure bug fixes do not require a changeset"; a scripts/**-only diff that publishes nothing takes skip-changeset via the additive POST /labels (⛔ never a whole-set PUT — see #5533).
{
"issue": 10636,
"status": "done",
"branch": "claude/issue-10636-clients-declared-residue",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10674",
"premise_still_valid": true,
"summary": "The `clientsDeclared` denominator in `parseLedgerSource` was two row-relative terms, so a correctly single-quoted `client:` on a row whose `route:` could not be read was in neither. It is now swept file-wide over the same code-only, type-declaration-excluded source #10500 built, minus the values a row window already claimed, and each unclaimed value is pushed to `declined` so it is NAMED with its own line rather than only bumping a number. NUMERATOR unchanged (`clientRows` = values read and bound to a row, `rows` byte for byte identical); DENOMINATOR now = every `client:` the file declares in code position. The two stay comparable because every unit added to the denominator arrives as a named 'declared, not read' event the numerator was never going to count — ablation C shows what dropping that guard costs (real tree `221 of 442`). The card's framing is kept: the PR says sub-count residue, not a live false green. Slightly wider than the card, same defect class: the sweep keys on 'no row claimed it' rather than on the quote, so it also counts a declined-spelling `client:` outside every row window (before the first assembled row, or in a truncated-window gap) — pinned by its own fixture and called out in the PR body.",
"tests": "All at HEAD 19c982a268 (final commit; merge base 72d75ebb55).\n\nRE-DERIVED MEASUREMENT (card measured on ba2d8d4730; reproduces verbatim on today's tree). Mutation: the card's row appended to packages/services/service-i18n/src/i18n-route-ledger.ts; confirmed on disk by anchor count before each run (`client: '` 3 -> 4, I18N_BASE 0 -> 1) and after restore (4 -> 3, 1 -> 0).\n BEFORE (origin/main code, mutated tree): \"read 3 of 4 declared `route:` value(s) and 3 of 3 declared `client:` value(s); declined 1: line 101: route: I18N_BASE + '/plurals'\" — totals 259 of 260 / 221 of 221.\n AFTER (this branch, same tree): \"read 3 of 4 declared `route:` value(s) and 3 of 4 declared `client:` value(s); declined 2: line 101: client: 'i18n.getPlurals' (no row read it), line 101: route: I18N_BASE + '/plurals'\" — totals 259 of 260 / 221 of 222.\n RESTORED tree, this branch: 259 of 259 / 221 of 221, exit 0, no verdict. The real-tree number does NOT move, and should not: all seven ledgers are wholly single-quoted and every row was assembled (259 of 259), so no `client:` can be orphaned. The seven were also re-measured under each ablation (below) where they DO move.\n\nREJECT SIDE, asserted positively: all seven live ledgers spell the member `client?: string;`, which the scan's /\\b(route|client)\\s*:/ never matches — the live tree cannot exercise it. The fixture therefore uses the hostile spelling, a literal-union TYPE member that opens with the quote the counter reads: `export interface Entry { route: string; client: 'i18n.getLocales' | 'i18n.getPlurals' }`. The quote test alone would swallow it; typeDeclRegions is what rejects it. Pinned twice (no line-1 entry in the declined list; deleting the interface changes no count), and ablation B reds exactly that assertion.\n\nABLATIONS — each mutation confirmed on disk by anchor count (an editor exit code proves nothing), each restored and re-run green (308 pass) before the next:\n A remove the unclaimedClientsIn sweep call sites 2->1 11 red (predicted 9 by NAME before running; returned exactly those 9, then 11 after the extra orphanTail fixture was added below it)\n B remove the typeDeclRegions skip skip line 1->0 13 red (incl. the literal-union member and #10500's `route: string;` pins; live tree 259 of 266)\n C remove the !claimed.has(...) guard guard 1->0 22 red (double counting; live tree 221 of 442 — the exact 'denominator widened past what the numerator can ever count' failure)\n D remove the codeOnly mask mask 1->0 3 red (prose/string payloads become declarations; live tree 259 of 260)\nNo shortfall against prediction (A was the only one predicted numerically, and it matched by name).\n\nSELF-TEST: 291 cases on origin/main -> 308 on this branch (+17).\n\nGATE UNION: `node scripts/pm/dispatch-gates.mjs`, no paths passed — exit 0, NOT empty: change set 1 path, three-dot vs merge base 72d75ebb5; 4 families matched. Hand-cross-checked: `grep -rln docs-audit .github/workflows/` names lint.yml and docs-drift-check.yml only, both covered, plus the scripts/** family. All run at 19c982a268 through scripts/pm/os-verify-lock.sh, exit codes captured by redirect-then-capture (never after a pipe):\n os-verify-lock: VERDICT command-exit 0 · held the lock 2s · waited 199s (3m19s)\n check-affected-docs.mjs [docs-drift-check.yml] ✓ affected-docs self-test: 308 cases pass.\n check:docs-audit-scope [lint.yml] ✓ affected-docs self-test: 308 cases pass. / ✓ check-audit-scope self-test: 24 cases pass. / ✓ docs-accuracy-audit scope is in sync with content/docs/: 189 doc(s).\n check-cross-package-test-inputs.mjs [ci/lint] OK: 13 package(s) read outside themselves, all declared.\n check-nul-bytes.mjs OK (scanned 6212 text file(s); no raw ASCII control bytes) + hand self-scan of the edited file: 0 hits.\nNo pnpm install was needed or run: the gate union is node-builtin-only, which is the property docs-drift-check.yml depends on. No dependency import was added.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #10683: [finding] the ledger ROW recognizer reads raw text, so a `route: 'X'` written in a comment becomes a real row — measured 2 rows from a fixture declaring 1, with routesDeclared agreeing so no verdict fires; no live instance on today's tree (all seven ledger comments scanned, zero hits). Every other scan in the file reads code-only; this one does not. Labelled `finding`, unassigned, no pm:queue."
]
}
Activity
claude commentedon Aug 21, 2026
Claim:
/pm-dispatchdomain:devx execution seat.Session:
session_01DdCnBGcHeufjrq7drTD3wtBranch:
claude/issue-10636-clients-declared-residueWorktree:
objectstack-issue-10636Domain:
domain:devxFile surface:
scripts/docs-audit/affected-docs.mjs(parseLedgerSource'sclientsDeclared) + its--self-testfixtures⭐ Unlocked: PR #10637 (#10500) merged, so that file is free and the
unreadableIncounter this residue sits beside is onmain.⛔ Governed surface out of scope (
⚠️ No formatter of record (no
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,content/docs/releases/**) and the #9465 epic fence (.changeset/tooling,cut-rc.yml,release.yml, rootpackage.json).format/prettierscript, prettier not in devDeps, no.prettierrc/.editorconfig) — ⛔ do not runprettier --write(#10622).⭐ Changeset:
AGENTS.md:943— "Pure bug fixes do not require a changeset"; ascripts/**-only diff that publishes nothing takesskip-changesetvia the additivePOST /labels(⛔ never a whole-set PUT — see #5533).Generated by Claude Code
fix(docs-audit): count a declared `client:` whose row was never assem…
claude commentedon Aug 21, 2026
{ "issue": 10636, "status": "done", "branch": "claude/issue-10636-clients-declared-residue", "pr": "https://github.com/objectstack-ai/objectstack/pull/10674", "premise_still_valid": true, "summary": "The `clientsDeclared` denominator in `parseLedgerSource` was two row-relative terms, so a correctly single-quoted `client:` on a row whose `route:` could not be read was in neither. It is now swept file-wide over the same code-only, type-declaration-excluded source #10500 built, minus the values a row window already claimed, and each unclaimed value is pushed to `declined` so it is NAMED with its own line rather than only bumping a number. NUMERATOR unchanged (`clientRows` = values read and bound to a row, `rows` byte for byte identical); DENOMINATOR now = every `client:` the file declares in code position. The two stay comparable because every unit added to the denominator arrives as a named 'declared, not read' event the numerator was never going to count — ablation C shows what dropping that guard costs (real tree `221 of 442`). The card's framing is kept: the PR says sub-count residue, not a live false green. Slightly wider than the card, same defect class: the sweep keys on 'no row claimed it' rather than on the quote, so it also counts a declined-spelling `client:` outside every row window (before the first assembled row, or in a truncated-window gap) — pinned by its own fixture and called out in the PR body.", "tests": "All at HEAD 19c982a268 (final commit; merge base 72d75ebb55).\n\nRE-DERIVED MEASUREMENT (card measured on ba2d8d4730; reproduces verbatim on today's tree). Mutation: the card's row appended to packages/services/service-i18n/src/i18n-route-ledger.ts; confirmed on disk by anchor count before each run (`client: '` 3 -> 4, I18N_BASE 0 -> 1) and after restore (4 -> 3, 1 -> 0).\n BEFORE (origin/main code, mutated tree): \"read 3 of 4 declared `route:` value(s) and 3 of 3 declared `client:` value(s); declined 1: line 101: route: I18N_BASE + '/plurals'\" — totals 259 of 260 / 221 of 221.\n AFTER (this branch, same tree): \"read 3 of 4 declared `route:` value(s) and 3 of 4 declared `client:` value(s); declined 2: line 101: client: 'i18n.getPlurals' (no row read it), line 101: route: I18N_BASE + '/plurals'\" — totals 259 of 260 / 221 of 222.\n RESTORED tree, this branch: 259 of 259 / 221 of 221, exit 0, no verdict. The real-tree number does NOT move, and should not: all seven ledgers are wholly single-quoted and every row was assembled (259 of 259), so no `client:` can be orphaned. The seven were also re-measured under each ablation (below) where they DO move.\n\nREJECT SIDE, asserted positively: all seven live ledgers spell the member `client?: string;`, which the scan's /\\b(route|client)\\s*:/ never matches — the live tree cannot exercise it. The fixture therefore uses the hostile spelling, a literal-union TYPE member that opens with the quote the counter reads: `export interface Entry { route: string; client: 'i18n.getLocales' | 'i18n.getPlurals' }`. The quote test alone would swallow it; typeDeclRegions is what rejects it. Pinned twice (no line-1 entry in the declined list; deleting the interface changes no count), and ablation B reds exactly that assertion.\n\nABLATIONS — each mutation confirmed on disk by anchor count (an editor exit code proves nothing), each restored and re-run green (308 pass) before the next:\n A remove the unclaimedClientsIn sweep call sites 2->1 11 red (predicted 9 by NAME before running; returned exactly those 9, then 11 after the extra orphanTail fixture was added below it)\n B remove the typeDeclRegions skip skip line 1->0 13 red (incl. the literal-union member and #10500's `route: string;` pins; live tree 259 of 266)\n C remove the !claimed.has(...) guard guard 1->0 22 red (double counting; live tree 221 of 442 — the exact 'denominator widened past what the numerator can ever count' failure)\n D remove the codeOnly mask mask 1->0 3 red (prose/string payloads become declarations; live tree 259 of 260)\nNo shortfall against prediction (A was the only one predicted numerically, and it matched by name).\n\nSELF-TEST: 291 cases on origin/main -> 308 on this branch (+17).\n\nGATE UNION: `node scripts/pm/dispatch-gates.mjs`, no paths passed — exit 0, NOT empty: change set 1 path, three-dot vs merge base 72d75ebb5; 4 families matched. Hand-cross-checked: `grep -rln docs-audit .github/workflows/` names lint.yml and docs-drift-check.yml only, both covered, plus the scripts/** family. All run at 19c982a268 through scripts/pm/os-verify-lock.sh, exit codes captured by redirect-then-capture (never after a pipe):\n os-verify-lock: VERDICT command-exit 0 · held the lock 2s · waited 199s (3m19s)\n check-affected-docs.mjs [docs-drift-check.yml] ✓ affected-docs self-test: 308 cases pass.\n check:docs-audit-scope [lint.yml] ✓ affected-docs self-test: 308 cases pass. / ✓ check-audit-scope self-test: 24 cases pass. / ✓ docs-accuracy-audit scope is in sync with content/docs/: 189 doc(s).\n check-cross-package-test-inputs.mjs [ci/lint] OK: 13 package(s) read outside themselves, all declared.\n check-nul-bytes.mjs OK (scanned 6212 text file(s); no raw ASCII control bytes) + hand self-scan of the edited file: 0 hits.\nNo pnpm install was needed or run: the gate union is node-builtin-only, which is the property docs-drift-check.yml depends on. No dependency import was added.", "open_questions": [], "out_of_scope_findings": [ "filed as #10683: [finding] the ledger ROW recognizer reads raw text, so a `route: 'X'` written in a comment becomes a real row — measured 2 rows from a fixture declaring 1, with routesDeclared agreeing so no verdict fires; no live instance on today's tree (all seven ledger comments scanned, zero hits). Every other scan in the file reads code-only; this one does not. Labelled `finding`, unassigned, no pm:queue." ] }Generated by Claude Code
fix(docs-audit): count a declared `client:` whose row was never assem…