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:
@@ -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");
|
||||
@@ -1178,3 +1178,24 @@ model IiosAuditLink {
|
||||
@@index([resourceType, resourceId])
|
||||
@@index([scopeId])
|
||||
}
|
||||
|
||||
/// Dead-letter / replay item (P9, mandated dlq_item). A consumer/relay failure is
|
||||
/// quarantined here (never silently swallowed — KG-13) and can be replayed by ops.
|
||||
model IiosDlqItem {
|
||||
id String @id @default(cuid())
|
||||
sourceType String // 'projection' | 'outbox' | 'outbound' | ...
|
||||
sourceId String
|
||||
failureStage String
|
||||
errorCode String?
|
||||
retryCount Int @default(0)
|
||||
payloadRef String? // the outbox eventId to re-dispatch
|
||||
consumerName String?
|
||||
scopeId String? // tenant quarantine
|
||||
status String @default("QUARANTINED") // QUARANTINED | RESOLVED | ARCHIVED
|
||||
createdAt DateTime @default(now())
|
||||
lastFailedAt DateTime @default(now())
|
||||
resolvedAt DateTime?
|
||||
|
||||
@@unique([consumerName, sourceId])
|
||||
@@index([scopeId, status])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user