Skip to content

Commit 0cbc523

Browse files
lesnik512claude
andcommitted
chore(planning): archive the circuit-breaker-rate-mode bundle (#69)
Ship bookkeeping for PR #69: fill the 0.13.0 release-notes PR number, mark the bundle shipped (pr: 69), move it from changes/active/ to changes/archive/, flip its Index line to Archived, and trim the deferred "CircuitBreaker v2" item to the still-open axes (count-based window, manual control + state). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 89fe1cd commit 0cbc523

5 files changed

Lines changed: 13 additions & 26 deletions

File tree

planning/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,11 @@ carry **no** frontmatter — living prose, dated by git.
7070

7171
### Active
7272

73-
- **[circuit-breaker-rate-mode](changes/active/2026-06-16.02-circuit-breaker-rate-mode/design.md)** (2026-06-16) — Add an opt-in time-based failure-rate trip mode to the circuit breaker (classic stays default). Closes deferred item "CircuitBreaker v2 (a)". Targets 0.13.0.
73+
_None._
7474

7575
### Archived (shipped)
7676

77+
- **[circuit-breaker-rate-mode](changes/archive/2026-06-16.02-circuit-breaker-rate-mode/design.md)** (#69, 2026-06-16) — Added an opt-in time-based failure-rate trip mode to the circuit breaker (classic stays default). Shipped 0.13.0; closed deferred item "CircuitBreaker v2 (a)".
7778
- **[per-verb-with-response](changes/archive/2026-06-16.01-per-verb-with-response/design.md)** (#68, 2026-06-16) — Added `get_with_response``request_with_response` siblings (required `response_model`, returns `(Response, T)`) to both clients. Shipped 0.12.0; closed the deferred "Per-verb-with-response siblings" item.
7879
- **[custom-decoder-guide](changes/archive/2026-06-15.01-custom-decoder-guide/change.md)** (#67, 2026-06-15) — Docs: a "write your own `ResponseDecoder`" guide for Seam B, mirroring `docs/middleware.md`. Closed deferred item G6.
7980
- **[audit-doc-fixes](changes/archive/2026-06-14.06-audit-doc-fixes/change.md)** (#66, 2026-06-14) — Closed the [deep-audit](audits/2026-06-14-deep-audit.md) doc-accuracy findings: `Client.stream()` docs, terminal-call attribution, the four auto-raise sites, the pydantic upper bound, and root import paths.

planning/changes/active/2026-06-16.02-circuit-breaker-rate-mode/design.md renamed to planning/changes/archive/2026-06-16.02-circuit-breaker-rate-mode/design.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
2-
status: draft
2+
status: shipped
33
date: 2026-06-16
44
slug: circuit-breaker-rate-mode
55
supersedes: null
66
superseded_by: null
7-
pr: null
8-
outcome: null
7+
pr: 69
8+
outcome: Shipped 0.13.0 — opt-in time-based failure-rate trip mode (failure_rate_threshold + window_seconds + minimum_calls) on both breakers; classic stays default. Closed the "CircuitBreaker v2 (a)" deferred item; count-based windows, slow-call axis, and manual control + state remain deferred.
99
---
1010

1111
# Design: CircuitBreaker v2 — time-based failure-rate trip mode

planning/changes/active/2026-06-16.02-circuit-breaker-rate-mode/plan.md renamed to planning/changes/archive/2026-06-16.02-circuit-breaker-rate-mode/plan.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
---
2-
status: draft
2+
status: shipped
33
date: 2026-06-16
44
slug: circuit-breaker-rate-mode
55
spec: circuit-breaker-rate-mode
6-
pr: null
6+
pr: 69
77
---
88

99
# circuit-breaker-rate-mode — implementation plan

planning/deferred.md

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,13 @@ As of 0.7.0, all planned epics (3, 4, 5, 6) are closed — see the [change Index
88

99
### Resilience
1010

11-
- **CircuitBreaker v2 — rolling-window / failure-rate mode** (`src/httpware/middleware/resilience/circuit_breaker.py`) — the 0.10.0 breaker ships only the *classic consecutive-failure* model (open after N counted failures in a row; any success resets the streak). That can't catch *partial* degradation (e.g. a steady 50% error rate that alternates success/fail never trips). Deferred to v2 in the 0.10.0 spec; the config was shaped so a rate mode is purely additive (a new opt-in `failure_rate_threshold` + window + `minimum_calls`, with classic remaining the default). Demand-gated: build when someone needs rate-based tripping.
11+
- **CircuitBreaker v2 — remaining axes** (`src/httpware/middleware/resilience/circuit_breaker.py`) — 0.13.0 shipped axis **(a)**, the opt-in **time-based** failure-rate trip mode (`failure_rate_threshold` + `window_seconds` + `minimum_calls`; classic stays default). Still open, each independent and demand-gated:
1212

13-
**Comparison with the reference implementations** (verified against current docs, 2026-06-13):
13+
- **Count-based window variant** — a `window_type="count"` selector (ring buffer of the last N outcomes) alongside the shipped time-based window. Resilience4j offers both; we chose time-based first as the better HTTP-service fit. Additive: a new window-type knob, time-based remaining the default. Build if someone needs volume-relative (not time-relative) windows.
14+
- **(b) Manual control + read-only `state`**`force_open`/`force_closed` and a `state` introspection property (Resilience4j's registry, Polly's `StateProvider`/`ManualControl`). Parked as YAGNI in the 0.10.0 audit (decision 4: events-only control surface). Independent of the trip mode.
15+
- **(c) Slow-call-rate dimension***don't*: Resilience4j-only, and redundant with `AsyncTimeout`. Recorded here only so a future reader doesn't re-propose it.
1416

15-
| Axis | httpware v1 (shipped) | Resilience4j | Polly v8 |
16-
|---|---|---|---|
17-
| Trip model | consecutive count (`failure_threshold=5`) | failure **rate** over sliding window (`failureRateThreshold=50%`) | failure **rate** over time window (`FailureRatio=0.1`) |
18-
| Window | none (one counter) | count-based (default, size 100) *or* time-based | time-based only (`SamplingDuration=30s`) |
19-
| Min-volume floor | n/a | `minimumNumberOfCalls=100` | `MinimumThroughput=100` |
20-
| Consecutive-count mode | only mode | non-default | **removed in v8** (was v7 default) |
21-
| Half-open recovery | one probe, `success_threshold` consecutive successes (default 1) | permits N calls (default 10), closes on rate over them | one trial call, success→close (≈ httpware default) |
22-
| OPEN→HALF_OPEN | lazy (next request) | lazy, or optional timer (`automaticTransition…`) | lazy (next request after `BreakDuration=5s`) |
23-
| Failure classification | HTTP-native: `failure_status_codes` (5xx), **429/4xx = success** | generic exception predicate (`recordExceptions`/`ignoreExceptions`) | generic predicate (`ShouldHandle`, default all except cancellation) |
24-
| Slow-call trip axis | none (latency is `AsyncTimeout`'s job) | yes — `slowCallRateThreshold` (100%) / `slowCallDurationThreshold` (60s) | none |
25-
| Control surface | events-only (no `state`/`reset`/`isolate` — audit decision 4) | registry: state + metrics + manual transitions | `StateProvider` (read) + `ManualControl` (Isolate/Close) |
26-
27-
**Takeaways for scoping v2:** (1) Polly v8 *deleted* consecutive-count; Resilience4j doesn't default to it — so httpware v1's only mode is the one both treat as legacy/non-default. Adding rate mode while *keeping* classic is a small edge neither offers. (2) "Polly-v8-equivalent" = just the rate-over-window mode. "Resilience4j-equivalent" additionally implies count-vs-time window choice and (separately) manual control + state introspection. (3) httpware's HTTP-native classification (429-as-success out of the box) is already *ahead* of both generic-predicate libraries — don't regress it. (4) Skip the slow-call axis (Resilience4j-only; redundant with `AsyncTimeout`).
28-
29-
Three separable additions, rough priority: **(a) rate-over-window trip mode** (the core ask; additive opt-in), **(b) manual control + read-only `state`** (independent; both libraries have it, httpware parked it as YAGNI), **(c) slow-call-rate dimension** (don't — covered by `AsyncTimeout`).
30-
31-
**Concurrency note:** the window recorder (ring buffer for count-based; time-bucketed counters for time-based) is more state than v1's single counter, but recording an outcome stays a synchronous mutation, so the async lock-free atomicity invariant and the sync `threading.Lock` both still hold. Time-based eviction must read `_now()` inside the same synchronous critical section.
17+
**Don't regress:** httpware's HTTP-native failure classification (429/4xx = success out of the box) is already ahead of the generic-predicate breakers — preserve it in any v2 work.
3218

3319
### Documentation
3420

planning/releases/0.13.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,4 @@ The following remain deferred and are not part of 0.13.0:
7373

7474
## Shipped via
7575

76-
PR #XX — time-based failure-rate trip mode for the circuit breaker.
76+
PR #69 — time-based failure-rate trip mode for the circuit breaker.

0 commit comments

Comments
 (0)