Skip to main content

Domain Model

:::info Source Sourced from services/ai-gateway-service/DOMAIN_MODEL.md in the documentation repo. :::

1. Aggregates

Prompt (root)

type PromptId = Branded<string, 'PromptId'>;

interface Prompt {
id: PromptId;
tenantId: TenantId | null; // null = platform prompt
name: string; // dot-namespaced (e.g. 'tutor.rag.respond')
version: SemVer;
template: string; // Jinja-ish templating
inputSchema: JSONSchema;
outputSchema?: JSONSchema;
modelPreference: ModelPreference;
safetyPolicy: SafetyPolicy;
evalSetRef?: string;
status: 'draft' | 'active' | 'deprecated';
createdBy: UserId;
createdAt: ISODate;
}

interface ModelPreference {
preferred: ModelId[]; // ordered preference (local first often)
fallbacks: ModelId[];
maxLatencyMs?: number;
minQualityScore?: number;
eligibleLocalities?: ('local'|'cloud')[];
}

interface SafetyPolicy {
categories: Record<'sexual'|'violence'|'hate'|'self_harm'|'illegal', 'block'|'warn'|'allow'>;
piiRedaction: 'block' | 'redact' | 'allow';
promptInjectionPolicy: 'shield' | 'detect' | 'allow';
}

Model

type ModelId = Branded<string, 'ModelId'>;
interface Model {
id: ModelId;
family: 'chat' | 'embedding' | 'image' | 'tts' | 'stt' | 'moderation' | 'classifier';
vendor: string;
contextWindow?: number;
costPer1KIn: number;
costPer1KOut: number;
capabilities: string[];
status: 'available' | 'deprecated';
locality: 'local' | 'cloud';
residency?: ('us'|'eu'|'me'|'ap')[];
baaSigned?: boolean; // HIPAA eligibility
}

AICompletion (append-only record)

type CompletionId = Branded<string, 'CompletionId'>;
interface AICompletion {
id: CompletionId;
tenantId: TenantId;
userId: UserId;
promptId?: PromptId;
promptHash: string;
modelId: ModelId;
inputTokens: number;
outputTokens: number;
costMicroUSD: number;
latencyMs: number;
output: JSONValue;
safety: { input: SafetyVerdict; output: SafetyVerdict };
cacheHit: boolean;
traceId: string;
startedAt: ISODate;
finishedAt: ISODate;
decisionId?: string;
}

Embedding

type EmbeddingId = Branded<string, 'EmbeddingId'>;
interface Embedding {
id: EmbeddingId;
tenantId: TenantId;
modelId: ModelId;
vector: number[];
sourceRef: { kind: 'block' | 'lesson' | 'listing' | 'document'; id: string };
createdAt: ISODate;
}

AIBudget

interface AIBudget {
tenantId: TenantId;
period: 'day' | 'month';
limitMicroUSD: number;
usedMicroUSD: number;
resetAt: ISODate;
softAlertPct: number; // e.g., 80
hardCap: boolean;
}

SafetyVerdict

interface SafetyVerdict {
categories: Record<string, { score: number; action: 'allow' | 'warn' | 'block' }>;
promptInjectionScore: number;
piiFound: PIIFinding[];
overallAction: 'allow' | 'warn' | 'block';
}

AIAuditEntry

interface AIAuditEntry {
id: ULID;
tenantId: TenantId;
actor: { type: string; id: string };
completionId?: CompletionId;
event: 'call' | 'refusal' | 'override' | 'dispute';
provenance: AIProvenance;
at: ISODate;
}

2. Invariants

  1. Every AICompletion has a SafetyVerdict for input and output.
  2. Prompt inputSchema validated; outputSchema (if specified) enforced on output.
  3. Prompt versions append-only per tenant.
  4. Embeddings partitioned by tenant; no cross-tenant k-NN.
  5. Cost accumulates atomically toward AIBudget.usedMicroUSD.
  6. Budget exceeded → refusal (ai.refused.budget).
  7. PII redaction mandatory for restricted tenants.

3. Domain Events

  • ai.completion.finished.v1
  • ai.completion.refused.v1
  • ai.budget.exhausted.v1
  • ai.safety.violation.v1
  • ai.prompt.version_published.v1
  • ai.embedding.created.v1
  • ai.audit.entry.v1

4. Diagram

Service (e.g., authoring) ──▶ AIClient port (frozen F09)


ai-gateway-service

├─▶ budget check (tenant)
├─▶ prompt registry (resolve version)
├─▶ input safety (moderation + PII + injection)
├─▶ cache lookup (promptHash + modelId)
├─▶ model router (local → small → large)
├─▶ provider call
├─▶ output safety (moderation + schema)
├─▶ cache store
├─▶ cost accounting
├─▶ audit + provenance
└─▶ return with AIProvenance