From dfd87cdde7e69ec7b3ea628a3b35e69d7f31566b Mon Sep 17 00:00:00 2001 From: maaz519 Date: Wed, 1 Jul 2026 14:24:53 +0530 Subject: [PATCH] feat(service): P6.1 route tables (route_binding, route_decision, moderation_flag) + enums + events Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/iios-contracts/src/events.ts | 2 + .../20260701085451_route_init/migration.sql | 78 +++++++++++++++++ packages/iios-service/prisma/schema.prisma | 86 +++++++++++++++++++ .../iios-service/src/test-utils/reset-db.ts | 1 + 4 files changed, 167 insertions(+) create mode 100644 packages/iios-service/prisma/migrations/20260701085451_route_init/migration.sql diff --git a/packages/iios-contracts/src/events.ts b/packages/iios-contracts/src/events.ts index 6d0585c..317b278 100644 --- a/packages/iios-contracts/src/events.ts +++ b/packages/iios-contracts/src/events.ts @@ -41,6 +41,8 @@ export const IIOS_EVENTS = { ticketCreated: 'com.insignia.iios.support.ticket.created.v1', ticketStateChanged: 'com.insignia.iios.support.ticket.state.changed.v1', callbackRequested: 'com.insignia.iios.support.callback.requested.v1', + routeDecisionPreviewed: 'com.insignia.iios.route.decision.previewed.v1', + routeApproved: 'com.insignia.iios.route.approved.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/20260701085451_route_init/migration.sql b/packages/iios-service/prisma/migrations/20260701085451_route_init/migration.sql new file mode 100644 index 0000000..9ad0676 --- /dev/null +++ b/packages/iios-service/prisma/migrations/20260701085451_route_init/migration.sql @@ -0,0 +1,78 @@ +-- CreateEnum +CREATE TYPE "IiosRouteMode" AS ENUM ('MANUAL', 'AUTOMATIC', 'HYBRID', 'SIMULATION_ONLY'); + +-- CreateEnum +CREATE TYPE "IiosOutputFormat" AS ENUM ('FORWARD', 'THREADED', 'DIGEST', 'SUMMARY', 'TRANSCRIPT'); + +-- CreateEnum +CREATE TYPE "IiosRouteDecisionState" AS ENUM ('ALLOW', 'DENY', 'REVIEW', 'SUPPRESS', 'SIMULATED'); + +-- CreateTable +CREATE TABLE "IiosRouteBinding" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "originChannelType" TEXT NOT NULL, + "originRef" TEXT, + "destinationChannelType" TEXT NOT NULL, + "destinationRef" TEXT, + "restrictionProfile" TEXT, + "mode" "IiosRouteMode" NOT NULL DEFAULT 'SIMULATION_ONLY', + "outputFormat" "IiosOutputFormat" NOT NULL DEFAULT 'FORWARD', + "frequency" TEXT NOT NULL DEFAULT 'IMMEDIATE', + "includeAttachments" BOOLEAN NOT NULL DEFAULT false, + "requiresReview" BOOLEAN NOT NULL DEFAULT true, + "rulepackVersion" TEXT NOT NULL DEFAULT 'v1', + "enabled" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosRouteBinding_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosRouteDecision" ( + "id" TEXT NOT NULL, + "interactionId" TEXT NOT NULL, + "routeBindingId" TEXT NOT NULL, + "decisionState" "IiosRouteDecisionState" NOT NULL, + "reasonCodes" TEXT[], + "previewPayload" JSONB NOT NULL, + "outputFormat" "IiosOutputFormat" NOT NULL, + "rulepackVersion" TEXT NOT NULL, + "simulationId" TEXT, + "decidedByActorId" TEXT, + "executed" BOOLEAN NOT NULL DEFAULT false, + "executedCommandId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosRouteDecision_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosModerationFlag" ( + "id" TEXT NOT NULL, + "interactionId" TEXT NOT NULL, + "flagType" TEXT NOT NULL, + "severity" TEXT NOT NULL DEFAULT 'LOW', + "proposedBy" TEXT NOT NULL DEFAULT 'RULE', + "confidence" DOUBLE PRECISION, + "explanation" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "resolvedAt" TIMESTAMP(3), + + CONSTRAINT "IiosModerationFlag_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "IiosRouteBinding_scopeId_originChannelType_idx" ON "IiosRouteBinding"("scopeId", "originChannelType"); + +-- CreateIndex +CREATE INDEX "IiosRouteDecision_decisionState_idx" ON "IiosRouteDecision"("decisionState"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosRouteDecision_interactionId_routeBindingId_key" ON "IiosRouteDecision"("interactionId", "routeBindingId"); + +-- CreateIndex +CREATE INDEX "IiosModerationFlag_interactionId_idx" ON "IiosModerationFlag"("interactionId"); + +-- AddForeignKey +ALTER TABLE "IiosRouteDecision" ADD CONSTRAINT "IiosRouteDecision_routeBindingId_fkey" FOREIGN KEY ("routeBindingId") REFERENCES "IiosRouteBinding"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/iios-service/prisma/schema.prisma b/packages/iios-service/prisma/schema.prisma index cebc88a..91cbb4e 100644 --- a/packages/iios-service/prisma/schema.prisma +++ b/packages/iios-service/prisma/schema.prisma @@ -121,6 +121,29 @@ enum IiosCallbackStatus { CANCELLED } +enum IiosRouteMode { + MANUAL + AUTOMATIC + HYBRID + SIMULATION_ONLY +} + +enum IiosOutputFormat { + FORWARD + THREADED + DIGEST + SUMMARY + TRANSCRIPT +} + +enum IiosRouteDecisionState { + ALLOW + DENY + REVIEW + SUPPRESS + SIMULATED +} + // ─── Kernel tables ──────────────────────────────────────────────── /// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null). @@ -577,3 +600,66 @@ model IiosRateLimitBucket { @@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]) +} diff --git a/packages/iios-service/src/test-utils/reset-db.ts b/packages/iios-service/src/test-utils/reset-db.ts index e51045e..3783b80 100644 --- a/packages/iios-service/src/test-utils/reset-db.ts +++ b/packages/iios-service/src/test-utils/reset-db.ts @@ -8,6 +8,7 @@ import type { PrismaClient } from '@prisma/client'; export async function resetDb(prisma: PrismaClient): Promise { await prisma.$executeRawUnsafe( `TRUNCATE TABLE + "IiosRouteDecision","IiosRouteBinding","IiosModerationFlag", "IiosInboundRawEvent","IiosDeliveryAttempt","IiosOutboundCommand","IiosRateLimitBucket", "IiosTicketStateHistory","IiosTicketThreadLink","IiosCallbackRequest","IiosTicket", "IiosSupportTeamMember","IiosSupportQueue",