// IIOS Interaction Kernel — P1 schema (native portal only). // Lifted from the Bottom-Up Evolution Atlas §10 kernel DDL. ONLY kernel tables: // no support / inbox / route / AI tables (those are earned in later phases). generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── Enums (one value per line) ─────────────────────────────────── enum IiosActorKind { HUMAN AI SERVICE BOT UNKNOWN } enum IiosHandleKind { PORTAL_USER EMAIL PHONE WHATSAPP SLACK MATTERMOST TELEGRAM EXTERNAL_VISITOR } enum IiosInteractionKind { MESSAGE EMAIL SYSTEM_NOTICE INBOX_WORK SUPPORT_CASE MEETING_REQUEST DIGEST SUMMARY NOTIFICATION } enum IiosMessagePartKind { TEXT HTML MARKDOWN MEDIA_REF FILE_REF VOICE_REF LOCATION STRUCTURED_JSON } enum IiosDeliveryState { RECEIVED NORMALIZED POLICY_HELD STORED DELIVERED READ FAILED DELETED_LOCAL REDACTED } enum IiosReceiptKind { DELIVERED READ ACKED FAILED SUPPRESSED RATE_LIMITED } enum IiosInboxItemKind { NEEDS_REPLY NEEDS_REVIEW NEEDS_APPROVAL MENTION SUPPORT_UPDATE MEETING_FOLLOWUP DIGEST SYSTEM_ALERT CRM_OWNER_INTEREST } enum IiosInboxState { OPEN SNOOZED DONE ARCHIVED CANCELLED STALE } enum IiosTicketState { NEW OPEN PENDING_CUSTOMER PENDING_INTERNAL RESOLVED CLOSED CANCELLED } enum IiosTicketPriority { P0 P1 P2 P3 P4 } enum IiosCallbackStatus { PENDING SCHEDULED COMPLETED MISSED CANCELLED } enum IiosRouteMode { MANUAL AUTOMATIC HYBRID SIMULATION_ONLY } enum IiosOutputFormat { FORWARD THREADED DIGEST SUMMARY TRANSCRIPT } enum IiosRouteDecisionState { ALLOW DENY REVIEW SUPPRESS SIMULATED } // ─── AI enums (P7) ──────────────────────────────────────────────── enum IiosAiJobType { CLASSIFY SUMMARIZE EXTRACT } enum IiosAiJobStatus { PENDING RUNNING DONE FAILED ABSTAINED } enum IiosAiArtifactType { CLASSIFICATION SUMMARY TRANSCRIPT DIGEST EXTRACTION } enum IiosAiArtifactStatus { PROPOSED ACCEPTED REJECTED SUPERSEDED } enum IiosAiClaimType { MODERATION_FLAG EVENT FAQ INTENT PRIORITY } enum IiosAiClaimValidation { UNVALIDATED ACCEPTED REJECTED } // ─── Calendar / Meeting enums (P8) ──────────────────────────────── enum IiosMeetingType { ZOOM PHONE IN_PERSON CALLBACK INTERNAL } enum IiosMeetingStatus { REQUESTED SCHEDULED CONFIRMED CANCELLED COMPLETED NO_SHOW } enum IiosMeetingRequestStatus { PENDING PROPOSED ACCEPTED DECLINED SCHEDULED CANCELLED } enum IiosParticipantRole { ORGANIZER REQUIRED OPTIONAL } enum IiosAttendanceStatus { INVITED ACCEPTED DECLINED TENTATIVE ATTENDED NO_SHOW } enum IiosParticipantVisibility { FULL LIMITED NONE } enum IiosConsentStatus { UNKNOWN GRANTED DENIED REVOKED } enum IiosTranscriptStatus { PENDING READY BLOCKED } enum IiosSummaryStatus { PROPOSED APPROVED PUBLISHED REJECTED } enum IiosActionItemStatus { OPEN IN_PROGRESS DONE CANCELLED } enum IiosCalendarProviderType { SIMULATED GOOGLE OUTLOOK ICS } enum IiosCalendarSyncStatus { IDLE SYNCING ERROR } enum IiosCalendarEventStatus { CONFIRMED TENTATIVE CANCELLED } // ─── Kernel tables ──────────────────────────────────────────────── /// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null). model IiosScope { id String @id @default(cuid()) orgId String appId String buId String? tenantId String? workspaceId String? projectId String? userScopeId String? cellId String @default("cell-default") // P9 cell-partition hook createdAt DateTime @default(now()) sourceHandles IiosSourceHandle[] channels IiosChannel[] threads IiosThread[] interactions IiosInteraction[] annotations IiosInteractionAnnotation[] inboxItems IiosInboxItem[] supportQueues IiosSupportQueue[] tickets IiosTicket[] callbacks IiosCallbackRequest[] @@index([orgId, appId, tenantId]) } /// A channel-specific handle (phone/email/portal user). NOT identity — stays /// UNVERIFIED until MDM resolves it; no silent merge. model IiosSourceHandle { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) kind IiosHandleKind externalId String displayName String? canonicalEntityId String? // set only when MDM resolves; null = unresolved confidence Float @default(0) verificationState String @default("UNVERIFIED") firstSeenAt DateTime @default(now()) lastSeenAt DateTime @default(now()) metadata Json? redactedAt DateTime? // set when this subject has been erased (DSR, P9) actors IiosActorRef[] @@unique([scopeId, kind, externalId]) @@index([canonicalEntityId]) } /// The local actor (human / AI / service / unknown) behind an interaction. model IiosActorRef { id String @id @default(cuid()) kind IiosActorKind sourceHandleId String? sourceHandle IiosSourceHandle? @relation(fields: [sourceHandleId], references: [id]) platformPrincipalRef String? canonicalEntityId String? displayName String? createdAt DateTime @default(now()) threadsCreated IiosThread[] @relation("ThreadCreatedBy") participations IiosThreadParticipant[] interactions IiosInteraction[] annotations IiosInteractionAnnotation[] receipts IiosMessageReceipt[] unreadCounters IiosUnreadCounter[] inboxItemsOwned IiosInboxItem[] teamMemberships IiosSupportTeamMember[] ticketsRequested IiosTicket[] @relation("TicketRequester") ticketsAssigned IiosTicket[] @relation("TicketAssignee") callbacksRequested IiosCallbackRequest[] } /// A configured channel surface (PORTAL in P1). model IiosChannel { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) channelType String channelName String direction String @default("BOTH") capabilityContract Json? externalRef String? enabled Boolean @default(true) createdAt DateTime @default(now()) threads IiosThread[] interactions IiosInteraction[] @@index([scopeId, channelType]) } /// A conversation/thread on a channel. model IiosThread { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) channelId String? channel IiosChannel? @relation(fields: [channelId], references: [id]) threadKind String @default("CONVERSATION") subject String? externalThreadRef String? status String @default("OPEN") visibilityClass String @default("PRIVATE") createdByActorId String? createdByActor IiosActorRef? @relation("ThreadCreatedBy", fields: [createdByActorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt metadata Json? participants IiosThreadParticipant[] interactions IiosInteraction[] unreadCounters IiosUnreadCounter[] inboxItems IiosInboxItem[] ticketLinks IiosTicketThreadLink[] @@index([scopeId, status]) } model IiosThreadParticipant { threadId String thread IiosThread @relation(fields: [threadId], references: [id], onDelete: Cascade) actorId String actor IiosActorRef @relation(fields: [actorId], references: [id]) participantRole String @default("MEMBER") joinedAt DateTime @default(now()) leftAt DateTime? @@id([threadId, actorId]) } /// The semantic envelope. Idempotent per (scope, idempotencyKey). model IiosInteraction { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) kind IiosInteractionKind threadId String? thread IiosThread? @relation(fields: [threadId], references: [id]) channelId String? channel IiosChannel? @relation(fields: [channelId], references: [id]) actorId String? actor IiosActorRef? @relation(fields: [actorId], references: [id]) parentInteractionId String? parent IiosInteraction? @relation("InteractionParent", fields: [parentInteractionId], references: [id]) children IiosInteraction[] @relation("InteractionParent") externalId String? idempotencyKey String occurredAt DateTime @default(now()) receivedAt DateTime @default(now()) status IiosDeliveryState @default(RECEIVED) dataClass String @default("internal") // envelope data class; drives retention window (P9) policyDecisionRef String? consentReceiptRef String? crreBundleRef String? traceId String? metadata Json? parts IiosMessagePart[] receipts IiosMessageReceipt[] annotations IiosInteractionAnnotation[] inboxItems IiosInboxItem[] ticketsCreatedFrom IiosTicket[] @@unique([scopeId, idempotencyKey]) @@index([threadId, occurredAt]) } /// A generic annotation an actor attaches to an interaction. Both `annotationType` /// and `value` are OPAQUE, app-supplied strings the kernel stores and aggregates /// but never interprets — e.g. the chat app writes type "reaction" / value "👍". /// The same primitive backs pins, saves, flags, tags later. No chat vocabulary here. model IiosInteractionAnnotation { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) targetInteractionId String target IiosInteraction @relation(fields: [targetInteractionId], references: [id], onDelete: Cascade) actorId String actor IiosActorRef @relation(fields: [actorId], references: [id]) annotationType String value String createdAt DateTime @default(now()) @@unique([scopeId, targetInteractionId, actorId, annotationType, value]) @@index([targetInteractionId]) } model IiosMessagePart { id String @id @default(cuid()) interactionId String interaction IiosInteraction @relation(fields: [interactionId], references: [id], onDelete: Cascade) partIndex Int kind IiosMessagePartKind bodyText String? contentRef String? mimeType String? sizeBytes BigInt? checksumSha256 String? metadata Json? @@unique([interactionId, partIndex]) } /// Transactional outbox — written in the same tx as the business row. model IiosOutboxEvent { eventId String @id @default(cuid()) aggregateType String aggregateId String eventType String cloudEvent Json partitionKey String status String @default("PENDING") attempts Int @default(0) nextAttemptAt DateTime @default(now()) createdAt DateTime @default(now()) publishedAt DateTime? @@index([status, nextAttemptAt, createdAt]) } /// Idempotent-consumer ledger for the outbox relay / downstream consumers. model IiosProcessedEvent { consumerName String eventId String processedAt DateTime @default(now()) resultHash String? @@id([consumerName, eventId]) } /// Per-resource retention snapshot (P9). Absolute lifecycle timestamps frozen from the /// resource's age + policy window; read by the retention sweep. delete = redact-in-place. model IiosRetentionPolicySnapshot { id String @id @default(cuid()) policyKey String scopeSnapshotId String targetType String // 'interaction' targetId String dataClass String retainUntil DateTime? archiveAfter DateTime deleteAfter DateTime sourceVersion String @default("v1") status String @default("ACTIVE") // ACTIVE | ARCHIVED | REDACTED createdAt DateTime @default(now()) @@unique([targetType, targetId]) @@index([status, deleteAfter]) @@index([scopeSnapshotId, status]) } /// Legal/compliance hold (P9 DSR). An ACTIVE, unexpired hold over a target BLOCKS /// erasure/retention deletion until an authorised release. Never auto-released. model IiosComplianceHold { id String @id @default(cuid()) scopeId String targetType String // 'data_subject' | 'message' | 'media' targetId String holdReason String appliedByActorId String? status String @default("ACTIVE") // ACTIVE | RELEASED createdAt DateTime @default(now()) expiresAt DateTime? releasedAt DateTime? @@index([scopeId, targetType, targetId, status]) } /// Projection replay cursor (P9, KG-06). Ordered high-water-mark + rolling checksum per /// (projection, topic, partition) proving replay reproduces the same projection outcome. model IiosProjectionCursor { id String @id @default(cuid()) projectionName String sourceTopic String partitionKey String lastEventId String lastOffset Int @default(0) checksum String updatedAt DateTime @updatedAt @@unique([projectionName, sourceTopic, partitionKey]) @@index([partitionKey]) } /// Idempotent-command ledger (P9). Suppresses duplicate externally-visible mutations: /// same (scope, command, key) replays the cached response; a different request → conflict. model IiosIdempotencyCommand { id String @id @default(cuid()) scopeId String? commandName String idempotencyKey String schemaVersion String @default("v1") actorRefId String? requestHash String responseHash String? response Json? status String @default("IN_FLIGHT") // IN_FLIGHT | COMPLETED | FAILED createdAt DateTime @default(now()) completedAt DateTime? @@unique([scopeId, commandName, idempotencyKey]) @@index([scopeId, status]) } // ─── P2: messaging — receipts & unread ──────────────────────────── /// Delivery/read receipt for a message interaction, per actor. model IiosMessageReceipt { id String @id @default(cuid()) interactionId String interaction IiosInteraction @relation(fields: [interactionId], references: [id], onDelete: Cascade) actorId String? actor IiosActorRef? @relation(fields: [actorId], references: [id]) receiptKind IiosReceiptKind occurredAt DateTime @default(now()) providerReceiptRef String? metadata Json? @@unique([interactionId, actorId, receiptKind]) @@index([interactionId]) } /// Per-(thread, actor) unread count + last-read marker. model IiosUnreadCounter { threadId String thread IiosThread @relation(fields: [threadId], references: [id], onDelete: Cascade) actorId String actor IiosActorRef @relation(fields: [actorId], references: [id]) unreadCount Int @default(0) lastReadInteractionId String? updatedAt DateTime @updatedAt @@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]) } // ─── P4: Support (specialization over message + inbox) ──────────── /// A support queue; agents (team members) are assigned tickets from it. model IiosSupportQueue { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) name String supportTier String @default("REGULAR") slaPolicyRef String? // SLA placeholder (no timer logic until P7) assignmentPolicy Json? enabled Boolean @default(true) createdAt DateTime @default(now()) members IiosSupportTeamMember[] tickets IiosTicket[] @@index([scopeId]) } /// An agent's membership + capacity in a queue. Agent = an ActorRef that is a member. model IiosSupportTeamMember { queueId String queue IiosSupportQueue @relation(fields: [queueId], references: [id], onDelete: Cascade) actorId String actor IiosActorRef @relation(fields: [actorId], references: [id]) role String @default("AGENT") maxActive Int @default(3) activeCount Int @default(0) availabilityState String @default("OFFLINE") @@id([queueId, actorId]) @@index([actorId]) } /// Support work object. Linked many-to-many to threads; the thread stays generic. model IiosTicket { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) queueId String? queue IiosSupportQueue? @relation(fields: [queueId], references: [id]) requesterActorId String? requesterActor IiosActorRef? @relation("TicketRequester", fields: [requesterActorId], references: [id]) assignedActorId String? assignedActor IiosActorRef? @relation("TicketAssignee", fields: [assignedActorId], references: [id]) subject String state IiosTicketState @default(NEW) priority IiosTicketPriority @default(P3) issueClass String? issueSubclass String? slaDueAt DateTime? // SLA placeholder (no timer logic until P7) createdFromInteractionId String? createdFromInteraction IiosInteraction? @relation(fields: [createdFromInteractionId], references: [id]) traceId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt metadata Json? threadLinks IiosTicketThreadLink[] stateHistory IiosTicketStateHistory[] callbacks IiosCallbackRequest[] @@index([scopeId, state]) @@index([assignedActorId, state]) } /// M:N link between tickets and kernel threads (one thread ↔ many tickets, and vice-versa). model IiosTicketThreadLink { ticketId String ticket IiosTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade) threadId String thread IiosThread @relation(fields: [threadId], references: [id], onDelete: Cascade) relationKind String @default("PRIMARY") linkedAt DateTime @default(now()) @@id([ticketId, threadId]) @@index([threadId]) } /// Ticket state-transition audit. model IiosTicketStateHistory { id String @id @default(cuid()) ticketId String ticket IiosTicket @relation(fields: [ticketId], references: [id], onDelete: Cascade) fromState IiosTicketState? toState IiosTicketState actorId String? reasonCode String? at DateTime @default(now()) @@index([ticketId]) } /// Callback request placeholder (the Meeting module is P8; meetingRef is a stub). model IiosCallbackRequest { id String @id @default(cuid()) scopeId String scope IiosScope @relation(fields: [scopeId], references: [id], onDelete: Cascade) ticketId String? ticket IiosTicket? @relation(fields: [ticketId], references: [id]) requesterActorId String requesterActor IiosActorRef @relation(fields: [requesterActorId], references: [id]) preferChannel String preferredTime DateTime? notes String? status IiosCallbackStatus @default(PENDING) meetingRef String? createdAt DateTime @default(now()) @@index([scopeId, status]) } // ─── P5: Adapters (anti-corruption layer, simulation mode) ──────── /// Raw inbound provider payload (claim-check / replay). Verified before storing. model IiosInboundRawEvent { id String @id @default(cuid()) channelType String externalEventId String? signatureStatus String // VERIFIED | INVALID | UNSIGNED status String @default("RECEIVED") // RECEIVED | NORMALIZED | REJECTED | FAILED headers Json? payload Json traceId String? interactionId String? createdAt DateTime @default(now()) @@unique([channelType, externalEventId]) @@index([status]) } /// A signed command to send on an external channel — executed by the sandbox sink. model IiosOutboundCommand { id String @id @default(cuid()) channelType String target String payload Json status String @default("PENDING") // PENDING | SENT | FAILED | RATE_LIMITED | BLOCKED scopeId String? // owning tenant scope (P9 tenant fencing + per-tenant quota) idempotencyKey String @unique providerRef String? consentReceiptRef String? // CMP consent receipt this send went out under (P9) createdAt DateTime @default(now()) attempts IiosDeliveryAttempt[] @@index([channelType, status]) } model IiosDeliveryAttempt { id String @id @default(cuid()) commandId String command IiosOutboundCommand @relation(fields: [commandId], references: [id], onDelete: Cascade) attemptNo Int status String providerRef String? latencyMs Int? errorCode String? at DateTime @default(now()) @@index([commandId]) } /// Per-(channelType, key) token/window bucket for outbound throttling. model IiosRateLimitBucket { channelType String bucketKey String windowStart DateTime @default(now()) count Int @default(0) resetAt DateTime @@id([channelType, bucketKey]) } // ─── P6: Routing (preview-first community forwarding) ────────────── /// One origin→destination route rule. Preview-first: SIMULATION_ONLY + disabled /// + requiresReview by default. Restricted destinations are deny-by-default. model IiosRouteBinding { id String @id @default(cuid()) scopeId String originChannelType String originRef String? destinationChannelType String destinationRef String? restrictionProfile String? // e.g. CHILD / SENIORS / ADULT / PUBLIC mode IiosRouteMode @default(SIMULATION_ONLY) outputFormat IiosOutputFormat @default(FORWARD) frequency String @default("IMMEDIATE") includeAttachments Boolean @default(false) requiresReview Boolean @default(true) rulepackVersion String @default("v1") enabled Boolean @default(false) createdAt DateTime @default(now()) decisions IiosRouteDecision[] @@index([scopeId, originChannelType]) } /// Per-(interaction, binding) routing decision + rendered preview. No send during simulation. model IiosRouteDecision { id String @id @default(cuid()) interactionId String routeBindingId String routeBinding IiosRouteBinding @relation(fields: [routeBindingId], references: [id], onDelete: Cascade) decisionState IiosRouteDecisionState reasonCodes String[] previewPayload Json outputFormat IiosOutputFormat rulepackVersion String simulationId String? decidedByActorId String? executed Boolean @default(false) executedCommandId String? createdAt DateTime @default(now()) @@unique([interactionId, routeBindingId]) @@index([decisionState]) } /// A deterministic (RULE) or later AI-proposed flag on an interaction. AI can /// propose but never allow a route (P7). model IiosModerationFlag { id String @id @default(cuid()) interactionId String flagType String severity String @default("LOW") proposedBy String @default("RULE") confidence Float? explanation String? createdAt DateTime @default(now()) resolvedAt DateTime? @@index([interactionId]) } // ─── AI Interaction SDK tables (P7) — proposal layer, never decides ───────── /// A scoped, purposed AI work order (classify / summarize / extract). Carries the /// provenance the critics mandate (input_scope_hash, policy_decision_id, purpose, /// replay_version, traceId, idempotencyKey). Idempotent per key. model IiosAiJob { id String @id @default(cuid()) scopeId String jobType IiosAiJobType interactionId String? inputWindowRef String purpose String status IiosAiJobStatus @default(PENDING) modelPolicyRef String @default("golden-v1") policyDecisionRef String? inputScopeHash String replayVersion String @default("v1") abstentionReason String? traceId String? idempotencyKey String @unique createdAt DateTime @default(now()) artifacts IiosAiArtifact[] @@index([scopeId, jobType]) @@index([interactionId]) } /// Model/prompt/tool execution metadata + cost. The budget governor (KG-12) sums /// costUnits per scope. In P7 provider='fake' (deterministic golden model). model IiosAiModelRun { id String @id @default(cuid()) scopeId String provider String @default("fake") model String modelVersion String promptVersion String toolPolicyRef String? tokensIn Int @default(0) tokensOut Int @default(0) costUnits Int @default(0) latencyMs Int @default(0) createdAt DateTime @default(now()) artifacts IiosAiArtifact[] toolCalls IiosAiToolCall[] @@index([scopeId]) } /// A durable AI proposal/output. NEVER committed truth by itself — status starts /// PROPOSED and only a human accept (or a deterministic gate) advances it. model IiosAiArtifact { id String @id @default(cuid()) jobId String job IiosAiJob @relation(fields: [jobId], references: [id], onDelete: Cascade) modelRunId String modelRun IiosAiModelRun @relation(fields: [modelRunId], references: [id], onDelete: Cascade) artifactType IiosAiArtifactType status IiosAiArtifactStatus @default(PROPOSED) confidence Float? contentRef Json explanationRef String? abstentionReason String? acceptedByActorId String? createdAt DateTime @default(now()) claims IiosAiClaim[] evidence IiosAiEvidenceLink[] @@index([jobId]) @@index([status]) } /// A structured extracted claim (event date, intent, priority, FAQ, moderation /// flag). validationStatus stays UNVALIDATED until a human/deterministic accept. model IiosAiClaim { id String @id @default(cuid()) artifactId String artifact IiosAiArtifact @relation(fields: [artifactId], references: [id], onDelete: Cascade) claimType IiosAiClaimType claimJson Json confidence Float? validationStatus IiosAiClaimValidation @default(UNVALIDATED) acceptedByRef String? createdAt DateTime @default(now()) @@index([artifactId]) } /// Source citation for an AI claim/artifact (message / segment / KB chunk). model IiosAiEvidenceLink { id String @id @default(cuid()) artifactId String artifact IiosAiArtifact @relation(fields: [artifactId], references: [id], onDelete: Cascade) sourceType String sourceId String spanRef String? relevanceScore Float? createdAt DateTime @default(now()) @@index([artifactId]) } /// Tool calls made by an agent under MCP/tool policy. Schema now; exercised when a /// real provider + tools land in P9. model IiosAiToolCall { id String @id @default(cuid()) modelRunId String modelRun IiosAiModelRun @relation(fields: [modelRunId], references: [id], onDelete: Cascade) toolName String toolInputHash String toolOutputRef String? policyRefId String? status String @default("PENDING") createdAt DateTime @default(now()) @@index([modelRunId]) } /// Reference to a vector embedding record/index; does not expose text. Schema now; /// exercised when RAG lands in P9. model IiosEmbeddingRef { id String @id @default(cuid()) sourceType String sourceId String embeddingModel String vectorStoreRef String embeddingHash String version String createdAt DateTime @default(now()) @@unique([sourceType, sourceId, version]) } // ─── Calendar / Meeting SDK tables (P8) — interaction specialization ──────── /// A request to schedule/reschedule/cancel a meeting. Genesis: a P4 callback /// (callbackRequestId), an accepted P7 AI EVENT claim (sourceClaimId), or direct. model IiosMeetingRequest { id String @id @default(cuid()) scopeId String interactionId String? requestedByActorId String targetActorId String? requestedWindowJson Json meetingType IiosMeetingType @default(CALLBACK) status IiosMeetingRequestStatus @default(PENDING) resultingMeetingId String? callbackRequestId String? sourceClaimId String? createdAt DateTime @default(now()) @@index([scopeId, status]) } /// A meeting: Zoom / phone / in-person / callback / internal. Owns calendar /// semantics; a message is only a related artifact. model IiosMeeting { id String @id @default(cuid()) scopeId String interactionId String? calendarEventId String? meetingType IiosMeetingType providerRef String? joinUrlRef String? status IiosMeetingStatus @default(REQUESTED) organizerActorId String threadId String? title String startAt DateTime? endAt DateTime? timezone String @default("UTC") createdAt DateTime @default(now()) participants IiosMeetingParticipant[] transcripts IiosMeetingTranscript[] summaries IiosMeetingSummary[] actionItems IiosMeetingActionItem[] @@index([scopeId, status]) } /// Attendee with role, attendance, per-attendee recording consent + visibility. /// The consent + visibility model is the P8 safety spine (KG-14 / Scenario 6). model IiosMeetingParticipant { id String @id @default(cuid()) meetingId String meeting IiosMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) actorRefId String? sourceHandleId String? role IiosParticipantRole @default(REQUIRED) attendanceStatus IiosAttendanceStatus @default(INVITED) recordingConsent IiosConsentStatus @default(UNKNOWN) visibility IiosParticipantVisibility @default(FULL) createdAt DateTime @default(now()) @@unique([meetingId, actorRefId]) @@index([meetingId]) } /// Transcript artifact metadata. Created only after the consent gate passes; /// otherwise status=BLOCKED with no segments. model IiosMeetingTranscript { id String @id @default(cuid()) meetingId String meeting IiosMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) sourceType String @default("SIMULATED") transcriptRef String consentRefId String? status IiosTranscriptStatus @default(PENDING) language String @default("en") createdAt DateTime @default(now()) segments IiosTranscriptSegment[] @@index([meetingId]) } /// Speaker-attributed transcript segment. model IiosTranscriptSegment { id String @id @default(cuid()) transcriptId String transcript IiosMeetingTranscript @relation(fields: [transcriptId], references: [id], onDelete: Cascade) segmentNo Int speakerActorId String? startMs Int endMs Int contentRef String confidence Float? createdAt DateTime @default(now()) @@index([transcriptId]) } /// AI or human meeting summary; links to a P7 ai_artifact. model IiosMeetingSummary { id String @id @default(cuid()) meetingId String meeting IiosMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) aiArtifactId String? summaryType String @default("AI") status IiosSummaryStatus @default(PROPOSED) approvedByActorId String? publishedAt DateTime? createdAt DateTime @default(now()) @@index([meetingId]) } /// Generic task/follow-up (Inbox-owned source of truth), derived from meetings, /// support, messages or CRM workflows. model IiosActionItem { id String @id @default(cuid()) scopeId String sourceInteractionId String? sourceMessageId String? ownerActorId String? ownerTeamId String? title String status IiosActionItemStatus @default(OPEN) dueAt DateTime? completedAt DateTime? createdAt DateTime @default(now()) meetingLinks IiosMeetingActionItem[] @@index([scopeId, status]) } /// Meeting-derived action item linked to a generic action_item. model IiosMeetingActionItem { id String @id @default(cuid()) meetingId String meeting IiosMeeting @relation(fields: [meetingId], references: [id], onDelete: Cascade) actionItemId String actionItem IiosActionItem @relation(fields: [actionItemId], references: [id], onDelete: Cascade) ownerActorId String? sourceSegmentId String? status IiosActionItemStatus @default(OPEN) createdAt DateTime @default(now()) @@unique([meetingId, actionItemId]) @@index([meetingId]) } /// Connected calendar/provider account or service mailbox; stores refs, not secrets. model IiosCalendarProviderAccount { id String @id @default(cuid()) scopeId String actorRefId String? providerType IiosCalendarProviderType @default(SIMULATED) accountHandleToken String secretRef String? syncStatus IiosCalendarSyncStatus @default(IDLE) createdAt DateTime @default(now()) events IiosCalendarEvent[] cursors IiosCalendarSyncCursor[] @@index([scopeId]) } /// Imported or created calendar event with policy-scoped metadata. model IiosCalendarEvent { id String @id @default(cuid()) scopeId String providerAccountId String? providerAccount IiosCalendarProviderAccount? @relation(fields: [providerAccountId], references: [id], onDelete: Cascade) externalEventId String? title String startAt DateTime endAt DateTime status IiosCalendarEventStatus @default(CONFIRMED) visibilityClass String @default("internal") interactionId String? createdAt DateTime @default(now()) @@unique([providerAccountId, externalEventId]) @@index([scopeId]) } /// Availability / scheduling constraints for an actor or team. model IiosAvailabilityWindow { id String @id @default(cuid()) scopeId String actorRefId String? teamProjectionId String? startAt DateTime endAt DateTime source String @default("USER") recurrenceJson Json? status String @default("ACTIVE") createdAt DateTime @default(now()) @@index([scopeId, actorRefId]) } /// Incremental sync cursor / replay position for a provider calendar. model IiosCalendarSyncCursor { id String @id @default(cuid()) providerAccountId String providerAccount IiosCalendarProviderAccount @relation(fields: [providerAccountId], references: [id], onDelete: Cascade) externalCursor String? lastSyncAt DateTime? status IiosCalendarSyncStatus @default(IDLE) errorCode String? createdAt DateTime @default(now()) @@unique([providerAccountId]) } // ─── Observability (P9) ─────────────────────────────────────────── /// Binds an IIOS action to its trace / actor / resource for cross-service causality /// and "replay a decision" (mandated iios_audit_link). Written at decision points. model IiosAuditLink { id String @id @default(cuid()) traceId String? correlationId String? scopeId String? actorRefId String? action String resourceType String resourceId String createdAt DateTime @default(now()) @@index([traceId]) @@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]) }