Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions docs/demos/retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ a dead backend can't be amplified.

<div class="hw-demo" id="retry-demo"></div>

<p>That was <b>one</b> client. The real danger of blind retries shows up at scale — when <b>many</b> clients retry the same outage at once.</p>
<p>That was <b>one</b> client recovering from a blip. The real danger of blind retries shows up at <b>scale</b> — when many clients hit a real outage at once.</p>

<div class="hw-demo" id="retry-herd"></div>

Expand All @@ -20,22 +20,8 @@ document.addEventListener('DOMContentLoaded', function () {
? { ok: false, ms: 0.05, label: 'blip' } : { ok: true, ms: 0.05 },
chainB: { retry: { maxAttempts: 3, baseDelay: 0.1, maxDelay: 5.0 },
budget: { ttl: 10.0, minRetriesPerSec: 10.0, percentCanRetry: 0.2 } } },
{ id: 'sustained', label: 'Sustained outage', dur: 12.5,
fault: (now) => (now >= 2.0 && now < 9.0)
? { ok: false, ms: 0.05, label: 'DOWN' } : { ok: true, ms: 0.05 },
chainB: { retry: { maxAttempts: 3, baseDelay: 0.1, maxDelay: 5.0 },
budget: { ttl: 10.0, minRetriesPerSec: 10.0, percentCanRetry: 0.2 } } },
],
buildStops: (scenario) => scenario.id === 'sustained' ? [
{ when: (s) => s.now >= 1.2, spot: ['ifA', 'ifB'], title: 'A backend blip appears',
body: 'Both clients hit the same transient errors. Watch how each responds.' },
{ when: (s) => s.now >= 2.4, spot: ['ifB'], title: 'httpware retries the blip',
body: 'The plain client surfaces the error immediately. httpware retries with backoff — most of these recover on attempt 2 or 3, invisibly to the caller.' },
{ when: (s) => s.mw.budgetExhausted, spot: ['ifB'], title: 'The budget refuses to amplify',
body: 'On a SUSTAINED outage, blind retries would multiply load. The budget is spent — httpware STOPS retrying and fails fast, protecting the dying backend instead of hammering it.' },
{ when: (s) => s.now >= 10.0, spot: ['ifA', 'ifB'], title: 'Blip: recovered. Outage: contained',
body: 'Retry rescues transient errors without turning a real outage into a storm. That cap is the whole reason the budget exists.' },
] : [
buildStops: () => [
{ when: (s) => s.now >= 1.2, spot: ['badWrapA', 'badWrapB'], title: 'A backend blip appears',
body: 'Both clients are about to hit the same transient errors. Keep your eye on the ✗ failed counts — they start equal at zero.' },
{ when: (s) => s.now >= 2.4, spot: ['badWrapA', 'badWrapB'], title: 'Plain surfaces every error; httpware retries',
Expand All @@ -47,7 +33,7 @@ document.addEventListener('DOMContentLoaded', function () {

HttpwareDemo.mountHerd('#retry-herd', {
clients: 20,
intro: 'A real backend rarely dies cleanly — it <b>flaps</b>: fails, recovers, fails again. These strips show <b>backend call-rate over time</b> for twenty clients through three dips. Press play and watch the shape.',
intro: 'Retry rescues a transient blip (above) — but it can’t fix an <i>outage</i>: retried or not, the caller sees the same failures. There the danger isn’t caller failures, it’s <b>amplification</b>. A real backend rarely dies cleanly — it <b>flaps</b>: fails, recovers, fails again. These strips show <b>backend call-rate over time</b> for twenty clients through three dips. Press play and watch the shape.',
scenario: { id: 'storm', dur: 12.5,
fault: (now) => {
const down = (now >= 2.0 && now < 4.0) || (now >= 5.5 && now < 7.5) || (now >= 9.0 && now < 11.0);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
summary: Dropped the redundant "Sustained outage" scenario from the single-client retry demo (retry can't help the caller on a sustained outage, so its counters read ~equal and misled) and moved the myth-bust into the herd intro, which now owns the outage story with numbers that pop.
---

# Change: Drop the single-client retry demo's sustained-outage scenario

**Lane:** lightweight — docs demo only, one file (`docs/demos/retry.md`), no capability contract moves.

## Goal

The single-client retry demo's "Sustained outage" scenario shows plain ✗82 vs
httpware ✗81 — near-identical, because retry genuinely cannot rescue a caller
from a *sustained* outage (the backend is down the whole time). That is correct
model behavior, but it makes the scenario read as "no difference", against the
suite's design where every scenario spotlights a number that pops. Its stop copy
also overclaimed ("httpware STOPS retrying and **fails fast**") while the
fast-failed counter stays 0 — a budget-exhausted request still hits the backend
once and is counted as ✗, not a fast-fail.

## Approach

Remove the sustained scenario from the single-client demo, leaving only the
"Brief blip" scenario, where retry's caller-visible win is self-evident (plain
✗6 vs httpware ✗0). The sustained/amplification story now lives — with numbers
that pop (naive ~18× vs httpware ~3×) — in the herd view directly below, added
in `2026-07-19.02`. Preserve the "retry rescues blips, not outages" myth-bust as
one line of the herd intro rather than a whole flat scenario, and align the
bridging prose (the single-client demo now shows a blip, not an outage).

`buildStops` loses its `scenario.id === 'sustained'` branch and becomes the
single blip stop-list.

## Files

- `docs/demos/retry.md` — remove the `sustained` scenario + its stop branch;
simplify `buildStops`; add the myth-bust to the herd `intro`; retune the
bridging `<p>`.

## Verification

- [ ] `just docs-build --strict` clean; `just check-planning` OK.
- [ ] jsdom harnesses green: `verify-demos.js` (retry single-client tour still
completes with the one blip scenario), `verify-herd.js`, `verify-real-page.js`.
- [ ] `node --check docs/demos/engine.js` (unchanged, sanity).