PR F: Idempotency-Key + replay protection on POST /plans and /execute #10

Merged
nsatoshi merged 1 commits from devin/1776876189-idempotency into main 2026-04-22 17:18:26 +00:00

1 Commits

Author SHA1 Message Date
Devin
3650415d02 PR F: idempotency keys + replay protection (arch step 9 / \u00a713 / \u00a715)
Some checks failed
CI / Frontend Lint (pull_request) Failing after 5s
CI / Frontend Type Check (pull_request) Failing after 6s
CI / Frontend Build (pull_request) Failing after 7s
CI / Frontend E2E Tests (pull_request) Failing after 8s
CI / Orchestrator Build (pull_request) Failing after 6s
CI / Contracts Compile (pull_request) Failing after 6s
CI / Contracts Test (pull_request) Failing after 6s
Code Quality / SonarQube Analysis (pull_request) Failing after 21s
Code Quality / Code Quality Checks (pull_request) Failing after 5s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 4s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 3s
Adds an Idempotency-Key middleware backed by a new idempotency_keys
table. Covers arch \u00a713 security (replay protection) and \u00a715
non-functional requirement (idempotent event handling, resilience to
duplicate messages).

Middleware (src/middleware/idempotency.ts):
  - Mounted on POST /api/plans and POST /api/plans/:planId/execute.
  - Key format /^[A-Za-z0-9_\-:.]{8,255}$/; malformed -> 400.
  - On hit, replays cached status+body with Idempotent-Replayed: true.
  - Reuse with a different body hash -> 422 idempotency_key_reused.
  - Scopes by (method, path, key) so the same key is safe across
    unrelated endpoints.
  - Only 2xx is cached. Non-2xx stays retryable.
  - res.json() is shimmed so handlers need no changes.
  - Fail-open on dedup-store unavailability (warn log).

Migration 004 (db/migrations/004_idempotency_keys.ts):
  - idempotency_keys(method, path, key, request_hash, status_code,
    response_body, created_at, expires_at) with UNIQUE(method,path,key)
    and an expires_at index. 24h TTL.

Tests: tests/unit/idempotency.test.ts \u2014 6 cases covering no-header
pass-through, malformed-key 400, replay on second call, 422 on body
divergence, retryable non-2xx, (method,path,key) scoping.

tsc clean. 80 tests pass across 7 suites.
2026-04-22 17:18:09 +00:00