feat(service): P3.1 inbox tables + extract ActorResolver

IiosInboxItem + IiosInboxItemStateHistory (migration inbox-init). Extracted
resolveScope/resolveActor/ensureParticipant into a shared ActorResolver
(IdentityModule) so message + inbox layers share identity resolution; MessageService
delegates (P2 tests unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 02:41:50 +05:30
parent 4d05f7c1b1
commit 7b174e24fa
7 changed files with 214 additions and 54 deletions
+72 -2
View File
@@ -75,6 +75,26 @@ enum IiosReceiptKind {
RATE_LIMITED
}
enum IiosInboxItemKind {
NEEDS_REPLY
NEEDS_REVIEW
NEEDS_APPROVAL
SUPPORT_UPDATE
MEETING_FOLLOWUP
DIGEST
SYSTEM_ALERT
CRM_OWNER_INTEREST
}
enum IiosInboxState {
OPEN
SNOOZED
DONE
ARCHIVED
CANCELLED
STALE
}
// ─── Kernel tables ────────────────────────────────────────────────
/// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null).
@@ -93,6 +113,7 @@ model IiosScope {
channels IiosChannel[]
threads IiosThread[]
interactions IiosInteraction[]
inboxItems IiosInboxItem[]
@@index([orgId, appId, tenantId])
}
@@ -135,6 +156,7 @@ model IiosActorRef {
interactions IiosInteraction[]
receipts IiosMessageReceipt[]
unreadCounters IiosUnreadCounter[]
inboxItemsOwned IiosInboxItem[]
}
/// A configured channel surface (PORTAL in P1).
@@ -177,6 +199,7 @@ model IiosThread {
participants IiosThreadParticipant[]
interactions IiosInteraction[]
unreadCounters IiosUnreadCounter[]
inboxItems IiosInboxItem[]
@@index([scopeId, status])
}
@@ -219,8 +242,9 @@ model IiosInteraction {
traceId String?
metadata Json?
parts IiosMessagePart[]
receipts IiosMessageReceipt[]
parts IiosMessagePart[]
receipts IiosMessageReceipt[]
inboxItems IiosInboxItem[]
@@unique([scopeId, idempotencyKey])
@@index([threadId, occurredAt])
@@ -299,3 +323,49 @@ model IiosUnreadCounter {
@@id([threadId, actorId])
}
// ─── P3: Inbox (work/awareness surface) ───────────────────────────
/// A durable action/awareness item owned by one actor. Fed by the event
/// projector (deterministic, collapse-per-thread). NOT a thin unread projection.
model IiosInboxItem {
id String @id @default(cuid())
scopeId String
scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade)
ownerActorId String
ownerActor IiosActorRef @relation(fields: [ownerActorId], references: [id])
kind IiosInboxItemKind
state IiosInboxState @default(OPEN)
title String
summary String?
priority String @default("NORMAL")
dueAt DateTime?
sourceInteractionId String?
sourceInteraction IiosInteraction? @relation(fields: [sourceInteractionId], references: [id])
threadId String?
thread IiosThread? @relation(fields: [threadId], references: [id])
traceId String?
actionRef Json?
policyDecisionRef String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
metadata Json?
stateHistory IiosInboxItemStateHistory[]
@@index([ownerActorId, state, priority])
}
/// Audit trail of inbox-item state transitions.
model IiosInboxItemStateHistory {
id String @id @default(cuid())
inboxItemId String
inboxItem IiosInboxItem @relation(fields: [inboxItemId], references: [id], onDelete: Cascade)
fromState IiosInboxState?
toState IiosInboxState
actorId String?
reasonCode String?
at DateTime @default(now())
@@index([inboxItemId])
}