Initial Commit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Capability Spec Delta: retry (spec-conformance)
|
||||
|
||||
**Package**: `@suaveplan/retry`
|
||||
**Status**: PROPOSED
|
||||
|
||||
This delta annotates each existing requirement in `openspec/specs/retry/spec.md` with a `**Verified by:**` line and clarifies the language of RT-05 (4-arg `withTimeout`) and RT-10 (opt-in helper-based OTel emission). No public-API changes.
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### RT-05 — Timeout
|
||||
|
||||
`withTimeout(fn, ms, attemptNumber, signal?)` MUST reject with `AttemptTimeoutError` (code `RETRY.TIMEOUT`) if `fn` does not settle within `ms` milliseconds. The internal timeout is cleaned up on both success and failure. Accepts an optional `signal` for external cancellation, forwarded to `fn`.
|
||||
|
||||
**Verified by:** `src/retry/retry.test.ts::"resolves with fn result when within budget"` AND `src/retry/retry.test.ts::"throws AttemptTimeoutError when fn takes too long"` AND `src/retry/retry.test.ts::"AttemptTimeoutError has correct attemptNumber and timeoutMs"` AND `src/retry/retry.test.ts::"propagates fn rejection"` AND `src/retry/retry.test.ts::"passes signal to fn"`
|
||||
|
||||
### RT-10 — OTel instrumentation
|
||||
|
||||
`retry.attempt.total` counter (by outcome: success/failure/exhausted/aborted), `retry.attempt.delay_ms` histogram (by strategy), `circuit_breaker.state_change.total` counter (by from/to), `circuit_breaker.call.duration_ms` histogram MUST be available via `createRetryMetrics(meter)` for opt-in emission through `@opentelemetry/api`. All record functions are no-ops when no exporter is registered.
|
||||
|
||||
**Verified by:** `src/otel/retry-otel.test.ts::"creates all four instruments"` AND `src/otel/retry-otel.test.ts::"calls add with outcome and strategy attributes"` AND `src/otel/retry-otel.test.ts::"records delay with strategy attribute"` AND `src/otel/retry-otel.test.ts::"records from/to attributes"` AND `src/otel/retry-otel.test.ts::"records duration and outcome"`
|
||||
19
openspec/archive/2026-05-02-retry-spec-conformance/tasks.md
Normal file
19
openspec/archive/2026-05-02-retry-spec-conformance/tasks.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Tasks: retry-spec-conformance
|
||||
|
||||
## 1. New tests
|
||||
|
||||
- [x] 1.1 Add `src/retry/retry.spec-conformance.test.ts` with six cases: generic narrowing (type-level), deterministic seeded-Rng jitter, per-attempt signal forwarding, mid-backoff abort discriminator, backoff factory purity, RetryExhaustedError cause chain.
|
||||
- [x] 1.2 Add `src/circuit-breaker/circuit-breaker.spec-conformance.test.ts` with three cases: full FSM walk in one assertion, open never invokes fn, CircuitOpenError.context.openedAt carries injected-clock timestamp.
|
||||
- [x] 1.3 Add `src/types/retry.schemas.spec-conformance.test.ts` with six cases: round-trip parse + reject for every published schema.
|
||||
|
||||
## 2. Spec annotations
|
||||
|
||||
- [x] 2.1 Add `**Verified by:**` line under every `### RT-NN —` block in `openspec/specs/retry/spec.md`.
|
||||
- [x] 2.2 Update RT-05 to match the 4-arg `withTimeout(fn, ms, attemptNumber, signal?)` signature.
|
||||
- [x] 2.3 Update RT-10 to reflect helper-based opt-in OTel emission (record functions are no-ops without exporter).
|
||||
|
||||
## 3. Verify
|
||||
|
||||
- [x] 3.1 `bunx turbo lint typecheck test --filter=@suaveplan/retry` green.
|
||||
- [x] 3.2 `bunx turbo test:coverage --filter=@suaveplan/retry` 100/100/100/100.
|
||||
- [x] 3.3 `bun scripts/openspec-conformance.ts @suaveplan/retry` exits 0.
|
||||
Reference in New Issue
Block a user