Files
support_backend/specs/003-ticketing/plan.md
T
saqib mirandClaude Sonnet 5 4751cf3164 docs: plan and design artifacts for ticketing feature
/speckit-plan output for 003-ticketing: technical context and
constitution gate check (all PASS), Phase 0 research (9 decisions:
ticket code format, explicit 12-state lifecycle transition table,
optimistic concurrency via version column, idempotency-key upsert
reusing 002's CustomerReference pattern, explicit-reference-only
recurring-problem linking, config-driven message visibility mapping,
presigned-PUT attachment pipeline, a fail-closed placeholder malware
scanner since none exists in this stack, and adding MinIO to Docker
Compose for local/test S3-compatible storage), Phase 1 data model
(Ticket/Problem/TicketMessage/TicketAttachment plus the inbound
request -> ticket creation behavior), the lifecycle/messages/
attachments contract, and a 6-scenario quickstart.

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

8.6 KiB

Implementation Plan: Ticket Creation, Messages & Attachments

Branch: 003-ticketing | Date: 2026-09-02 | Spec: spec.md

Input: Feature specification from specs/003-ticketing/spec.md

Summary

Add the Ticket/Problem/TicketMessage/TicketAttachment domain: creating a ticket (and its problem) immediately from a validated inbound request (wiring into 002-saas-integration's POST /v1/support/requests, which today only echoes trusted context back), a typed message timeline with enforced internal-note privacy, and an attachment pipeline built on the already-scaffolded S3-compatible storageService plus a new async malware-scan job on the already-scaffolded attachments-queue. Idempotency-key enforcement (deferred from 002-saas-integration's FR-012) is implemented here since Ticket now exists.

Technical Context

Language/Version: TypeScript 5.4 / Node.js 20+.

Primary Dependencies: Prisma (new models), BullMQ (new attachment-scan job, using the existing attachments-queue and QueueManager), @aws-sdk/client-s3 + @aws-sdk/s3-request-presigner (already wired via storageService — no new dependency), Zod.

Storage: PostgreSQL via Prisma (new Ticket, Problem, TicketMessage, TicketAttachment models per docs/06-database-schema.md) + S3-compatible object storage (AWS S3 in test/production, MinIO locally — per the user's confirmed choice, matching doc 04's explicit guidance and the existing storageConfig/storageService scaffold).

Testing: Vitest — unit tests for the state-machine transition table and message-visibility mapping; integration tests for ticket creation (incl. idempotency and recurring-problem linking), message read-scoping, and the attachment upload → scan → download flow, against a real Postgres/Redis/S3-compatible target (MinIO via docker-compose.test.yml, extended by this feature — see research.md).

Target Platform: Same Fastify modular monolith. Extends the already-scaffolded src/modules/ticketing/{tickets,messages,attachments} modules (currently: tickets has a bare GET /tickets stub; messages/attachments are unimplemented skeletons).

Project Type: Backend service — single project.

Performance Goals: Ticket creation (FR-001) must complete synchronously within the inbound request's own response — no async/eventual-consistency gap between "request accepted" and "ticket exists" (this is the whole point of Constitution Principle "ticket created immediately"). Malware scanning is explicitly asynchronous (SC-005 only requires it's enforced before download, not before upload completes).

Constraints: MUST NOT store attachment file bytes in PostgreSQL (FR-011); MUST NOT make an attachment downloadable before its scan clears (FR-013); ticket status updates MUST use optimistic concurrency (FR-007); every ticket/message/attachment query MUST be tenant-scoped (FR-015).

Scale/Scope: One inbound-flow change (002-saas-integration's stub handler becomes real ticket creation), full CRUD-ish surface for messages or a customer/agent to read, and an attachment upload/download surface. Explicitly excludes investigation/root-cause/solution/resolution (Phase 9) and AI diagnosis (Phase 4) per 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 Ticket/message/attachment queries are scoped by the already-validated reqContext (productId/customerId/tenantId) from 002-saas-integration's trust boundary — never a caller-supplied id alone (FR-015). PASS
II. Configuration Over Hardcoding Message-type visibility mapping, attachment size/type limits, and scan-status gate are all defined as data/config, not scattered conditionals — see data-model.md. PASS
III. Layered Architecture With Enforced Module Boundaries Extends the existing tickets/messages/attachments modules through their own controller/service/repository layers and public index.ts; the scan job lives in src/jobs/attachments/ per the existing scaffold, calling the attachments module's repository through its public API only. PASS
IV. AI Recommends, Deterministic Policy Decides Not applicable — no AI in this feature (explicitly deferred to Phase 4). PASS — N/A
V. Evidence-Based Verification Not applicable — resolution/verification is Phase 9. PASS — N/A
VI. Durable Audit & History Ticket status transitions and attachment scan-status changes are written as SYSTEM_EVENT ticket messages (visible per FR-008's type-driven visibility), giving a durable, queryable history without a separate audit mechanism for this feature's own state changes. PASS
VII. Concurrency-Safe, Durable Job Handling Ticket status updates use optimistic concurrency (a version column, FR-007); the malware-scan job is idempotent (re-running it for an already-scanned attachment is a no-op); idempotency-key enforcement (FR-004) reuses the same atomic-upsert pattern as 002-saas-integration's CustomerReference.findOrCreate. PASS
VIII. Problem and Ticket Are Separate, Related Entities Directly implements this principle (FR-003) — this is the feature that first creates both entities. PASS
Technology & Platform Constraints Uses only already-present dependencies (Prisma, BullMQ, AWS SDK, Zod) — no new runtime dependency. PASS

No violations requiring Complexity Tracking justification.

Post-Design Constitution Re-check

All gates above remain PASS after Phase 1 design. One design detail worth calling out: the malware scanner (research.md) fails closed (defaults to infected, never clean) precisely because Principle V's spirit ("evidence-based, not assumed") applies here even though this feature's own scope is pre-Phase-9 — an attachment pipeline that silently marked everything "clean" would be asserting a safety property with no evidence behind it.

Project Structure

Documentation (this feature)

specs/003-ticketing/
├── plan.md              # This file
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── quickstart.md        # Phase 1 output
├── contracts/           # Phase 1 output
└── tasks.md             # Phase 2 output (/speckit-tasks — not created here)

Source Code (repository root)

supporthub-api/
├── prisma/
│   └── schema.prisma                          # MODIFIED — add Ticket, Problem, TicketMessage,
│                                                 TicketAttachment models per docs/06
├── src/
│   ├── modules/
│   │   ├── catalog/products/
│   │   │   └── routes/inbound-request.routes.ts  # MODIFIED — actually create a ticket instead
│   │   │                                            of echoing context back
│   │   └── ticketing/
│   │       ├── tickets/                        # EXTENDED (existing scaffold) — create/get/
│   │       │   ├── controller/                    list/updateStatus, state machine, idempotency
│   │       │   ├── service/
│   │       │   ├── repository/
│   │       │   ├── routes/
│   │       │   ├── schema/
│   │       │   ├── mapper/
│   │       │   └── types/
│   │       ├── messages/                        # EXTENDED (existing scaffold) — post/list,
│   │       │   └── ...                             visibility-scoped reads
│   │       └── attachments/                      # EXTENDED (existing scaffold) — upload
│   │           └── ...                             (presign + confirm), scan-gated download
│   └── jobs/
│       └── attachments/                          # EXTENDED (existing scaffold) — real malware
│           └── index.ts                            scan worker (pluggable scanner, see research.md)
└── tests/
    ├── unit/ticketing/                           # state machine, visibility mapping
    └── integration/                              # creation/idempotency, messages, attachments

Structure Decision: Single project, extending the three already-scaffolded ticketing submodules rather than restructuring them — docs/07-backend-architecture.md's module layout already anticipated exactly this shape. The inbound-request handler from 002-saas-integration is modified in place (it's the one integration point between "request trusted" and "ticket exists"), not duplicated.

Complexity Tracking

No constitution violations — table intentionally omitted.