fix(tasks): align OutboxRelay with outbox_event status/next_attempt_at/idempotency_key

The relay only looked at published_at and ignored the ADR-002 §2.1 columns
added in db 0002_foundation: rows stayed status='pending' forever after
dispatch, failures were retried every tick without backoff, and the
row-level idempotency_key was never used as the engine enqueue key.

- claim status='pending' AND next_attempt_at<=now()
- enqueue with idempotency_key (fallback outbox:<id>)
- success: status=dispatched, published_at, attempts+1
- failure: attempts+1, exponential backoff (cap 5 min), last_error; dead at maxAttempts
- test helper table now mirrors db 0001+0002; tests cover backoff and dead

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-27 18:59:06 +08:00
1 parent 608e567edf
commit 31729f2bac
4 files changed
+56 -15

No files matched your search

+7 -2
View File
@@ -58,14 +58,19 @@ export async function dropSchemas(schemas: { biz: string; ow: string }) {
}
}
/** 与 packages/db 0001_init 的 outbox_event 同构 */
/** 与 packages/db 0001_init + 0002_foundation 的 outbox_event 同构 */
export async function createOutboxTable(db: TestDb) {
await sql`CREATE TABLE outbox_event (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
at timestamptz NOT NULL DEFAULT now(),
type text NOT NULL,
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
published_at timestamptz)`.execute(db);
published_at timestamptz,
idempotency_key text UNIQUE,
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'dispatched', 'failed', 'dead')),
attempts integer NOT NULL DEFAULT 0,
next_attempt_at timestamptz NOT NULL DEFAULT now(),
last_error jsonb)`.execute(db);
}
export async function createSideEffectTable(db: TestDb) {