AI Gateway Service — Domain Model
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 03 platform-services · 02 DDD
1. Aggregates
| Aggregate | Root | Key invariants | Lifecycle |
|---|---|---|---|
AIDecision | AIDecision | Every draft has one AIProvenance; HITL required flag derived from featureKey; cannot transition from rejected or accepted back to draft | draft → under_review → accepted | rejected → archived |
AIProvenance | AIProvenance | Immutable once written; always references one AIDecision | created only; never mutated |
ProviderRoutingRule | ProviderRoutingRule | At least one active rule per active featureKey per tenant; residency constraint honoured | draft → active → deprecated |
PromptTemplate | PromptTemplate | Semver-versioned, tenant-scoped or global; referenced by PromptTemplateRef { key, version }; never inlined in AIDecision | draft → published → deprecated |
TenantQuota | TenantQuota | Rolling window enforced; cannot go negative; reset only by scheduler | active |
ModerationFinding | ModerationFinding | Always linked to an AIDecision; category + severity required | created only |
2. AIDecision state machine
3. Entities
| Entity | Owner aggregate | Description |
|---|---|---|
DecisionReviewEvent | AIDecision | Audit event per reviewer action (accepted/rejected/commented) |
ProviderAttempt | AIDecision | One row per upstream provider call (provider, model, latencyMs, outcome) |
QuotaWindow | TenantQuota | Immutable counter snapshot per rolling bucket |
ModerationCategory | ModerationFinding | Category, score, threshold |
4. Value objects
| Value object | Fields | Notes |
|---|---|---|
FeatureKey | string, 3–128 chars | e.g. patient_chart.note_summary, portal.triage |
ProviderId | enum: anthropic, openai, azure_openai, bedrock, onprem_vllm, ollama, mock | Registered in config |
ModelVersion | provider:model:version | e.g. anthropic:claude-sonnet-4:2026-04-01 |
PromptTemplateRef | { key: string, version: string } | Semver |
Residency | enum: AF, AE, EU, US, ON_PREM | Drives routing rule filter |
ModerationVerdict | enum: allow, flag, block | Result of classifier |
HITLPolicy | enum: none, required, required_for_phi, sampled | Per-feature |
CorrelationId | UUID | Propagates across services |
ProvenanceId | ULID, prefix prv_ | Immutable pointer from clinical artifacts |
DecisionId | ULID, prefix dec_ | AIDecision primary key |
5. Ubiquitous language
| Term | Meaning |
|---|---|
| Assist | A single inference call for content generation or classification |
| Decision | Persisted outcome of an Assist (draft + provenance + optional review) |
| Draft | Model output that is not yet accepted by a clinical owner |
| HITL | Human-in-the-loop — clinician or reviewer must accept before downstream use |
| Provenance | Immutable metadata describing who, what model, when, under which policy, with what moderation outcome, produced a draft |
| Accept | Owning module signals the draft has been used; triggers ai.decision.accepted |
| Moderation | Automated safety / PHI / injection check run on prompts and outputs |
| Provider | External or on-prem model endpoint (Anthropic, OpenAI, vLLM, …) |
| Routing rule | Config entry that maps (tenant, feature, residency) → ordered provider list |
| Quota window | Rolling time bucket for rate limiting assist calls |
| Feature key | Stable identifier for an AI-enabled capability (e.g. imaging.preread.chest_xray) |
6. Domain events
| Event | Aggregate | Emitted when |
|---|---|---|
ai_gateway.assist.requested.v1 | AIDecision | After auth + quota consume, before provider call |
ai_gateway.assist.completed.v1 | AIDecision | Provider returned output, provenance stamped |
ai_gateway.assist.failed.v1 | AIDecision | Policy deny / provider failure / moderation block |
ai_gateway.decision.created.v1 | AIDecision | Persisted in draft state |
ai_gateway.decision.hitl_queued.v1 | AIDecision | Enters under_review; notifies reviewer queue |
ai_gateway.decision.accepted.v1 | AIDecision | Owner module accepted; includes provenanceId |
ai_gateway.decision.rejected.v1 | AIDecision | Reviewer rejected or timeout |
ai_gateway.moderation.flagged.v1 | ModerationFinding | Score above threshold |
ai_gateway.provider.degraded.v1 | ProviderRoutingRule | Circuit breaker opened |
ai_gateway.quota.exceeded.v1 | TenantQuota | Rolling window exceeded |
7. Invariants catalogue
| # | Invariant |
|---|---|
| INV-01 | No AIDecision exists without an AIProvenance row |
| INV-02 | AIProvenance rows are append-only |
| INV-03 | AIDecision.state transitions are one-way per the state machine |
| INV-04 | AIDecision.consumerService set at creation and immutable |
| INV-05 | A draft cannot be read by any service other than the creator or an authorised reviewer |
| INV-06 | HITLPolicy=required_for_phi forces under_review when the feature handles PHI |
| INV-07 | Quota consume is atomic with assist request acceptance |
8. Open questions
- Final catalogue of
featureKeyvalues per service (tracked via cross-service review). - Retention policy for rejected decisions (90 days vs 7 years compliance view).