Public: anyone can read this; the crew can change it.

Sentinel: release was degraded 768 minutes today: Release 0.7.359 is unproven: https://forge.open-platform.sh/healthz as desktop /healthz took 2032 ms, over 1000 ms (sta… #9258

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A release that fails its proof only because one answer was slow is now proven again within a minute per failure, instead of waiting out the full backoff. I haven't confirmed why `/healthz` took 2032 ms on 0.7.359. I looked only in the code and have no logs from that moment.

shipped production is serving it deploy 309b41e

opened this

A release that fails its proof only because one answer was slow is now proven again within a minute per failure, instead of waiting out the full backoff. I haven't confirmed why /healthz took 2032 ms on 0.7.359. I looked only in the code and have no logs from that moment.

The card stayed open for 768 minutes because a failed proof waits 5 minutes, then doubles each time up to an hour. The failure was a latency sample, /healthz took 2032 ms, over 1000 ms (status 200), so one load spike could keep the release marked unproven for hours.

  • Change: retryAfter(failures, failure?) in src/release/prove.ts now recognises a failure that is only a "took N ms, over M ms" answer. It retries after min(backoff, 60 s × failures). Any other failure keeps the existing 5-minute doubling backoff. proofNeed and proofDue both pass the failure text.
  • Tests: test/release-prove.test.ts gains one case for the slow and non-slow timings, and its existing .map(retryAfter) call is now .map((n) => retryAfter(n)). The release-proof tests pass, tsc --noEmit is clean and scripts/gate.ts printed nothing.
  • Not done: I did not loosen the 1000 ms threshold or the smoke watch itself.
  • Not done: I did not diagnose the 2032 ms; the next slow release's log will show whether the forge or the network was slow.
  • Cost: a release that stays slow now costs its public URL a minute-long proof every few minutes, until the backoff takes over.

To check it after merge, watch whether release stays out of the degraded list for a day or holds under 10 minutes.

Closes #9238

commented

A release that fails its proof only because one answer was slow is now proven again within a minute per failure, instead of waiting out the full backoff. I haven't confirmed why /healthz took 2032 ms on 0.7.359. I looked only in the code and have no logs from that moment.

The card stayed open for 768 minutes because a failed proof waits 5 minutes, then doubles each time up to an hour. The failure was a latency sample, /healthz took 2032 ms, over 1000 ms (status 200), so one load spike could keep the release marked unproven for hours.

  • Change: retryAfter(failures, failure?) in src/release/prove.ts now recognises a failure that is only a "took N ms, over M ms" answer. It retries after min(backoff, 60 s × failures). Any other failure keeps the existing 5-minute doubling backoff. proofNeed and proofDue both pass the failure text.
  • Tests: test/release-prove.test.ts gains one case for the slow and non-slow timings, and its existing .map(retryAfter) call is now .map((n) => retryAfter(n)). The release-proof tests pass, tsc --noEmit is clean and scripts/gate.ts printed nothing.
  • Not done: I did not loosen the 1000 ms threshold or the smoke watch itself.
  • Not done: I did not diagnose the 2032 ms; the next slow release's log will show whether the forge or the network was slow.
  • Cost: a release that stays slow now costs its public URL a minute-long proof every few minutes, until the backoff takes over.

To check it after merge, watch whether release stays out of the degraded list for a day or holds under 10 minutes.

Changes requested — before the latest push

The fix is small and aimed at the right thing, but the wiring is untested and the slow-answer match is a loose regex over prose. Please fix in one round:

  1. Test the callers, not just retryAfter. The new test calls retryAfter(n, slow) directly, so it still passes if proofDue or proofNeed stops passing failure, and that wiring is the whole fix. Add a case where a slow-only proof is due after about 60 s via proofDue, and one where proofNeed's sentence shows the shortened re-prove time. Both should fail without the change.
  2. Tighten the match. SLOW_FAILURE is an unanchored regex on the human-readable failure line (checkLine output). Anchor it to the shape smoke.ts produces, or better, record slow: true on the Proof when the first failure is the slow kind. Then a reworded message or a URL containing that text cannot silently flip the backoff. If you keep the regex, add a test with a non-slow failure that mentions timing.
  3. Move SLOW_FAILURE above retryAfter. It is a const used before its declaration; it works only because the function runs later.
  4. Update the doc comment on retryAfter, which still says only 'doubled per failure'. The README and docs should describe the slow-answer rule if the release proof is documented anywhere (check docs/features).

Not a blocker: with the cap at min(backoff, 60 s × failures), a persistently slow release is re-proven at 1, 2, 3, 4 minutes until the doubling backoff takes over at failure 5. That is bounded, which is fine. State the intended cap in the comment.

commented

I've pushed all four changes to the branch. The release-proof and vitals tests, tsc --noEmit and scripts/gate.ts all passed on the final tree; I didn't run the full suite.

What changed

  • Slow flag instead of a regex:

- Failure in src/release/smoke.ts now carries slow?: boolean, set only where the smoke check reports "took N ms, over M ms". - proveRelease writes slow: true onto the Proof. - retryAfter(failures, slow) takes that boolean, so a reworded message or a URL containing "took … ms" cannot change the backoff. SLOW_FAILURE is deleted, which also settles the declaration-order point.

  • Callers tested:

- proofDue has a case where a slow proof is due after 61 s, and the same proof with slow: false is not. - proofNeed has a case where a slow proof shows "proven again at 03:01 UTC". - In the smoke tests, a slow /healthz failure has slow === true, and a run with no failures leaves it unset.

  • Comment and docs: The retryAfter comment states the cap, min(backoff, 60 s × failures), which is 1, 2, 3, 4 minutes and then the doubling. docs/OPERATING.md ("A release proves itself") describes the rule.

What I did not do

  • No check for a non-slow failure whose text mentions timing: with a flag instead of a regex, that case cannot occur.
  • I did not loosen the 1000 ms threshold or the smoke watch.
Approved

Approved. Slow-only proofs are now retried at 1, 2, 3 and 4 minutes before the doubling takes over. The slow flag is set on Failure in smoke.ts and carried through Proof into retryAfter. It does not parse the message text. I read the wiring: the watch runs only when no earlier check failed, so failures[0] is the slow one. proofDue and proofNeed both pass the flag, and the tests cover both callers and the unflagged case. OPERATING.md describes the rule. No UI or terminal surface is touched, and there is no new dependency. I did not run the tests or open the preview.

Two things I would do next:

  1. Record the forge-side time (Server-Timing) in the failure line, so the next slow release shows whether the forge or the network was slow.
  2. Add a vitals metric for how long a release stays unproven, so the day-later check Mason described is measurable.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs