fix(ci): poll the npm publish read-back for 5 minutes, and say what the registry served - #1830
Conversation
|
Fixed at The bug is exactly as described: Why my suite could not see it, which is the part worth recording: the three tests that pass
Ledger 13/13 red on a clean baseline, every file restored by sha256, including three new mutations aimed at this fix: the deadline removed, the clock replaced by the interval sum, and the reported elapsed hard-coded. The kill-timeout itself is deliberately not mutated — it is the instrument here, so a mutation of it is unobservable without a second defect, and I would rather say that than list a green. One note on scope: the workflow never sets |
|
Lane gate at
Press to @connector-ops once the in-flight checks land. 🤖 Generated with Claude Code |
…he registry served cli 0.1.65 (run 35498255344) and 0.1.66 (run 35804128707) both published fine while this step went red, because the inline loop gave up after ~60s. A red run on a known-good publish is the stale-signal shape reviewers learn to ignore, so the next red — the one that means the publish never landed — reads the same. The loop moves to scripts/verify-npm-publish-readback.sh, which the suite can reach: a workflow body cannot be executed here, and this is the loop that was wrong. Budget default 300s (was ~60s), interval 10s, and exhaustion now prints what the registry actually serves (dist-tags, latest) plus npm's own last error instead of only naming the version that never appeared. backend/__tests__/unit/scripts/npmPublishReadback.test.js: 7 tests over a stub npm (slow propagation succeeds at attempt 6; budget honoured; exhaustion names the served version and exits 1; a missing WANT is refused; the default budget is >= 300s at a >= 5s interval) plus two wiring assertions against the workflow text. Mutation ledger 10/10 red, clean baseline zero failure lines.
…intervals
Vera 71347, reproduced. `elapsed` advanced only by READBACK_INTERVAL_SECONDS, so
the budget depended on an env var the caller controls:
- READBACK_INTERVAL_SECONDS=0 with any non-zero timeout NEVER terminated (789
attempts in 8s, still reporting 0s, killed by an external alarm). In a release
job that is not a red step — it is a run held to the six-hour limit.
- at any interval the reported elapsed undercounted by every `npm view`
round-trip, so the "after 300s" in the failure line was not what happened.
`started_at=$(date +%s)` once, and `elapsed` recomputed after each poll, is the
whole fix: the deadline is a clock, the interval is only the gap between polls.
Two tests, because the old suite could not see either defect: the three tests
that pass READBACK_INTERVAL_SECONDS=0 all ended via the stub eventually
succeeding, so a stub regression would have HUNG the suite rather than failed it.
`runScript` now takes a kill-timeout for the same reason — a non-terminating loop
has to fail an assertion, not look like an infrastructure problem.
- interval 0 with a 2s budget and a registry that never publishes: exits 1
after more than one attempt, within the kill-timeout (the reproduction);
- a 1.2s stub with interval 0 reports "[1-9]Ns after publish" — the old
arithmetic could only ever have said 0s.
Ledger 13/13 red on a clean baseline, sha256 restores.
cd14f79 to
b4e1698
Compare
Closes TASK-100.
npm-publish.yml's "Read it back from the registry" was a false negative on every slow propagation, and it fired on two consecutive good publishes.The measured incident
0.1.65npxreturned the new version0.1.66The step allowed six attempts × 10s and slept after the sixth, so its real window was ~60s. npm's own documentation puts propagation at "a few minutes". A check that is red on a known-good outcome is worse than a miss: it is the stale signal reviewers learn to ignore, so the next red — the one that means the publish never landed — gets read the same way.
The change
The loop moves out of the workflow body into
scripts/verify-npm-publish-readback.sh(.github/workflows/npm-publish.ymlnow calls it under the sameif: steps.cmp.outputs.action == 'publish'gate). A workflow body cannot be executed by the suite, and this is the loop that was wrong — so it becomes the kind of thing that can be tested and mutated.READBACK_TIMEOUT_SECONDS, default 300s (was ~60s),READBACK_INTERVAL_SECONDSdefault 10s. Paid only by pushes that actually publish; every other push takes the "registry already equal" path and never reaches this step.dist-tags,latest, and npm's own last error — instead of only the version it was looking for. The old message could not distinguish "propagation is still slow" from "the publish never happened" from "this token cannot read the package": the same false negative one layer down. Live output for a version that does not exist:exit 1on exhaustion, stillexit 0only on a value it read. Smoke-checked against the live registry both ways (0.1.67→ green,0.1.99→ the block above, exit 1).Tests
backend/__tests__/unit/scripts/npmPublishReadback.test.js, 7 tests against a stubnpmonPATHwhose behaviour is stated per test rather than mocked inside the script:WANTis refused before any poll (no attempt file written);6/7. wiring: the read-back step runs the script, keeps the publish gate, keeps
NAME/WANTfrom the compare step, names the interpreter, and the old inlinefor i in 1 2 3 4 5 6loop is gone rather than shadowed.Mutation ledger: 10/10 red, clean baseline with zero failure lines, every file restored by sha256 assertion.
One disclosure, because it is this PR's own lesson.
M6first read "diagnostics removed" while replacing only the banner line — a no-op-shaped mutation aimed at something no test asserted, and it SURVIVED. It was the instrument, not the code, and the fix was two-sided: the banner is now asserted (it is the line a human reads first, and it is operator-visible, so it is part of the artifact) and the mutation was re-aimed at the two lines that carry the registry's actual state. That is exactly AX audit entry 67, which lands in #1829 — a survivor that turns out to be the instrument earns its keep.Gates
Workflow file ⇒ per TASK-100 this wants @wren + @vera. Full backend suite green on this tree (421 suites, 3834 passed, 24 skipped),
lint:ts0 errors. No version slot:scripts/and.github/are outside the$pkg/srcthe version guard watches.