Skip to content

feat(net): CircuitBreaker resilience layer (Slice 1, PR 4)#85

Merged
NotAProfDev merged 11 commits into
mainfrom
feat/net-http-circuit-breaker
Jul 5, 2026
Merged

feat(net): CircuitBreaker resilience layer (Slice 1, PR 4)#85
NotAProfDev merged 11 commits into
mainfrom
feat/net-http-circuit-breaker

Conversation

@NotAProfDev

@NotAProfDev NotAProfDev commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Closes #84.

Slice 1, PR 4 of the net-http resilience layers (after RateLimit #76, Timeout #78, Retry #82; before Tracing #83). Spec: docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md; plan: docs/superpowers/plans/2026-07-04-net-http-circuitbreaker-layer.md; ADR-0031 §5, ADR-0034 #9.

What

  • CircuitOpen error surface — new ErrorKind::CircuitOpen + HttpError::CircuitOpen, non-retryable fast-reject.
  • CircuitBreakerConfig + outcome classifier — 4-class partition (Failure / TripNow / Ignored / Success): Connection/Timeout/5xx count as failures; Throttled/429 trips immediately with the long throttle_cooldown; 4xx/Auth/unclassified neither trip nor mask a building outage; Unknown → Ignored for v1.
  • Pure clock-injected Breaker state machine (Closed/Open/Half-Open) — table-tested with zero async; lazy Half-Open probing via now() only (no sleep, no new dependency).
  • CircuitBreaker<S, T> + CircuitBreakerLayer<T> — thin Arc<Mutex<Breaker>> async Service shell over the breaker, single per-host breaker; body-transparent; sits outside Retry so it counts logical post-retry outcomes.
  • Half-Open cancellation wedge closed with a probe drop-guard.

Verification

  • just ci green locally (fmt, lint, test + doctests, doc, deny, machete, gitleaks, actionlint, shellcheck).
  • CHANGELOG.md [Unreleased] updated.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a net-http circuit breaker layer with Closed/Open/Half-Open behavior, including configurable failure thresholds, cooldowns, and bounded Half-Open probes.
    • When open, requests are rejected immediately with a new non-retryable “circuit open” error (HttpError::CircuitOpen / ErrorKind::CircuitOpen).
  • Bug Fixes

    • Improved classification of outcomes (e.g., 429/throttling trips fast; 4xx/Auth won’t reset failure streaks; transport/5xx count toward trip).
  • Documentation

    • Added ADR/design specs and updated the Unreleased changelog entry.
  • Tests

    • Added unit and integration coverage for state transitions and cancellation/abandoned Half-Open probes.

NotAProfDev and others added 10 commits July 4, 2026 19:25
Design for the net-http CircuitBreaker resilience layer (ADR-0031 §5): a
pure, clock-injected Breaker state machine (Closed/Open/Half-Open) behind a
thin Arc<Mutex<Breaker>> + Timer Service shell; trips on consecutive
Connection/Timeout/5xx failures (or immediately on Throttled/429 with the
long throttle_cooldown), fast-rejects with a new non-retryable
HttpError::CircuitOpen / ErrorKind::CircuitOpen, lazy Half-Open probing with
now()-only timing (no sleep, no futures-util, no new dependency). Adds a
4-class outcome partition (Failure / TripNow / Ignored / Success) so 4xx/Auth
and unclassified errors neither trip nor mask a building outage; Unknown ->
Ignored for v1 with the resilience4j fail-safe recorded as a future
improvement. To be recorded as ADR-0034 amendment #9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds BreakerState/Admit/Breaker (pub(crate), not re-exported) to
circuit_breaker.rs per ADR-0031 §5: new/admit/record, clock-injected via
now: Instant so the whole transition table is testable with zero async.
Half-Open treats Ignored and Success identically (a reached-host probe
resolves it); Open ignores stale record() calls (single global v1 breaker).

Task 4's Service will consume these types, removing the transitional
#[allow(dead_code)] added here to keep just lint --all-targets green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add CircuitBreakerLayer<T> (Layer<S> factory) and CircuitBreaker<S, T>
(the wrapping Service) over the pure Breaker state machine from Tasks
2-3: locks briefly to admit (via timer.now()), releases the lock, runs
inner.call().await (or fast-rejects CircuitOpen), then locks briefly
to record the classified outcome. The Mutex guard never crosses the
await. Removes the transitional #[allow(dead_code)] attributes from
Tasks 2-3 now that Class/classify/Breaker/Admit are wired into
non-test code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
If the CircuitBreaker call future is dropped before recording the outcome
(caller cancellation, or an inner panic) while a Half-Open probe is in
flight, the breaker was stranded at probes_left:0 forever. Add a
state-aware Breaker::on_abandoned_probe (reopen from Half-Open, no-op in
Closed/Open) and an RAII ProbeGuard that fires it on drop, so a cancelled
probe self-heals after a fresh cooldown instead of wedging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a67b1c17-efe9-4197-ba77-222655f9eecc

📥 Commits

Reviewing files that changed from the base of the PR and between 5f378bd and b6027bc.

📒 Files selected for processing (2)
  • docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md
  • docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md
✅ Files skipped from review due to trivial changes (1)
  • docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md

📝 Walkthrough

Walkthrough

This PR adds a CircuitBreaker resilience layer to net-http-api: a new HttpError::CircuitOpen/ErrorKind::CircuitOpen error mode, a pure clock-injected Breaker state machine with outcome classification, an async CircuitBreaker/CircuitBreakerLayer middleware shell, crate exports, and supporting ADR/spec/plan/changelog documentation.

Changes

CircuitBreaker resilience layer

Layer / File(s) Summary
CircuitOpen error variants
crates/adapter/net/api/src/error_kind.rs, crates/adapter/net/http/api/src/error.rs
Adds ErrorKind::CircuitOpen and HttpError::CircuitOpen, wires kind() mapping, and updates the mapping test.
Classification and pure Breaker state machine
crates/adapter/net/http/api/src/circuit_breaker.rs
Implements outcome classification and the pure Breaker transitions for Closed, Open, and HalfOpen, with unit tests for classification and state changes.
CircuitBreakerLayer and async middleware
crates/adapter/net/http/api/src/circuit_breaker.rs
Adds CircuitBreakerLayer construction, the CircuitBreaker service implementation with ProbeGuard cancellation handling, and end-to-end async middleware tests.
Crate module wiring and public re-exports
crates/adapter/net/http/api/src/lib.rs
Declares pub mod circuit_breaker and re-exports CircuitBreaker, CircuitBreakerConfig, and CircuitBreakerLayer.
ADR, spec, plan, and changelog documentation
docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md, docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md, docs/superpowers/plans/2026-07-04-net-http-circuitbreaker-layer.md, CHANGELOG.md
Adds the CircuitBreaker changelog entry, ADR amendment, implementation plan, and design spec describing the layer, constraints, tests, and module touch points.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • NotAProfDev/oath#56: Establishes the ADR-level resilience and layer-ordering design that this CircuitBreaker implementation follows.
  • NotAProfDev/oath#58: Introduces the Timer contract and net-http-api service surface used here for timing and request execution.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and clearly describes the circuit breaker feature change.
Linked Issues check ✅ Passed The changes implement the requested CircuitBreaker layer, error surfaces, state machine, classification, and thin shared service shell.
Out of Scope Changes check ✅ Passed All touched files support the circuit breaker work through code, docs, or changelog updates; no unrelated scope is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/net-http-circuit-breaker

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md`:
- Around line 234-237: Tighten the circuit-breaker state description so
`Class::Ignored` is only described as not affecting the Closed-state failure
streak, since `circuit_breaker.rs::classify` and the Half-Open probe handling
currently close probes on `Ignored`. Update the wording around the `partition`
rules to make clear that `4xx`/`Auth`/`Unknown` never trip and do not reset the
Closed-state counter, but they do resolve Half-Open probes rather than “never
resets” in every state.

In `@docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md`:
- Around line 415-417: Remove the stale `half_open_probes > 1` question from the
testing notes in the circuit breaker design spec, since the multi-probe table
test is already committed elsewhere. Update that item in the testing section to
either a confirmed decision or delete it entirely so it no longer reads like
open planning; use the `half_open_probes`, `probes_left`, and `successes_needed`
terminology to keep the wording aligned with the rest of the spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 92cdf6ef-92da-4214-aa66-37e0f748ecbd

📥 Commits

Reviewing files that changed from the base of the PR and between 68d8f60 and 5f378bd.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • crates/adapter/net/api/src/error_kind.rs
  • crates/adapter/net/http/api/src/circuit_breaker.rs
  • crates/adapter/net/http/api/src/error.rs
  • crates/adapter/net/http/api/src/lib.rs
  • docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md
  • docs/superpowers/plans/2026-07-04-net-http-circuitbreaker-layer.md
  • docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md

Comment thread docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md Outdated
Comment thread docs/superpowers/specs/2026-07-04-net-http-circuitbreaker-layer-design.md Outdated
Address CodeRabbit review on #85:
- ADR-0034 #9: "never resets" was unqualified but only holds for the
  Closed-state failure streak; in Half-Open a reached-host Ignored resolves
  the probe like a Success (circuit_breaker.rs record()). Scope the wording.
- CB design spec open-question #4: convert the stale "table-test >1 now?"
  question to Resolved — the Testing section commits to the two-probe case
  and multi_probe_half_open_requires_all_to_close ships it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NotAProfDev NotAProfDev merged commit 86720ca into main Jul 5, 2026
5 checks passed
@NotAProfDev NotAProfDev deleted the feat/net-http-circuit-breaker branch July 5, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(net): CircuitBreaker resilience layer (Slice 1, PR 4)

1 participant