# Capability Spec: idempotency - **Capability**: `idempotency` - **Package**: `@suaveplan/idempotency` - **Change**: enterprise-idempotency - **Date**: 2026-04-19 --- ## ADDED Requirements All requirements in this section are new for genesis. The main-branch package provides the baseline public-API surface; everything below is either a carry-over preserved verbatim or a genesis-specific enhancement. ### REQ-IDMP-001 — Structured error hierarchy The package MUST define an `IdempotencyError` base class that extends `SuaveplanError` from `@suaveplan/error`. All error types thrown by the package MUST extend `IdempotencyError`. Error codes MUST follow the `IDEMPOTENCY.*` dotted namespace and be registered with `defineCodes` / `registerIdempotencyErrorCodes()` on the shared `ErrorCodes` singleton. Required codes: | Code | HTTP | Severity | Retryable | |------|------|----------|-----------| | `IDEMPOTENCY.KEY_CONFLICT` | 409 | warning | false | | `IDEMPOTENCY.RESPONSE_MISMATCH` | 409 | error | false | | `IDEMPOTENCY.STORE_ERROR` | 503 | error | true (exponential, max 3) | | `IDEMPOTENCY.KEY_EXPIRED` | 410 | warning | false | | `IDEMPOTENCY.KEY_INVALID` | 400 | warning | false | | `IDEMPOTENCY.LOCK_ACQUISITION_FAILED` | 503 | error | true | | `IDEMPOTENCY.VERSION_CONFLICT` | 409 | warning | false | ### REQ-IDMP-002 — Zod schemas for all public config types Every public configuration and record type MUST have a corresponding Zod schema exported from the package. Schemas must be usable for runtime validation at the boundary (e.g. HTTP middleware deserialization). Schema exports: - `IdempotencyStatusSchema` - `IdempotencyRecordSchema` - `IdempotencyGuardConfigSchema` - `DistributedGuardConfigSchema` - `KeyGeneratorConfigSchema` - `KeyVersioningConfigSchema` - `IdempotencyMetricsConfigSchema` - `IdempotencyMiddlewareConfigSchema` ### REQ-IDMP-003 — Injectable clock and RNG No public API may call `Date.now()` or `Math.random()` directly. Every time-dependent and random-dependent operation MUST accept an optional `clock?: Clock` and `rng?: Rng` (from `@suaveplan/types`) so tests can run deterministically with `FakeClock` and a seeded RNG. Affected components: `IdempotencyGuard`, `DistributedIdempotencyGuard`, `IdempotencyMetrics`, `generateIdempotencyKey`. ### REQ-IDMP-004 — AbortSignal support throughout Every async public method that performs IO or waits on an internal lock MUST accept an optional `{ signal?: AbortSignal }` option. On abort, pending waits MUST reject with a `DOMException` named `AbortError` (or an `IdempotencyError` wrapping it). The signal MUST be propagated to the user-supplied `fn` where possible. Affected: `IdempotencyGuard.execute`, `DistributedIdempotencyGuard.execute`. ### REQ-IDMP-005 — OTel instrumentation via `@opentelemetry/api` The package MUST instrument all externally-visible operations using `@opentelemetry/api` meters. Instrumentation MUST be a no-op when no `MeterProvider` is registered. Required instruments: | Instrument | Type | Labels | |-----------|------|--------| | `idempotency.request.total` | Counter | `outcome: hit \| miss \| conflict \| mismatch \| error` | | `idempotency.execution.duration_ms` | Histogram | — | | `idempotency.lock.acquisition.total` | Counter | `result: acquired \| failed \| retried` | | `idempotency.lock.wait_ms` | Histogram | — | | `idempotency.store.operation.total` | Counter | `operation: get \| set \| delete \| cleanup` | ### REQ-IDMP-006 — Property-based tests with fast-check The following properties MUST be verified with `fast-check` arbitraries: - `compositeKey` output is deterministic and stable for equal inputs across arbitrary string arrays. - `fingerprintPayload` produces distinct hex strings for structurally distinct payloads with overwhelming probability (collision resistance property). - `generateIdempotencyKey` never produces duplicate keys across N calls when supplied a deterministic RNG. - `IdempotencyGuard.execute` never invokes `fn` more than once for the same key when called concurrently from multiple coroutines. - `KeyVersioning` never retains more than `maxVersions` entries regardless of call ordering. ### REQ-IDMP-007 — Benchmarks with mitata A benchmark suite MUST be maintained under `src/__benchmarks__/`. Baseline numbers MUST be published in `README.md`. Required benchmarks: - `MemoryIdempotencyStore.get` hot path — 10 000 cached reads. - `IdempotencyGuard.execute` with cached hit — zero-execution overhead. - `compositeKey` throughput — 1 000 000 iterations. - `fingerprintPayload` throughput — 10 000 small payloads. ### REQ-IDMP-008 — Store adapter extensibility `IdempotencyStoreAdapter` MUST remain a structural interface (not a class) so that downstream packages can provide custom implementations without inheriting from a base class. The four required methods are: `get(key) → Promise | undefined>`, `set(key, record) → Promise`, `delete(key) → Promise`, `cleanup(beforeTimestamp) → Promise`. Similarly, `LockProvider` MUST remain a structural interface with `acquire(resource, ttlMs) → Promise` and `release(resource) → Promise`. Implementations (memory, Redis) are provided by the package as conveniences, not as required base classes. ### REQ-IDMP-009 — Real services for integration tests; no mock stores in unit tests `MemoryIdempotencyStore` and `MemoryLockProvider` are full real implementations and MUST be used directly in unit tests — not as mocks. `RedisIdempotencyStore`, `RedisLockProvider`, and `DatabaseIdempotencyStore` MUST be tested against real services. Integration tests MUST be placed in `tests/integration/` and MUST be guarded by environment variables (`TEST_REDIS_URL`, `TEST_DATABASE_URL`). No `vi.mock` or `jest.mock` of store or lock provider internals is permitted. ### REQ-IDMP-010 — 100% test coverage (lines / statements / branches / functions) As a Layer 1 foundation package, `@suaveplan/idempotency` MUST achieve 100/100/100/100 coverage (Istanbul provider). The coverage threshold MUST be enforced in `vitest.config.ts`. The `bunx turbo test:coverage --filter=@suaveplan/idempotency` command MUST exit non-zero if any threshold is not met. --- ## Public Exports ### Classes | Export | Module | |--------|--------| | `IdempotencyGuard` | `src/guard/idempotency-guard` | | `DistributedIdempotencyGuard` | `src/distributed/distributed-guard` | | `MemoryLockProvider` | `src/distributed/memory-lock-provider` | | `RedisLockProvider` | `src/distributed/redis-lock-provider` | | `MemoryIdempotencyStore` | `src/store/memory-store` | | `DatabaseIdempotencyStore` | `src/store/database-store` | | `RedisIdempotencyStore` | `src/store/redis-store` | | `KeyVersioning` | `src/versioning/key-versioning` | | `IdempotencyMetrics` | `src/metrics/idempotency-metrics` | ### Error classes | Export | Extends | |--------|---------| | `IdempotencyError` | `SuaveplanError` | | `IdempotencyConflictError` | `IdempotencyError` | | `IdempotencyMismatchError` | `IdempotencyError` | | `LockAcquisitionError` | `IdempotencyError` | | `VersionConflictError` | `IdempotencyError` | ### Functions | Export | Signature | |--------|-----------| | `compositeKey` | `(...parts: string[]) → string` | | `fingerprintPayload` | `(input: unknown, hashFn?: (data: Uint8Array) => string) → string` | | `generateIdempotencyKey` | `(config?: KeyGeneratorConfig) → string` | | `createIdempotencyMiddleware` | `(config: IdempotencyMiddlewareConfig) → MiddlewareHandler` | | `registerIdempotencyErrorCodes` | `() → void` | ### Types | Export | Kind | |--------|------| | `IdempotencyStatus` | type alias | | `ConflictResolutionStrategy` | type alias | | `IdempotencyRecord` | interface | | `IdempotencyStoreAdapter` | interface | | `IdempotencyDatabaseAdapter` | interface | | `RedisClientAdapter` | interface | | `LockProvider` | interface | | `IdempotencyGuardConfig` | interface | | `DistributedGuardConfig` | interface | | `DatabaseIdempotencyStoreConfig` | interface | | `RedisIdempotencyStoreConfig` | interface | | `RedisLockProviderConfig` | interface | | `KeyGeneratorConfig` | interface | | `KeyVersioningConfig` | interface | | `IdempotencyMetricsConfig` | interface | | `IdempotencyMetricsSnapshot` | interface | | `MetricsReporter` | type alias | | `MiddlewareResult` | type alias (discriminated union) | | `VersionEntry` | interface | | `IdempotencyErrorCode` | type alias (union) | ### Constants / registries | Export | Kind | |--------|------| | `IDEMPOTENCY_ERROR_METADATA` | `Readonly>` | ### Zod schemas | Export | Validates | |--------|-----------| | `IdempotencyStatusSchema` | `IdempotencyStatus` | | `IdempotencyRecordSchema` | `IdempotencyRecord` | | `IdempotencyGuardConfigSchema` | `IdempotencyGuardConfig` | | `DistributedGuardConfigSchema` | `DistributedGuardConfig` | | `KeyGeneratorConfigSchema` | `KeyGeneratorConfig` | | `KeyVersioningConfigSchema` | `KeyVersioningConfig` | | `IdempotencyMetricsConfigSchema` | `IdempotencyMetricsConfig` | | `IdempotencyMiddlewareConfigSchema` | `IdempotencyMiddlewareConfig` | --- ## MODIFIED Requirements None — this is a greenfield package on genesis; there are no prior spec entries to modify. ## REMOVED Requirements None.