The nightly cleanup deleted idempotency keys older than 24h. The TREK client
replays queued mutations with their X-Idempotency-Key on reconnect, so a device
offline longer than a day had its keys GC'd before it returned — the replayed
POST was then treated as new and created a duplicate.
- raise the TTL to 30 days (DEFAULT_IDEMPOTENCY_TTL_SECONDS), overridable via
IDEMPOTENCY_TTL_SECONDS
- extract purgeExpiredIdempotencyKeys(now, ttl, db) (mirrors cleanupOldBackups)
with an injectable db, and have the cron job call it
- tests: 30-day default eviction, 25-day key retained (was dropped at 24h),
env override
H7 (exactly-once across the lost-response window) is deferred: a correct fix
must store the response in the same DB transaction as the entity write. Doing
it in the generic interceptor (reserve-before-handler) cannot store the real
response body for the crash case, which would break the client's temp->real id
remapping on replay (mutationQueue.flush relies on the entity in the body). It
needs a per-service change and is tracked separately.