fix(check-published-readme-exports): refuse the third no-population state — imports read, nothing resolved - #10410
Merged
os-zhuang merged 1 commit intoAug 20, 2026
Conversation
…tate — imports read, nothing resolved
`check:published-readme-exports` hard-refuses two no-population states with
exit 1 ("the scan read nothing", "no imports at all"), because a green over an
unread tree is worthless (#4690). A third state on the same axis was not
refused: documents exist, import statements exist, but no import resolves to a
workspace member — `targets` stays empty, so `bound` stays empty in every
document, both halves check nothing, and the run exits 0.
Measured before the fix, on a fixture publishing one document and three
non-workspace imports: exit 0, under a green line reading `3 import
statement(s), 0 workspace type entr(ies)` with three zeros below it.
The population axis is now one staged function, `populationRefusal`: documents
-> import statements -> workspace type entries. Each stage is consulted only
because the previous one was non-zero, which makes the three refusals mutually
exclusive by construction rather than by careful wording, and a stage the
caller has not measured is omitted rather than passed as 0 (so `publishedDocs`,
which knows only the document count, cannot speak for the other two). Stage 3
prints the two counts that are NOT zero — exactly what stage 2 cannot say —
because the two have different remedies: stage 2 points at the extractor,
stage 3 at workspace resolution.
Pass 1's census is lifted out of `run()` as `reachedTargets`, so the self-test
builds the no-population state with the same code the run uses. A hand-written
`targets: 0` would pin the refusal's arithmetic while proving nothing about the
scan that produces the zero — the exact vacuity this refusal exists to catch.
#9870 left two more quantities a run can zero; neither is in the refusal, and
that was measured rather than reasoned. `derivedReceivers` is 0 on a wholly
legitimate population of one (a fence that imports a name and calls a static on
it without constructing anything), so refusing there would redden a real tree.
`unreadCalls`/`unreadReceivers` are the complement of what was checked, so a
no-population run drives them UP, not to zero — the empty fixture reports
`NOT read: 3` against `0 ... checked`. `targets.size` is the whole condition
because it is the one quantity that is zero iff both halves are vacuous.
Verified: the healthy tree's output is byte-identical to the pre-change gate's
(60 documents, 212 imports, 49 workspace type entries); the existing stage-2
refusal is unchanged; a population of one stays green. Three ablations each
drove the new self-test red — neutering the refusal, stopping the import
counter, and widening the refusal to `targets <= 1` — with byte-identical
restores between.
Fixes #9911
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
marked this pull request as ready for review
August 20, 2026 18:22
os-zhuang
enabled auto-merge
August 20, 2026 18:23
This was referenced Aug 20, 2026
os-zhuang
deleted the
claude/issue-9911-readme-exports-empty-population
branch
August 20, 2026 18:58
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 free
to 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 #9911
check:published-readme-exportsis emphatic about #4690 — "zero is a broken scan, not a clean repo" — and hard-refuses two no-population states with exit 1. A third state on the same axis was not refused: documents exist, import statements exist, but no import resolves to a workspace member.targetsstays empty, soboundstays empty in every document, both halves check nothing, and the run exits 0.Reproduced first, on the gate as it stood
A fixture publishing one document and three non-workspace imports, run against a byte-identical copy of
origin/main's gate (sha2565933313337c5…, verified againstgit show origin/main:):Three zeros,
0 workspace type entr(ies), and a pass in CI. #9767 made it legible; this PR makes it a verdict.The shape of the fix
The population axis is now one staged function,
populationRefusal:What distinguishes stage 3 from the two already there. Each stage is consulted only because the previous one was non-zero, so the three are mutually exclusive by construction rather than by careful wording — stage 3 cannot shadow or overlap the two above it. What separates them for a reader is the remedy each implies: stage 2 sends you to the extractor, stage 3 to workspace resolution. So stage 3 prints the two counts that are not zero, which is precisely what stage 2 cannot say. A stage the caller has not measured is omitted, never passed as
0—publishedDocsknows only the document count and must not speak for the other two (pinned).Pass 1's census is lifted out of
run()asreachedTargets, so the self-test constructs the state with the same code the run uses. A hand-writtentargets: 0would pin the refusal's arithmetic while proving nothing about the scan that produces the zero — the exact vacuity this card is about.Why the refusal stops at
targets— measured, not reasoned#10365 (#9870) landed on this file two hours before this card and left two more quantities a no-population run touches. Neither is in the refusal, and the reason is a measurement on a fixture with a wholly legitimate population of one:
derivedReceiversunreadCalls/unreadReceiversNOT read: 2— and 3 on the empty fixtureSo the handover's "two more quantities a no-population run would zero" holds for
derivedReceiversonly; theNOT readpair moves the other way. A zero-test on it would be backwards, and a ceiling-test on it is a coverage ratchet — a different card, wanting a population that exists.targets.sizeis the whole condition because it is the one quantity that is zero iff both halves are structurally vacuous.Evidence
It fires on the real state and nothing else — same three fixtures, gate before vs after:
The healthy tree is untouched. Both gate versions run against this repo with a full build, output diffed:
49 workspace type entr(ies)against a threshold of 0 — the refusal has the whole tree as headroom.Ablations — a green self-test proves nothing until it goes red. Three mutations, each confirmed on disk by anchored
grep -con both the injected and the removed text (never a baregit diff --stat), each restored to a sha256-identical file with the self-test re-run green in between:if (false && targets === 0)) → 1 failure, the stage-3 assertion.expected {"imports":3,"targets":0}/actual {"imports":0,"targets":0}. ⭐ This is the one that matters: it proves the reject side is asserted positively. Without theimports: 3count, anextractImportsthat had stopped matching entirely would still producetargets: 0and sail through as a correctly-firing refusal.targets <= 1→ caught by populationRefusal — a population of ONE is never refused, the false-red direction.Gates (derived with
node scripts/pm/dispatch-gates.mjs, re-derived on the final commit057a3ac7; verdict lines quoted from each gate's own output, exit codes captured before any pipe):pnpm check:published-readme-exports—EXIT=0, self-test + run both green (insideos-verify-lock.sh, reads builtdist/*.d.ts).pnpm check:published-readme-links—EXIT=0. It importspublishedDocs, whose refusal this PR rewires, so it is run deliberately:152 outbound link(s) across 60 published markdown file(s).pnpm check:cross-package-test-inputs—EXIT=0,All 60 self-test cases passed.(pure source scanner, run outside the lock — declared.)pnpm check:nul-bytes—EXIT=0,scanned 6117 text file(s) … no raw ASCII control bytes, plus an independentgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'on the changed file.pnpm build—EXIT=0(inside the lock, held 369s).Clause ② — not triggered. The path limb is
packages/spec/src/**; this is rootscripts/. On content: this changes a gate's verdict, not a product contract or the public authorable surface.Changeset:
skip-changeset. Judged by publish surface, not habit — the diff is one file under rootscripts/, which publishes nothing, and it repairs nopackages/*README (the real tree has 0 findings, so the new refusal surfaces nothing to fix).Out of scope
#10367 — the namespace-import branch that can never fire (
namespaceSymbolhardcodednull). Deliberately untouched. This PR makes it neither easier nor harder to fire:reachedTargetsmoved the census that decides which packages are reached, whilenamespaceSymbol: nullis set later inresolveTarget, on the resolution path this change does not enter. The new refusal cannot mask it either — a tree reaching zero targets now exits 1 beforeresolveTargetis ever built.Generated by Claude Code
Generated by Claude Code