From e29d6c1714b715ea7259774381ff1d14d46f93bb Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Tue, 9 Jun 2026 10:43:14 -0400 Subject: [PATCH] fix(03): IN-02 treat unmapped 4xx as hard fail (no full-backoff retry) --- apps/api/src/broker/outboxWorker.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/api/src/broker/outboxWorker.ts b/apps/api/src/broker/outboxWorker.ts index 8c661c2..ab9af4f 100644 --- a/apps/api/src/broker/outboxWorker.ts +++ b/apps/api/src/broker/outboxWorker.ts @@ -333,7 +333,23 @@ async function dispatchRow(row: OutboxRow): Promise { return { success: true, conflict: false, hardFail: false, transient: false } } - // Unknown status — treat as transient to avoid silent data loss + // IN-02: an unmapped 4xx (e.g. 405, 409, 422) is a permanent client error — retrying it + // for the full backoff window just delays settling and burns the attempt budget before + // dead-lettering. The transient-eligible 4xx codes (408 request timeout, 429 too many + // requests) are already in TRANSIENT_STATUSES and handled above, so any remaining 4xx + // here is a hard fail. 5xx, network failures, and truly unknown statuses still fall + // through to transient so genuinely recoverable conditions keep their retries. + if (status >= 400 && status < 500) { + return { + success: false, + conflict: false, + hardFail: true, + transient: false, + error: `Hard fail: HTTP ${status} for uid=${row.uid}`, + } + } + + // Unknown / 5xx status — treat as transient to avoid silent data loss return { success: false, conflict: false,