Priority: High · Area: Idempotency / correctness · Est. effort: 8–12 h
📌 Problem
src/middleware/idempotency.ts documents its own scope at lines 7–10:
"same key within the TTL replays the cached response instead of re-running… State lives in a plain Map local to the returned middleware"
and implements it as:
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // idempotency.ts:29
const cache = new Map<string, CachedResponse>(); // idempotency.ts:78
Three problems follow.
- Cross-replica duplicates. The cache is per-process. A client that times out and retries — the exact scenario idempotency keys exist for — can be routed to a second replica that has never seen the key, and the operation executes twice. On settlements, that is a duplicated financial action.
- Per-middleware scoping. The
Map is local to each returned middleware instance, so two mounts do not share state. The guarantee depends on middleware topology rather than on the key.
- Unbounded memory. Entries hold full cached responses for a 24-hour TTL. Nothing shown at the declaration site evicts them proactively, so a client sending many distinct keys grows the map with response bodies for a day at a time.
🎯 Design decision required
State and defend:
- Where the state belongs. A shared store is the fix, but this service currently has no persistence layer at all (tracked as a separate issue — its dependencies are
compression, cors, express). So: do you build on whatever store that issue introduces, or introduce something narrower here? Coordinate and argue your sequencing.
- What a replay returns. Replaying the stored response requires storing bodies — which is what causes the memory problem. Is a stored response the right model, or should a duplicate return a conflict and force a re-query? Argue from the client's perspective.
- Eviction and bounds. Regardless of store, state the eviction strategy and a hard bound. A 24-hour TTL with no size cap is not a bound.
🧩 Requirements and context
- Requests without an idempotency key must be entirely unaffected — prove it with a test.
- The middleware's public options shape should stay stable; list any change.
- Tests must run with no external dependency, as they do today.
- Cached responses must not retain sensitive headers. Check what is stored and report it.
- Concurrent requests with the same key must not both execute — a TTL cache alone does not prevent this if both arrive before either completes. Address the race explicitly.
🛠️ Suggested execution
- Write a failing test: two middleware instances, same key, both execute.
- Write a second: N distinct keys, assert the cache is bounded.
- Write a third: two concurrent same-key requests, assert only one executes.
- Implement per your decision.
- Show all three passing.
✅ Acceptance criteria
🚫 Out of scope
- The rate limiter's in-memory state — separate issue.
- Introducing the general persistence layer — separate issue, though you may build on it.
- Changing which routes opt into idempotency.
🧪 Verification
npm ci
npm test src/middleware/idempotency.test.ts
npm run lint && npm run build && npm test
📤 What your PR must include
- The three tests, failing before and passing after.
- Your store, replay-semantics and eviction decisions with reasoning.
- Your sequencing relative to the persistence issue.
- What sensitive data, if any, was being cached.
Closes #<n>.
🔒 Security notes
Idempotency on a settlement API is a financial-correctness control: it is what makes a client-side retry safe. Scoping it to one process means the protection silently disappears the moment the service runs more than one replica — and the failure is a duplicated settlement, not an error. Separately, caching full responses for 24 hours is both a memory-exhaustion vector and a place where sensitive response data sits in process memory far longer than the request that produced it.
📋 Guidelines
- Minimum 95% test coverage on changed lines
- Clear documentation
- Timeframe: 96 hours from assignment
- One logical change per commit; no merge commits
💬 Join our community
Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.
Telegram: https://t.me/Grainlify
Priority: High · Area: Idempotency / correctness · Est. effort: 8–12 h
📌 Problem
src/middleware/idempotency.tsdocuments its own scope at lines 7–10:and implements it as:
Three problems follow.
Mapis local to each returned middleware instance, so two mounts do not share state. The guarantee depends on middleware topology rather than on the key.🎯 Design decision required
State and defend:
compression, cors, express). So: do you build on whatever store that issue introduces, or introduce something narrower here? Coordinate and argue your sequencing.🧩 Requirements and context
🛠️ Suggested execution
✅ Acceptance criteria
npm run lint,npm run buildandnpm testpass.🚫 Out of scope
🧪 Verification
📤 What your PR must include
Closes #<n>.🔒 Security notes
Idempotency on a settlement API is a financial-correctness control: it is what makes a client-side retry safe. Scoping it to one process means the protection silently disappears the moment the service runs more than one replica — and the failure is a duplicated settlement, not an error. Separately, caching full responses for 24 hours is both a memory-exhaustion vector and a place where sensitive response data sits in process memory far longer than the request that produced it.
📋 Guidelines
💬 Join our community
Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.
Telegram: https://t.me/Grainlify