3.7 KiB
OpenSpec Change: retry-spec-conformance
- Change ID: retry-spec-conformance
- Package:
@suaveplan/retry - Status: PROPOSED
- Date: 2026-04-26
Summary
Add **Verified by:** lines to every requirement in openspec/specs/retry/spec.md, fill four implicit-only test scenarios (RetryConfig generic threading, deterministic jitter under an injected Rng, per-attempt AbortSignal forwarding to fn, mid-backoff abort yielding RetryAbortedError), pin the full circuit-breaker FSM walk in a single test, and round-trip every published Zod schema (RT-09) so bun scripts/openspec-conformance.ts @suaveplan/retry exits 0. No public-API changes.
Current state
@suaveplan/retry is structurally solid: retry/withRetry/withTimeout/sleep engine, three backoff factories (constant/linear/exponential) with four jitter modes, CircuitBreaker FSM with EventBus-emitted stateChange, four structured errors (RetryExhaustedError, RetryAbortedError, AttemptTimeoutError, CircuitOpenError), OTel recorder helpers, eight Zod schemas, fast-check property tests, 100% coverage. Conformance gaps are documentary plus four un-pinned scenarios:
- 11/11 requirements lack
**Verified by:**lines (cf.openspec/AGENTS.md§13.1). - "RetryConfig generic threading" — type-level scenario with no
expectTypeOfassertion. - "Deterministic jitter under injected Rng" — covered indirectly by single-shot rng tests but no two-strategy cross-check.
- "Signal forwarded to fn on every attempt" — covered for one attempt only.
- "Mid-backoff abort yields
RetryAbortedError, notRetryExhaustedError" — implicit inaborts between attemptsbut no explicit type check. - "Full FSM walk closed→open→half-open→closed→open→half-open→open in one trace" — separate transitions covered, full path not pinned.
- Zod schemas (RT-09) are exported but never round-tripped in tests.
Spec drift noted (no code change)
The withTimeout signature in the implementation is (fn, ms, attemptNumber, signal?) (4 args), while the spec previously read (fn, ms, signal?). The implementation is the more complete shape — attemptNumber is required to populate AttemptTimeoutError.attemptNumber. This delta updates the spec language to match the shipped signature; no code change.
What changes
- Add
**Verified by:**line under every### RT-NN —block inopenspec/specs/retry/spec.md. - Update RT-05 spec language to match the shipped 4-arg
withTimeoutsignature. - Update RT-10 spec language so it reflects the opt-in helper-based emission model (no auto-instrumentation in
retry()/CircuitBreaker.call()). - Add
src/retry/retry.spec-conformance.test.tscovering:retry<T>generic narrowing (expectTypeOf), deterministic seeded-Rng jitter sequence, signal-forwarded-to-fn-on-every-attempt, mid-backoff abort instanceofRetryAbortedErrornotRetryExhaustedError, backoff factory purity,RetryExhaustedErrorcause chain. - Add
src/circuit-breaker/circuit-breaker.spec-conformance.test.tscovering: full closed→open→half-open→closed→open→half-open→open trace in one assertion,open-state never invokes fn,CircuitOpenError.context.openedAtcarries the injected-clock timestamp. - Add
src/types/retry.schemas.spec-conformance.test.tscovering: round-trip parse + reject for every published schema (JitterMode,ConstantBackoffOptions,LinearBackoffOptions,ExponentialBackoffOptions,RetryConfig,CircuitBreakerConfig). - Re-run
bun scripts/openspec-conformance.ts @suaveplan/retryand confirm exit 0.
Impact
- Public API unchanged.
- Three new test files (~280 lines combined).
openspec/specs/retry/spec.mdgains 11**Verified by:**lines and two language clarifications (RT-05, RT-10).