diff --git a/packages/iios-contracts/src/events.ts b/packages/iios-contracts/src/events.ts index 317b278..776a7a5 100644 --- a/packages/iios-contracts/src/events.ts +++ b/packages/iios-contracts/src/events.ts @@ -43,6 +43,9 @@ export const IIOS_EVENTS = { callbackRequested: 'com.insignia.iios.support.callback.requested.v1', routeDecisionPreviewed: 'com.insignia.iios.route.decision.previewed.v1', routeApproved: 'com.insignia.iios.route.approved.v1', + aiJobCreated: 'com.insignia.iios.ai.job.created.v1', + aiArtifactProposed: 'com.insignia.iios.ai.artifact.proposed.v1', + aiArtifactAccepted: 'com.insignia.iios.ai.artifact.accepted.v1', notificationQueued: 'com.insignia.iios.notification.queued.v1', deliveryAttempted: 'com.insignia.iios.delivery.attempted.v1', } as const; diff --git a/packages/iios-contracts/src/index.ts b/packages/iios-contracts/src/index.ts index 1719f25..f884a6a 100644 --- a/packages/iios-contracts/src/index.ts +++ b/packages/iios-contracts/src/index.ts @@ -2,6 +2,7 @@ export * from './scope'; export * from './enums'; export * from './ingest'; export * from './ports'; +export * from './inference'; export * from './events'; export * from './commands'; export * from './errors'; diff --git a/packages/iios-contracts/src/inference.ts b/packages/iios-contracts/src/inference.ts new file mode 100644 index 0000000..74c4686 --- /dev/null +++ b/packages/iios-contracts/src/inference.ts @@ -0,0 +1,61 @@ +/** + * The AI inference port (P7). A standalone contract — deliberately NOT part of the + * sealed `IiosPlatformPorts`: AI is an internal "AI Factory" whose model execution + * routes through the Capability Broker in P9. In P7 it is a faked, deterministic, + * golden provider (no network). Every result carries the provenance the critics + * mandate (model/prompt version, tokens, cost, confidence, abstention, evidence). + */ + +export type AiJobType = 'CLASSIFY' | 'SUMMARIZE' | 'EXTRACT'; + +export type AiArtifactType = 'CLASSIFICATION' | 'SUMMARY' | 'TRANSCRIPT' | 'DIGEST' | 'EXTRACTION'; + +export type AiClaimType = 'MODERATION_FLAG' | 'EVENT' | 'FAQ' | 'INTENT' | 'PRIORITY'; + +export interface InferenceRequest { + jobType: AiJobType; + text: string; + purpose: string; + scopeId: string; + /** The kernel interaction this inference is about — used as the default evidence source. */ + interactionId?: string; +} + +export interface InferenceClaim { + claimType: AiClaimType; + claimJson: Record; + confidence: number; +} + +export interface InferenceEvidence { + sourceType: string; + sourceId: string; + spanRef?: string; + relevanceScore?: number; +} + +export interface InferenceModelRun { + model: string; + modelVersion: string; + promptVersion: string; + tokensIn: number; + tokensOut: number; + costUnits: number; + latencyMs: number; +} + +export interface InferenceResult { + artifactType: AiArtifactType; + content: unknown; + confidence: number; + explanation?: string; + /** Set when the model declines to assert a claim (Scenario 7 abstention). */ + abstentionReason?: string; + claims: InferenceClaim[]; + evidence: InferenceEvidence[]; + modelRun: InferenceModelRun; +} + +export interface InferencePort { + infer(req: InferenceRequest): Promise; +} diff --git a/packages/iios-service/prisma/migrations/20260701105607_ai_init/migration.sql b/packages/iios-service/prisma/migrations/20260701105607_ai_init/migration.sql new file mode 100644 index 0000000..25cfeb6 --- /dev/null +++ b/packages/iios-service/prisma/migrations/20260701105607_ai_init/migration.sql @@ -0,0 +1,173 @@ +-- CreateEnum +CREATE TYPE "IiosAiJobType" AS ENUM ('CLASSIFY', 'SUMMARIZE', 'EXTRACT'); + +-- CreateEnum +CREATE TYPE "IiosAiJobStatus" AS ENUM ('PENDING', 'RUNNING', 'DONE', 'FAILED', 'ABSTAINED'); + +-- CreateEnum +CREATE TYPE "IiosAiArtifactType" AS ENUM ('CLASSIFICATION', 'SUMMARY', 'TRANSCRIPT', 'DIGEST', 'EXTRACTION'); + +-- CreateEnum +CREATE TYPE "IiosAiArtifactStatus" AS ENUM ('PROPOSED', 'ACCEPTED', 'REJECTED', 'SUPERSEDED'); + +-- CreateEnum +CREATE TYPE "IiosAiClaimType" AS ENUM ('MODERATION_FLAG', 'EVENT', 'FAQ', 'INTENT', 'PRIORITY'); + +-- CreateEnum +CREATE TYPE "IiosAiClaimValidation" AS ENUM ('UNVALIDATED', 'ACCEPTED', 'REJECTED'); + +-- CreateTable +CREATE TABLE "IiosAiJob" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "jobType" "IiosAiJobType" NOT NULL, + "interactionId" TEXT, + "inputWindowRef" TEXT NOT NULL, + "purpose" TEXT NOT NULL, + "status" "IiosAiJobStatus" NOT NULL DEFAULT 'PENDING', + "modelPolicyRef" TEXT NOT NULL DEFAULT 'golden-v1', + "policyDecisionRef" TEXT, + "inputScopeHash" TEXT NOT NULL, + "replayVersion" TEXT NOT NULL DEFAULT 'v1', + "abstentionReason" TEXT, + "traceId" TEXT, + "idempotencyKey" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiJob_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAiModelRun" ( + "id" TEXT NOT NULL, + "scopeId" TEXT NOT NULL, + "provider" TEXT NOT NULL DEFAULT 'fake', + "model" TEXT NOT NULL, + "modelVersion" TEXT NOT NULL, + "promptVersion" TEXT NOT NULL, + "toolPolicyRef" TEXT, + "tokensIn" INTEGER NOT NULL DEFAULT 0, + "tokensOut" INTEGER NOT NULL DEFAULT 0, + "costUnits" INTEGER NOT NULL DEFAULT 0, + "latencyMs" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiModelRun_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAiArtifact" ( + "id" TEXT NOT NULL, + "jobId" TEXT NOT NULL, + "modelRunId" TEXT NOT NULL, + "artifactType" "IiosAiArtifactType" NOT NULL, + "status" "IiosAiArtifactStatus" NOT NULL DEFAULT 'PROPOSED', + "confidence" DOUBLE PRECISION, + "contentRef" JSONB NOT NULL, + "explanationRef" TEXT, + "abstentionReason" TEXT, + "acceptedByActorId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiArtifact_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAiClaim" ( + "id" TEXT NOT NULL, + "artifactId" TEXT NOT NULL, + "claimType" "IiosAiClaimType" NOT NULL, + "claimJson" JSONB NOT NULL, + "confidence" DOUBLE PRECISION, + "validationStatus" "IiosAiClaimValidation" NOT NULL DEFAULT 'UNVALIDATED', + "acceptedByRef" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiClaim_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAiEvidenceLink" ( + "id" TEXT NOT NULL, + "artifactId" TEXT NOT NULL, + "sourceType" TEXT NOT NULL, + "sourceId" TEXT NOT NULL, + "spanRef" TEXT, + "relevanceScore" DOUBLE PRECISION, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiEvidenceLink_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosAiToolCall" ( + "id" TEXT NOT NULL, + "modelRunId" TEXT NOT NULL, + "toolName" TEXT NOT NULL, + "toolInputHash" TEXT NOT NULL, + "toolOutputRef" TEXT, + "policyRefId" TEXT, + "status" TEXT NOT NULL DEFAULT 'PENDING', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosAiToolCall_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IiosEmbeddingRef" ( + "id" TEXT NOT NULL, + "sourceType" TEXT NOT NULL, + "sourceId" TEXT NOT NULL, + "embeddingModel" TEXT NOT NULL, + "vectorStoreRef" TEXT NOT NULL, + "embeddingHash" TEXT NOT NULL, + "version" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "IiosEmbeddingRef_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosAiJob_idempotencyKey_key" ON "IiosAiJob"("idempotencyKey"); + +-- CreateIndex +CREATE INDEX "IiosAiJob_scopeId_jobType_idx" ON "IiosAiJob"("scopeId", "jobType"); + +-- CreateIndex +CREATE INDEX "IiosAiJob_interactionId_idx" ON "IiosAiJob"("interactionId"); + +-- CreateIndex +CREATE INDEX "IiosAiModelRun_scopeId_idx" ON "IiosAiModelRun"("scopeId"); + +-- CreateIndex +CREATE INDEX "IiosAiArtifact_jobId_idx" ON "IiosAiArtifact"("jobId"); + +-- CreateIndex +CREATE INDEX "IiosAiArtifact_status_idx" ON "IiosAiArtifact"("status"); + +-- CreateIndex +CREATE INDEX "IiosAiClaim_artifactId_idx" ON "IiosAiClaim"("artifactId"); + +-- CreateIndex +CREATE INDEX "IiosAiEvidenceLink_artifactId_idx" ON "IiosAiEvidenceLink"("artifactId"); + +-- CreateIndex +CREATE INDEX "IiosAiToolCall_modelRunId_idx" ON "IiosAiToolCall"("modelRunId"); + +-- CreateIndex +CREATE UNIQUE INDEX "IiosEmbeddingRef_sourceType_sourceId_version_key" ON "IiosEmbeddingRef"("sourceType", "sourceId", "version"); + +-- AddForeignKey +ALTER TABLE "IiosAiArtifact" ADD CONSTRAINT "IiosAiArtifact_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "IiosAiJob"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosAiArtifact" ADD CONSTRAINT "IiosAiArtifact_modelRunId_fkey" FOREIGN KEY ("modelRunId") REFERENCES "IiosAiModelRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosAiClaim" ADD CONSTRAINT "IiosAiClaim_artifactId_fkey" FOREIGN KEY ("artifactId") REFERENCES "IiosAiArtifact"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosAiEvidenceLink" ADD CONSTRAINT "IiosAiEvidenceLink_artifactId_fkey" FOREIGN KEY ("artifactId") REFERENCES "IiosAiArtifact"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IiosAiToolCall" ADD CONSTRAINT "IiosAiToolCall_modelRunId_fkey" FOREIGN KEY ("modelRunId") REFERENCES "IiosAiModelRun"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/iios-service/prisma/schema.prisma b/packages/iios-service/prisma/schema.prisma index 91cbb4e..c903ead 100644 --- a/packages/iios-service/prisma/schema.prisma +++ b/packages/iios-service/prisma/schema.prisma @@ -144,6 +144,51 @@ enum IiosRouteDecisionState { 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 +} + // ─── Kernel tables ──────────────────────────────────────────────── /// Frozen six-vector scope. orgId + appId are REQUIRED (no global-by-null). @@ -663,3 +708,138 @@ model IiosModerationFlag { @@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]) +} diff --git a/packages/iios-service/src/test-utils/reset-db.ts b/packages/iios-service/src/test-utils/reset-db.ts index 3783b80..069bd6b 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 + "IiosAiEvidenceLink","IiosAiClaim","IiosAiToolCall","IiosAiArtifact","IiosAiModelRun","IiosAiJob","IiosEmbeddingRef", "IiosRouteDecision","IiosRouteBinding","IiosModerationFlag", "IiosInboundRawEvent","IiosDeliveryAttempt","IiosOutboundCommand","IiosRateLimitBucket", "IiosTicketStateHistory","IiosTicketThreadLink","IiosCallbackRequest","IiosTicket",