Files
support_backend/README.md
T

201 lines
14 KiB
Markdown
Raw Normal View History

2026-08-19 16:29:17 +05:30
# Development
docker compose --env-file .env.development -f docker-compose.development.yml up -d --build
# Test
docker compose --env-file .env.test -f docker-compose.test.yml up --build
# Production
docker compose --env-file .env.prod -f docker-compose.prod.yml up --build -d
# Stop
docker compose -f docker-compose.prod.yml down
# Local environment setup
`.env.development`, `.env.test`, and `.env.prod` are gitignored (they hold real credentials) —
copy `.env.example` to the one you need and fill in real values before running any command above.
# CI/CD
Every push/PR triggers the Jenkins pipeline defined in `Jenkinsfile`. Stage order:
checkout → install → environment validation → typecheck → lint → format check → unit test →
integration test → E2E test → build → Docker build → publish → deploy. Publish/deploy only run
on branches with a configured deploy target (`main` → prod, `develop`/`test` → test); other
branches validate and build only. Pipeline run status and per-stage logs are visible in the
Jenkins UI for the relevant job — see `specs/001-ci-pipeline/quickstart.md` for how to validate
the pipeline itself, and `specs/001-ci-pipeline/contracts/pipeline-stage-contract.md` for the
guarantees each stage makes.
Required Jenkins credentials (see the header comment in `Jenkinsfile` for exact IDs): per target
environment (`test`, `prod`) a Postgres password, Redis password, JWT secret, and AWS access
key/secret, plus one shared Docker registry username/password. None of these are ever read from
a file in this repository.
# SaaS Integration
`POST /v1/support/requests` is the trust boundary a registered SaaS product calls through — every
request must carry a `Authorization: Bearer <signed-token>` header (HMAC-SHA256, signed with the
integration's own secret) and a body matching the inbound contract. See
`specs/002-saas-integration/contracts/inbound-request-contract.md` for the full validation order
and error codes, and `specs/002-saas-integration/quickstart.md` for runnable scenarios.
Admins manage integrations under `/admin/products/:externalProductId/integration` (register) and
`/admin/integrations/:integrationId/{rotate,revoke,status,audit-trail}`. **These admin routes are
not yet actually access-controlled** — `fastify.authenticate` is a stub pending the `identity/auth`
module; don't expose them outside a trusted network until that's implemented.
Rate limits (`rateLimitPerMinute`, `rateLimitPerUserPerMinute`) are set per integration at
registration time and enforced via a Redis-backed fixed-window counter, independent of the
global `@fastify/rate-limit` floor already applied to every route.
# Ticketing
A validated inbound request (see "SaaS Integration" above) creates a `Ticket` and `Problem`
immediately — before any diagnosis. See `specs/003-ticketing/contracts/ticket-lifecycle-contract.md`
for the full lifecycle state machine, message-visibility rules, and attachment pipeline, and
`specs/003-ticketing/quickstart.md` for runnable scenarios.
- **Status transitions**: `PATCH /tickets/:ticketId/status` requires `expectedVersion` (optimistic
concurrency — a stale version is rejected with `409`, never silently overwritten) and only
accepts transitions defined in the state machine (`400 INVALID_TRANSITION` otherwise).
- **Messages**: `POST/GET /tickets/:ticketId/messages` (customer-scoped — internal note types are
never returned) and `GET /agent/tickets/:ticketId/messages` (agent-scoped — everything). A
message's customer-visibility is always derived from its type, never caller-supplied.
- **Attachments**: presigned-PUT upload (`POST .../attachments/upload-url`
`POST .../attachments/confirm`) against MinIO/S3 — file bytes never transit this API. **Nothing
is downloadable yet** (`GET .../attachments/:attachmentId/download-url` always returns `409`):
the malware scanner is a placeholder that fails closed until a real one
(`src/modules/ticketing/attachments/mapper/malware-scanner.ts`) replaces it.
- Local/test object storage is MinIO — see the `minio` service in `docker-compose.development.yml`
/ `docker-compose.test.yml` and the `AWS_S3_ENDPOINT` value in the corresponding `.env.*` file.
# Product Knowledge
Admin CRUD for `KnowledgeEntry`/`ErrorCode`/`KnownIssue`/`Runbook`, plus `GET /knowledge/retrieve`
— a filtered (not semantic/vector) query the future AI-support feature will call. See
`specs/004-product-knowledge/contracts/knowledge-contract.md` for the full route list and
`specs/004-product-knowledge/quickstart.md` for runnable scenarios.
- **Versioning**: editing a published `KnowledgeEntry` or `Runbook` never overwrites it in
place — it creates a new row (`version` incremented, `isCurrentVersion: true`), and the prior
version stays queryable (`GET /admin/knowledge/:code/versions`). Requires `expectedVersion`;
a stale value is rejected with `409`, same concurrency pattern as ticket status updates.
- **Retrieval** (`GET /knowledge/retrieve?productId=&feature=&category=`) only ever returns
`published`, currently-effective, current-version entries scoped to the given product —
`validated` entries are ranked ahead of `unvalidated` ones. An unregistered `productId` returns
an empty array, not an error.
- Full semantic/embedding-based retrieval is intentionally not implemented here — see
`specs/004-product-knowledge/spec.md` Assumptions.
# AI Support
A ticket's AI session diagnoses the problem via a real Anthropic Claude call, applies a
configurable confidence-band policy, and — on "proceed" — reasons and acts through a small,
permission/risk-gated tool system, optionally walking a matching runbook step by step. See
`specs/005-ai-support/contracts/ai-support-contract.md` for the full route list and
`specs/005-ai-support/quickstart.md` for runnable scenarios.
- **Requires a real `ANTHROPIC_API_KEY`** (per explicit product decision — not a mock or
pluggable-interface phase). The app boots and every non-AI test still passes without one; an AI
session simply fails closed (escalates) if a reasoning call is attempted with none configured.
`AI_SUPPORT_MODEL`/`AI_SUPPORT_EFFORT` and the system-wide confidence-threshold/question-budget
defaults are all environment-configurable, never hardcoded (doc 11 §B2).
- **Session lifecycle**: a session starts automatically (queued, off the hot path of
`POST /v1/support/requests`) when a ticket is created, and its status mirrors onto
`Ticket.status` through 003-ticketing's *existing* state machine (`AI_ANALYZING`
`AI_TROUBLESHOOTING``AI_VERIFYING``AI_RESOLVED`, or `HUMAN_ESCALATION` from any point) —
see `specs/005-ai-support/research.md` "AISupportSession.status drives Ticket.status". A
customer reply is `POST /tickets/:ticketId/ai-session/messages`; the current session (with its
diagnosis and conversation) is `GET /tickets/:ticketId/ai-session`.
- **Confidence policy**: `PUT/GET /admin/products/:externalProductId/ai-policy` sets
per-product (optionally per-category) `highThreshold`/`lowThreshold`/`maxClarifyingQuestions`,
falling back to env-configured system defaults when nothing is configured — applies to the
very next diagnosis, no deploy required.
- **Tool system**: every tool call the AI proposes is evaluated by a deterministic policy gate
(`src/modules/ai-support/tools/service/policy-gate.ts`) before anything executes — the gate
never reads the AI's own proposal/justification text, only the tool's declared risk level and
product scope. Low-risk tools (`getTicketSnapshot`, `searchProductKnowledge`,
`verifyProductResolution`, `escalateToHuman`) auto-execute; `overrideTicketPriority` is
high-risk and **always** stays `pending_approval` — there's no human-approval UI yet (Phase 10),
so it never actually runs, by design, not by oversight. Every proposal, decision, and result is
recorded and auditable via `GET /tickets/:ticketId/ai-session/actions`.
- **`verifyProductResolution` is a documented, fail-closed placeholder** (same pattern as
`ticketing/attachments`'s malware scanner) — it always returns `confirmed: false`, since there's
no real per-product operational signal to check yet (doc 11 §A2). A ticket is only ever marked
`AI_RESOLVED` on a passing result from this tool, never from what the customer says alone — so
in practice, genuinely automatic AI resolution won't happen until a real verification signal
replaces this placeholder.
- **Runbook engine**: when a diagnosis's `problemType` matches a runbook's `key` for the
product (004-product-knowledge), the application — never the model — tracks which step is
current (`currentStepIndex`) and advances it by exactly one at a time; exhausting every step
without resolving escalates with the full attempted sequence attached.
- Semantic/vector retrieval, product-signal webhook verification, model routing/fallback, cost
dashboards, and localization are intentionally out of scope here — see
`specs/005-ai-support/spec.md` Assumptions.
# Support Organization
Admin CRUD for `Team`/`Agent`/`AgentSkill`/`AgentAvailability`, plus a fully dynamic
`HierarchyNode` tree — this system's explicit routing differentiator (doc 01: "changing one must
never require a code change"). See
`specs/006-support-organization/contracts/support-org-contract.md` for the full route list and
`specs/006-support-organization/quickstart.md` for runnable scenarios.
- **Teams and agents**: `POST/PATCH/GET /admin/teams`, `POST/PATCH/GET /admin/agents`
active/inactive only, never hard deletion. Deactivating a team never cascades into deactivating
its agents.
- **Skills and availability**: `PUT /admin/agents/:agentId/skills/:skillTag` upserts a
proficiency level (never duplicates the same tag). `PUT /admin/agents/:agentId/availability`
upserts the agent's single current record — status/working hours/current load — using
**last-write-wins**, not this system's usual `expectedVersion` optimistic-concurrency pattern:
availability is frequently-changing operational telemetry, not a durable business record (see
`specs/006-support-organization/research.md`).
- **The dynamic hierarchy**: `POST/PUT /admin/hierarchy-nodes` build an arbitrary, nestable tree
scoped to product/category/priority, each node carrying an `assignmentStrategy` and
`slaPolicyId`/`escalationPolicyId` **reference** (no such policy tables exist yet — Phase 7/8)
and opaque `entryConditions`/`exitConditions`. A reparenting edit that would make a node its own
ancestor is rejected with `400 CYCLE_DETECTED`; every create/edit/activate/deactivate writes an
`AuditLog` row.
- **Capability-eligibility lookup** (`GET /support-org/capability-eligibility?skills=&productId=&
categoryId=&priorityId=`) is a **read-only** contract for the future orchestration/assignment
phase (Phase 7) to call — it answers "who is capability-eligible" (active agent, active team,
holds every required skill, composed with any matching hierarchy node's own skills), and
deliberately **never** filters by availability, working hours, or current load (doc 05 §3:
"capability is evaluated before availability"). It does not make an assignment decision.
- This feature supersedes the pre-existing `identity/agents` scaffold stub, which queried a
generic `User`/`UserRole` model unrelated to this system's real architecture (nothing else uses
it — `CustomerReference`, built in 002, is the real customer-identity mechanism).
`identity/customers` and `identity/auth` are untouched — out of scope here.
# Orchestration and Assignment
When a ticket reaches `HUMAN_ESCALATION`, this system automatically resolves 006's hierarchy/
capability-eligibility lookup for it and assigns exactly one eligible agent through a pluggable
strategy — no caller has to trigger this. See
`specs/007-orchestration-assignment/contracts/orchestration-contract.md` for the full route list
and `specs/007-orchestration-assignment/quickstart.md` for runnable scenarios.
- **Trigger**: entirely event-driven — `ticketsService.updateStatus` (003) publishes a domain
event on every status change, and this feature's subscriber (registered alongside 005's own
AI-session-ending one) reacts when `newStatus === 'HUMAN_ESCALATION'`. Neither `ticketing` nor
`ai-support` has any import of `orchestration` — the event bus is what keeps that direction
one-way. **The event bus itself was fixed while building this feature**: `EventBus.publish` now
actually awaits its subscribers (it previously fired them via `EventEmitter.emit`, which never
waits for an async listener) — without that fix, a status-update HTTP call could return before
orchestration (or 005's own hook) had actually finished. `registerDomainEventHandlers()` is
called from `buildApp()` itself now (idempotently — see its own code comment), not only from
`server.ts`'s production startup path, so this is true in tests too, not just production.
- **Strategies**: `ROUND_ROBIN` (concurrency-safe via an atomic Redis `INCR`, verified under real
concurrent load in `tests/concurrency/round-robin.test.ts`), `LEAST_LOADED`, `SKILL_BASED` are
auto-selected from the matched hierarchy node's own `assignmentStrategy` (or a configurable
system default, `ORCHESTRATION_DEFAULT_STRATEGY`, when no node matched). `MANUAL`/`DIRECT` are
never auto-selected — they only ever come from `POST /admin/tickets/:ticketId/assignment`'s
explicit `agentId`. `LEAST_LOADED`/`SKILL_BASED` ties fall through to the same concurrency-safe
cursor `ROUND_ROBIN` uses, never an arbitrary/unstable ordering.
- **A ticket's "required skill"** comes from its most recent AI diagnosis's `problemType` (005),
when one exists, unioned with any matching hierarchy node's own `skills` (006) — when neither
exists, there's no skill constraint at all (every active agent is eligible), never zero eligible
agents; see `specs/007-orchestration-assignment/research.md`.
- **History**: `Assignment` is a version-row-per-period model (reassigning supersedes the current
row, never overwrites it); `AssignmentHistory` is a separate, purely-additive event log —
`GET /tickets/:ticketId/assignment` (current) and `GET /tickets/:ticketId/assignment-history`
(everything, including "no eligible agent" outcomes with `agentId: null`).
- `currentLoad` (006) is read by `LEAST_LOADED`, never written by this feature — no
increment-on-assign/decrement-on-resolve lifecycle exists yet (that's a later phase's job once
ticket resolution itself is built).
- SLA policy execution and rule-driven escalation are intentionally out of scope here — doc 05
documents them alongside orchestration, but the roadmap places them in Phase 8. See
`specs/007-orchestration-assignment/spec.md` Assumptions.