# 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 `expectTypeOf` assertion. - "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`, not `RetryExhaustedError`" — implicit in `aborts between attempts` but 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 1. Add `**Verified by:**` line under every `### RT-NN —` block in `openspec/specs/retry/spec.md`. 2. Update RT-05 spec language to match the shipped 4-arg `withTimeout` signature. 3. Update RT-10 spec language so it reflects the opt-in helper-based emission model (no auto-instrumentation in `retry()` / `CircuitBreaker.call()`). 4. Add `src/retry/retry.spec-conformance.test.ts` covering: `retry` generic narrowing (`expectTypeOf`), deterministic seeded-Rng jitter sequence, signal-forwarded-to-fn-on-every-attempt, mid-backoff abort instanceof `RetryAbortedError` not `RetryExhaustedError`, backoff factory purity, `RetryExhaustedError` cause chain. 5. Add `src/circuit-breaker/circuit-breaker.spec-conformance.test.ts` covering: full closed→open→half-open→closed→open→half-open→open trace in one assertion, `open`-state never invokes fn, `CircuitOpenError.context.openedAt` carries the injected-clock timestamp. 6. Add `src/types/retry.schemas.spec-conformance.test.ts` covering: round-trip parse + reject for every published schema (`JitterMode`, `ConstantBackoffOptions`, `LinearBackoffOptions`, `ExponentialBackoffOptions`, `RetryConfig`, `CircuitBreakerConfig`). 7. Re-run `bun scripts/openspec-conformance.ts @suaveplan/retry` and confirm exit 0. ## Impact - Public API unchanged. - Three new test files (~280 lines combined). - `openspec/specs/retry/spec.md` gains 11 `**Verified by:**` lines and two language clarifications (RT-05, RT-10).