feat(service): NestJS kernel skeleton + 10-table kernel schema (P1.1+1.2)
Reuses support-service bootstrap/health/prisma patterns. Kernel-only Prisma
schema (scope/source_handle/actor/channel/thread/participant/interaction/
message_part/outbox/processed_event) with orgId/appId NOT NULL and
@@unique([scopeId, idempotencyKey]). Health verified: {status:ok,db:true}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@insignia/iios-service",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"start": "nest start",
|
||||||
|
"dev": "nest start --watch",
|
||||||
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"prisma:migrate": "prisma migrate dev",
|
||||||
|
"prisma:studio": "prisma studio"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@insignia/iios-contracts": "workspace:*",
|
||||||
|
"@nestjs/common": "^11.1.27",
|
||||||
|
"@nestjs/core": "^11.1.27",
|
||||||
|
"@nestjs/platform-express": "^11.1.27",
|
||||||
|
"@prisma/client": "^6.2.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.15.1",
|
||||||
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"rxjs": "^7.8.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@insignia/iios-testkit": "workspace:*",
|
||||||
|
"@nestjs/cli": "^11.0.23",
|
||||||
|
"@nestjs/schematics": "^11.1.0",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
|
"@types/node": "^26.0.1",
|
||||||
|
"prisma": "^6.2.1",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosActorKind" AS ENUM ('HUMAN', 'AI', 'SERVICE', 'BOT', 'UNKNOWN');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosHandleKind" AS ENUM ('PORTAL_USER', 'EMAIL', 'PHONE', 'WHATSAPP', 'SLACK', 'MATTERMOST', 'TELEGRAM', 'EXTERNAL_VISITOR');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosInteractionKind" AS ENUM ('MESSAGE', 'EMAIL', 'SYSTEM_NOTICE', 'INBOX_WORK', 'SUPPORT_CASE', 'MEETING_REQUEST', 'DIGEST', 'SUMMARY', 'NOTIFICATION');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosMessagePartKind" AS ENUM ('TEXT', 'HTML', 'MARKDOWN', 'MEDIA_REF', 'FILE_REF', 'VOICE_REF', 'LOCATION', 'STRUCTURED_JSON');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "IiosDeliveryState" AS ENUM ('RECEIVED', 'NORMALIZED', 'POLICY_HELD', 'STORED', 'DELIVERED', 'READ', 'FAILED', 'DELETED_LOCAL', 'REDACTED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosScope" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"orgId" TEXT NOT NULL,
|
||||||
|
"appId" TEXT NOT NULL,
|
||||||
|
"buId" TEXT,
|
||||||
|
"tenantId" TEXT,
|
||||||
|
"workspaceId" TEXT,
|
||||||
|
"projectId" TEXT,
|
||||||
|
"userScopeId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosScope_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosSourceHandle" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"kind" "IiosHandleKind" NOT NULL,
|
||||||
|
"externalId" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT,
|
||||||
|
"canonicalEntityId" TEXT,
|
||||||
|
"confidence" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"verificationState" TEXT NOT NULL DEFAULT 'UNVERIFIED',
|
||||||
|
"firstSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"metadata" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosSourceHandle_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosActorRef" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"kind" "IiosActorKind" NOT NULL,
|
||||||
|
"sourceHandleId" TEXT,
|
||||||
|
"platformPrincipalRef" TEXT,
|
||||||
|
"canonicalEntityId" TEXT,
|
||||||
|
"displayName" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosActorRef_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosChannel" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"channelType" TEXT NOT NULL,
|
||||||
|
"channelName" TEXT NOT NULL,
|
||||||
|
"direction" TEXT NOT NULL DEFAULT 'BOTH',
|
||||||
|
"capabilityContract" JSONB,
|
||||||
|
"externalRef" TEXT,
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosChannel_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosThread" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"channelId" TEXT,
|
||||||
|
"threadKind" TEXT NOT NULL DEFAULT 'CONVERSATION',
|
||||||
|
"subject" TEXT,
|
||||||
|
"externalThreadRef" TEXT,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'OPEN',
|
||||||
|
"visibilityClass" TEXT NOT NULL DEFAULT 'PRIVATE',
|
||||||
|
"createdByActorId" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"metadata" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosThread_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosThreadParticipant" (
|
||||||
|
"threadId" TEXT NOT NULL,
|
||||||
|
"actorId" TEXT NOT NULL,
|
||||||
|
"participantRole" TEXT NOT NULL DEFAULT 'MEMBER',
|
||||||
|
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"leftAt" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "IiosThreadParticipant_pkey" PRIMARY KEY ("threadId","actorId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosInteraction" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"scopeId" TEXT NOT NULL,
|
||||||
|
"kind" "IiosInteractionKind" NOT NULL,
|
||||||
|
"threadId" TEXT,
|
||||||
|
"channelId" TEXT,
|
||||||
|
"actorId" TEXT,
|
||||||
|
"parentInteractionId" TEXT,
|
||||||
|
"externalId" TEXT,
|
||||||
|
"idempotencyKey" TEXT NOT NULL,
|
||||||
|
"occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"receivedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"status" "IiosDeliveryState" NOT NULL DEFAULT 'RECEIVED',
|
||||||
|
"policyDecisionRef" TEXT,
|
||||||
|
"consentReceiptRef" TEXT,
|
||||||
|
"crreBundleRef" TEXT,
|
||||||
|
"traceId" TEXT,
|
||||||
|
"metadata" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosInteraction_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosMessagePart" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"interactionId" TEXT NOT NULL,
|
||||||
|
"partIndex" INTEGER NOT NULL,
|
||||||
|
"kind" "IiosMessagePartKind" NOT NULL,
|
||||||
|
"bodyText" TEXT,
|
||||||
|
"contentRef" TEXT,
|
||||||
|
"mimeType" TEXT,
|
||||||
|
"sizeBytes" BIGINT,
|
||||||
|
"checksumSha256" TEXT,
|
||||||
|
"metadata" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosMessagePart_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosOutboxEvent" (
|
||||||
|
"eventId" TEXT NOT NULL,
|
||||||
|
"aggregateType" TEXT NOT NULL,
|
||||||
|
"aggregateId" TEXT NOT NULL,
|
||||||
|
"eventType" TEXT NOT NULL,
|
||||||
|
"cloudEvent" JSONB NOT NULL,
|
||||||
|
"partitionKey" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||||
|
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"nextAttemptAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"publishedAt" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "IiosOutboxEvent_pkey" PRIMARY KEY ("eventId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "IiosProcessedEvent" (
|
||||||
|
"consumerName" TEXT NOT NULL,
|
||||||
|
"eventId" TEXT NOT NULL,
|
||||||
|
"processedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"resultHash" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "IiosProcessedEvent_pkey" PRIMARY KEY ("consumerName","eventId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosScope_orgId_appId_tenantId_idx" ON "IiosScope"("orgId", "appId", "tenantId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosSourceHandle_canonicalEntityId_idx" ON "IiosSourceHandle"("canonicalEntityId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "IiosSourceHandle_scopeId_kind_externalId_key" ON "IiosSourceHandle"("scopeId", "kind", "externalId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosChannel_scopeId_channelType_idx" ON "IiosChannel"("scopeId", "channelType");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosThread_scopeId_status_idx" ON "IiosThread"("scopeId", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosInteraction_threadId_occurredAt_idx" ON "IiosInteraction"("threadId", "occurredAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "IiosInteraction_scopeId_idempotencyKey_key" ON "IiosInteraction"("scopeId", "idempotencyKey");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "IiosMessagePart_interactionId_partIndex_key" ON "IiosMessagePart"("interactionId", "partIndex");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IiosOutboxEvent_status_nextAttemptAt_createdAt_idx" ON "IiosOutboxEvent"("status", "nextAttemptAt", "createdAt");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosSourceHandle" ADD CONSTRAINT "IiosSourceHandle_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosActorRef" ADD CONSTRAINT "IiosActorRef_sourceHandleId_fkey" FOREIGN KEY ("sourceHandleId") REFERENCES "IiosSourceHandle"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosChannel" ADD CONSTRAINT "IiosChannel_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosThread" ADD CONSTRAINT "IiosThread_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosThread" ADD CONSTRAINT "IiosThread_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "IiosChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosThread" ADD CONSTRAINT "IiosThread_createdByActorId_fkey" FOREIGN KEY ("createdByActorId") REFERENCES "IiosActorRef"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosThreadParticipant" ADD CONSTRAINT "IiosThreadParticipant_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "IiosThread"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosThreadParticipant" ADD CONSTRAINT "IiosThreadParticipant_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "IiosActorRef"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosInteraction" ADD CONSTRAINT "IiosInteraction_scopeId_fkey" FOREIGN KEY ("scopeId") REFERENCES "IiosScope"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosInteraction" ADD CONSTRAINT "IiosInteraction_threadId_fkey" FOREIGN KEY ("threadId") REFERENCES "IiosThread"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosInteraction" ADD CONSTRAINT "IiosInteraction_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "IiosChannel"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosInteraction" ADD CONSTRAINT "IiosInteraction_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "IiosActorRef"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosInteraction" ADD CONSTRAINT "IiosInteraction_parentInteractionId_fkey" FOREIGN KEY ("parentInteractionId") REFERENCES "IiosInteraction"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "IiosMessagePart" ADD CONSTRAINT "IiosMessagePart_interactionId_fkey" FOREIGN KEY ("interactionId") REFERENCES "IiosInteraction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 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?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
sourceHandles IiosSourceHandle[]
|
||||||
|
channels IiosChannel[]
|
||||||
|
threads IiosThread[]
|
||||||
|
interactions IiosInteraction[]
|
||||||
|
|
||||||
|
@@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?
|
||||||
|
|
||||||
|
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[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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[]
|
||||||
|
|
||||||
|
@@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)
|
||||||
|
policyDecisionRef String?
|
||||||
|
consentReceiptRef String?
|
||||||
|
crreBundleRef String?
|
||||||
|
traceId String?
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
parts IiosMessagePart[]
|
||||||
|
|
||||||
|
@@unique([scopeId, idempotencyKey])
|
||||||
|
@@index([threadId, occurredAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
|
import { HealthController } from './health.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [HealthController],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { PrismaService } from './prisma/prisma.service';
|
||||||
|
|
||||||
|
@Controller('health')
|
||||||
|
export class HealthController {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async check(): Promise<{ status: string; db: boolean }> {
|
||||||
|
let db = false;
|
||||||
|
try {
|
||||||
|
await this.prisma.$queryRaw`SELECT 1`;
|
||||||
|
db = true;
|
||||||
|
} catch {
|
||||||
|
db = false;
|
||||||
|
}
|
||||||
|
return { status: 'ok', db };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap(): Promise<void> {
|
||||||
|
const app = await NestFactory.create(AppModule);
|
||||||
|
app.useGlobalPipes(
|
||||||
|
new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }),
|
||||||
|
);
|
||||||
|
app.enableCors({ origin: true, credentials: true });
|
||||||
|
const port = Number(process.env.PORT ?? 3200);
|
||||||
|
await app.listen(port);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`iios-service listening on :${port}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootstrap();
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [PrismaService],
|
||||||
|
exports: [PrismaService],
|
||||||
|
})
|
||||||
|
export class PrismaModule {}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
|
async onModuleInit(): Promise<void> {
|
||||||
|
await this.$connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": false,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules", "dist", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
||||||
|
}
|
||||||
Generated
+3126
-10
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user