Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
# 02 — Support auto-response + rich ticket contract
|
||||
|
||||
**Status:** Design only — no application code in this document.
|
||||
**Agent:** 2/10 (`support-auto`)
|
||||
**Date:** 2026-08-05
|
||||
**Repo:** `F:/laragon/www/_MY/descrybe-v2`
|
||||
**Machine contract:** [02-contract.json](./02-contract.json)
|
||||
|
||||
---
|
||||
|
||||
## 0. North star
|
||||
|
||||
Give support tickets **richer customer context**, then on create (first response only by default) attempt:
|
||||
|
||||
1. **Deterministic match** against FAQ / knowledge articles or reply templates (keyword + intent).
|
||||
2. If match confidence is below threshold → **AI assist** using the platform admin **`support`** AI role.
|
||||
3. If AI fails / is low-confidence / customer replies again → **human handoff** (queue + claim desk from [11-support-design](../admin-roles-support/11-support-design.md)).
|
||||
|
||||
Auto replies are always **clearly labeled** (`author_role=system` + `auto_*` metadata). Staff may **disable auto per ticket**.
|
||||
|
||||
```
|
||||
Customer Create ticket (+ tags / category / optional SKU / context snapshot)
|
||||
│
|
||||
▼
|
||||
[sync] FAQ / template matcher ──confidence ≥ T_match──► post labeled system reply
|
||||
│ below threshold
|
||||
▼
|
||||
[async job] AI assist (AIRoleSupport) ──ok──► draft internal note OR auto-send (admin)
|
||||
│ fail / low conf
|
||||
▼
|
||||
Human handoff (open/unassigned queue; no further auto unless staff re-enables)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventory basis
|
||||
|
||||
| Source | Status |
|
||||
|--------|--------|
|
||||
| [01-inventory.md](./01-inventory.md) / [01-extension-points.json](./01-extension-points.json) | **Authoritative** desk + AI inventory (agent 1). Confirms stub gate, `ai_roles.support`, no FAQ matcher, Create post-commit as natural hook. |
|
||||
| [11-support-design.md](../admin-roles-support/11-support-design.md) / [11-support-api-contract.json](../admin-roles-support/11-support-api-contract.json) | Desk roles, claim queue, CSAT, message `author_role ∈ user\|agent\|system`. |
|
||||
| Schema `025` + `027`/`029`/`030` | Tickets/messages/notifications + desk indexes, `staff_role`, CSAT. No tags / SKU / KB / auto flags. |
|
||||
| `support.Ticket` / `CreateInput` (`apps/api/internal/support/types.go`) | Priority + category already on create; no tags/SKU/context. |
|
||||
| `support.Create` (`tickets.go`) | Post-commit extension point for matcher → optional `TryAutoReplyLLM` (never Completer in Create). |
|
||||
| `support.TryAutoReplyLLM` (`ai_auto_reply.go`) | **Only** allowed LLM entry for tickets; currently returns `ErrAIAutoReplyDisabled`. |
|
||||
| `platformsettings.AIRoleSupport` = `"support"` | Admin-configurable slot in `ai_roles` (GET/PUT `/api/admin/settings` via `admin_settings_handlers.go`). |
|
||||
| `aiprovider.ResolveCompleterForRole(..., RoleSupport)` | Platform completer for support role; **no env fallback** when unset (tests assert). |
|
||||
| Web admin | `apps/web/src/lib/admin-ai-roles.ts` — role `support`; UI `/admin/settings` → AI roles tab. |
|
||||
| Docs Ask | Rule tree (`DocsAskGuide` / `docs-guide`) — pattern for content match **before** LLM; must not use `AIRoleSupport`. |
|
||||
| FAQ today | Marketing `FaqSection` only — **no** support knowledge base / canned ticket replies. |
|
||||
| Jobs | Postgres poller / `jobs.Queue` pattern (River deferred) — prefer same style for AI auto jobs. |
|
||||
| Plan keys | `support.center|ticket_create|ticket_thread` only — **no** plan key for auto-reply (platform admin gate). |
|
||||
|
||||
**ASSUMPTION:** Extending `TryAutoReplyLLM` remains the single AI gate; FAQ matching is a sibling `TryAutoReplyMatch` (or internal stages of one orchestrator) that never calls an LLM. Explicit product gate = `support_auto.enabled` **and** `ai_roles.support.enabled` (inventory §4.3).
|
||||
|
||||
---
|
||||
|
||||
## 2. Richer ticket fields
|
||||
|
||||
### 2.1 Additive fields on `support_tickets`
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `priority` | enum `low\|normal\|high` | yes (default `normal`) | **Exists** — preserve. |
|
||||
| `tags` | `TEXT[]` or join table | no | Max 10 tags; each ≤ 40 runes; lowercase slug-ish; staff + customer may set on create; staff may PATCH. |
|
||||
| `related_product_id` | `UUID` nullable | no | Company-scoped catalog product when resolvable. Validate `company_id` match — never cross-tenant. |
|
||||
| `related_sku` | `TEXT` nullable | no | Free-text SKU (≤ 128). Stored even if product id unknown; used for match features + staff UI. |
|
||||
| `category` | taxonomy slug | yes | See §2.2. |
|
||||
| `customer_context` | `JSONB` | no (server-filled) | Snapshot at create — see §2.3. Immutable after create (staff-visible; customers see limited subset). |
|
||||
| `auto_reply_disabled` | `BOOLEAN NOT NULL DEFAULT false` | — | Staff toggle; also set automatically on human handoff / customer follow-up after auto. |
|
||||
| `auto_reply_status` | enum | — | `none\|matched\|ai_draft\|ai_sent\|skipped\|failed\|handed_off`. |
|
||||
| `auto_reply_attempted_at` | `TIMESTAMPTZ` | — | Last orchestrator run. |
|
||||
| `auto_reply_message_id` | `UUID` nullable | — | Public auto message when posted. |
|
||||
|
||||
**Contracts preserved:** existing create body (`subject`, `category`, `priority`, `body`) remains valid. New fields are additive optional.
|
||||
|
||||
### 2.2 Category taxonomy
|
||||
|
||||
Today’s CHECK: `billing | bug | account | other`.
|
||||
|
||||
**Chosen approach:** keep seed slugs for backward compatibility; move allow-list to **`support_categories`** (platform-scoped admin taxonomy):
|
||||
|
||||
| Column | Purpose |
|
||||
|--------|---------|
|
||||
| `slug` | Stable API value (PK) |
|
||||
| `label` | Admin/UI display |
|
||||
| `parent_slug` | Optional hierarchy (1 level deep MVP) |
|
||||
| `sort_order` | List order |
|
||||
| `is_active` | Soft-disable without breaking historical tickets |
|
||||
| `match_intents` | Optional string[] of intent keys for matcher |
|
||||
|
||||
Seed rows: existing four categories. Admin may add e.g. `integrations`, `processing`, `export`, `billing_credits`.
|
||||
|
||||
**BREAKING (soft):** create/update must reject unknown **active** slugs; historical tickets may retain inactive slugs (read-only display).
|
||||
|
||||
Create API: `category` still a string slug (default `other`).
|
||||
|
||||
### 2.3 Customer context snapshot
|
||||
|
||||
Filled **server-side** on `Create` from the authenticated company/user — never trust client JSON for authz-sensitive facts.
|
||||
|
||||
```json
|
||||
{
|
||||
"captured_at": "RFC3339",
|
||||
"company_id": "uuid",
|
||||
"company_name": "string",
|
||||
"plan_slug": "string|null",
|
||||
"user_id": "uuid",
|
||||
"user_email": "string",
|
||||
"user_name": "string|null",
|
||||
"locale": "string|null",
|
||||
"timezone": "string|null",
|
||||
"related_product": {
|
||||
"id": "uuid|null",
|
||||
"sku": "string|null",
|
||||
"title": "string|null"
|
||||
},
|
||||
"signals": {
|
||||
"open_ticket_count": 0,
|
||||
"last_ticket_category": "string|null"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Snapshot is denormalized; later plan changes do not rewrite it.
|
||||
- Customer GET may omit `signals` / email of others; staff GET sees full snapshot.
|
||||
- No API keys, SMTP secrets, BYOK material, or raw payment tokens in snapshot.
|
||||
- Cap JSON size (~4 KiB); drop optional `signals` if over budget.
|
||||
|
||||
---
|
||||
|
||||
## 3. Knowledge + templates (match corpus)
|
||||
|
||||
New platform-scoped content (not per-tenant KB for MVP — support is platform desk):
|
||||
|
||||
### 3.1 `support_kb_articles`
|
||||
|
||||
| Field | Notes |
|
||||
|-------|-------|
|
||||
| `id`, `slug`, `title`, `body_md` | Public-safe answer text |
|
||||
| `category_slugs` | Overlap boost with ticket category |
|
||||
| `keywords` | TEXT[] — lowercase tokens |
|
||||
| `intent_keys` | TEXT[] — e.g. `password_reset`, `stripe_invoice` |
|
||||
| `is_published` | Matcher ignores unpublished |
|
||||
| `priority_weight` | Tie-break |
|
||||
|
||||
### 3.2 `support_reply_templates`
|
||||
|
||||
| Field | Notes |
|
||||
|-------|-------|
|
||||
| `id`, `name`, `body` | Staff-authored reply (may include `{{subject}}` placeholders — allowlist only) |
|
||||
| `keywords`, `intent_keys`, `category_slugs` | Same as KB |
|
||||
| `is_active` | |
|
||||
|
||||
**ASSUMPTION:** MVP matcher is lexical (keyword overlap + intent + category boost), not embeddings. Vector search may reuse `AIRoleVectorization` later — out of scope here.
|
||||
|
||||
---
|
||||
|
||||
## 4. Auto-response pipeline
|
||||
|
||||
### 4.1 Triggers
|
||||
|
||||
| Event | Behavior |
|
||||
|-------|----------|
|
||||
| Ticket **create** | Primary: run orchestrator once. |
|
||||
| Customer **first reply** after create with no prior auto | Optional admin flag `retry_on_first_customer_reply` (default **false**). |
|
||||
| Customer reply **after** an auto public message | **Handoff** — set `auto_reply_disabled=true`, `auto_reply_status=handed_off`; do not re-run AI. |
|
||||
| Staff public reply | Disable further auto on that ticket. |
|
||||
| Staff sets `auto_reply_disabled=true` | Skip forever until staff clears flag. |
|
||||
|
||||
Never invoke LLM from `Create` / `ReplyAsUser` / `ReplyAsAgent` directly — only via `TryAutoReplyLLM` (AI stage) after FAQ stage.
|
||||
|
||||
### 4.2 Stage A — FAQ / template match (sync, cheap)
|
||||
|
||||
Input: subject + body + category + tags + related_sku + intent heuristics (simple regex/keyword map).
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "kb_article|template|none",
|
||||
"ref_id": "uuid|null",
|
||||
"confidence": 0.0,
|
||||
"body": "string|null",
|
||||
"label": "string"
|
||||
}
|
||||
```
|
||||
|
||||
- If `confidence >= settings.match_confidence_threshold` and auto enabled → **post** public message:
|
||||
- `author_role = "system"`
|
||||
- `author_user_id = null`
|
||||
- `is_internal_note = false`
|
||||
- metadata: `auto_source`, `auto_confidence`, `auto_ref_type`, `auto_ref_id`
|
||||
- Prefix or footer (localized): clearly states automated reply (e.g. “Automated answer from help center”).
|
||||
- Set ticket `status` → `pending` (waiting on customer), `auto_reply_status=matched`.
|
||||
- Notify customer (`agent_reply` or new kind `auto_reply` — prefer **new** `auto_reply` for analytics; additive CHECK).
|
||||
- **Do not** assign an agent on FAQ match (stays unassigned for claim).
|
||||
|
||||
**Performance:** sync inside create transaction **after** commit of ticket+first message is preferred: commit ticket → match → insert system message in a short follow-up tx so create latency stays low. Matcher must be O(corpus) with small corpus + indexes on keywords GIN; hard cap ≤ 50 ms CPU budget or abort to AI stage.
|
||||
|
||||
### 4.3 Confidence threshold → Stage B
|
||||
|
||||
If no match or `confidence < T_match`:
|
||||
|
||||
- If `settings.ai_enabled` and support AI configured → enqueue **async** AI job (§5).
|
||||
- Else → `auto_reply_status=skipped` (or `handed_off` if desk expects human-only).
|
||||
|
||||
Default **`match_confidence_threshold` = 0.78** (admin-configurable 0.50–0.95).
|
||||
|
||||
### 4.4 Message labeling (customer + staff)
|
||||
|
||||
| Audience | Presentation |
|
||||
|----------|----------------|
|
||||
| Customer | System bubble with badge “Automated” / “AI-assisted”; never look like a named human agent. |
|
||||
| Staff | Same + `auto_source`, confidence, KB/template link; AI drafts as **internal notes** when delivery=`draft`. |
|
||||
|
||||
Extend `support_messages` (additive):
|
||||
|
||||
```sql
|
||||
auto_source TEXT CHECK (auto_source IS NULL OR auto_source IN ('kb','template','ai')),
|
||||
auto_confidence REAL,
|
||||
auto_ref_type TEXT,
|
||||
auto_ref_id UUID,
|
||||
is_auto_reply BOOLEAN NOT NULL DEFAULT false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. AI assist
|
||||
|
||||
### 5.1 Provider resolution (discovered)
|
||||
|
||||
| Symbol / path | Role |
|
||||
|---------------|------|
|
||||
| `platformsettings.AIRoleSupport` (`"support"`) | Admin slot in `ai_roles`. |
|
||||
| `platformsettings.ResolveAIConfig(ctx, AIRoleSupport)` | Decrypt key; provider/base_url/model/enabled. |
|
||||
| `aiprovider.RoleSupport` / `ResolveCompleterForRole(ctx, companyID, RoleSupport)` | Completer; platform-only for support (no env fallback). |
|
||||
| `support.TryAutoReplyLLM(ctx, ticketID)` | **Sole** product gate for ticket LLM sends/drafts. |
|
||||
| Admin UI | `/admin/settings` + `admin-ai-roles.ts` role `support`. |
|
||||
| API | `GET/PUT /api/admin/settings` → `ai_roles.support`. |
|
||||
|
||||
**ASSUMPTION:** Support auto uses **platform** `ai_roles.support`, never tenant BYOK. `companyID` passed to resolver is the ticket’s company for future bindings only; MVP ignores company BYOK for RoleSupport (already the case in `ResolveCompleterForRole` switch).
|
||||
|
||||
### 5.2 Admin AI override vs global
|
||||
|
||||
Under support-auto settings (§8):
|
||||
|
||||
| Setting | Behavior |
|
||||
|---------|----------|
|
||||
| `ai_use_global_support_role: true` (default) | Use `ResolveCompleterForRole(..., RoleSupport)` as configured in admin. |
|
||||
| `ai_model_override` / `ai_provider_override` / `ai_base_url_override` | Optional; applied only when `ai_use_global_support_role` is true **and** overrides non-empty — still uses the **same API key** from `AIRoleSupport` (never a second secret store). |
|
||||
| Role disabled / missing key | AI stage → `failed` → human handoff; log without secrets. |
|
||||
|
||||
### 5.3 Prompt contract
|
||||
|
||||
System prompt (server-owned, not admin free-text MVP):
|
||||
|
||||
- Answer only from provided KB snippets + ticket text.
|
||||
- No inventing billing credits, invoices, or other tenants’ data.
|
||||
- If unsure, say so and ask one clarifying question OR signal `handoff=true`.
|
||||
- Output JSON: `{ "body": "...", "confidence": 0-1, "handoff": bool, "citations": ["kb:slug"] }`.
|
||||
|
||||
User/content payload:
|
||||
|
||||
- Redacted ticket subject + body (§6).
|
||||
- Category, tags, related_sku (company-scoped).
|
||||
- Top-N KB snippets from cheap lexical retrieval (even if below FAQ auto-send threshold).
|
||||
- Customer context snapshot **sanitized** (no emails of other users; plan slug ok).
|
||||
|
||||
### 5.4 Delivery modes
|
||||
|
||||
| `ai_delivery` | Action |
|
||||
|---------------|--------|
|
||||
| `draft` (default, safer) | Insert **internal note** (`is_internal_note=true`, `author_role=system`, `is_auto_reply=true`); notify assignee or unassigned queue via staff notification; `auto_reply_status=ai_draft`. Customer sees nothing yet. |
|
||||
| `auto_send` | Post **public** system message (labeled AI-assisted); status → `pending`; `auto_reply_status=ai_sent`. |
|
||||
|
||||
Require `ai_confidence >= settings.ai_confidence_threshold` (default **0.65**) and `handoff=false` to send/draft; else handoff.
|
||||
|
||||
### 5.5 Async job (preferred)
|
||||
|
||||
**Sync path forbidden for LLM** on the HTTP create request.
|
||||
|
||||
Job sketch (`support_auto_jobs` or reuse processing-style pending table):
|
||||
|
||||
| Column | Notes |
|
||||
|--------|-------|
|
||||
| `id`, `ticket_id`, `company_id` | Tenant key for isolation |
|
||||
| `status` | `pending\|running\|done\|failed` |
|
||||
| `attempt`, `last_error` | No PII in error; truncate |
|
||||
| `created_at`, `updated_at` | |
|
||||
|
||||
Worker:
|
||||
|
||||
1. Skip if `auto_reply_disabled` or ticket closed/resolved or already matched/sent.
|
||||
2. Rate-limit check (§6).
|
||||
3. Call `TryAutoReplyLLM` (implementation loads ticket, snippets, completer, posts draft/send).
|
||||
4. Idempotent: unique partial index on `(ticket_id) WHERE status IN ('pending','running')` or ticket-level lock.
|
||||
|
||||
**ASSUMPTION:** Follow existing `jobs.Queue` + worker poller pattern (`apps/api/internal/jobs/river.go` comment: River deferred). Name the worker kind `support_auto_reply`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Security
|
||||
|
||||
| Control | Rule |
|
||||
|---------|------|
|
||||
| Tenant isolation | All ticket reads/writes filter `company_id`. KB is platform-global **content** but never includes other companies’ tickets in prompts. Related product id validated against ticket `company_id`. |
|
||||
| Prompt isolation | Completer called with **one ticket’s** redacted text only; never batch multi-tenant prompts. |
|
||||
| Secret redaction | Before match features + AI: strip patterns for API keys, Bearer tokens, `sk-`, AWS keys, private key PEM, connection strings; replace with `[REDACTED]`. Do not store raw secrets in `customer_context` or auto job logs. |
|
||||
| Rate limits | Per `company_id`: default 10 AI jobs / hour; platform global: 30 AI jobs / minute. FAQ match not counted toward AI limits. Reuse sliding-window pattern (`httpapi/ratelimit.go`). Exceed → `skipped`/`failed` + handoff, HTTP create still 201. |
|
||||
| Disable per ticket | Staff PATCH `{ "auto_reply_disabled": true }`; also auto-set on handoff / customer follow-up after auto. |
|
||||
| AuthZ | Settings: `RequirePlatformAdmin`. Disable flag: `RequireSupportDesk` on assigned/visible ticket. KB CRUD: platform admin. |
|
||||
| Customer visibility | Never expose internal AI drafts, confidence, or admin settings. |
|
||||
| Logging | No API keys, no full bodies at info level; structured `ticket_id`, `company_id`, `auto_source`, `confidence`. |
|
||||
| Docs Ask | Remains rule-based; must not use `AIRoleSupport` / `AIRoleDocsAPI` (existing invariant). |
|
||||
|
||||
---
|
||||
|
||||
## 7. Performance
|
||||
|
||||
| Path | Guidance |
|
||||
|------|----------|
|
||||
| FAQ/template match | **Sync**, bounded corpus, GIN on keywords; target < 50 ms; no external I/O. |
|
||||
| AI assist | **Async** job only; HTTP returns before LLM. |
|
||||
| Create API | Do not hold open DB transactions across LLM. |
|
||||
| Indexes | `(auto_reply_status)`, GIN `tags`, FK `related_product_id`, job `(status, created_at)`. |
|
||||
| List endpoints | Do not embed full `customer_context` in list — detail only (or truncated). |
|
||||
| N+1 | Matcher loads published KB once per process with short TTL cache (e.g. 60s) — invalidate on admin KB write. |
|
||||
|
||||
---
|
||||
|
||||
## 8. Admin settings
|
||||
|
||||
Stored under platform settings (JSON key `support_auto` in `values` **or** first-class object on PublicView — implementers pick one; prefer first-class for typed admin UI).
|
||||
|
||||
| Key | Type | Default | Purpose |
|
||||
|-----|------|---------|---------|
|
||||
| `enabled` | bool | `false` | Master switch for any auto first response. |
|
||||
| `faq_enabled` | bool | `true` | Stage A. |
|
||||
| `ai_enabled` | bool | `false` | Stage B (still requires `ai_roles.support` configured + enabled). |
|
||||
| `match_confidence_threshold` | number | `0.78` | FAQ/template auto-send. |
|
||||
| `ai_confidence_threshold` | number | `0.65` | Min model confidence. |
|
||||
| `ai_delivery` | `draft\|auto_send` | `draft` | |
|
||||
| `ai_use_global_support_role` | bool | `true` | Use admin `ai_roles.support`. |
|
||||
| `ai_provider_override` | string? | null | Optional; key still from support role. |
|
||||
| `ai_model_override` | string? | null | |
|
||||
| `ai_base_url_override` | string? | null | |
|
||||
| `rate_limit_per_company_per_hour` | int | `10` | AI jobs. |
|
||||
| `rate_limit_platform_per_minute` | int | `30` | AI jobs. |
|
||||
| `retry_on_first_customer_reply` | bool | `false` | |
|
||||
|
||||
API (additive):
|
||||
|
||||
| Method | Path | Auth |
|
||||
|--------|------|------|
|
||||
| GET/PUT | `/api/admin/settings` (embed `support_auto`) | Platform admin |
|
||||
| CRUD | `/api/admin/support/kb/articles*` | Platform admin |
|
||||
| CRUD | `/api/admin/support/templates*` | Platform admin |
|
||||
| CRUD | `/api/admin/support/categories*` | Platform admin |
|
||||
| PATCH | `/api/admin/support/tickets/{id}` | Support desk — `auto_reply_disabled`, tags, related_*, priority, category |
|
||||
|
||||
Customer create (additive body fields): `tags?`, `related_product_id?`, `related_sku?` (category taxonomy as today).
|
||||
|
||||
---
|
||||
|
||||
## 9. Human handoff
|
||||
|
||||
Set `auto_reply_status=handed_off`, `auto_reply_disabled=true` when:
|
||||
|
||||
1. AI returns `handoff=true` or confidence below threshold.
|
||||
2. AI provider error / timeout / unset completer.
|
||||
3. Rate limit exceeded for AI.
|
||||
4. Customer sends another message after an automated public reply.
|
||||
5. Staff claims/replies or explicitly disables auto.
|
||||
|
||||
Effects: ticket remains `open` (or current status); stays claimable; optional internal system note “Needs human review” (not customer-visible). No further auto jobs for that ticket unless staff sets `auto_reply_disabled=false` (rare; audit).
|
||||
|
||||
---
|
||||
|
||||
## 10. Alignment with support desk (agent 11)
|
||||
|
||||
- Auto does **not** assign `assignee_admin_user_id` (preserves unassigned queue + claim).
|
||||
- Public auto reply may move status `open` → `pending` (same as agent public reply default).
|
||||
- Internal AI drafts do **not** change status / do **not** notify customer.
|
||||
- CSAT / claim / staff roles unchanged.
|
||||
- `TryAutoReplyLLM` opt-in replaces the “keep disabled” note in 11-support §11.9 when this feature ships behind `support_auto.enabled`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Implementation phasing (later agents)
|
||||
|
||||
1. Migration: ticket rich fields + message auto columns + categories + KB + templates + auto jobs.
|
||||
2. Admin settings + KB/template/category CRUD.
|
||||
3. Sync matcher + labeled system post on create.
|
||||
4. Implement `TryAutoReplyLLM` + async worker + redaction + rate limits.
|
||||
5. Staff UI: disable auto, show auto metadata, AI draft notes.
|
||||
6. Customer UI: tags/SKU/category; automated badge on messages.
|
||||
7. Tests: tenant isolation, redaction, threshold branches, handoff on user reply, AI disabled stub, rate limit.
|
||||
|
||||
**Out of scope:** per-tenant KB, embedding retrieval, multi-turn AI bot, attachments, SLA clocks.
|
||||
|
||||
---
|
||||
|
||||
## 12. Contracts preserved / breaking
|
||||
|
||||
**Preserved**
|
||||
|
||||
- Existing customer/staff ticket JSON fields; additive only.
|
||||
- `author_role` enum values; system used for auto.
|
||||
- Admin `ai_roles.support` shape; no new secret store.
|
||||
- Desk visibility / claim rules from agent 11.
|
||||
|
||||
**BREAKING (soft)**
|
||||
|
||||
- Category allow-list may grow; clients must tolerate unknown **historical** categories.
|
||||
- New notification kind `auto_reply` requires CHECK constraint migration.
|
||||
|
||||
**Reconciled with 01-inventory:** symbols/paths match; Create post-commit hook preferred; settings stay in platform JSON (`support_auto` + existing `ai_roles.support`); no plan-permission key for auto-reply in MVP.
|
||||
Reference in New Issue
Block a user