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,179 @@
# OpenSpec Change: enterprise-idempotency
- **Change ID**: enterprise-idempotency
- **Package**: `@suaveplan/idempotency`
- **Category**: universal
- **Target path**: `packages/universal/idempotency`
- **Status**: PROPOSED
- **Date**: 2026-04-19
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/idempotency.md`
## Summary
Greenfield implementation of `@suaveplan/idempotency` for the genesis branch. The existing main-branch idempotency package (98.68% test coverage) provides the baseline public-API surface to preserve; this change delivers enterprise-grade idempotency key guarding and deduplication with a full state machine (`pending → completed | failed`), pluggable store adapters (memory, database, Redis), distributed locking via a `LockProvider` interface, HTTP middleware, key generation and fingerprinting, key versioning, metrics collection, injectable clock and RNG, `AbortSignal` support throughout, structured errors with stable `IDEMPOTENCY.*` codes, Zod schemas, OTel instrumentation, property-based tests, and benchmarks.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build.
## Available workspace deps on genesis
| Package | Available |
|---------|-----------|
| `@suaveplan/error` | ✅ |
| `@suaveplan/types` | ✅ |
| `@suaveplan/events` | ✅ |
| `@suaveplan/crypto-utils` | ✅ |
| `@suaveplan/crypto-hash` | ✅ |
| `@suaveplan/crypto-curve` | ✅ |
| `@suaveplan/crypto-cipher` | ✅ |
| `@suaveplan/crypto` | ✅ |
| `@suaveplan/crypto-keys` | ✅ |
| `@suaveplan/logger` | ✅ |
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
| `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly |
| `@suaveplan/streams` | ❌ not yet built — skip streaming features |
| `@suaveplan/feature-flags` | ❌ not yet built — skip |
| `@suaveplan/retry` | ❌ in progress — skip retry integration for now |
External deps (via catalog):
- `zod` → `catalog:forms`
- `@opentelemetry/api` → `catalog:observability`
- `tslib` → `catalog:build`
Dev deps (via catalog):
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
- `fast-check` → `catalog:testing`
- `mitata` → `catalog:testing`
## Preserved public API surface (from main branch)
### Distributed
- `DistributedIdempotencyGuard` class — distributed-safe guard backed by a `LockProvider`
- `LockAcquisitionError` extends `SuaveplanError`
- `MemoryLockProvider` — in-process lock provider for unit tests and single-node deployments
- `RedisLockProvider` — Redis-backed lock provider with lease/refresh semantics
- `RedisLockProviderConfig` — configuration type for `RedisLockProvider`
### Guard
- `IdempotencyConflictError` extends `SuaveplanError` — thrown when a pending record is detected for the same key
- `IdempotencyGuard` class — single-node guard with state machine and pluggable store
- `IdempotencyMismatchError` extends `SuaveplanError` — thrown when the request fingerprint differs from the stored record
### Key generation
- `compositeKey(...parts: string[]) → string` — assembles a namespaced key from ordered parts
- `fingerprintPayload(input: unknown, hashFn?) → string` — deterministic hex fingerprint of arbitrary input
- `generateIdempotencyKey(config?: KeyGeneratorConfig) → string` — generates a random, optionally prefixed key
### Error codes
- `IdempotencyErrorCode` — union type of all stable error code strings
- `IDEMPOTENCY_ERROR_METADATA` — `Record<IdempotencyErrorCode, CodeMetadata>` mapping
- `registerIdempotencyErrorCodes()` — registers all codes on the shared `ErrorCodes` singleton
### Metrics
- `IdempotencyMetrics` class — collects and exposes a rolling `IdempotencyMetricsSnapshot`
### Middleware
- `createIdempotencyMiddleware<TReq, TRes>(config)` → middleware handler function
- `MiddlewareResult<TRes>` — discriminated union returned by the middleware (`hit | miss | conflict`)
### Store adapters
- `IdempotencyStoreAdapter<T>` — interface: `get`, `set`, `delete`, `cleanup`
- `IdempotencyDatabaseAdapter` — interface for plugging in a raw database client
- `RedisClientAdapter` — interface for plugging in a raw Redis client
- `MemoryIdempotencyStore` — in-process TTL store, GC via `cleanup()`
- `DatabaseIdempotencyStore` — adapter-backed persistent store
- `DatabaseIdempotencyStoreConfig` — configuration type
- `RedisIdempotencyStore` — Redis-backed store with atomic set-if-not-exists
- `RedisIdempotencyStoreConfig` — configuration type
### Types
- `ConflictResolutionStrategy` — `'reject' | 'overwrite' | 'version'`
- `DistributedGuardConfig` — configuration for `DistributedIdempotencyGuard`
- `IdempotencyGuardConfig` — configuration for `IdempotencyGuard`
- `IdempotencyMetricsConfig` — configuration for `IdempotencyMetrics`
- `IdempotencyMetricsSnapshot` — point-in-time metrics snapshot shape
- `IdempotencyMiddlewareConfig<TReq, TRes>` — configuration for middleware
- `IdempotencyRecord<T>` — stored record shape with status, response, fingerprint, timestamps
- `IdempotencyStatus` — `'pending' | 'completed' | 'failed'`
- `IdempotencyStoreAdapter<T>` — store interface
- `KeyGeneratorConfig` — key prefix + separator options
- `KeyVersioningConfig` — store + strategy + maxVersions options
- `LockProvider` — interface: `acquire(resource, ttlMs)`, `release(resource)`
- `MetricsReporter` — callback type `(snapshot: IdempotencyMetricsSnapshot) => void`
- `VersionEntry<T>` — versioned record snapshot
### Versioning
- `KeyVersioning` class — multi-version record management with configurable conflict strategy
- `VersionConflictError` extends `SuaveplanError` — thrown when version limit is exceeded
## New additions for genesis
### Structured errors
- `IdempotencyError` base class extends `SuaveplanError` with codes under `IDEMPOTENCY.*` namespace
- Codes: `IDEMPOTENCY.KEY_CONFLICT`, `IDEMPOTENCY.RESPONSE_MISMATCH`, `IDEMPOTENCY.STORE_ERROR`, `IDEMPOTENCY.KEY_EXPIRED`, `IDEMPOTENCY.KEY_INVALID`, `IDEMPOTENCY.LOCK_ACQUISITION_FAILED`, `IDEMPOTENCY.VERSION_CONFLICT`
- `IdempotencyConflictError`, `IdempotencyMismatchError`, `LockAcquisitionError`, `VersionConflictError` all extend `IdempotencyError`
### Zod schemas
- `IdempotencyGuardConfigSchema`, `DistributedGuardConfigSchema`, `KeyGeneratorConfigSchema`, `KeyVersioningConfigSchema`, `IdempotencyMetricsConfigSchema`, `IdempotencyMiddlewareConfigSchema`
- `IdempotencyRecordSchema`, `IdempotencyStatusSchema`
### Injectable clock + RNG
- `IdempotencyGuardConfig.clock?: Clock` — for deterministic TTL and timeout computation (FakeClock in tests)
- `IdempotencyGuardConfig.rng?: Rng` — for deterministic key generation
- `DistributedGuardConfig.clock?: Clock`
- `KeyGeneratorConfig.rng?: Rng`
### AbortSignal support
- `IdempotencyGuard.execute(key, fn, opts?: { signal? })` — cancels pending-record wait on abort
- `DistributedIdempotencyGuard.execute(key, fn, opts?: { signal? })` — propagates signal through lock acquisition retries and fn invocation
### OTel instrumentation
- Counter: `idempotency.request.total` (by outcome label: `hit | miss | conflict | mismatch | error`)
- Histogram: `idempotency.execution.duration_ms` (guard execution latency)
- Counter: `idempotency.lock.acquisition.total` (by result: `acquired | failed | retried`)
- Histogram: `idempotency.lock.wait_ms` (time spent acquiring a distributed lock)
- Counter: `idempotency.store.operation.total` (by operation: `get | set | delete | cleanup`)
### Property-based tests (fast-check)
- `compositeKey` output is always deterministic for equal inputs
- `fingerprintPayload` is collision-resistant: distinct inputs produce distinct hashes across arbitrary payloads
- `generateIdempotencyKey` never produces duplicate outputs across N iterations with a mocked RNG
- `IdempotencyGuard.execute` never executes `fn` more than once for the same key when called concurrently
- `KeyVersioning` never exceeds `maxVersions` stored entries regardless of call order
### Benchmarks (mitata)
- `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
## Notes on integration testing
- `RedisIdempotencyStore` and `RedisLockProvider` require a real Redis instance. Integration tests use a `redis://localhost:6379` connection; no mock Redis. Tests are placed in `tests/integration/` and are guarded by a `TEST_REDIS_URL` environment variable.
- `DistributedIdempotencyGuard` is unit-tested with `MemoryLockProvider` only. Integration tests covering the full distributed path with Redis are in `tests/integration/`.
- `DatabaseIdempotencyStore` integration tests require `TEST_DATABASE_URL` environment variable; unit tests use `MemoryIdempotencyStore` as a stand-in.
## Impact
- Adds one new package: `packages/universal/idempotency`
- Adds one new spec capability: `idempotency`
- No existing package consumers on genesis yet