# 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`, `orchestration/sla`→`orchestration/escalation` (a breach sweep calls escalation firing directly, research.md), and `orchestration/escalation`→`orchestration/assignments` (007, for the new scoped-assignment method) are all one-directional — no cycle, since 007 doesn't import anything from 008 and escalation never imports sla back. | 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.*