Files
support_backend/specs/008-sla-escalation/spec.md
T
saqib mirandClaude Sonnet 5 1ad9007e79 docs: spec for SLA and escalation feature (008)
Phase 8 of the roadmap: SLA policy engine (most-specific-match, calendar-
aware due dates), durable pause/resume and breach detection (never an
in-memory timer), and a rule-driven escalation engine that re-assigns via
007's engine scoped to a specific target node. Bounded to the two
SLA-derived trigger types this feature can compute a real signal for;
the other eight doc 05 §6 trigger types remain valid rule configuration
without a wired event source yet. First feature to give 006's
HierarchyNode.slaPolicyId/escalationPolicyId fields a real target.

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

18 KiB

Feature Specification: SLA and Escalation

Feature Branch: 008-sla-escalation

Created: 2026-09-03

Status: Draft

Input: User description: "Phase 8 of docs/10-implementation-roadmap.md: SLA policy engine, business calendar/holiday support, durable pause/resume via BullMQ, rule-driven escalation engine, escalation event audit. Per docs/05-orchestration-sla-escalation.md §5-6 and docs/06-database-schema.md 'Domain: SLA' / 'Domain: Escalation'."

User Scenarios & Testing (mandatory)

User Story 1 - An admin defines SLA policies as configuration (Priority: P1)

An administrator defines an SLA policy — first-response/investigation/resolution/customer- response time limits — optionally scoped to a product, category, problem type, and/or priority, and tied to a business calendar. Nothing about SLA thresholds is hardcoded anywhere in the system.

Why this priority: Every later capability in this feature reads a policy that has to exist first.

Independent Test: Create an SLA policy with a resolution limit; confirm it's retrievable and its fields are stored exactly as given.

Acceptance Scenarios:

  1. Given an admin creates an SLA policy with a resolution time limit, When it's saved, Then it's retrievable with every field exactly as given, active: true by default.
  2. Given a policy scoped to a product/category/problem type/priority, When two policies could both apply to the same ticket, Then the more specific one is preferred — same most-specific-match convention this system already uses for confidence policy (005) and capability scope (006).
  3. Given an admin creates a business calendar with working hours and holidays, When it's referenced by a policy, Then due-date calculations for tickets under that policy use it.

User Story 2 - An SLA run starts automatically when a ticket is assigned, with calendar-aware due dates (Priority: P1)

When orchestration (007) assigns a ticket, an SLA run starts for it automatically, with due dates computed against the applicable policy's business calendar — never a naive createdAt + N hours that ignores weekends, holidays, or working hours.

Why this priority: Nothing else in this feature — breach detection, pause/resume, escalation — has anything to act on until a real, calendar-correct due date exists.

Independent Test: Assign a ticket under a policy with a resolution limit and a calendar whose working hours exclude a weekend; confirm the computed due date skips the excluded time rather than counting straight through it.

Acceptance Scenarios:

  1. Given a ticket is assigned (007), When an applicable SLA policy resolves for it (User Story 1's most-specific-match rule), Then an SLA run starts with a resolution due date computed against that policy's calendar.
  2. Given no policy matches, When a ticket is assigned, Then no SLA run is created rather than applying an arbitrary default — this feature does not invent a policy that was never configured.
  3. Given a calendar with defined working hours and a holiday, When a due date is computed, Then time outside working hours and on holidays is excluded from the countdown.

User Story 3 - SLA pause and resume are durable, not in-memory (Priority: P1)

When a ticket's status indicates it's waiting on the customer, its SLA run pauses — the clock stops counting against the agent. When it resumes, the clock continues from where it left off, never from zero and never having kept counting while paused. This state survives a process restart.

Why this priority: Constitution Principle VII names this exact scenario — an SLA that keeps counting during a customer-caused delay, or that resets on a restart, actively misrepresents whether a real commitment was honored.

Independent Test: Start an SLA run, pause it, wait, resume it; confirm the resulting due date reflects the paused duration being excluded, not counted twice.

Acceptance Scenarios:

  1. Given a running SLA, When the ticket transitions to a waiting-for-customer state, Then the run's status becomes paused and its due date stops approaching.
  2. Given a paused SLA, When the ticket transitions back to in-progress, Then the run's status becomes running again and the remaining time is preserved from the pause point.
  3. Given a paused or running SLA run, When the process restarts, Then its state is unchanged on restart — pause/resume state is never held only in memory.

User Story 4 - A breach is detected durably, never silently missed (Priority: P2)

When a running SLA's due date passes without the corresponding milestone happening, the run is marked breached — detected by a durable background job, not a timer that only fires if the process happens to still be running at the right moment.

Why this priority: Depends on User Stories 2-3 (a real due date, correctly paused/resumed) existing first. A breach that's never detected is worse than no SLA at all — it's a false sense of a commitment being tracked.

Independent Test: Start an SLA run with a very short resolution limit; confirm it's marked breached once the due date passes, even simulating the checking job running in a separate process invocation from the one that started the run.

Acceptance Scenarios:

  1. Given a running SLA run whose due date has passed, When the breach-detection job next runs, Then the run's status becomes breached and its breachedAt timestamp is recorded.
  2. Given an SLA run that completes (its milestone actually happens) before its due date, When breach detection later runs, Then it is not marked breached — completion is checked against the actual event, not assumed from elapsed time alone.
  3. Given a paused SLA run, When breach detection runs while it's paused, Then it is never marked breached — a paused clock cannot breach.

User Story 5 - A breach automatically triggers rule-driven escalation (Priority: P2)

An administrator defines escalation rules — trigger condition, target support-hierarchy node, who to notify. When an SLA breach (or another configured trigger) occurs, the matching rule fires automatically: the ticket moves toward the rule's target node and orchestration (007) re-assigns it there, and an escalation event is durably recorded. This is never a hardcoded if L1 then L2 — it's evaluated against configured rules.

Why this priority: Depends on User Story 4 (a real breach signal to trigger on). This is the other half of "durable SLA enforcement actually matters" — detecting a breach that nobody acts on isn't meaningfully different from not detecting it.

Independent Test: Configure an escalation rule for resolution_breach targeting a specific hierarchy node; breach an SLA run; confirm an escalation event is recorded, the ticket is re-assigned via 007 scoped to that specific node, and the rule's configured notification target is recorded (not necessarily delivered — see Assumptions).

Acceptance Scenarios:

  1. Given an active escalation rule for resolution_breach scoped to a target node, When a matching SLA run breaches, Then an EscalationEvent is recorded with the rule, the reason, and triggeredBy: system.
  2. Given an escalation event fires, When it completes, Then 007's assignment engine re-runs scoped specifically to the rule's target node — not a fresh, unscoped resolution — and the ticket's assignment history (007) reflects the new assignment with strategy reused from whatever the target node itself configures.
  3. Given no escalation rule matches a breach, When the breach is detected, Then the SLA run is still marked breached (User Story 4) — the absence of a matching rule doesn't suppress breach detection, it only means no automatic escalation follows.
  4. Given multiple active rules could match the same trigger, When more than one does, Then every matching rule fires its own escalation event — this feature does not pick just one.

User Story 6 - A human can manually trigger escalation, audited the same way (Priority: P3)

An agent or admin can explicitly escalate a ticket to a specific target node, for a stated reason, without waiting for an automatic trigger — recorded through the same EscalationEvent audit trail as an automatic one.

Why this priority: Depends on User Story 5's event/re-assignment mechanism already existing. Automatic triggers won't cover every real reason to escalate (doc 05 §10 names several this feature doesn't compute automatically — see Assumptions); a human needs an explicit path that still produces the same durable record.

Independent Test: Manually escalate a ticket to a named target node with a reason; confirm an EscalationEvent with triggeredBy set to the calling actor is recorded and the ticket is re-assigned via 007 to that node.

Acceptance Scenarios:

  1. Given an admin manually escalates a ticket to a target node, When it completes, Then an EscalationEvent is recorded with triggeredBy set to the actor (never system), and 007 re-assigns the ticket scoped to that node.
  2. Given a manual escalation targets a node that doesn't exist, When it's attempted, Then it's rejected — never a dangling escalation event pointing nowhere.

Edge Cases

  • What happens if a ticket has no assigned agent yet when its SLA would otherwise start? An SLA run only starts on a successful assignment (User Story 2) — an unassigned ticket (007's "no eligible agent" outcome) has no SLA run to track, consistent with there being no one yet to hold to a commitment.
  • What happens if a business calendar has no working hours configured at all for a given day? That day contributes zero time toward any due-date countdown — an unconfigured day is never silently treated as 24 available hours.
  • What happens if an SLA policy's calendar is deleted or unreferenced after runs already exist against it? Out of scope — this feature does not implement calendar deletion, only creation and the active state every other configuration entity in this system already uses.
  • What happens when a ticket is reopened after being resolved, with a completed SLA run already on record? Out of scope for this feature to define a new run automatically — reopening (a future problem-resolution-phase concept) may need its own SLA-run-restart decision; this feature's SLA runs are 1:1 with a ticket's first, straightforward assignment→resolution lifecycle.
  • What happens if two escalation rules would move a ticket to the same target node at once (a race between an automatic breach and a simultaneous manual escalation)? Both EscalationEvent rows are recorded (never lost — durable audit is unconditional); 007's own re-assignment path already handles a ticket being assigned twice in quick succession correctly (it's the same version-row-per-period Assignment mechanism 007 already built and tested for concurrent writes), so no new concurrency mechanism is needed here.

Requirements (mandatory)

Functional Requirements

  • FR-001: The system MUST let an admin create an SLA policy with first-response/ investigation/resolution/customer-response time limits, optionally scoped to product/category/ problem type/priority, referencing a business calendar.
  • FR-002: When multiple SLA policies could apply to the same ticket context, the system MUST prefer the most specific match — never an arbitrary or first-found selection.
  • FR-003: The system MUST let an admin create a business calendar with working hours and holidays.
  • FR-004: Due-date computation MUST exclude time outside a calendar's working hours and on its holidays — MUST NOT compute a due date as a naive elapsed-time addition that ignores the calendar.
  • FR-005: An SLA run MUST start automatically when a ticket is successfully assigned (007), using the most-specific-matching policy (FR-002); when no policy matches, no run is created.
  • FR-006: An SLA run's status MUST be one of running, paused, warning, breached, or completed, matching doc 06's defined set.
  • FR-007: When a ticket transitions to a waiting-for-customer state, its SLA run MUST pause; when it transitions back, the run MUST resume with its remaining time preserved — never reset to the full original duration and never having continued counting while paused.
  • FR-008: SLA pause/resume state and due dates MUST be durable — recomputable and correct after a process restart, never dependent on an in-memory timer (Constitution Principle VII).
  • FR-009: A running SLA run whose due date has passed MUST be detected and marked breached by a durable background job — never missed because the triggering process wasn't running at the exact due moment.
  • FR-010: An SLA run that completes its milestone before its due date MUST NOT be marked breached, regardless of what a naive elapsed-time check alone would suggest.
  • FR-011: A paused SLA run MUST NOT be marked breached while paused.
  • FR-012: The system MUST let an admin create an escalation rule — trigger type, condition, target hierarchy node, and who to notify — scoped to a policy, matching doc 06's EscalationRule shape.
  • FR-013: An SLA breach (FR-009) MUST be evaluated against every active escalation rule configured for resolution_breach (and, where applicable, the other SLA-derived trigger types this feature computes — see Assumptions); every matching rule MUST fire its own escalation event — never just the first match.
  • FR-014: Firing an escalation rule MUST record a durable EscalationEvent (rule, reason, triggeredBy, timestamp) and MUST re-run 007's assignment engine scoped specifically to the rule's targetNodeId — never a fresh, unscoped resolution that could land elsewhere.
  • FR-015: A breach with no matching escalation rule MUST still be recorded as breached (FR-009) — the absence of a rule never suppresses breach detection itself.
  • FR-016: The system MUST let an admin or agent manually escalate a ticket to a specific, existing target node with a reason, recorded through the same EscalationEvent mechanism as an automatic escalation, with triggeredBy set to the calling actor.
  • FR-017: A manual escalation targeting a nonexistent hierarchy node MUST be rejected.

Key Entities

  • SLA Policy: A configured set of time limits (first response/investigation/resolution/ customer response) scoped to product/category/problem type/priority, referencing a business calendar — never a hardcoded threshold.
  • SLA Run: The durable, per-ticket tracking of one policy's due dates and status against a real ticket, survivable across a process restart.
  • Business Calendar / Holiday: Working hours and excluded dates a due-date calculation respects — the mechanism that keeps SLA math honest.
  • Escalation Policy / Rule: Configured trigger conditions and target hierarchy nodes — never a hardcoded escalation ladder.
  • Escalation Event: The durable, audited record of every escalation, automatic or manual, including which rule (if any) fired it and who/what triggered it.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: 100% of due-date calculations under a policy with a configured calendar exclude non-working time — verified against a calendar with at least one full excluded day.
  • SC-002: 100% of SLA runs correctly reflect pause/resume across a simulated process restart — the paused duration is never double-counted and never dropped.
  • SC-003: 100% of SLA runs whose due date has passed are marked breached within one breach-detection job cycle, even when the detecting process is a different invocation than the one that started the run.
  • SC-004: 100% of SLA breaches with a matching active escalation rule produce both an EscalationEvent and a re-assignment scoped to the rule's target node.
  • SC-005: 100% of manual escalations targeting a nonexistent node are rejected, never producing a dangling event.

Assumptions

  • This feature does not build a notification-delivery mechanism — an escalation rule's notify field (doc 06) is stored and returned as configured, but this feature does not send an email/Slack/webhook notification; platform/notifications (doc 07) remains an untouched module group, same convention as every prior feature leaving an adjacent, not-yet-built module alone.
  • Only resolution_breach and first_response_breach are wired to a real trigger signal in this feature — doc 05 §6 lists ten trigger types; the other eight (inactivity, priority increase, customer escalation request, repeated reopen, manual [built as its own user story, User Story 6, not a rule trigger], product defect, dependency timeout, critical incident) require signals this codebase doesn't compute yet (idle-time tracking, reopen counting, explicit defect/incident flagging) — EscalationRule.triggerType accepts any of doc 06's values as configuration data, but only the two SLA-breach types are ever actually evaluated by this feature. This mirrors 006's assignmentStrategy/slaPolicyId fields being stored as real data before this feature gave them a real consumer.
  • Investigation SLA and customer-response SLA are stored as policy fields (FR-001) but this feature only computes/tracks the resolution and first-response due dates on SLARun — doc 06's SLARun itself only models firstResponseDueAt/resolutionDueAt explicitly; investigation and customer-response timers would need their own due-date fields doc 06 doesn't define, which is a refinement left for whichever future phase actually needs to enforce them (matching this system's "refine the conceptual schema when a feature needs the refinement, not speculatively" convention).
  • SLA run creation happens once, on a ticket's first successful assignment — reopening, multiple resolution cycles, and re-running an SLA clock for a reassigned-after-resolution ticket are out of scope (Edge Cases) — that's future problem-resolution-phase territory.
  • Breach detection runs on a durable, periodically-scheduled BullMQ job (a repeatable job, not a per-run delayed job scheduled at creation time) — checking every active running run's due date against the current time on each tick, rather than scheduling one delayed job per SLA run. This is a deliberate simplicity/robustness tradeoff, not an aspiration to replace later — see research.md for the full reasoning.