feat(service): P3.2 InboxProjector + message.read event + scheduled relay

Event-driven projector creates/collapses NEEDS_REPLY items from message.sent
(traceId propagated; inbox.item.created emitted) and resolves them to DONE on
message.read; idempotent via processed-event ledger. markRead now emits a
message.read outbox event (atomic). OutboxRelay runs on a 500ms timer in the
app (off in tests). Shared resetDb (TRUNCATE CASCADE) fixes cross-spec FK
isolation. 32 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 02:48:44 +05:30
parent 7b174e24fa
commit a2b95afaf0
13 changed files with 340 additions and 56 deletions
@@ -0,0 +1,16 @@
import type { PrismaClient } from '@prisma/client';
/**
* FK-safe full reset for DB-backed specs. TRUNCATE … CASCADE clears every kernel
* table regardless of relation order, so specs don't each need to know the full
* delete order (which broke once Inbox added FK references to interaction/thread/actor).
*/
export async function resetDb(prisma: PrismaClient): Promise<void> {
await prisma.$executeRawUnsafe(
`TRUNCATE TABLE
"IiosInboxItemStateHistory","IiosInboxItem","IiosUnreadCounter","IiosMessageReceipt",
"IiosOutboxEvent","IiosProcessedEvent","IiosMessagePart","IiosInteraction",
"IiosThreadParticipant","IiosThread","IiosActorRef","IiosSourceHandle","IiosChannel","IiosScope"
RESTART IDENTITY CASCADE`,
);
}