Files
support_backend/specs/005-ai-support/data-model.md
T
saqib mirandClaude Sonnet 5 eceb00632d docs: correct 005-ai-support design to reuse the existing ticket state machine
003-ticketing's ticket-state-machine.ts already defines AI_ANALYZING/
AI_TROUBLESHOOTING/AI_VERIFYING/AI_RESOLVED/HUMAN_ESCALATION ticket
statuses, clearly authored anticipating this feature. Corrects the plan
before implementation: AISupportSession.status now drives Ticket.status
through the existing ticketsService.updateStatus (reusing its optimistic
concurrency), instead of an isolated status field the rest of the system
never sees. Also clarifies that knowledge retrieval is an in-process
service call through knowledge's index.ts, not an HTTP loopback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:55:01 +05:30

6.9 KiB
Raw Blame History

Phase 1 Data Model: AI Support Agent

All new models use cuid() ids. Refines docs/06-database-schema.md's conceptual AISupportSession/AIDiagnosis/AIInteraction/AIAction/AIActionResult/ AIKnowledgeReference shapes; adds AIConfidencePolicy (research.md — not in doc 06, required by FR-005).

AISupportSession

Field Type Notes
id String @id @default(cuid())
ticketId String FK → Ticket. One active session per ticket at a time (FR-001) — enforced in the repository (partial-condition check, not a DB constraint, since a ticket accumulates multiple ended sessions over time if re-escalated and re-opened)
status String analyzing | troubleshooting | verifying | resolved | escalated | ended_by_agent (doc 06's enum, plus ended_by_agent for FR-023). Every transition except ended_by_agent is mirrored onto Ticket.status via the existing 003 state machine (AI_ANALYZING/AI_TROUBLESHOOTING/AI_VERIFYING/AI_RESOLVED/HUMAN_ESCALATION) through ticketsService.updateStatus(..., 'ai') — research.md "AISupportSession.status drives Ticket.status"
activeRunbookKey String? Set when a diagnosis matches a runbook (research.md "Runbook engine")
currentStepIndex Int? App-owned index into the active runbook's steps; null when no runbook is active
clarifyingQuestionsAsked Int @default(0) Counted against AIConfidencePolicy.maxClarifyingQuestions (FR-009)
toolCallCount Int @default(0) Counted against the per-session hard cap (doc 11 §B2 — Assumptions)
startedAt DateTime @default(now())
endedAt DateTime?

Relations: diagnoses AIDiagnosis[], interactions AIInteraction[], actions AIAction[], knowledgeRefs AIKnowledgeReference[].

Index: (ticketId, status) — the exact shape the "one active session per ticket" check and the ticket-detail view both query on.

AIDiagnosis

Field Type Notes
id String @id @default(cuid())
sessionId String FK → AISupportSession
product String Echoes the ticket's product for readability; not a second source of truth for scoping (the session's ticketIdTicket.productId remains authoritative)
feature String?
problemType String Matched against Runbook.key for the runbook-engine trigger (research.md)
severity String
confidence Float 01; the value the confidence-band policy (FR-004) is applied to
possibleCauses String[]
createdAt DateTime @default(now())

Never updated in place — a session accumulates one row per diagnosis attempt (initial + each re-diagnosis after a customer reply), matching FR-002's "never overwriting a prior one."

AIInteraction

Field Type Notes
id String @id @default(cuid())
sessionId String FK → AISupportSession
role String customer | ai
content String
createdAt DateTime @default(now())

An AIInteraction with role: ai that's a clarifying question or guided-step message is also written as a TicketMessage (type: AI_MESSAGE) via the existing messages module — AIInteraction is the session's own ordered transcript for reasoning-call context; TicketMessage is the customer-visible record. They're intentionally two records: the session transcript may include turns not meant to duplicate onto the ticket (e.g., an internal re-diagnosis triggered by a tool result, with no new customer-facing text).

AIAction / AIActionResult

Field (AIAction) Type Notes
id String @id @default(cuid())
sessionId String FK → AISupportSession
toolName String Must match a name in the code-defined registry (research.md)
input Json The model's proposed input, before any validation
riskLevel String Copied from the registry at evaluation time (low/medium/high) — a durable record of what risk tier applied, independent of later registry changes
evaluationOutcome String approved | pending_approval | refused — the deterministic gate's decision (research.md), always recorded even when nothing executes
refusalReason String? Set when evaluationOutcome = refused (unknown tool, product not in scope, etc.)
approvedBy String? system-policy for auto-approved low-risk; null while pending_approval; an agent id if a future approval UI fills it in
createdAt DateTime @default(now())
Field (AIActionResult) Type Notes
id String @id @default(cuid())
actionId String @unique FK → AIAction — only exists when evaluationOutcome = approved and execution actually ran
output Json
status String success | failed
createdAt DateTime @default(now())

A pending_approval or refused AIAction has no AIActionResult row — the absence itself is the record of "never executed" (FR-013 requires the proposal+evaluation to be recorded either way, not that every proposal produces a result).

AIKnowledgeReference

Field Type Notes
id String @id @default(cuid())
sessionId String FK → AISupportSession
knowledgeId String KnowledgeEntry.id (004) — not a DB-level FK across module boundaries per this codebase's convention of modules only depending on each other's index.ts, but a plain string reference resolved through knowledge's exported repository
relevanceScore Float? Null for now — 004's retrieval doesn't emit a numeric score (structured filtering + validation-status ranking, not a similarity score); reserved for a future semantic-retrieval layer (spec.md Assumptions)
createdAt DateTime @default(now())

One row per knowledge entry actually included in a reasoning call's context — the durable record of what the AI was actually shown, satisfying doc 03 §9's "AI must never invent... expose internal notes" concern from the audit side (you can always answer "what knowledge did the AI see for this session").

AIConfidencePolicy

Field Type Notes
id String @id @default(cuid())
productId String? Null = system-wide default row (optional; env-var defaults cover the no-row case too — research.md)
categoryId String? Null = applies to every category of productId
highThreshold Float confidence >= highThreshold → proceed
lowThreshold Float confidence < lowThreshold → escalate; between the two → ask
maxClarifyingQuestions Int FR-009's cap
updatedAt DateTime @updatedAt

Constraints: @@unique([productId, categoryId]). highThreshold > lowThreshold is validated at the service layer (Zod refinement), not the DB.

Ticket (relation added by this feature)

aiSessions AISupportSession[] — the forward relation doc 06 already specified on Ticket but that couldn't be added until AISupportSession existed (same pattern 004 used for Product's relations).