Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): read the receivers a README builds, and print the blind spot that remains (#9870) - #10365
Merged
os-zhuang merged 1 commit intoAug 20, 2026
Conversation
… spot that remains (#9870) `check:published-readme-exports` typed a receiver only when the fence had imported it, so its call-site half was checking 8 call sites while 262 across 39 of 60 published documents went unread — and the green line's `60 published document(s)` read as coverage of documents rather than of the import-bound receivers inside them. Measured with the gate's own `publishedDocs()`, the two escape routes the card named are not the same size: 109 of the 251 never-import-bound receivers are BUILT from a name the fence did import (`const kernel = new ObjectKernel()`, `const stack = await bootStack(app)`), while call-expression receivers have a population of zero with an import-bound base — so the first is implemented and the second deliberately is not, rather than shipping a rule that scans nothing. Call-site half: 8 → 78 checks. One finding in the 70 newly-read sites, and it is real: plugin-hono-server's README booted a kernel with `await kernel.start()` where `ObjectKernel` ships `bootstrap()`. Zero false positives — every ambiguity in `extractLocalBindings` rejects rather than guesses, because a wrong green leaves a site unread while a wrong red accuses a correct README on a merge-blocking gate whose baseline refuses to mute it. The green line now also states what it could NOT read (120 calls on 72 receivers), marked as visibility rather than a verdict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 7 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-zhuang
marked this pull request as ready for review
August 20, 2026 16:58
os-zhuang
enabled auto-merge
August 20, 2026 16:58
This was referenced Aug 20, 2026
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang
deleted the
claude/issue-9870-readme-exports-unresolvable-receivers
branch
August 20, 2026 17:48
This was referenced Aug 20, 2026
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9870
check:published-readme-exportscould type a receiver only when the fence hadimported it. Its call-site half was therefore checking 8 call sites while
262 went unread — and the green line's
60 published document(s)read ascoverage of documents, when what it covered was the import-bound receivers
inside them.
The measurement, before any sweeping
Swept with the gate's own
publishedDocs(), so the population is the gate's.The card's numbers reproduce, and they have moved. At
f9d7acf8(the treePR #9868 measured, 11 minutes after this card was filed) the census lands on
235 call sites / 60 documents exactly, with
driver-sql 27/6,metadata 10/2anddriver-mongodb 10/2matching the card's table row for row.The definition that reproduces it: bare-identifier member calls inside code
fences whose receiver is neither import-bound from a workspace package nor
console/this. Two deltas worth recording:mainthe count is 225, not 235. PR docs(driver-sql): rewrite the published README to the shipped driver surface (#9867) #9906 repaireddriver-sql's README on 2026-08-19 — so the "1 proven fabricated" instanceis no longer in the tree; it was fixed, not found again. The honest statement
of repo state today is 225 unmeasured, 0 proven-live.
runtime/README.mdcounts 47/18 against the card's 41/15 (mine is asuperset), and affected documents 39 against 38. The headline reproduces; two
rows differ by a definitional hair.
The two escape routes the card named are not the same size. Classifying all
262 by whether any widening could reach the receiver's type:
const x = new <imported class>(…)const x = [await] <imported fn>(…)console,Object,Date)kernel.getService('mcp').registerTool(…))zfrom zod), other⛔ So the call-expression rule is deliberately not written. Every one of
those 13 is rooted in a free variable or a non-workspace import
(
z.string(…).url(,res.status(…).json(). A rule for the shape would shipscanning an empty population — the #4690 failure this gate's own header raises
to a hard error. It becomes writable the day a fence chains off an import-bound
base, against a population that exists.
What the widening surfaces — the number this card was gated on
1 finding in 70 newly-read call sites. Zero false positives.
Adjudicated by hand and real:
ObjectKernelshipsbootstrap()andshutdown()and nostart. It reads plausibly because theIKernelinterface does declare
start()— but the concrete class the fence constructsdoes not implement that name, and eight sibling READMEs spell the same step
await kernel.bootstrap(). Fixed here, with a changeset; the gate refuses itsown baseline as an author remedy, so a finding gets repaired or it does not ship.
No sweep was truncated: the widening's entire output is one site, and it is in
this PR.
The first version of this widening ran green while measuring nothing. The
derived binding never carried
hasMemberVia, so a?? (() => true)fallbackfired for all 70 sites it had just claimed to widen onto — the green line said
78 … call(s) checkedand 70 of those checks were the fallback returning "notknowable". Louder than before and measuring less: #4690 rebuilt inside the fix.
A findings-only assertion cannot see it, because the correct answer for the
vacuous case is also "no findings".
Two changes, so it cannot recur: an unanswerable receiver is now not readable
(it stays out of
callChecksand lands in theNOT readcount, where awidening that widened nothing announces itself), and the self-test asserts the
counters for both an answering and a non-answering target.
Instrumented re-run of the fixed version, all 78 sites: 77
DECIDED:found,1
DECIDED:missing, 0 bail-outs — no site reaches a verdict through anany/ index-signature / overload escape hatch.Why zero false positives
Every ambiguity in
extractLocalBindingsrejects rather than guesses,because the two failure directions are not symmetric: a wrong green leaves a
site unread (where all 262 already sit — no loss), while a wrong red accuses a
correct README on a merge-blocking gate whose baseline its own file refuses to
mute. Rejected: names declared twice from different sources, names with any
other initialiser shape, and names also introduced as a parameter,
for/catchbinding, destructuring pattern or
function/classdeclaration.The green line now states its actual domain
This is the card's own preferred option (1), measured by the gate on every run
so it cannot drift:
Visibility only — no new merge-blocking context, no exit code changes.
Verification
All at
4ce4cc392, the final commit; exit codes captured before any pipe.Gate set derived with
node scripts/pm/dispatch-gates.mjs(no args — it takesits own change set from the merge base), all 13 green:
check:published-readme-exports(+--self-test),check:changeset-gate-self-tests,check:cross-package-test-inputs,check:objectui-changeset,check:slot-lookup,check:test-source-alias,check:type-source-resolution,check:nul-bytes,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,docs-audit/check-affected-docs. Pluseslint --no-inline-configon the changedscript.
Four ablations, each confirmed on disk before running and restored
byte-identically (
git diff --quiet HEADverified on every restore leg):bootstrap()→start()in the real READMEextractLocalBindingsbinds nothing() => truefallbackNOT read:clauseThe zero-hit has its positive control: ablation 1 is the same command that
reports "no violations", shown firing on the real tree.
Scope
Clause ②: the path limb is
packages/spec/src/**and this is not that. Onthe content limb my reading is not triggered: no product contract's
accept/reject behaviour changes and no public surface widens. What widens is a
CI recognizer's own accept/reject; the only product-visible change is a
one-word README repair.
#9911 is untouched, as instructed — the gate still exits 0 on
targets.size === 0. Noted for whoever takes it: this PR changes what thepopulation is, the quantity that refusal is about. Specifically,
targets(workspace type entries reached by imports) is unaffected — this widening adds
no import statements and no new type entries, so #9911's
targets.size === 0condition is neither easier nor harder to trigger. What did change is
measured.callChecks, which now has a non-vacuous floor:derivedReceiversandthe
NOT readpair give #9911 two more quantities that a no-population runwould zero, if its author wants them.
Generated by Claude Code