Skip to content

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

Closed
os-steve wants to merge 1 commit into
mainfrom
claude/issue-10901-declinedin-discriminators
Closed

fix(docs-audit): give declinedIn the type-declaration discriminator#11495
os-steve wants to merge 1 commit into
mainfrom
claude/issue-10901-declinedin-discriminators

Conversation

@os-steve

@os-steveos-steve commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes#10901

⚠️Merge-time note — please check the squash message before merging.
The body above is clean, but commit dc5d11b6s message is a SEPARATELY PARSED source and it still
carries a closing keyword immediately before a reference to #10794 (the same defect this body just had).
With one commit on the branch, GitHub defaults the squash body to that commit body, so merging as-is
would register a link against #10794 — a card a human closed not planned on 2026-08-21, and which this
PR explicitly does not act on. Removing it needs an amend + force-push, which this seat is not permitted
to do, so it is left here as a visible guard: delete that sentence from the squash message at merge,
or have the amend done by someone who may force-push. The Fixes line above is the only intended
closing reference on this PR.

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 dc5d11b6.

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 dc5d11b6. 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.


Generated by Claude Code


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 the card that closes#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
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@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.

@os-steveClaude

os-steve commented Aug 24, 2026

Copy link
Copy Markdown
CollaboratorAuthor

Superseded by #11500not a change of substance. The diff is byte-identical: same blob (cac567a9bfb8c5691b3c914ed4b0acbbc5f7ca4d), same patch (md5 f99e1da7e5c3c5df6e19aaaa54a90808), same one file, same +184 / -14.

Why this one could not simply be edited: this PR's body was fixed in place, but commit dc5d11b6's message is a separately parsed source and still carried a closing keyword immediately before a reference to #10794 (message line 26). The squash body on this repo is the branch commit message rather than the PR description, so that keyword would have landed on main and registered against a card a human closed not planned on 2026-08-21 — which this work explicitly does not act on.

Removing it from a pushed commit needs an amend plus a force-push, and git push --force / --force-with-lease is an unconditional Prime Directive in AGENTS.md that no seat can grant an exception to. So the branch was re-landed cleanly instead: a fresh branch off current origin/main, the tree brought across by content rather than by cherry-picking the commit, and the same message with only that one sentence reworded. No history was rewritten and no prohibited operation was used.

Closing in favour of #11500 so that only one open PR claims #10901 at a time.

Do not reopen and merge this PR. Deleting this branch was authorized, but ref deletion is blocked for this seat (403 on both the REST ref endpoint and the git transport — an org/proxy policy denial, reported rather than worked around), so commit dc5d11b6 and its message survive on the server. If this PR is ever reopened and merged, that message lands on main and registers a closing link against #10794 — a card a human closed not planned. Merge #11500 instead; its content is byte-identical. If this branch should go away, deletion has to be done by someone whose credentials permit it.


Generated by Claude Code


Generated by Claude Code

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