Files
2026-05-27 23:29:53 +01:00

43 lines
3.7 KiB
Markdown

# 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<T> 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<T> 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<T>` 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).