# MVP Punch List

**Goal:** Ship the core ticket loop for a single tenant (customer zero), running locally, cloud-portable from day one.

**Core loop:** Line manager submits → AI triages → Expert sees summary + draft response → Expert sends → KB learns

---

## What's In MVP

| Included | Excluded (post-MVP) |
|---|---|
| AI Hooks 1, 4, 5, 6 | Hook 2 (sentiment/urgency) |
| Magic link auth only | SSO / SAML |
| Single tenant via YAML config | Multi-tenant admin UI |
| Web UI only | WhatsApp / Slack |
| DB-native KB (Karpathy-style markdown in DB) | Git-backed KB option |
| Inngest local dev mode | Nightly batch enrichment (Hook 7) |

---

## Session 1 — Repo Scaffold & Local Infrastructure

> **Development environment:** All ongoing development runs inside a VS Code Dev Container for sandboxing. Session 1 creates the container definition; open the project with "Reopen in Container" after this session to begin Session 2.

- `.devcontainer/devcontainer.json` + `.devcontainer/docker-compose.yml`: Node 22, pnpm, PostgreSQL 16 + pgvector sidecar, port forwards for app (3000), Inngest (8288), Postgres (5432)
- `create-next-app` with TypeScript strict, Tailwind, App Router, pnpm, `src/` directory
- Drizzle ORM setup: `drizzle.config.ts`, initial migration for `tenants`, `tenant_configs`, `users`
- Auth.js magic link wired up with a test user seeded via `scripts/seed.ts`
- `.env.example` with all required vars documented
- `tenant.config.yaml` example file
- Vitest configured; one smoke test that the DB connection resolves

**Deliverable:** Open project in Dev Container → `pnpm dev` starts app → magic link login works for seeded user

---

## Session 2 — Core Schema & State Machine
- Remaining migrations: `tickets`, `ticket_messages`, `ai_artifacts`, `audit_log`
- State machine as a pure TypeScript module (`src/lib/ticket-state-machine.ts`) — exhaustive transition map, throws on invalid transitions
- Unit tests covering every allowed and disallowed transition
- Tenant config loader: reads `tenant.config.yaml`, validates shape, seeds `tenant_configs` table

**Deliverable:** State machine is fully tested and authoritative; config YAML drives all copy and thresholds

---

## Session 3 — Line Manager Submission Flow
- Line manager UI: submission form with character limit from config, submit button
- `POST /api/tickets` — creates ticket + first `ticket_message`, transitions to `SUBMITTED`, fires event
- Inngest setup (local dev mode via `inngest dev`): `completeness-assessment` job subscribes to `ticket.submitted`
- Completeness agent (Hook 1): calls LLM, writes `ai_artifacts` row, transitions to `NEEDS_INFO` or `TRIAGED`
- `NEEDS_INFO` UI: line manager sees clarification questions, submits additional info, re-triggers pipeline
- Integration test: seed a ticket, run the job with a mocked LLM fixture, assert DB state

**Deliverable:** Line manager can submit a ticket and receive clarification questions if incomplete

---

## Session 4 — Triage Completion & Expert Inbox
- Summarization agent (Hook 4): chained after completeness passes, writes `summary` artifact, updates `tickets.title`, transitions to `AWAITING_EXPERT`
- Expert inbox UI: reverse-chronological list, shows title, updated date, sender name, status badge
- SSE endpoint `GET /api/inbox/events` — pushes new ticket notifications to open expert sessions
- Role guard: inbox route requires `expert` or `admin` role
- Integration test: full pipeline from submission to `AWAITING_EXPERT` with fixture LLM responses

**Deliverable:** Expert sees new tickets appear in inbox in real time after line manager submits

---

## Session 5 — Knowledge Base Foundation
- Migrations: `knowledge_entries`, `knowledge_entry_revisions`
- KB structured as Karpathy-style markdown (see `docs/knowledge_system.md`): `index.md`, `log.md`, entity/concept pages — stored in DB, exported as flat markdown
- KB CRUD API: `GET/POST /api/knowledge`, `GET/PUT /api/knowledge/:id`
- KB UI: list + create/edit form for expert (Markdown editor)
- pgvector indexer: on `knowledge_entry.created/updated`, embed the body and upsert to vector store
- `GET /api/knowledge/search?q=` — semantic search endpoint
- Seed a small set of realistic KB entries for testing

**Deliverable:** Expert can author KB entries that are immediately semantically searchable

---

## Session 6 — Expert Detail View & Response Drafting
- Ticket detail view: all messages in thread, AI summary artifact, "Generate Response" button
- Transition to `EXPERT_REVIEWING` on detail view open
- Response draft agent (Hook 5): vector search over KB, builds prompt with top-5 entries, writes `suggested_response` artifact
- Detail view renders draft + KB sources used + any `knowledge_gaps`
- If `knowledge_gaps` non-empty: UI prompts expert to select/create KB entries, re-triggers draft
- `POST /api/tickets/:id/respond` — expert approves + sends, transitions to `RESPONDED`

**Deliverable:** Expert can open a ticket, see an AI-drafted response grounded in the KB, edit it, and send it

---

## Session 7 — KB Learning Loop
- KB update proposal agent (Hook 6): fires on `ticket.responded`, diffs expert reply vs AI draft, writes `kb_update_proposal` artifact
- LLM updates `index.md` and `log.md` as part of every ingest (Karpathy ingest pattern)
- "Knowledge Review" panel in expert UI: lists pending proposals with accept/edit/dismiss
- `PATCH /api/knowledge/proposals/:id` — writes accepted proposals to `knowledge_entries` with `source: 'llm'`, `is_verified: true`

**Deliverable:** Every expert response is an opportunity to grow the KB automatically

---

## Session 8 — E2E Tests & Cloud-Readiness
- Playwright E2E: line manager journey (submit → clarification → accepted), expert journey (inbox → detail → respond), KB journey (create entry → appears in draft)
- `Dockerfile` for the Next.js app (multi-stage, production build)
- Verify `docker compose up` runs production build locally
- Document one-click deploy path in `docs/deployment.md`

**Deliverable:** Full test suite green; app is one config change away from cloud deploy

---

## Session 9 — Proper Persona Separation & Full E2E Coverage

> **Context:** Session 8 shipped E2E tests but the journeys use the expert account for everything, including ticket submission. This conflates roles and means the factory-manager side of the app has no dedicated test persona. Session 9 separates the two sides cleanly.

### Seed changes
- Add `factorymgr1@example.com` as a seeded `line_manager` user in `scripts/seed.ts`
- Add the email to `tenant.config.yaml` under a new `access.initial_line_manager_invites` list (mirrors the pattern used for expert invites)
- Update the seed script to read this list and insert users with `role: 'line_manager'`
- `factorymgr1@example.com` must **not** appear in expert or dual-access invite lists — their access is strictly `/submit` and their own ticket history

### E2E test rewrite (`tests/e2e/mvp.spec.ts`)
The current file has two tests, both signed in as the expert. Replace with four tests that exercise distinct personas:

1. **Factory manager — submit and track**
   - Sign in as `factorymgr1@example.com` via magic link (same `AUTH_DEV_EMAIL_MODE=dev-link` path)
   - Submit a ticket on `/submit`
   - Assert confirmation state is visible
   - Assert the factory manager **cannot** navigate to `/inbox` or `/knowledge` (role guard redirects)

2. **Expert — inbox and respond** *(requires Inngest job runner to have processed the ticket)*
   - Sign in as `expert@example.com`
   - Navigate to `/inbox`, assert the submitted ticket appears
   - Open the ticket detail, assert the AI summary artifact is visible
   - Send a response via the respond action
   - Assert ticket status updates to `RESPONDED`

3. **KB journey** *(currently uses expert submitting a ticket — fix the persona)*
   - Sign in as `expert@example.com`
   - Navigate directly to `/knowledge`
   - Create or search a KB entry
   - Assert result visibility
   - Remove the ticket-submission step from this test (submission belongs in test 1)

4. **Theme toggle** *(no persona change needed — keep as-is)*

### Auth helper refactor
- Rename `signInAsExpert` → two helpers: `signInAs(page, email)` or separate `signInAsExpert` / `signInAsFactoryManager` functions
- Both use the same magic-link dev flow; parameterise by email only

### Role-guard assertions
- Confirm `/inbox` returns a redirect or access-denied for `factorymgr1@example.com`
- Confirm `/knowledge` returns a redirect or access-denied for `factorymgr1@example.com`
- These guard tests are the canonical check that RBAC is wired end-to-end, not just at the API layer

### Other test files to review
- Check `src/**/*.test.ts` and `src/**/*.integration.test.ts` for any hardcoded `expert@example.com` used in a factory-manager role — replace with `factorymgr1@example.com` where the test intent is a line-manager action
- Do not change tests that legitimately use the expert's dual-access `can_submit_tickets` capability; those test a different code path

**Deliverable:** Two stable seed personas; E2E tests exercise each role independently; role guards verified end-to-end; all 29+ unit tests still passing

---

---

## Phase 2 — Structured Intake & Compliance Field Set

> **Context:** Domain expert feedback from Ramon (17 years Sr Mgr / Director of Quality, medical device manufacturing) identified that the free-text submission model is too unstructured for regulated environments. In ISO 13485 / FDA 21 CFR Part 820 contexts, traceability and categorisation are compliance requirements, not optional polish. These sessions add a structured intake form alongside the existing narrative description, a project management layer, and structured expert resolution fields — all configurable per tenant so Pablo's categories work today and future tenants can bring their own taxonomy.
>
> **Backward compatibility:** All new columns are nullable in the migration so existing tickets are unaffected. The form enforces required fields going forward.

---

## Session 10 — Structured Intake: Schema & Config Foundation

> **Goal:** Put all the data structures in place before touching any UI. Nothing visible changes for users yet, but every subsequent session builds on this.

### Database migrations
- **`projects` table:** `id`, `tenant_id`, `name` (unique per tenant, normalised capitalisation on write), `created_by_id` (FK → users), `is_active` (bool, default true), `created_at`, `updated_at`
- **New columns on `tickets`:**
  - `project_id` — FK → projects, nullable (required by form going forward; null for pre-migration tickets)
  - `department` — text, nullable (e.g. "Quality", "Ops")
  - `date_of_occurrence` — date (not timestamp), nullable — when the issue *happened*, not when it was submitted
  - `issue_category` — text, nullable (e.g. "Validation", "Equipment", "Compliance", "Deviation")
  - `affected_system` — text, nullable (e.g. "Sealing", "Testing", "Assembly")
  - `user_priority` — text enum (`critical` / `high` / `medium` / `low`), nullable — user-declared priority, distinct from AI-assessed `urgency_score`
  - `success_criteria` — text, nullable (R11: "What does success look like?")
  - `action_requested` — text, nullable (R12: "Approval of proposed strategy", "Guidance on next steps", etc.)
- **New columns on `tickets` for expert resolution (S fields):**
  - `resolution_severity` — text, nullable (S06: expert-assessed severity, distinct from user-declared priority)
  - `resolution_concerns` — text, nullable (S07: "Process", "Knowledge", "Confusion", etc.)
  - `resolution_summary` — text, nullable (S04: expert-edited version of AI summary; null means AI summary is canonical)

### Tenant config schema (`src/lib/tenant-config-loader.ts`)
Add a new `intake` section to `TenantConfigSchema` with configurable dropdown options (all arrays of strings, all with sensible defaults):
```yaml
intake:
  departments: ["Quality", "Operations", "Engineering", "Regulatory"]
  issue_categories: ["Validation", "Equipment", "Compliance", "Deviation", "Other"]
  affected_systems: ["Sealing", "Testing", "Assembly", "Packaging", "Other"]
  action_requested_options:
    - "Approval of proposed strategy"
    - "Guidance on next steps"
    - "Regulatory interpretation"
    - "Other"
  resolution_severity_levels: ["Critical", "Major", "Minor", "Observation"]
  resolution_concern_types: ["Process gap", "Knowledge gap", "Confusion / unclear guidance", "Other"]
```

### `tenant.config.yaml` update
Add the `intake:` section above with Pablo's specific categories for customer zero.

### Seed script update
- Read `access.initial_projects` (new list in config) and insert rows into the `projects` table for the tenant on seed/re-seed
- Add Pablo's initial project list to `tenant.config.yaml` (at minimum one example project)

### Tests
- `tenant-config-loader.test.ts`: add cases for the new `intake` section — valid config, defaults when omitted, invalid values
- Smoke test: assert new `tickets` columns exist and accept null without error

**Deliverable:** All new DB columns and config structures in place; existing tickets and tests unaffected; config loader validates the new `intake` section

---

## Session 11 — Structured Intake: Project Management & Submission Form

> **Goal:** Expert can manage the project list; line manager sees and must complete all structured fields on submission.

### Admin: project management
- New page `/admin/projects` (requires `expert` or `admin` role — experts need to manage this, not just admins)
- Lists active projects with name, created date, created by
- "Add project" inline form: text input, normalises capitalisation on save (e.g. "atlas" → "Atlas"), posts to `POST /api/admin/projects`
- "Deactivate" action per row: sets `is_active = false`, soft-removes from autocomplete; posts to `PATCH /api/admin/projects/[id]`
- New API routes: `GET /api/admin/projects`, `POST /api/admin/projects`, `PATCH /api/admin/projects/[id]`
- Link to Projects from the nav (expert/admin only)

### Submission form (`/submit`)
Replace the current single-textarea form with a structured form. Field order and required status:

| Field | Type | Required | Notes |
|---|---|---|---|
| Project | Autocomplete | Yes | Pulls active projects for tenant; defaults to the last project this user submitted against (stored in localStorage or user session); full name shown, slug used internally |
| Department | Select | Yes | Options from `tenant_config.intake.departments` |
| Date of Occurrence | Date picker | Yes | Must be ≤ today; no future dates |
| Issue Category | Select | Yes | Options from `tenant_config.intake.issue_categories` |
| Affected System / Process | Select | Yes | Options from `tenant_config.intake.affected_systems` |
| Priority | Select | Yes | Fixed enum: Critical / High / Medium / Low; maps to `user_priority` |
| Problem Description | Textarea | Yes | Existing field (R10); char limit from config |
| What does success look like? | Textarea | Yes | R11; shorter, ~500 char limit |
| Action Requested | Select | Yes | Options from `tenant_config.intake.action_requested_options` |

- Submit button stays disabled until all required fields are filled
- Character counters on textarea fields
- Project autocomplete: type-to-filter against active project list fetched from `GET /api/admin/projects`; "last used" default pre-filled from localStorage on mount; no ability to create a new project inline (that is backlog — see note below)

### API: `POST /api/tickets`
- Accept and validate all new structured fields
- All required on new submissions; API returns 422 if any are missing (guards against direct API calls bypassing form validation)
- Store `user_priority` separately from `priority` (AI-assessed); user-declared value is never overwritten by AI

### Hook 1 (completeness agent) update
- Structured fields are now a prerequisite gate before the LLM completeness check runs: if any required field is missing on the ticket, immediately transition to `NEEDS_INFO` without an LLM call, with a system message listing the missing fields
- If all structured fields present, proceed with LLM completeness check as before (the LLM now also receives the structured fields as additional context)

### E2E test updates
- Update factory manager submit tests to fill in all structured fields
- Test that submitting without a required field keeps the button disabled
- Test that the project autocomplete shows the seeded project list

**Deliverable:** Expert can manage the project list; line manager must complete all structured fields to submit; API validates and stores all fields; Hook 1 gates on structured completeness before LLM

> **Backlog note — new project on the fly:** Line managers cannot create new projects. If a project is missing from the list, they must ask an expert/admin to add it via `/admin/projects`. A future session can add an "propose new project" flow from the submission form (creates a pending project that an expert approves).

---

## Session 12 — Structured Intake: Expert Resolution Fields & AI Integration

> **Goal:** Expert captures structured resolution data; all AI agents use the full structured context; the KB learning loop uses resolution fields to produce better proposals.

### Expert ticket detail — structured intake display
- Add a collapsible "Submission details" panel above the message thread showing all R fields in a clean read-only layout: project, department, date of occurrence, issue category, affected system, user-declared priority, success criteria, action requested
- This panel is visible to both expert and line manager (the line manager submitted these — they should see them confirmed)

### Expert response form updates
Add three new fields to the response form (below the draft textarea, before the Send button):

| Field | Type | Required | Notes |
|---|---|---|---|
| Severity | Select | Yes | Options from `tenant_config.intake.resolution_severity_levels`; expert's assessment, distinct from user-declared priority |
| Concerns | Select | Yes | Options from `tenant_config.intake.resolution_concern_types`; tags the root cause category |
| Resolution summary | Textarea | No | S04: expert can edit the AI summary to capture key facts in their own words; pre-filled with AI summary if one exists; null means use AI summary as-is |

### API: `POST /api/tickets/[id]/respond`
- Accept and store `resolution_severity`, `resolution_concerns`, `resolution_summary`
- All three stored on the `tickets` row

### AI agent prompt updates (all hooks)
Each agent in `src/inngest/` and each prompt template in `prompts/` receives the structured fields as additional context in the prompt. Specific changes:

- **Hook 1 (completeness):** Pass issue category, affected system, and action requested to the LLM — it can now ask more targeted clarification questions given the declared category
- **Hook 2 (sentiment/urgency — when implemented):** Treat `user_priority = critical` as a floor; AI urgency score can only match or exceed it, never downgrade it
- **Hook 3 (auto-tagging — when implemented):** Seed the tag suggestion with `issue_category` and `affected_system` already declared by user; AI confirms or augments rather than guessing from scratch
- **Hook 4 (summarization):** Include project name, issue category, affected system, and action requested in the summary prompt context; update `tickets.title` format to reflect category (e.g. "[Validation] Sealing line deviation — Atlas project")
- **Hook 5 (response draft):** Include success criteria and action requested in the draft prompt — the AI now knows whether the user wants approval or guidance and can tailor the response style accordingly
- **Hook 6 (KB update proposal):** Include `resolution_severity` and `resolution_concerns` in the proposal prompt; proposals for "Knowledge gap" concern type should be flagged as high-priority KB additions

### Tests
- Integration tests for respond endpoint with S fields
- Update full-pipeline integration test to include structured fields on the seed ticket
- Prompt snapshot tests updated to reflect new context fields

**Deliverable:** Expert captures severity and concern type on every response; all AI agents use the full structured intake context; KB proposals are enriched with resolution classification

---

## Session 13 — Structured Intake: Inbox, Reporting Views & Polish

> **Goal:** Structured data surfaces visibly throughout the app; the inbox becomes a useful triage dashboard rather than a flat list.

### Expert inbox enhancements
- Add `issue_category` chip and `user_priority` badge to each ticket row (priority badge color-coded: red for critical, orange for high, etc.)
- Add filter controls above the list: filter by project, issue category, user priority, status
- Add sort options: by date submitted (current default), date of occurrence, user priority

### Ticket reference number format update
- Update the reference number generation to include issue category abbreviation: e.g. `AQ-VAL-0042` (Validation), `AQ-EQP-0043` (Equipment)
- Configurable abbreviation map in `tenant.config.yaml` under `intake.category_abbreviations`
- Existing reference numbers unchanged (migration is additive)

### Line manager "my tickets" list (`/tickets`)
- This was already on the roadmap (item 2.3) — implement it here since the structured fields make the list much more useful
- Columns: reference number, project, issue category, date of occurrence, status, last updated
- Sorted by date of occurrence descending

### Seed data update
- Add 3–5 realistic seed tickets with all structured fields populated (across different categories and projects) so the dev environment demonstrates the full UI without needing to submit manually

### E2E test additions
- Expert inbox filter: filter by issue category, assert only matching tickets shown
- Expert inbox priority badge: submit a Critical ticket, assert red badge appears in inbox
- Line manager ticket list: assert submitted ticket appears with correct category and project

**Deliverable:** Inbox is a real triage dashboard; line managers can track their submissions; dev environment has realistic seed data; all tests passing

---

## Local → Cloud Migration (no code changes required)

| Local | Cloud equivalent |
|---|---|
| `docker compose` Postgres | Supabase / Railway Postgres / AWS RDS |
| `inngest dev` | Inngest Cloud (same SDK, one env var) |
| `next dev` | Vercel / Railway / any Node.js host |
| `.env.local` | Host provider's environment variable UI |
