Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -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).

View File

@@ -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"`

View 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.