Files
support_backend/specs/008-sla-escalation/plan.md
T
saqib mirandClaude Sonnet 5 bb31e9d641 tasks: task breakdown for SLA and escalation feature (008)
48 tasks across 9 phases (6 user stories + setup/foundational/polish),
sequenced US1 (policy definition) -> US2 (calendar-aware run creation) ->
US3 (durable pause/resume, P1-complete MVP) -> US4 (breach detection) ->
US5 (breach-triggered escalation) -> US6 (manual escalation), each
dependent on the last since every story builds on the previous one's
mechanism rather than being independently orderable.

Also folds in two design refinements found while cross-checking the
existing scaffold against research.md's plan: TICKET_ASSIGNED (defined in
domain-events.ts since 007, never published) becomes the real wiring point
for SLA-run creation, and src/jobs/sla//src/jobs/escalation/ turn out to
already exist as their own stub scaffolding, reused rather than
duplicated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:27:40 +05:30

164 lines
11 KiB
Markdown

# Implementation Plan: SLA and Escalation
**Branch**: `008-sla-escalation` | **Date**: 2026-09-03 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `specs/008-sla-escalation/spec.md`
## Summary
Populate the existing `platform/business-calendars`, `orchestration/sla`, and
`orchestration/escalation` stub directories (today: `isWorkingHour` hardcoded `true`, a
`SlaDueDateCalculator` doing naive `createdAt + hours` addition, an `EscalationEngine` that
always returns `{ escalated: false }`) with the real engine: `business-calendars` walks a
`BusinessCalendar`'s `workingHours`/`Holiday` records via `luxon` to compute calendar-aware
durations; `sla` resolves the most-specific matching `SLAPolicy` on a ticket's first successful
007 assignment, computes `firstResponseDueAt`/`resolutionDueAt` through the calendar walk,
durably pauses/resumes on `WAITING_FOR_CUSTOMER` transitions (a `TICKET_UPDATED` domain-event
subscriber), and detects breaches via a repeatable BullMQ job; `escalation` resolves the
applicable `EscalationPolicy`, fires an `EscalationEvent` per matching active `EscalationRule` on
a breach (or on a manual request), and re-assigns through a new, specifically-scoped entry point
added to 007's `AssignmentEngine`.
## Technical Context
**Language/Version**: TypeScript 5.4 / Node.js 20+.
**Primary Dependencies**: Prisma (new models), Zod, BullMQ (already a dependency — new
repeatable job, same queue infrastructure as 003's attachment scan and 005's AI session queues),
`luxon` (**new** — the first date/timezone library in this codebase; research.md).
**Storage**: PostgreSQL via Prisma (new `SLAPolicy`, `SLARun`, `BusinessCalendar`, `Holiday`,
`EscalationPolicy`, `EscalationRule`, `EscalationEvent` models). No new infrastructure — reuses
`src/infrastructure/queue` for the breach-detection job, same as every prior BullMQ consumer.
**Testing**: Vitest — unit tests for the calendar-walk algorithm (weekend/holiday exclusion,
partial-day clipping, timezone correctness), the most-specific SLA-policy match, and pause/resume
arithmetic; integration tests for the full assignment→SLA-run→pause/resume→breach→escalation
flow against real Postgres/Redis, including one test that rebuilds `buildApp()` mid-test to
verify pause/resume state survives a genuine process-restart boundary (Constitution Principle
VII, quickstart Scenario 3) — the first feature in this codebase whose correctness depends on
that guarantee specifically, not just within-process concurrency safety.
**Target Platform**: Same Fastify modular monolith. Populates existing module directories:
`src/modules/platform/business-calendars/`, `src/modules/orchestration/{sla,escalation}/`. Adds
one new BullMQ worker registration alongside the existing ones in `src/infrastructure/queue`.
**Project Type**: Backend service — single project.
**Performance Goals**: The breach-detection job must complete a full scan-and-mark pass in
well under its own tick interval even as `SLARun` rows accumulate — indexed on
`(status, resolutionDueAt)` so the query stays a targeted range scan, not a table scan. Not
otherwise performance-sensitive.
**Constraints**: MUST NOT compute due dates naively (FR-004); MUST create an `SLARun` only on a
successful assignment with a matching policy (FR-005); MUST survive a process restart for
pause/resume and breach detection (FR-007/FR-008/FR-009, Constitution Principle VII); MUST never
mark a completed-in-time or paused run breached (FR-010/FR-011); MUST re-assign scoped to the
rule's exact `targetNodeId`, not a fresh unscoped resolution (FR-014).
**Scale/Scope**: Three populated modules, one new dependency, three admin CRUD surfaces (SLA
policies, business calendars, escalation policies/rules), one manual-escalation endpoint, one
new BullMQ repeatable job, one new method on 007's `AssignmentEngine`. Explicitly excludes:
notification delivery, 8 of doc 05's 10 escalation trigger types, investigation/customer-response
timers, SLA restart on ticket reopen (see 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 | SLA/escalation reference `Ticket`/`HierarchyNode`/`Agent` — all SupportHub's own domain. No SaaS identity touched. | PASS |
| II. Configuration Over Hardcoding | Every SLA target, calendar, and escalation rule is admin-configured data, not a hardcoded constant — replacing the literal hardcoded-`true`/naive-arithmetic stubs is the point of this feature. | PASS |
| III. Layered Architecture With Enforced Module Boundaries | Three modules follow the standard shape; `orchestration/sla``platform/business-calendars` and `orchestration/escalation``orchestration/sla` (for breach signals) and →`orchestration/assignments` (007, for the new scoped-assignment method) are all one-directional — no cycle, since 007 doesn't import anything from 008. | PASS |
| IV. AI Recommends, Deterministic Policy Decides | No AI involvement in this feature at all — every decision (policy match, breach, escalation) is deterministic. | PASS — N/A |
| V. Evidence-Based Verification | Not applicable — no resolution/verification concept in this feature. | PASS — N/A |
| VI. Durable Audit & History | `EscalationEvent` is the durable, append-only record doc 06 defines for every escalation, automatic or manual — mirrors `AssignmentHistory`'s established shape. | PASS |
| VII. Concurrency-Safe, Durable Job Handling | This principle's "state must survive a process restart" clause is directly load-bearing here for the first time as the primary correctness requirement (not just a concurrent-request race) — pause/resume and breach detection are both pure-DB-state-plus-polling-job, no in-memory timer anywhere (research.md, quickstart Scenario 3). | PASS |
| VIII. Problem and Ticket Are Separate, Related Entities | SLA/escalation reference `Ticket`, not `Problem` — doesn't touch the distinction. | PASS — N/A |
| Technology & Platform Constraints | Prisma + Zod + existing BullMQ infrastructure, plus the one new `luxon` dependency (justified in research.md — no timezone-correct alternative already exists in this codebase). | PASS |
No violations requiring Complexity Tracking justification.
## Post-Design Constitution Re-check
All gates above remain PASS after Phase 1 design. Worth calling out against Principle VII
explicitly: pause/resume shifts a single absolute `DateTime` column and breach detection is a
plain polling query — by design there is no code path in this feature that could even *appear*
to depend on in-memory state surviving a restart, which is what makes the restart-boundary
integration test (quickstart Scenario 3) a meaningful verification rather than a formality.
## Project Structure
### Documentation (this feature)
```text
specs/008-sla-escalation/
├── 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)
```text
supporthub-api/
├── package.json # MODIFIED — add luxon, @types/luxon
├── prisma/
│ └── schema.prisma # MODIFIED — add SLAPolicy, SLARun,
│ BusinessCalendar, Holiday, EscalationPolicy,
│ EscalationRule, EscalationEvent
├── src/
│ ├── events/
│ │ └── handlers/index.ts # MODIFIED — first real publish of the
│ │ existing-but-unused TICKET_ASSIGNED event
│ │ (from 007's persistAndTransition), plus two
│ │ new TICKET_UPDATED subscribers (pause/
│ │ resume, completion) — research.md
│ ├── jobs/
│ │ └── sla/index.ts # REPLACED stub — schedules the repeatable
│ │ breach-detection job (research.md); jobs/
│ │ escalation/ stays untouched (reserved for a
│ │ future notification-dispatch step)
│ └── modules/
│ ├── platform/
│ │ └── business-calendars/ # REPLACED stub — full standard shape +
│ │ ├── controller/ routes/ schema/ calculators/ for the day-walk algorithm
│ │ │ repository/ service/ types/
│ │ │ mapper/ constants/ index.ts
│ │ └── calculators/
│ └── orchestration/
│ ├── assignments/ # 007, MODIFIED — persistAndTransition
│ │ └── engine/assignment.engine.ts publishes TICKET_ASSIGNED; new
│ │ assignToSpecificNode() method for
│ │ escalation's scoped re-assignment
│ ├── sla/ # REPLACED stub — full standard shape, keeps
│ │ ├── controller/ routes/ schema/ its existing calculators/ dir (due-date
│ │ │ repository/ service/ types/ calculator replaced, not removed) and adds
│ │ │ mapper/ constants/ index.ts engine/ for breach evaluation
│ │ ├── engine/ (policy resolution + breach detection)
│ │ └── calculators/ (due-date calculator, replaced)
│ └── escalation/ # REPLACED stub — full standard shape, keeps
│ ├── controller/ routes/ schema/ engine/ for rule matching + firing
│ │ repository/ service/ types/
│ │ mapper/ constants/ index.ts
│ └── engine/
└── tests/
├── unit/
│ ├── platform/business-calendars/ # calendar-walk algorithm
│ └── orchestration/{sla,escalation}/ # policy match, breach logic, rule match
└── integration/ # full flow incl. restart-boundary test
```
**Structure Decision**: Single project. `business-calendars` gets a full standard shape (not
internal-only) since it needs its own CRUD surface for calendars/holidays, unlike 007's
internal-only `routing` module. `sla` and `escalation` each keep the `engine/` extension doc 07
§8 reserves for modules with real decision logic, matching 005/007 precedent.
## Complexity Tracking
*No constitution violations — table intentionally omitted.*