Extends the existing PATCH /admin/agents/:agentId with an optional userId to finish wiring 010's Agent.userId link, and adds GET /agents/me/tickets + GET /admin/agents/:agentId/tickets sharing one ticketing/tickets service method, backed by a new Assignment @@index([agentId, isCurrent]). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.8 KiB
Implementation Plan: Agent Ticket Queue
Branch: 011-agent-ticket-queue | Date: 2026-09-07 | Spec: spec.md
Input: Feature specification from specs/011-agent-ticket-queue/spec.md
Summary
Finishes wiring Agent.userId (added in 010-identity-auth as schema-only) by extending the
existing PATCH /admin/agents/:agentId with an optional userId, then adds the ticket-query
this unblocks: GET /agents/me/tickets (agent's own session) and
GET /admin/agents/:agentId/tickets (admin, explicit agent) — both returning the same
summarized, dashboard-ready projection of every ticket currently assigned to that agent.
Technical Context
Language/Version: TypeScript 5.4 / Node.js 20+ (unchanged).
Primary Dependencies: None new — reuses Prisma, the existing identity/agents and
ticketing/tickets modules, and 010's requireRole.
Storage: PostgreSQL via Prisma. Adds one index (Assignment @@index([agentId, isCurrent]))
— the query this feature introduces (all current assignments for one agent) has no supporting
index today; the existing [ticketId, isCurrent] index doesn't serve an agent-first lookup.
Testing: Vitest — unit test for the "no linked Agent" rejection path; integration tests against real Postgres/Redis for linking, the agent's-own-session query, the admin explicit- agent query, and cross-agent isolation (one agent never sees another's tickets).
Target Platform: Same Fastify modular monolith. Modifies identity/agents (linking
endpoint, userId already returned by existing reads) and ticketing/tickets (new summary
query + routes) — no new module, since "list my tickets" is a ticketing concern reading
orchestration's Assignment state, matching 003's existing module boundary (ticketing already
depends on orchestration's public surface for status-transition side effects).
Project Type: Backend service — single project.
Performance Goals: The ticket-summary query is one indexed query for current assignments plus a single batched fetch of their tickets (with product/customer/SLA-run relations) — no N+1 per-ticket round trip, matching FR-003/SC-001's "single request" requirement.
Constraints: MUST NOT let an agent's own-session call accept a client-supplied agentId
(FR-004 — always resolved from the session's own linked Agent row). MUST reject a session
with no linked Agent row distinguishably from an empty list (FR-006).
Scale/Scope: One new admin endpoint (link), two new read endpoints (agent-self, admin- explicit) sharing one service method, one new Prisma index. Explicitly excludes: a general ticket search/filter endpoint, pagination, and self-service linking (spec.md Assumptions).
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle / Section | Check | Result |
|---|---|---|
| I. SaaS Is the Sole Identity & Access Authority | Purely internal to SupportHub's own domain (agent roster, ticket assignment) — no SaaS/customer identity involved. | PASS — N/A |
| II. Configuration Over Hardcoding | No new configurable values introduced. | PASS — N/A |
| III. Layered Architecture With Enforced Module Boundaries | The new query lives in ticketing/tickets (the module that owns Ticket), reading Assignment via orchestration's own public index.ts export — no reach-through to orchestration's internals. The link endpoint lives in identity/agents, alongside its existing agent CRUD. |
PASS |
| IV. AI Recommends, Deterministic Policy Decides | Not applicable. | PASS — N/A |
| V. Evidence-Based Verification | Not applicable. | PASS — N/A |
| VI. Durable Audit & History | No new mutable state beyond the Agent.userId link itself, which Agent's own updatedAt already timestamps. |
PASS |
| VII. Concurrency-Safe, Durable Job Handling | Read-only queries plus one simple linking write guarded by the existing @unique constraint on Agent.userId (a concurrent double-link race is rejected by the database itself, not application logic). |
PASS |
| VIII. Problem and Ticket Are Separate, Related Entities | Not applicable — no problem-management involvement. | PASS — N/A |
| Technology & Platform Constraints | No new dependencies or infrastructure. | PASS |
No violations requiring Complexity Tracking justification.
Project Structure
Documentation (this feature)
specs/011-agent-ticket-queue/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
└── tasks.md
Source Code (repository root)
supporthub-api/
├── prisma/
│ └── schema.prisma # MODIFIED — Assignment @@index([agentId, isCurrent])
└── src/
└── modules/
├── identity/
│ └── agents/ # MODIFIED — link-user endpoint alongside existing agent CRUD
│ ├── controller/ routes/ schema/
│ └── service/
└── ticketing/
└── tickets/ # MODIFIED — new agent-assigned-tickets summary query
├── controller/ routes/ schema/
└── service/ mapper/
└── tests/
├── unit/identity/ # "no linked Agent" rejection unit test
└── integration/ # linking flow + both list endpoints + cross-agent isolation
Structure Decision: Single project, no new module. The link endpoint extends
identity/agents (already owns agent CRUD); the ticket-summary query extends
ticketing/tickets (already owns Ticket) rather than a new module, since this is one small
read query, not a new bounded concern.
Complexity Tracking
No constitution violations — table intentionally omitted.