Files
suaveplan/openspec/archive/2026-04-19-enterprise-idempotency/specs/idempotency/spec.md
2026-05-27 23:29:53 +01:00

9.2 KiB

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<T> 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<IdempotencyRecord<T> | undefined>, set(key, record) → Promise<void>, delete(key) → Promise<boolean>, cleanup(beforeTimestamp) → Promise<number>.

Similarly, LockProvider MUST remain a structural interface with acquire(resource, ttlMs) → Promise<boolean> and release(resource) → Promise<void>. 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 <TReq, TRes>(config: IdempotencyMiddlewareConfig<TReq, TRes>) → MiddlewareHandler
registerIdempotencyErrorCodes () → void

Types

Export Kind
IdempotencyStatus type alias
ConflictResolutionStrategy type alias
IdempotencyRecord<T> interface
IdempotencyStoreAdapter<T> 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<TRes> type alias (discriminated union)
VersionEntry<T> interface
IdempotencyErrorCode type alias (union)

Constants / registries

Export Kind
IDEMPOTENCY_ERROR_METADATA Readonly<Record<IdempotencyErrorCode, CodeMetadata>>

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.