# 05 — Ticket detail enrichment (tags, categories, activity, auto flags) **Agent:** 5/10 · **Series:** `support-auto` **Migration:** `apps/api/sql/schema/031_support_ticket_detail.sql` **Contract:** [02-contract.md](./02-contract.md) §§2, 4.4, 6–7 --- ## Goal Enrich support tickets for “more detailed support” without breaking existing create/list/get contracts: | Capability | Storage / API | |------------|---------------| | Tags | `support_tickets.tags TEXT[]` — create + staff PATCH (`set_tags`) | | Better categories | `support_categories` taxonomy; seeds expand beyond `billing\|bug\|account\|other` | | Related resource refs | `related_product_id` → `processed_products` (tenant-checked), `related_sku` | | Customer context snapshot | `customer_context JSONB` — server-filled on create; staff GET full; customer GET redacted | | Disable auto-reply | `auto_reply_disabled` — staff PATCH; auto-set on human reply / customer follow-up after auto | | Match / AI metadata | `auto_reply_status`, `auto_reply_meta`, `auto_reply_*` message columns | | Activity timeline | `support_ticket_activity` — auto / AI / human events on **staff GET** | --- ## Migration coordination (agents 3 / 4) **One additive series — do not fork ticket columns.** | File | Owner | Contents | |------|-------|----------| | `031_support_ticket_detail.sql` | **agent 5** | Ticket rich fields, message `auto_*` columns, `support_categories`, `support_ticket_activity`, notification kind `auto_reply` | | `032_*` (KB / templates / jobs) | **agent 3** | `support_kb_articles`, `support_reply_templates`, matcher tables/jobs — **do not** re-`ALTER` ticket/message columns from 031 | | AI fallback | **agent 4** | Reuse `RecordAutoReplyOutcome`, message `auto_source='ai'`, statuses `ai_draft` / `ai_sent` / `failed` / `handed_off` | Shared helpers for agents 3–4: - `support.RecordAutoReplyOutcome(ctx, ticketID, status, disabled, messageID, meta, activityKind)` - Message columns: `is_auto_reply`, `auto_source` ∈ `kb|template|ai`, `auto_confidence`, `auto_ref_type`, `auto_ref_id` - Ticket: `auto_reply_status` ∈ `none|matched|ai_draft|ai_sent|skipped|failed|handed_off` --- ## API changes (additive) ### Customer create — `POST /api/support/tickets` Existing: `subject`, `category`, `priority`, `body`. Additive optional: ```json { "tags": ["woo", "export"], "related_product_id": "uuid", "related_sku": "SKU-123" } ``` `related_product_id` must belong to the caller’s `company_id` or the API returns `invalid related_product_id`. ### List — customer + staff Returns (in addition to prior fields): `tags`, `related_product_id`, `related_sku`, `auto_reply_disabled`, `auto_reply_status`, `resolved_by_user_id`. **Does not** embed full `customer_context`, `auto_reply_meta`, or `activity` (detail-only). ### GET customer — `GET /api/support/tickets/{id}` Detail fields + messages (with `is_auto_reply` / `auto_source` when present). `customer_context` redacts `signals` and `user_email`. `auto_reply_meta` and `activity` omitted. ### GET staff — `GET /api/admin/support/tickets/{id}` Full snapshot + `auto_reply_meta` + `activity[]` timeline + messages including internal notes and auto metadata. ### Staff PATCH — `PATCH /api/admin/support/tickets/{id}` Additive fields on `AdminUpdateInput`: | Field | Notes | |-------|-------| | `category` | Active taxonomy slug | | `tags` + `set_tags: true` | Replace tag set (omit `set_tags` to leave unchanged) | | `related_product_id` / `clear_related_product` | Tenant-scoped | | `related_sku` | Optional string | | `auto_reply_disabled` | Staff kill-switch for auto pipeline | --- ## Activity timeline kinds `created`, `customer_message`, `agent_message`, `system_message`, `auto_reply`, `ai_draft`, `ai_sent`, `ai_failed`, `handed_off`, `claimed`, `released`, `status_changed`, `auto_disabled`, `auto_enabled`, `note` --- ## Behavior hooks 1. **Create** — writes `customer_context`, activity `created`; leaves `auto_reply_status=none` for agent 3 matcher. 2. **Customer reply after matched/ai_sent** — sets `auto_reply_disabled=true`, `auto_reply_status=handed_off`. 3. **Staff public reply** — sets `auto_reply_disabled=true`. 4. **Staff toggles `auto_reply_disabled`** — activity `auto_disabled` / `auto_enabled`. Create / Reply\* still **must not** call LLM completers directly (agent 4 owns `TryAutoReplyLLM`). --- ## Tests | Test | File | |------|------| | Tag / SKU / category validation | `ticket_detail_test.go` | | Context redaction | `ticket_detail_test.go` | | Create rejects bad tags (ClientError) | `ticket_detail_test.go` | | Existing CRUD auth / claim / CSAT | unchanged; pre-031 column fallbacks keep them green until goose up | --- ## Verify locally ```bash # from apps/api goose -dir sql/schema postgres "$DATABASE_URL" up go test ./internal/support/ -count=1 ``` --- ## ASSUMPTIONS - ASSUMPTION: Agent 3 lands KB in `032_*` without duplicating 031 ticket ALTERs. (**Confirmed:** `032_support_kb_auto_reply.sql` references 031.) - ASSUMPTION: `plans.name` is stored as `customer_context.plan_slug` (plans have no slug column). - ASSUMPTION: List payloads stay lean; UI agents read detail GET for timeline / context. - NOTE for agent 10: two files share version `033_*` (`033_support_auto_ai_config.sql` and `033_support_auto_security_perf.sql`) — rename one before goose up.