feat(iios): wire projectors + relay into DLQ (P9 chaos-safe)
Projectors register a replay handler and route bus errors to dlq.onConsumerFailure (dead-letter + clear the claim-then-fail claim) instead of swallowing them. Relay dead-letters an outbox event after IIOS_OUTBOX_MAX_ATTEMPTS instead of retrying forever. Chaos test proves poison → DLQ → replay resolves while sibling events keep flowing (KG-13/KG-06). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { DlqService } from '../outbox/dlq.service';
|
||||
import { resetDb } from '../test-utils/reset-db';
|
||||
import { makeFakePorts, makeFakeInference } from '@insignia/iios-testkit';
|
||||
import { IIOS_EVENTS, type CloudEvent } from '@insignia/iios-contracts';
|
||||
@@ -104,7 +105,7 @@ describe('AI × routing (P7, KG-05: AI proposes, never approves)', () => {
|
||||
|
||||
it('AiProjector auto-classifies a channel-bearing interaction, idempotently', async () => {
|
||||
const { interactionId, event } = await ingestInbound('There is a party at 9 PM tonight!');
|
||||
const p = new AiProjector(asService, new OutboxBus(), ai());
|
||||
const p = new AiProjector(asService, new OutboxBus(), new DlqService(asService), ai());
|
||||
await p.onNormalized(event);
|
||||
await p.onNormalized(event); // replay
|
||||
expect(await prisma.iiosAiJob.count({ where: { interactionId, jobType: 'CLASSIFY' } })).toBe(1);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||
import { CloudEvent, IIOS_EVENTS } from '@insignia/iios-contracts';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { OutboxBus } from '../outbox/outbox.bus';
|
||||
import { DlqService } from '../outbox/dlq.service';
|
||||
import { AiJobService } from './ai.service';
|
||||
|
||||
/**
|
||||
@@ -18,11 +19,13 @@ export class AiProjector implements OnModuleInit {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly bus: OutboxBus,
|
||||
private readonly dlq: DlqService,
|
||||
private readonly ai: AiJobService,
|
||||
) {}
|
||||
|
||||
onModuleInit(): void {
|
||||
this.bus.on(IIOS_EVENTS.interactionNormalized, (p) => void this.onNormalized(p as CloudEvent).catch(() => {}));
|
||||
this.dlq.registerHandler(this.consumer, (e) => this.onNormalized(e));
|
||||
this.bus.on(IIOS_EVENTS.interactionNormalized, (p) => void this.onNormalized(p as CloudEvent).catch((err) => this.dlq.onConsumerFailure(this.consumer, p as CloudEvent, err)));
|
||||
}
|
||||
|
||||
async onNormalized(event: CloudEvent): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user