> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/control-flow/retry. # Tasks: enterprise-retry ## 1. Scaffold - [x] 1.1 Create `packages/universal/retry/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton - [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul - [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake - [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml` ## 2. Types + errors - [x] 2.1 Implement `src/types/retry.types.ts` — `RetryConfig`, `RetryResult`, `RetryStrategy`, `DelayFunction`, `JitterMode`, `ConstantBackoffOptions`, `LinearBackoffOptions`, `ExponentialBackoffOptions` - [x] 2.2 Implement `src/errors/retry-error.ts` — `RetryError` base extends `SuaveplanError`; `RetryExhaustedError`, `AttemptTimeoutError` extend `RetryError`; `defineCodes` for `RETRY.EXHAUSTED`, `RETRY.ABORTED`, `RETRY.TIMEOUT` - [x] 2.3 Implement `src/circuit-breaker/circuit-breaker.types.ts` — `CircuitBreakerConfig`, `CircuitBreakerEventMap`, `CircuitState`; `CircuitOpenError` extends `RetryError` with `RETRY.CIRCUIT_OPEN` code - [x] 2.4 Zod schemas: `RetryConfigSchema`, `ConstantBackoffOptionsSchema`, `LinearBackoffOptionsSchema`, `ExponentialBackoffOptionsSchema`, `CircuitBreakerConfigSchema` - [x] 2.5 Tests: error construction, code narrowing, `.attempts` / `.lastError` / `.attemptNumber` / `.timeoutMs` fields - [x] 2.6 Implement `src/error-codes.ts` and auto-register via side-effect import in `src/index.ts` ## 3. Backoff strategies - [x] 3.1 `src/strategies/constant/constant.ts` — `createConstantBackoff(opts)` → `RetryStrategy`; `ConstantBackoffOptions` - [x] 3.2 `src/strategies/linear/linear.ts` — `createLinearBackoff(opts)` → `RetryStrategy`; `LinearBackoffOptions` - [x] 3.3 `src/strategies/exponential/exponential.ts` — `createExponentialBackoff(opts)` → `RetryStrategy`; `applyJitter(delay, mode, rng?)`, `clampDelay(delay, min, max)`; `ExponentialBackoffOptions` - [x] 3.4 All strategies accept injectable `rng?: Rng` for deterministic jitter - [x] 3.5 Unit tests: delay values at each attempt, clamping, jitter modes (full/equal/decorrelated/none) - [x] 3.6 Property-based tests: exponential `delay(n+1) >= delay(n)` with no jitter; jitter in `[0, d]`; clamp always in `[min, max]` - [x] 3.7 Co-located `.md` for each strategy ## 4. Core retry - [x] 4.1 Implement `src/retry/retry.ts`: - `sleep(ms, signal?)` — cancellable; resolves early on abort - `retry(fn, config)` → `Promise>` - `withRetry(fn, config)` → `Promise` — throws `RetryExhaustedError` on exhaustion, `RetryError` on abort - `withTimeout(fn, ms, signal?)` → `Promise` — throws `AttemptTimeoutError` - `createDelayFn(strategy)` → `DelayFunction` - `withCircuitBreaker(fn, breaker)` → `Promise` - [x] 4.2 `retry` uses injected `clock?: Clock` for sleep duration; `rng?: Rng` from config forwarded to strategy - [x] 4.3 `retry` honours `AbortSignal` between attempts (checks before each delay and after each call) - [x] 4.4 Unit tests: all config options, abort between attempts, timeout, exhaustion, `shouldRetry` predicate - [x] 4.5 Property-based tests: `withRetry` never exceeds `maxAttempts` calls to fn - [x] 4.6 Co-located `src/retry/retry.md` ## 5. Circuit breaker - [x] 5.1 Implement `src/circuit-breaker/circuit-breaker.ts` — `CircuitBreaker` class - State machine: `closed → open → half-open → closed` (or back to open) - `.call(fn, signal?)` — throws `CircuitOpenError` when open - `.reset()` — force back to closed - `.state` getter — `CircuitState` - Event emitter via `@suaveplan/events`: `stateChange` event with `CircuitBreakerEventMap` - `halfOpenProbes` attempts before promoting to closed - `timeout` window after which open resets to half-open (uses `clock?: Clock`) - [x] 5.2 Unit tests: failure threshold transitions, half-open probing, success promotion, manual reset, signal passing, clock injection - [x] 5.3 Property-based tests: after `failureThreshold` consecutive failures → state is `'open'` - [x] 5.4 Co-located `src/circuit-breaker/circuit-breaker.md` ## 6. OTel instrumentation - [x] 6.1 Counter `retry.attempt.total` by outcome label (success / failure / exhausted / aborted) - [x] 6.2 Histogram `retry.attempt.delay_ms` by strategy label - [x] 6.3 Counter `circuit_breaker.state_change.total` by from/to label - [x] 6.4 Histogram `circuit_breaker.call.duration_ms` - [x] 6.5 No-op without exporter (tested) ## 7. Benchmarks - [x] 7.1 `src/__benchmarks__/sleep.bench.ts` — `sleep(0)` call overhead baseline (mitata) - [x] 7.2 `src/__benchmarks__/retry.bench.ts` — `withRetry` with always-succeeding fn (0-retry overhead) - [x] 7.3 `src/__benchmarks__/backoff.bench.ts` — `createExponentialBackoff` delay computation 1 000 iterations - [x] 7.4 `src/__benchmarks__/circuit-breaker.bench.ts` — `CircuitBreaker.call` closed-state hot path 10 000 iterations - [x] 7.5 Publish baseline numbers in README ## 8. Docs - [x] 8.1 Co-located `.md` for every implementation file (≥200 words, examples, API reference) - [x] 8.2 Rewrite `README.md` with public API, circuit breaker state diagram, strategy comparison table, consumer guide - [x] 8.3 `bun run docs:check --filter=@suaveplan/retry` ≥ 90% ## 9. Verify and publish - [x] 9.1 `bunx turbo lint typecheck test --filter=@suaveplan/retry` green - [x] 9.2 `bunx turbo test:coverage --filter=@suaveplan/retry` at 100/100/100/100 - [x] 9.3 `bunx turbo build --filter=@suaveplan/retry` green - [x] 9.4 `bun scripts/publish.ts --package @suaveplan/retry` to Verdaccio at 0.0.0 - [x] 9.5 Archive OpenSpec change to `openspec/archive/2026-04-18-enterprise-retry/`