Skip to content

fix(docs-audit): give declinedIn the type-declaration discriminator - #11500

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-10901-declinedin-discriminators-v2
Aug 24, 2026
Merged

fix(docs-audit): give declinedIn the type-declaration discriminator#11500
os-steve merged 1 commit into
mainfrom
claude/issue-10901-declinedin-discriminators-v2

Conversation

@claude

@claudeclaudeBot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes#10901

declinedIn was the last route:/client: scan in scripts/docs-audit/affected-docs.mjs still deciding "is this in code position?" for itself. #10793 taught the row recognizer — and the first term of its denominator — to read through both of #10500's exact discriminators. Its complement did not move, so a literal-union route: TYPE member written in either of the two quotes the recognizer declines was billed as a value the parse failed to read: a named entry, a PARTIAL-read verdict, and exit 1, on a ledger that is completely accurate. That is the false red the type-declaration exclusion exists to prevent.

Route taken: (2), the narrow one — chosen on measurement, not on adjective

Triage ruled (2) and left (3) available "if it turns out equally cheap". It is not. I built (3) as a working probe (fold declinedIn into declarationsIn as its missing third filter, with the one declaration list computed once per file so the in-window call cannot mis-lex a slice) and ran the differential against origin/main on five shapes. (3) passes the existing 339 self-test cases, so the measurement had to be behavioural rather than a pass/fail:

shapeorigin/mainroute (3)route (2), this PR
A literal-union route: type member, double-quoteddeclined 1, exit 1declined 0declined 0
B same, backtickdeclined 1, exit 1declined 0declined 0
C declined spelling quoted in a comment (#10794)declined 1, exit 1declined 0declined 1, exit 1
D value on the next line after the colondeclined 2, routesDeclared 3declined 1, value unnameddeclined 2, routesDeclared 3
E, F, G trailing entry interface, quoted client: uniondeclined 1, exit 1declined 0declined 0

(3) changes four populations, two of them uninvited:

So (2) it is: it changes exactly the population this card is about, in both quote spellings, at both call sites. D is pre-existing and byte-identical on origin/main and on this branch — filed unassigned as #11494.

What changed

The region list arrives as a required parameter from the one caller that already computes it, with no default — a call site that forgot the discriminator would silently reintroduce exactly the second opinion this closes. offset makes the returned index absolute, so the in-window slice's coordinate translation happens once inside the function instead of at one of the two call sites.

Both call sites were reachable and both are covered:

  • a leading entry interface arrives through the file-wide route: sweep;
  • a trailing one lands inside a row window and reached the in-window client: sweep. Measured before the fix on that shape: clientsDeclared 2 on a file declaring one client, one named declined entry, exit 1.

⛔ The other discriminator is deliberately not applied. This still reads raw bytes, so a declined spelling quoted in a comment is still billed as an unread row (#10794). Applying codeOnly here is also not free of its own accord: it blanks string contents, and this scan's whole job is to quote the unread spelling back at the reader, so a masked window would name route: "" for every entry. The boundary is pinned in --self-test so whichever card eventually takes on #10794moves that pin rather than finding none.

Evidence

All figures below are from this branch at c2fc7771.

The non-vacuity proof is synthetic, and it has to be. The live delta is zero, so a green live run proves nothing about this change — stated as a prediction before running, then checked.

Fail-before / pass-after, identical case text, run against origin/main's declinedIn and against this one:

✗ self-test "a double-quoted literal-union `route:` TYPE member is not an unread row": declined → expected parseLedgerSource=0, got 1
✗ self-test "and a double-quoted member moves no denominator either": declared → expected "1 row / 1 route / 1 client", got "1 row / 2 route / 1 client"
✗ self-test "a double-quoted type member carries NO broken-scan verdict": brokenScan → expected bridgeCoverageFrom=0, got 1
… (backtick twins, both `client:` call-site cases, both trailing cases, the `type X = { … }` spelling)
✗ affected-docs self-test failed (15 case(s)). ← before
✓ affected-docs self-test: 366 cases pass. ← after (339 before this PR, +27)

Both quote spellings are pinned apart, not assumed. They come out of one regex alternation and one code path and were measured behaving identically — which is the reason to pin them separately rather than to trust one for both.

Live safety check — predicted NO CHANGE, and it held.--bridge-coverage before and after is byte-identical (md5 201a2b4850f62b5f9f0133547d0e907f both runs), exit 0 both:

 ledger rows read ........... 268 of 268 declared
client-bound ledger rows ... 222 of 222 declared
prose-quoted leads (no row) . 0
reachable ................ 45
UNREACHABLE .............. 177

The claimed live-zero was verified here rather than inherited, with a positive control. Across all seven ledgers: 0 quoted route:/client: leads inside any type declaration, out of 490 quoted code leads. The same scan finds the lead in the fixture (1 per spelling), so the zero is a measurement and not a broken scan.

The exclusion is still load-bearing — measured, not assumed. Ablating typeDeclRegions to return [] (mutation proven on disk by anchored grep; restored by an EXIT INT TERM trap; affected-docs.mjs is executed directly by node, so the edited bytes are the bytes that run):

origin/main, ablated: 268 of 275 declared, exit 1
this branch, ablated: 268 of 275 declared, exit 1 ← identical: not weakened
this branch, ablated, --self-test: 46 case(s) fail (15 before this PR)

That is today's tree's form of the note the file records (259 of 266): seven ledgers, one route: string; member each, 268 + 7 = 275.

--bridge-coverage's consumer is unaffected..github/workflows/docs-drift-check.yml reads only bridge.measured, bridge.reachable, bridge.clientRows and bridge.unreachable from the --json arm. This change does not touch rows, so none of those four can move; the header's shape is unchanged.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (change set taken from the script's own merge-base derivation, re-derived after the final commit — the set was unchanged), and run at c2fc7771. All green, each quoted from the gate's own verdict line:

  • pnpm check:docs-audit-scope✓ affected-docs self-test: 366 cases pass. / ✓ check-audit-scope self-test: 24 cases pass.
  • pnpm check:entry-guard✓ check:entry-guard: 139 scripts/ file(s) — every entry guard goes through invoked-as.mjs
  • pnpm check:parse-guard✓ check:parse-guard: 138 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
  • pnpm check:cross-package-test-inputs / node scripts/check-cross-package-test-inputs.mjsOK: 14 package(s) read outside themselves, all declared
  • pnpm check:pnpm-filter-targets✓ check:pnpm-filter-targets: 120/148 --filter occurrence(s) … resolve
  • pnpm check:pm-governed-merges✓ check-governed-merges --self-test: 129 assertions
  • node scripts/check-ci-filter-parity.mjsOK: all 89 declared cross-package glob(s) … are covered
  • node scripts/docs-audit/check-affected-docs.mjs✓ affected-docs self-test: 366 cases pass.
  • pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6448 text file(s) … no raw ASCII control bytes).

No narrowing to declare: pnpm lint (eslint . --no-inline-config, repo-wide) was run in full under the shared verify lock — exit 0 in 68s.

No changeset: this touches one CI/tooling script under scripts/ and publishes nothing, so the PR carries skip-changeset.

Closing-reference hygiene, and one measurement that changed how I report it

An earlier revision of this work carried a closing keyword before a reference to #10794 in both the PR
body and the commit message. #10794 was closed not planned by a human on 2026-08-21 and this change
explicitly does not act on it, so either would have recorded that decision as undone by an unrelated PR.
Both are gone here: the body was reworded, and this branch was re-landed from origin/main with the
commit message reworded too, since a pushed commit message cannot be corrected without a force-push.
The predecessor is #11495, closed as superseded; its diff and this one are byte-identical (same blob
cac567a9, same patch md5 f99e1da7e5c3c5df6e19aaaa54a90808, same +184 / -14).

⚠️resolve/resolves/resolved is itself a closing keyword, alongside close/closes/closed
and fix/fixes/fixed — rewording one into another is not a fix. The wording here (takes on) uses
none of them, and both body and message were rescanned for keyword+#n and keyword+URL forms.

⚠️The closed_by_pull_requests REST field is not usable as proof of this, in either direction.
After the fix it reads total=0 on #10794 — but it also reads total=0 on #10901, whose intended
closing line is intact at the top of this body, and a control over four other OPEN PRs in this repo carrying Fixes #n
(#11469, #11466, #11406, #11393, draft and non-draft alike) shows total=0 on all four target issues.
So its zero says nothing about any particular PR. The discriminating evidence is the source text that the
merge-time parser actually reads — the stored body and the commit message, both rescanned above — not the
field. (closingIssuesReferences via GraphQL would have been the direct check; it returns HTTP 403 for
this token.)


Generated by Claude Code

…#10901)
`declinedIn` was the last `route:`/`client:` scan in `affected-docs.mjs` still
deciding "is this in code position?" for itself. #10793 taught the row recognizer
and the first term of its denominator to read through both of #10500's exact
discriminators; its complement did not move, so a literal-union `route:` TYPE
member written in either of the two quotes the recognizer declines was billed as
a value the parse FAILED to read — a named entry, a PARTIAL-read verdict and
exit 1, on a ledger that is completely accurate.
The region list now arrives as a required parameter from the one caller that
already computes it, with no default: a call site that forgot the discriminator
would silently reintroduce exactly the second opinion this closes. `offset`
makes the returned index absolute, so the in-window slice's coordinate
translation happens once inside the function instead of at one of the two call
sites.
Both call sites were reachable and both are covered: a leading entry interface
arrives through the file-wide `route:` sweep, a trailing one lands inside a row
window and reached the in-window `client:` sweep (measured before the fix:
`clientsDeclared` 2 on a file declaring one client, exit 1).
⛔ The other discriminator is deliberately NOT applied. This still reads raw
bytes, so a declined spelling quoted in a COMMENT is still billed as an unread
row — that is #10794, closed `not planned`, and not this change's to reverse.
The boundary is pinned in `--self-test` so whichever card eventually takes on #10794
moves the pin rather than finding none.
Self-test: 339 -> 366 cases. 15 of the new cases fail against the previous
`declinedIn` and pass after. Live delta is zero and predicted so in advance:
0 quoted `route:`/`client:` leads inside any type declaration across all seven
ledgers, and `--bridge-coverage` is byte-identical before and after
(268 of 268 route / 222 of 222 client / 177 unreachable, exit 0).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-steve@claude