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
- Every
AICompletionhas aSafetyVerdictfor input and output. - Prompt
inputSchemavalidated;outputSchema(if specified) enforced on output. - Prompt versions append-only per tenant.
- Embeddings partitioned by tenant; no cross-tenant k-NN.
- Cost accumulates atomically toward
AIBudget.usedMicroUSD. - Budget exceeded → refusal (
ai.refused.budget). - PII redaction mandatory for restricted tenants.
3. Domain Events
ai.completion.finished.v1ai.completion.refused.v1ai.budget.exhausted.v1ai.safety.violation.v1ai.prompt.version_published.v1ai.embedding.created.v1ai.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