From 21772e7611a870745ffdf39d218a1cdce6ebe5bc Mon Sep 17 00:00:00 2001 From: maaz519 Date: Wed, 1 Jul 2026 17:51:35 +0530 Subject: [PATCH] feat(p8): calendar/meeting schema + enums + events + migration Task 8.1: 12 Calendar/Meeting tables (meeting_request, meeting, participant, transcript, segment, summary, generic action_item + meeting link, provider account, calendar_event, availability_window, sync_cursor) + 13 enums; migration calendar-init. Adds calendar.meeting.requested/scheduled/reminder.queued/ summarized events. resetDb truncates the new tables. Kernel already had the MEETING_REQUEST / MEETING_FOLLOWUP / callback.meetingRef stubs. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/iios-contracts/src/events.ts | 4 + .../migration.sql | 294 +++++++++++++++++ packages/iios-service/prisma/schema.prisma | 312 ++++++++++++++++++ .../iios-service/src/test-utils/reset-db.ts | 2 + 4 files changed, 612 insertions(+) create mode 100644 packages/iios-service/prisma/migrations/20260701122126_calendar_init/migration.sql diff --git a/packages/iios-contracts/src/events.ts b/packages/iios-contracts/src/events.ts index 776a7a5..4c78d27 100644 --- a/packages/iios-contracts/src/events.ts +++ b/packages/iios-contracts/src/events.ts @@ -46,6 +46,10 @@ export const IIOS_EVENTS = { aiJobCreated: 'com.insignia.iios.ai.job.created.v1', aiArtifactProposed: 'com.insignia.iios.ai.artifact.proposed.v1', aiArtifactAccepted: 'com.insignia.iios.ai.artifact.accepted.v1', + meetingRequested: 'com.insignia.iios.calendar.meeting.requested.v1', + meetingScheduled: 'com.insignia.iios.calendar.meeting.scheduled.v1', + meetingReminderQueued: 'com.insignia.iios.calendar.reminder.queued.v1', + meetingSummarized: 'com.insignia.iios.calendar.meeting.summarized.v1', notificationQueued: 'com.insignia.iios.notification.queued.v1', deliveryAttempted: 'com.insignia.iios.delivery.attempted.v1', } as const; diff --git a/packages/iios-service/prisma/migrations/20260701122126_calendar_init/migration.sql b/packages/iios-service/prisma/migrations/20260701122126_calendar_init/migration.sql new file mode 100644 index 0000000..ed6f22f --- /dev/null +++ b/packages/iios-service/prisma/migrations/20260701122126_calendar_init/migration.sql @@ -0,0 +1,294 @@ +-- CreateEnum +CREATE TYPE "IiosMeetingType" AS ENUM ('ZOOM', 'PHONE', 'IN_PERSON', 'CALLBACK', 'INTERNAL'); + +-- CreateEnum +CREATE TYPE "IiosMeetingStatus" AS ENUM ('REQUESTED', 'SCHEDULED', 'CONFIRMED', 'CANCELLED', 'COMPLETED', 'NO_SHOW'); + +-- CreateEnum +CREATE TYPE "IiosMeetingRequestStatus" AS ENUM ('PENDING', 'PROPOSED', 'ACCEPTED', 'DECLINED', 'SCHEDULED', 'CANCELLED'); + +-- CreateEnum +CREATE TYPE "IiosParticipantRole" AS ENUM ('ORGANIZER', 'REQUIRED', 'OPTIONAL'); + +-- CreateEnum +CREATE TYPE "IiosAttendanceStatus" AS ENUM ('INVITED', 'ACCEPTED', 'DECLINED', 'TENTATIVE', 'ATTENDED', 'NO_SHOW'); + +-- CreateEnum +CREATE TYPE "IiosParticipantVisibility" AS ENUM ('FULL', 'LIMITED', 'NONE'); + +-- CreateEnum +CREATE TYPE "IiosConsentStatus" AS ENUM ('UNKNOWN', 'GRANTED', 'DENIED', 'REVOKED'); + +-- CreateEnum +CREATE TYPE "IiosTranscriptStatus" AS ENUM ('PENDING', 'READY', 'BLOCKED'); + +-- CreateEnum +CREATE TYPE "IiosSummaryStatus" AS ENUM ('PROPOSED', 'APPROVED', 'PUBLISHED', 'REJECTED'); + +-- CreateEnum +CREATE TYPE "IiosActionItemStatus" AS ENUM ('OPEN', 'IN_PROGRESS', 'DONE', 'CANCELLED'); + +-- CreateEnum +CREATE TYPE "IiosCalendarProviderType" AS ENUM ('SIMULATED', 'GOOGLE', 'OUTLOOK', 'ICS'); + +-- CreateEnum +CREATE TYPE "IiosCalendarSyncStatus" AS ENUM ('IDLE', 'SYNCING', 'ERROR'); + +-- CreateEnum +CREATE TYPE "IiosCalendarEventStatus" AS ENUM ('CONFIRMED', 'TENTATIVE', 'CANCELLED'); + +-- CreateTable +CREATE TABLE "IiosMeetingRequest" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "interactionId" TEXT, + "requestedByActorId" TEXT NOT NULL, + "targetActorId" TEXT, + "requestedWindowJson" JSONB NOT NULL, + "meetingType" "IiosMeetingType" NOT NULL DEFAULT 'CALLBACK', + "status" "IiosMeetingRequestStatus" NOT NULL DEFAULT 'PENDING', + "resultingMeetingId" TEXT, + "callbackRequestId" TEXT, + "sourceClaimId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeetingRequest_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosMeeting" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "interactionId" TEXT, + "calendarEventId" TEXT, + "meetingType" "IiosMeetingType" NOT NULL, + "providerRef" TEXT, + "joinUrlRef" TEXT, + "status" "IiosMeetingStatus" NOT NULL DEFAULT 'REQUESTED', + "organizerActorId" TEXT NOT NULL, + "threadId" TEXT, + "title" TEXT NOT NULL, + "startAt" TIMESTAMP(3), + "endAt" TIMESTAMP(3), + "timezone" TEXT NOT NULL DEFAULT 'UTC', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeeting_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosMeetingParticipant" ( + "id" TEXT NOT NULL, + "meetingId" TEXT NOT NULL, + "actorRefId" TEXT, + "sourceHandleId" TEXT, + "role" "IiosParticipantRole" NOT NULL DEFAULT 'REQUIRED', + "attendanceStatus" "IiosAttendanceStatus" NOT NULL DEFAULT 'INVITED', + "recordingConsent" "IiosConsentStatus" NOT NULL DEFAULT 'UNKNOWN', + "visibility" "IiosParticipantVisibility" NOT NULL DEFAULT 'FULL', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeetingParticipant_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosMeetingTranscript" ( + "id" TEXT NOT NULL, + "meetingId" TEXT NOT NULL, + "sourceType" TEXT NOT NULL DEFAULT 'SIMULATED', + "transcriptRef" TEXT NOT NULL, + "consentRefId" TEXT, + "status" "IiosTranscriptStatus" NOT NULL DEFAULT 'PENDING', + "language" TEXT NOT NULL DEFAULT 'en', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeetingTranscript_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosTranscriptSegment" ( + "id" TEXT NOT NULL, + "transcriptId" TEXT NOT NULL, + "segmentNo" INTEGER NOT NULL, + "speakerActorId" TEXT, + "startMs" INTEGER NOT NULL, + "endMs" INTEGER NOT NULL, + "contentRef" TEXT NOT NULL, + "confidence" DOUBLE PRECISION, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosTranscriptSegment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosMeetingSummary" ( + "id" TEXT NOT NULL, + "meetingId" TEXT NOT NULL, + "aiArtifactId" TEXT, + "summaryType" TEXT NOT NULL DEFAULT 'AI', + "status" "IiosSummaryStatus" NOT NULL DEFAULT 'PROPOSED', + "approvedByActorId" TEXT, + "publishedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeetingSummary_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosActionItem" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "sourceInteractionId" TEXT, + "sourceMessageId" TEXT, + "ownerActorId" TEXT, + "ownerTeamId" TEXT, + "title" TEXT NOT NULL, + "status" "IiosActionItemStatus" NOT NULL DEFAULT 'OPEN', + "dueAt" TIMESTAMP(3), + "completedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosActionItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosMeetingActionItem" ( + "id" TEXT NOT NULL, + "meetingId" TEXT NOT NULL, + "actionItemId" TEXT NOT NULL, + "ownerActorId" TEXT, + "sourceSegmentId" TEXT, + "status" "IiosActionItemStatus" NOT NULL DEFAULT 'OPEN', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosMeetingActionItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosCalendarProviderAccount" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "actorRefId" TEXT, + "providerType" "IiosCalendarProviderType" NOT NULL DEFAULT 'SIMULATED', + "accountHandleToken" TEXT NOT NULL, + "secretRef" TEXT, + "syncStatus" "IiosCalendarSyncStatus" NOT NULL DEFAULT 'IDLE', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosCalendarProviderAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosCalendarEvent" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "providerAccountId" TEXT, + "externalEventId" TEXT, + "title" TEXT NOT NULL, + "startAt" TIMESTAMP(3) NOT NULL, + "endAt" TIMESTAMP(3) NOT NULL, + "status" "IiosCalendarEventStatus" NOT NULL DEFAULT 'CONFIRMED', + "visibilityClass" TEXT NOT NULL DEFAULT 'internal', + "interactionId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosCalendarEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAvailabilityWindow" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "actorRefId" TEXT, + "teamProjectionId" TEXT, + "startAt" TIMESTAMP(3) NOT NULL, + "endAt" TIMESTAMP(3) NOT NULL, + "source" TEXT NOT NULL DEFAULT 'USER', + "recurrenceJson" JSONB, + "status" TEXT NOT NULL DEFAULT 'ACTIVE', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAvailabilityWindow_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosCalendarSyncCursor" ( + "id" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "externalCursor" TEXT, + "lastSyncAt" TIMESTAMP(3), + "status" "IiosCalendarSyncStatus" NOT NULL DEFAULT 'IDLE', + "errorCode" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosCalendarSyncCursor_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "IiosMeetingRequest_scopeId_status_idx" ON "IiosMeetingRequest"("scopeId", "status"); + +-- CreateIndex +CREATE INDEX "IiosMeeting_scopeId_status_idx" ON "IiosMeeting"("scopeId", "status"); + +-- CreateIndex +CREATE INDEX "IiosMeetingParticipant_meetingId_idx" ON "IiosMeetingParticipant"("meetingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosMeetingParticipant_meetingId_actorRefId_key" ON "IiosMeetingParticipant"("meetingId", "actorRefId"); + +-- CreateIndex +CREATE INDEX "IiosMeetingTranscript_meetingId_idx" ON "IiosMeetingTranscript"("meetingId"); + +-- CreateIndex +CREATE INDEX "IiosTranscriptSegment_transcriptId_idx" ON "IiosTranscriptSegment"("transcriptId"); + +-- CreateIndex +CREATE INDEX "IiosMeetingSummary_meetingId_idx" ON "IiosMeetingSummary"("meetingId"); + +-- CreateIndex +CREATE INDEX "IiosActionItem_scopeId_status_idx" ON "IiosActionItem"("scopeId", "status"); + +-- CreateIndex +CREATE INDEX "IiosMeetingActionItem_meetingId_idx" ON "IiosMeetingActionItem"("meetingId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosMeetingActionItem_meetingId_actionItemId_key" ON "IiosMeetingActionItem"("meetingId", "actionItemId"); + +-- CreateIndex +CREATE INDEX "IiosCalendarProviderAccount_scopeId_idx" ON "IiosCalendarProviderAccount"("scopeId"); + +-- CreateIndex +CREATE INDEX "IiosCalendarEvent_scopeId_idx" ON "IiosCalendarEvent"("scopeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosCalendarEvent_providerAccountId_externalEventId_key" ON "IiosCalendarEvent"("providerAccountId", "externalEventId"); + +-- CreateIndex +CREATE INDEX "IiosAvailabilityWindow_scopeId_actorRefId_idx" ON "IiosAvailabilityWindow"("scopeId", "actorRefId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosCalendarSyncCursor_providerAccountId_key" ON "IiosCalendarSyncCursor"("providerAccountId"); + +-- AddForeignKey +ALTER TABLE "IiosMeetingParticipant" ADD CONSTRAINT "IiosMeetingParticipant_meetingId_fkey" FOREIGN KEY ("meetingId") REFERENCES "IiosMeeting"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosMeetingTranscript" ADD CONSTRAINT "IiosMeetingTranscript_meetingId_fkey" FOREIGN KEY ("meetingId") REFERENCES "IiosMeeting"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosTranscriptSegment" ADD CONSTRAINT "IiosTranscriptSegment_transcriptId_fkey" FOREIGN KEY ("transcriptId") REFERENCES "IiosMeetingTranscript"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosMeetingSummary" ADD CONSTRAINT "IiosMeetingSummary_meetingId_fkey" FOREIGN KEY ("meetingId") REFERENCES "IiosMeeting"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosMeetingActionItem" ADD CONSTRAINT "IiosMeetingActionItem_meetingId_fkey" FOREIGN KEY ("meetingId") REFERENCES "IiosMeeting"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosMeetingActionItem" ADD CONSTRAINT "IiosMeetingActionItem_actionItemId_fkey" FOREIGN KEY ("actionItemId") REFERENCES "IiosActionItem"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosCalendarEvent" ADD CONSTRAINT "IiosCalendarEvent_providerAccountId_fkey" FOREIGN KEY ("providerAccountId") REFERENCES "IiosCalendarProviderAccount"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosCalendarSyncCursor" ADD CONSTRAINT "IiosCalendarSyncCursor_providerAccountId_fkey" FOREIGN KEY ("providerAccountId") REFERENCES "IiosCalendarProviderAccount"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/iios-service/prisma/schema.prisma b/packages/iios-service/prisma/schema.prisma index c903ead..8aa17d9 100644 --- a/packages/iios-service/prisma/schema.prisma +++ b/packages/iios-service/prisma/schema.prisma @@ -189,6 +189,101 @@ enum IiosAiClaimValidation { 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). @@ -843,3 +938,220 @@ model IiosEmbeddingRef { @@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]) +} diff --git a/packages/iios-service/src/test-utils/reset-db.ts b/packages/iios-service/src/test-utils/reset-db.ts index 069bd6b..bb02b9f 100644 --- a/packages/iios-service/src/test-utils/reset-db.ts +++ b/packages/iios-service/src/test-utils/reset-db.ts @@ -8,6 +8,8 @@ import type { PrismaClient } from '@prisma/client'; export async function resetDb(prisma: PrismaClient): Promise { await prisma.$executeRawUnsafe( `TRUNCATE TABLE + "IiosMeetingActionItem","IiosActionItem","IiosTranscriptSegment","IiosMeetingTranscript","IiosMeetingSummary","IiosMeetingParticipant","IiosMeeting","IiosMeetingRequest", + "IiosCalendarSyncCursor","IiosCalendarEvent","IiosCalendarProviderAccount","IiosAvailabilityWindow", "IiosAiEvidenceLink","IiosAiClaim","IiosAiToolCall","IiosAiArtifact","IiosAiModelRun","IiosAiJob","IiosEmbeddingRef", "IiosRouteDecision","IiosRouteBinding","IiosModerationFlag", "IiosInboundRawEvent","IiosDeliveryAttempt","IiosOutboundCommand","IiosRateLimitBucket",