feat(p9): IiosDlqItem + DlqService (dead-letter + deterministic replay)

Task D.1: mandated dlq_item table (sourceType/sourceId/failureStage/errorCode/
retryCount/payloadRef/consumerName/scopeId/status). DlqService.record (idempotent
per consumer+sourceId, bumps retryCount), onConsumerFailure (quarantine + clear the
IiosProcessedEvent claim so it's replayable — fixes claim-then-fail), registerHandler
+ replay (clears claim, awaits the registered consumer handler → RESOLVED on success,
QUARANTINED+retry on repeat throw; outbox-sourced items reset to PENDING). Wired into
OutboxModule; resetDb truncates it. 3 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 01:28:48 +05:30
parent e5d3ce5ecd
commit 208fb0b4f0
6 changed files with 219 additions and 3 deletions
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "IiosDlqItem" (
"id" TEXT NOT NULL,
"sourceType" TEXT NOT NULL,
"sourceId" TEXT NOT NULL,
"failureStage" TEXT NOT NULL,
"errorCode" TEXT,
"retryCount" INTEGER NOT NULL DEFAULT 0,
"payloadRef" TEXT,
"consumerName" TEXT,
"scopeId" TEXT,
"status" TEXT NOT NULL DEFAULT 'QUARANTINED',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastFailedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"resolvedAt" TIMESTAMP(3),
CONSTRAINT "IiosDlqItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "IiosDlqItem_scopeId_status_idx" ON "IiosDlqItem"("scopeId", "status");
-- CreateIndex
CREATE UNIQUE INDEX "IiosDlqItem_consumerName_sourceId_key" ON "IiosDlqItem"("consumerName", "sourceId");