Repository navigation
test(release): run recovery rehearsal observation reads on an injected clock - #877
Merged
Merged
Conversation
…d clock The HTTP recovery rehearsal failed once with "exact value read unavailable" in audit-dispatch (#874). Observation reads in recovery/observe.mjs are the only recovery reader path that ignores injected timing: they always use Date.now and a real 15s per-attempt read timer. Holding one loopback listReleaseAssets response for 16s reproduces the reported failure exactly (ERROR READ_TIMEOUT_UNSETTLED); the natural trigger was not reproduced. - readerContext accepts an optional complete clock (now, sleep, setTimer, clearTimer), threaded through every observe entrypoint and the writer and audit observation snapshots. Omitted, production behavior is unchanged. - The rehearsal injects the fixture clock the writer and auditor already use, with a 100s read-timer floor, below the 120s per-case timeout, so a hung read still fails loudly. - The "read unavailable" error now names the envelope status, code and operation. - Regenerate the release-script pins and the pin-file digest snapshot. Refs #874 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
blove
temporarily deployed
to
vercel-preview
September 30, 2026 20:24 — with
GitHub Actions
Inactive
This branch was previously deployed
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.
Refs #874. This PR does not close the issue. I confirmed the failure mechanism but did not reproduce the natural trigger, so #874 should stay open.
What I found
The phase-deadline theory doesn't fit.
phaseDeadlineMsis 1,200,000 ms (20 minutes) and the failing case ran for 23s. The real-time limits a single observe read can hit are the 15s per-attempt race timer and the 15s HTTP timeout inrunRecoveryRead.The mechanism is confirmed. I held one loopback
listReleaseAssetsGET inaudit-dispatchfor 16s during theeffect 9 aftercase. That reproduces the reported failure exactly:failed in audit-dispatch; service errors: []; ... exact value read unavailable. The underlying envelope was{ status: "ERROR", code: "READ_TIMEOUT_UNSETTLED" }. In that same run, effect 9's fault had already been resumed infive-lanes, soresume()could not retry again. That matches the trace in the issue, where the failure came from the firstoperation()call.The natural trigger is not reproduced. I ran the 64-case test under ten
node -e ""spawn loops, the test(k8s-compat): size the docker smoke watchdog for a wedge, not a stopwatch #715 recipe. Ambient load average was about 147 on 10 cores. Cases slowed to 50–120s, and four hit the 120s case timeout, but no observe read ever came back non-PRESENT. Two triggers are still possible:READ_TIMEOUT_UNSETTLED.RECOVERY_DEADLINEorTIMEOUT.Date.nowdeadlines include sleep time, but the test runner's monotonic duration does not, which could explain a reported 23s case that contains a 15s timeout.The improved diagnostic below will tell these apart next time.
Only observe reads ignored injected timing. The recovery writer and auditor already pass the fixture's
authorityclock intorunRecoveryRead. Observe'sreaderContextalways usedDate.nowand real timers. The writer and audit dependency snapshots also dropped any observation key they didn't list, so nothing could be injected there.Changes
exact ${key} read unavailablenow includes the envelope status, code and operation, for example(ERROR READ_TIMEOUT_UNSETTLED)or(AMBIGUOUS NOT_FOUND_OR_HIDDEN release-assets).readerContextaccepts an optional complete clock (now,sleep,setTimer,clearTimer). A clock missing any of these is rejected. The clock is passed through every observe entrypoint, throughrouteRecoveryCandidate's internal calls, and through the writer and audit observation snapshots. When no clock is passed, behaviour is unchanged. No production caller passes one, and nothing production accepts is loosened.(ERROR READ_TIMEOUT_UNSETTLED).runRecoveryReadcalls still use the real 15s race timer, because they pass onlynowandsleep. They are out of scope here since the observed failure was in observe.recovery/{audit,observe,writer}.mjsand theSTARTING_SCRIPT_PIN_SHA256snapshot.No changeset: these are release-script changes only.
Verification (Node 24.20.0)
pnpm test:release-integrity: 33/33 pass.node --test scripts/release/test/workflow-contracts.test.mjs: 166/166 pass.node --test scripts/release/test/recovery-observe.test.mjs: 78/78 pass, including three new tests:Date.now;pnpm test:release-controller: 3986/3986 pass (18.3 min).🤖 Generated with Claude Code