Initial Commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: network-plugin-circuit-breaker (complete-network-plugins)
|
||||
|
||||
**Package**: `@suaveplan/network-plugin-circuit-breaker`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. First canonical spec for this package.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: MeshCircuitBreaker implements three-state breaker FSM
|
||||
|
||||
The package SHALL export `MeshCircuitBreaker`, `MeshCircuitBreakerOptions`, a `CircuitBreakerState` union `"CLOSED" | "OPEN" | "HALF_OPEN"`, and a `StateChangeListener` type. State transitions SHALL follow CLOSED→OPEN on failure threshold, OPEN→HALF_OPEN after cooldown, HALF_OPEN→CLOSED on success or HALF_OPEN→OPEN on failure. Illegal transitions SHALL throw `CircuitBreakerError(INVALID_TRANSITION)`.
|
||||
|
||||
#### Scenario: failure threshold opens the breaker
|
||||
|
||||
- **WHEN** the configured number of consecutive failures is recorded
|
||||
- **THEN** the breaker transitions to `OPEN` and notifies registered `StateChangeListener`s
|
||||
|
||||
#### Scenario: cooldown elapsed transitions to HALF_OPEN
|
||||
|
||||
- **WHEN** the breaker is `OPEN` and `cooldownMs` has elapsed
|
||||
- **THEN** the next probe transitions to `HALF_OPEN`
|
||||
|
||||
**Verified by:** `src/breaker/breaker.spec-conformance.test.ts::"opens after failure threshold"` AND `src/breaker/breaker.spec-conformance.test.ts::"transitions to HALF_OPEN after cooldown"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Calls through an open path throw PATH_OPEN
|
||||
|
||||
The breaker SHALL throw `CircuitBreakerError(PATH_OPEN)` when a call is attempted through a path that is in the `OPEN` state.
|
||||
|
||||
#### Scenario: call through open path
|
||||
|
||||
- **WHEN** `breaker.run(fn)` is called while state is `OPEN`
|
||||
- **THEN** the call throws `CircuitBreakerError` with `code === "CB.PATH_OPEN"` and `fn` is NOT invoked
|
||||
|
||||
**Verified by:** `src/breaker/breaker.spec-conformance.test.ts::"throws PATH_OPEN when path is open"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: createCircuitBreakerMiddleware integrates with the relay path
|
||||
|
||||
The package SHALL export `createCircuitBreakerMiddleware`, `CircuitBreakerMiddlewareOptions`, `RelayNext`, and a `PathIdBuilder` type. The middleware SHALL wrap each relay call in the breaker associated with the resolved path id.
|
||||
|
||||
#### Scenario: middleware short-circuits an open path
|
||||
|
||||
- **WHEN** the breaker for the resolved path id is `OPEN` and the middleware is invoked
|
||||
- **THEN** `next()` is NOT awaited and the middleware throws `CircuitBreakerError(PATH_OPEN)`
|
||||
|
||||
**Verified by:** `src/middleware/middleware.spec-conformance.test.ts::"short-circuits open path"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: HalfOpenProbe scouts recovery before reopening the path
|
||||
|
||||
The package SHALL export `HalfOpenProbe`, `HalfOpenProbeOptions`, and a `ProbeSender` type. The probe SHALL send a minimal canary message via the `ProbeSender` and SHALL transition the breaker to `CLOSED` only when the canary succeeds.
|
||||
|
||||
#### Scenario: canary succeeds
|
||||
|
||||
- **WHEN** the half-open probe runs and the `ProbeSender` resolves
|
||||
- **THEN** the breaker transitions to `CLOSED`
|
||||
|
||||
**Verified by:** `src/probe/probe.spec-conformance.test.ts::"closes breaker on canary success"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: CircuitBreakerRegistry manages per-path breaker instances
|
||||
|
||||
The package SHALL export `CircuitBreakerRegistry` and `CircuitBreakerRegistryOptions`. The registry SHALL lazily create a breaker per path id and SHALL surface stable lookup semantics.
|
||||
|
||||
#### Scenario: registry returns the same instance for the same path id
|
||||
|
||||
- **WHEN** `registry.for(pathId)` is called twice with the same id
|
||||
- **THEN** the same `MeshCircuitBreaker` instance is returned both times
|
||||
|
||||
**Verified by:** `src/registry/registry.spec-conformance.test.ts::"returns same instance for same path id"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Error-code registry is frozen and namespaced CB
|
||||
|
||||
`CB_CODES` SHALL be frozen under the `CB` namespace and SHALL include exactly: `PATH_OPEN`, `INVALID_TRANSITION`. Every code SHALL be reachable from a production throw site.
|
||||
|
||||
#### Scenario: registry membership
|
||||
|
||||
- **WHEN** `CB_CODES` is inspected
|
||||
- **THEN** every key maps to a value of the form `"CB.<KEY>"`
|
||||
|
||||
**Verified by:** `src/error-codes.spec-conformance.test.ts::"CB_CODES is frozen and prefixed CB"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: No bare-Error throws in production code
|
||||
|
||||
`src/` SHALL contain zero bare `throw new (Error|TypeError|RangeError|SyntaxError)\(` sites outside test files.
|
||||
|
||||
#### Scenario: source grep returns zero hits
|
||||
|
||||
- **WHEN** the grep is run
|
||||
- **THEN** the output is empty
|
||||
|
||||
**Verified by:** `tests/no-bare-throws.test.ts::"network-plugin-circuit-breaker src/ has no bare Error throws"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: README is auto-docs idempotent
|
||||
|
||||
The package `README.md` SHALL contain exactly one balanced auto-docs block; the generator SHALL produce a byte-identical file on a second run.
|
||||
|
||||
#### Scenario: marker count is exactly one
|
||||
|
||||
- **WHEN** `grep -c "begin:auto-docs" packages/network/network-plugin-circuit-breaker/README.md`
|
||||
- **THEN** output is `1`
|
||||
|
||||
**Verified by:** `scripts/generate-package-module-tree.test.ts::"README regen is idempotent for network-plugin-circuit-breaker"`
|
||||
Reference in New Issue
Block a user