Uh oh!
There was an error while loading. Please reload this page.
feat(scripts): --unread-report decomposes NOT read: per document, checksummed against the scalars - #10874
Conversation
…hecksummed `check:published-readme-exports` printed its blind spot as two whole-repo scalars and nothing else. They stop the green line reading as coverage (#9870) — what they cannot answer is the question every per-document hand read starts from: which document holds how many, and which sites are they? Answering it meant re-implementing `analyzeDocument`'s `readable` derivation and `countUnreadCalls`' body outside this file, which is the re-derivation this file's header refuses for `publishedDocs`, failing the same way: a hand read whose population silently disagrees with the gate's produces a defect count with the wrong denominator, and nothing anywhere goes red. `--unread-report` prints what the run already computed, per document. - `unreadCallSites` returns the sites; `countUnreadCalls` is now a CARDINALITY VIEW over it rather than a second walk, so the scalar and the rows cannot disagree about what an unread site is. - One statement in `analyzeDocument` produces the row and both scalars from the same object; `newMeasured()` defines the accumulator once, for the run and the self-test alike. - The rows are re-added and compared with the scalars before printing. On a mismatch the rows are WITHHELD and a refusal is printed instead: a breakdown that disagrees with the total above it is worse than no breakdown, because it lends the gate's authority to a population the gate did not measure. - The report header names the neighbouring whole-repo scalars as having no per-document share, and every row spells both numbers `unread …`. That is the fix for the invitation: this card was filed after `derivedReceivers` — printed one line above `NOT read:` — was quoted as one document's site count. ⛔ Visibility, not a verdict: no finding, no verdict and no exit code changes, and the CI invocation does not pass the flag. Measured, not asserted: without the flag this script's stdout, stderr and exit code are byte-identical to origin/main's on both a green and a red fixture tree. The self-test pins the checksum rather than trusting it — rows from the production path, four separate drifts (a row grown, either scalar moved, a whole row lost), each of which must refuse and withhold the breakdown. Part of #10815 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
✅ ACCEPT — reviewer of record: |
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32497328832 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#10815
check:published-readme-exportsprinted its blind spot as two whole-repo scalars andnothing else:
Those numbers stop the green line reading as coverage, which is what #9870 added them for.
What they cannot answer is the question every per-document hand read starts from: which
document holds how many, and which sites are they? Answering it meant re-implementing
analyzeDocument'sreadablederivation andcountUnreadCalls' body outside this file —the re-derivation this file's own header refuses for
publishedDocs, and it fails the sameway: a hand read whose population silently disagrees with the gate's produces a defect count
with the wrong denominator, and nothing anywhere goes red.
What this adds
An opt-in
--unread-reportflag printing what the run already computed, per document.⭐ The checksum, and why it withholds rather than warns
The rows are re-added and compared with the scalars before printing. On a mismatch the
rows are not printed at all — a breakdown that disagrees with the total above it is worse
than no breakdown, because it lends this gate's authority to a population the gate did not
measure. Suppressing the rows leaves the reader exactly what they had before this flag
existed, plus a sentence saying the decomposition could not be reconciled.
Three structural properties keep the two sides one thing rather than two:
unreadCallSitesreturns the sites;countUnreadCallsis now a cardinality view overit, not a second walk. The scalar and the rows cannot disagree about what an unread site
is.
analyzeDocumentproduces the row and both scalars from the sameobject.
newMeasured()defines the accumulator once, for the run and the self-test alike — so afield the run accumulates cannot be missing from a self-test fixture, which is a test that
cannot see the field and stays green.
⛔ Visibility, not a verdict — measured, not asserted
No finding, no verdict and no exit code changes, and the CI invocation does not pass the flag.
Rather than assert that, it was measured against
origin/main's copy of the script on thesame trees: without the flag, stdout, stderr and exit code are byte-identical, on a green
fixture tree (exit 0), a red one (exit 1) and the real unbuilt checkout (exit 1). With the
flag the exit codes are unchanged on all three; the report prints on both terminal paths, and
below the four population refusals and the unbuilt branch — a run that measured nothing drives
the unread count up, and a breakdown of that number would be the wrong-denominator hand read
this flag exists to prevent, handed out by the flag itself.
A checksum mismatch is likewise not a verdict: its text says it is a defect in this script,
not in any README, and the exit code is untouched.
Tests
--self-test, at9b786ca271. The checksum is pinned, not trusted: the rows come from theproduction
analyzeDocumentpath, and four separate drifts — a row grown, either scalar moved,a whole row lost — must each refuse and withhold the breakdown. Both directions were driven
by ablation on disk, not by argument:
grep -c)--self-testEXIT=0✓measured.unreadCalls += unread.sites.length + 1— scalar drifts from its rowEXIT=1, 14 failure(s), incl.expected …"scalars":[3,2] / actual …"scalars":[5,2]and the row pin readingactual [](rows correctly withheld)if (false && …)EXIT=1, 9 failure(s) — all four drift directions reportmust REFUSE/printed a per-document rowGates, derived with
node scripts/pm/dispatch-gates.mjs(no paths passed) at9b786ca271,plus the two
scripts/**gates that derivation is blind to. Each verdict is the gate's ownline, captured with
cmd > log 2>&1; ec=$?:check:cross-package-test-inputs—OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.(0)check:published-readme-exports --self-test—✓ … --self-test(0)node scripts/check-ci-filter-parity.mjs—OK: all 82 declared cross-package glob(s) (71 unique) are covered…(0)node scripts/check-cross-package-test-inputs.mjs— same OK line (0)check:entry-guard—✓ check:entry-guard: 129 scripts/ file(s) — every entry guard goes through invoked-as.mjs…(0)check:parse-guard— (0)check:nul-bytes—check-nul-bytes: OK (scanned 6292 text file(s)…; no raw ASCII control bytes).(0)check:published-readme-exportsneeds a built workspace and is left toCI's
typecheck-consumersjob, which builds first. On this unbuilt checkout it hard-errors bydesign (
194 package(s) are not built— #4690), identically fororigin/main's script andthis one. That also means the per-document numbers against the real tree are not measured
here: #10368's
47 sites / 18 receiversvs30 / 17is neither confirmed nor denied by thisPR, and out of scope for it — #10368 remains open and untouched. One
--unread-reportrun on abuilt tree now settles it.
Scope
⛔ Not a widening of what the gate can type — #9870's option D stays declined pending its
census. No new receiver kind is resolved;
unreadCallSitesis the existing matcher, theexisting fence and the existing dedup, returning the list it was already building instead of
its cardinality.
lineis carried, not counted, so no number moved.skip-changeset: this PR edits one CI-internal script and publishes nothing — the caselint.yml calls textbook for the label. Its own self-test half still runs in CI, in
typecheck-consumers, which carries no label exemption.Generated by Claude Code