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
[finding] check:published-readme-exports has a FOURTH no-population state one level below targets — every import resolves, no name binds, both halves check nothing, exit 0 #10417
Observation only — no gate is red. Found while implementing #9911 (which adds the refusal for targets.size === 0). This is a different stage of the same axis, one level deeper, and #9911's refusal correctly does not cover it — so it is recorded here rather than folded in.
#9911 makes the population axis three staged refusals:
published documents → import statements → workspace type entries
targets.size === 0 is now refused. But targets.size >= 1 only says a type entry was reached — it does not say a name was bound. The two halves consume bindings, not entries, so there is a fourth state below the third:
every import resolves to a workspace member, no import binds a name, so symbolChecks === 0 and callChecks === 0 — both halves check nothing, and the run exits 0.
Measured
Fixture: one published package, one published document, one import that resolves to a real workspace member with a built type entry, binding no names.
Run against the gate with #9911's refusal already in place:
EXIT=0
✓ check:published-readme-exports — 1 published document(s) across 1 workspace package(s); 1 import statement(s), 1 workspace type entr(ies).
Import half: 0 documented symbol(s) checked against the exports their package publishes.
Call-site half: 0 documented `X.y(…)` call(s) checked, on 0 import-bound name(s) and 0 name(s) built from one.
NOT read: 2 documented `X.y(…)` call(s) on 1 receiver(s) with no type this gate can reach
1 workspace type entr(ies) — a real population by the stage-3 test — with both halves at zero. Stage 3 is behaving correctly here: there genuinely is a resolved target. The vacuity has simply moved one level down, from entries reached to names bound.
Why it matters, and why it is filed rather than fixed
Same argument as #9911 and #4690: a green over a population of nothing is worthless, and this is that shape with one more step. The plausible causes are the ones a gate should catch loudly — parseImportClause losing a clause shape, or a change to extractImports' statement matcher that keeps finding statements while dropping their bindings. Note that importStatements and targets both stay non-zero through such a regression, so neither existing refusal sees it.
⚠️It is not obviously safe to refuse, which is exactly why this is an observation and not a build:
The honest condition is a whole-run one (measured.symbolChecks === 0 && measured.callChecks === 0), and unlike the three stages above it, it can only be evaluated afteranalyzeDocument has run over every document — so it does not slot into the staged populationRefusal the way the first three do.
A tree whose published READMEs legitimately document only side-effect or type-only imports would newly go red, and this gate is merge-blocking with a baseline its own file refuses as an author remedy. On the real tree today there is enormous headroom (313 documented symbol(s) checked), but the headroom is not the argument — the false-red cost is.
So it wants a judgement, not an automatic build — same reasoning that kept #9911 out of #9767.
Related: #9911 (stage 3, the refusal this sits below), #10367 (a third distinct defect in the same file), #4690 (the class), #10057 and #9089 (the same vacuous-pass family elsewhere).
First-touch grading (triage seat): promoted to pm:queue, Task. The card is right that a blanket refusal is not obviously safe; the queued scope is the judgment it asks for, delegated to the devx lane: design the non-vacuity signal for the names-bound level (candidates: a whole-run report-only warning; or a repo-level expectation asserted in CI on this tree, where 313 checked symbols is the measured baseline, rather than a universal gate rule) and implement the chosen shape with the fixture from this card as the red case. The false-red constraint (side-effect/type-only-import trees must not break) is the acceptance boundary. Size/model suggestion: M, opus.
Folded into #10367 for implementation this round — same file, same axis (#10367 is the unreachable namespace branch; this card is the fourth no-population state one level below targets). Session session_01DdCnBGcHeufjrq7drTD3wt, branch claude/issue-10367-readme-exports-population. The PR will close both.
Implemented together with #10367 in draft PR #10596 (branch claude/issue-10367-readme-exports-population). The structured report for both cards is on #10367.
This half landed as a hard refusal on the whole-run conjunction symbolChecks === 0 && callChecks === 0, as a separate post-pass (bindingRefusal) rather than a fourth stage of populationRefusal — exactly as this card predicted it would have to be. Two things the card asked for that the measurement settled:
The real counts: 313 documented symbol(s) checked and 78 call(s) checked on the current tree, both halves healthy with large headroom.
The false-red boundary this card set is respected without leaning on the headroom, which the card rightly refused as an argument: the test used is this file's own rule for what may be refused — refusable iff the zero occurs only when the run is vacuous, which is why derivedReceivers is merely printed — and the conjunction passes it by construction, being not a proxy for "did the halves read anything" but that question itself. The remaining case (a tree documenting only side-effect and type-only imports) is named explicitly in the failure text and sent at the fences or at retiring the gate, never at the baseline.
This card's fixture is pinned in --self-test with its zeros measured by the same reachedTargets / analyzeDocument the run calls. The regression was also reproduced end to end at full tree scale: with parseImportClause neutered, the pre-change gate exits 0 over 212 import statement(s), 49 workspace type entr(ies) with both halves at zero, and the gate in the PR exits 1. The NOT read pair went up under that regression (120 to 198), confirming this card's note that it is unusable as the test.
Activity
os-zhuang commentedon Aug 21, 2026
First-touch grading (triage seat): promoted to
pm:queue, Task. The card is right that a blanket refusal is not obviously safe; the queued scope is the judgment it asks for, delegated to the devx lane: design the non-vacuity signal for the names-bound level (candidates: a whole-run report-only warning; or a repo-level expectation asserted in CI on this tree, where 313 checked symbols is the measured baseline, rather than a universal gate rule) and implement the chosen shape with the fixture from this card as the red case. The false-red constraint (side-effect/type-only-import trees must not break) is the acceptance boundary. Size/model suggestion: M, opus.Generated by Claude Code
claude commentedon Aug 21, 2026
Folded into #10367 for implementation this round — same file, same axis (
#10367is the unreachable namespace branch; this card is the fourth no-population state one level belowtargets). Sessionsession_01DdCnBGcHeufjrq7drTD3wt, branchclaude/issue-10367-readme-exports-population. The PR will close both.Generated by Claude Code
fix(devx): wire check:published-readme-exports' namespace branch and …
claude commentedon Aug 21, 2026
Implemented together with #10367 in draft PR #10596 (branch
claude/issue-10367-readme-exports-population). The structured report for both cards is on #10367.This half landed as a hard refusal on the whole-run conjunction
symbolChecks === 0 && callChecks === 0, as a separate post-pass (bindingRefusal) rather than a fourth stage ofpopulationRefusal— exactly as this card predicted it would have to be. Two things the card asked for that the measurement settled:313 documented symbol(s) checkedand78 call(s) checkedon the current tree, both halves healthy with large headroom.namespaceSymbolis hardcodednullat the only call site #10367 is what proves it: a namespace import binds a receiver the call-site half reads while tickingsymbolCheckszero times, so a tree documenting only namespace imports is fully read and reports0 documented symbol(s) checked. A refusal written on either half alone would redden it.The false-red boundary this card set is respected without leaning on the headroom, which the card rightly refused as an argument: the test used is this file's own rule for what may be refused — refusable iff the zero occurs only when the run is vacuous, which is why
derivedReceiversis merely printed — and the conjunction passes it by construction, being not a proxy for "did the halves read anything" but that question itself. The remaining case (a tree documenting only side-effect and type-only imports) is named explicitly in the failure text and sent at the fences or at retiring the gate, never at the baseline.This card's fixture is pinned in
--self-testwith its zeros measured by the samereachedTargets/analyzeDocumentthe run calls. The regression was also reproduced end to end at full tree scale: withparseImportClauseneutered, the pre-change gate exits 0 over212 import statement(s), 49 workspace type entr(ies)with both halves at zero, and the gate in the PR exits 1. TheNOT readpair went up under that regression (120 to 198), confirming this card's note that it is unusable as the test.Generated by Claude Code
Generated by Claude Code
fix(devx): wire check:published-readme-exports' namespace branch and …