Uh oh!
There was an error while loading. Please reload this page.
fix(devx): wire check:published-readme-exports' namespace branch and refuse the fourth no-population state - #10596
Conversation
…refuse the fourth no-population state Fixes#10367Fixes#10417 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
commented
Aug 21, 2026
PM review — ACCEPT, arming. And your open question is answered: you were right, my brief was wrong.Reviewed against the tree and the PR, not against the report. Head ⭐ The changeset question — ratified, and the error was mineYou took
So the line "a On the scope addition, which is the best part of this PRYou lifted
⭐ That is the finding, not a detour. Wiring What earns the ACCEPT
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10367
Fixes#10417
Two no-population defects in
scripts/check-published-readme-exports.mjs, at adjacent stages of one axis. This gate's own header raises "zero is a broken scan, not a clean repo" (#4690) to a hard error; both cards are places where that principle was not actually enforced, and both produce the failure direction AGENTS.md calls worse than no verifier — a green printed over a population of nothing.The tree as measured, before anything changed
Built the 42 packages the gate reads, then ran it on the real tree:
Both halves healthy with enormous headroom — which is what the #10417 half of this PR was told to decide on, and it decided for the hard refusal. See below for why the headroom is not the argument.
#10367 — the namespace branch, wired (not deleted)
analyzeDocumentbinds a namespace import so the call-site half can read its members against the module's export set.run()'sresolveTargetwas the only production caller and returnednamespaceSymbol: nullunconditionally, so the branch bound nothing on every run this gate has ever made.hasMember'sSymbolFlags.Modulearm has always been able to answer the question; only the symbol was never supplied.The card's zero-twice-over claim re-verified on today's tree, not inherited: the pre-change gate and this one print byte-identical output on the real tree (
diffclean), so wiring changes no verdict today. The tree's five namespace imports are all relative specifiers, whichsplitSpecifierturns away first.Wired rather than deleted, per the ruling on the card: deleting coverage to answer an unreachable branch contradicts the thesis this file is built on, and the cost is nil because
exportsOfhas already resolved the module symbol into the shared cache.typeSurfacegainsmoduleSymbolOf(abs), reading the cacheexportsOffills.surfaceTarget(surface, abs)is lifted out ofrun()and exported. This is the part worth reviewing: every existing test drivesanalyzeDocumentthrough a fake resolver, so the suite pinned the branch that consumes the symbol and could never see that the supplier returnednull. A branch is only as exercised as its least-tested end. Lifting the supplier out makes that end assertable, so the regression cannot ship silently twice.*— an author would have read "but * (from '@objectstack/spec') has noxmember". It now names the package instead.#10417 — the fourth no-population state, refused
targets.size >= 1says an entry was reached, never that a name was bound, and both halves consume bindings. So there is a state below stage 3 that all three existing refusals pass. Not a fourth stage ofpopulationRefusal: the honest condition is whole-run and cannot be known untilanalyzeDocumenthas read every document. It lands as a separate post-pass,bindingRefusal.The condition is the conjunction
symbolChecks === 0 && callChecks === 0, and #10367 is what makes that load-bearing rather than stylistic — a namespace import binds a receiver the call-site half reads while tickingsymbolCheckszero times. 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 two cards constrain each other, which is the argument for landing them together.Placement was measured, not reasoned. It sits below the unbuilt branch: on an unbuilt checkout both counters are also zero, and
pnpm buildis the more actionable sentence. Confirmed by running the gate on an unbuilt tree — it prints192 package(s) are not built, not the vacuity refusal.On the false-red constraint, which is the acceptance boundary
The card is right that headroom is not the argument, so the argument is the file's own rule for what may be refused: a quantity is refusable when it is zero if and only if the run is vacuous, and not refusable when an ordinary healthy tree can produce the zero — which is exactly why
derivedReceiversis only printed. The conjunction passes that test by construction: it is not a proxy for "did the halves read anything", it is that question. The residual cost is a tree whose published fences document only side-effect and type-only imports — a tree with nothing for this gate to check at all — and the honest reply to that is this sentence rather than a silent pass. The failure text names that case explicitly and sends it at the fences or at retiring the gate, never at the baseline.Ablations — every mutation confirmed on disk before the result was read
No mutation was believed on an editor's exit code; each was verified with
grep -con the text being changed, before and after, and every restore was checked (git diff --quiet, orcmpagainst a pre-ablation copy). The gate reads source and built.d.ts, so no rebuild was needed between legs; the built tree was constant throughout.1. #10367, end-to-end on the real tree — "the day a published README namespace-imports a workspace package". Appended a fence to
packages/spec/README.mdimporting* as specfrom@objectstack/specwith one real export and one invented (marker counts 0 → 1 each, 4053 → 4166 bytes), then ran both gates against the same tree:namespaceSymbol: null)78 call(s) checkedunchanged,NOT read120 → 122. The blind spot is real and attributed to "no type this gate can reach", which is not the reason.documents \spec.defineNothingAtAll(…)` on a namespace import of '@objectstack/spec', but that package's published types export no `defineNothingAtAll`.`Then the same fence with only the valid member, to show the branch reads rather than merely reddens: pre-change
78 checked / 233 receivers / NOT read 121; this PR79 checked / 234 receivers / NOT read 120— the receiver is bound and its call leaves the unread bucket.defineViewresolved through the realchecker.getExportsOfModule, whose module symbol was confirmed to carrySymbolFlags.Module(flags512). README restored andcmp-verified byte-identical.2. #10417, end-to-end on the real tree — the exact regression the card names. Injected "
parseImportClausestops returning bindings" into both gates (marker 0 → 1, +84 bytes each):✓ … 212 import statement(s), 49 workspace type entr(ies).over0 documented symbol(s) checkedand0 … call(s) checked. The #10417 shape at full tree scale — the card measured it on a 1-document fixture.NOT readwent up (120 → 198) under the regression, confirming the card's point that the unread pair is unusable as the test.3. Self-test assertions, each broken deliberately.
--self-testsurfaceTarget'snamespaceSymbolback tonullthe PRODUCTION caller supplies a module symbol, never a nullconst ns = null)bindingRefusal — a namespace-only tree … is never refusedflipping, which is the conjunction argument firing livebindingRefusalalways returnsnulla run in which every import RESOLVED and not one BOUND A NAME is not refusedEvery self-test zero is measured by the same
reachedTargets/analyzeDocumentthe run calls, never typed into an assertion, and every reject side is asserted positively — the namespace fixture pins that a real export stays silent and that an invented one is reported, with the counters (receivers: 1, checked: 2, unread: 0) so that "bound nothing, read nothing" cannot pass as "checked two, found one".Gates
Derived with
node scripts/pm/dispatch-gates.mjs(no path arguments) against the real change set, then run on head commitdfcd87c2c6under the shared verify lock (VERDICT command-exit 0):No changeset: the diff is
scripts/**only and publishes nothing (skip-changeset). This diverges from the dispatch brief, which expected a changeset entry — the tree's practice is the opposite, and PR #10502 (ascripts/**-only gate PR merged the same day) is the precedent:skip-changeset, no changeset file.Generated by Claude Code