Phase 0 research resolves the business-calendar working-hours algorithm (day-by-day walk via luxon, the first date/timezone dependency in this codebase), the workingHours JSON shape, most-specific SLA-policy match (reusing 005/006's resolution pattern), durable pause/resume (absolute due-date shift, no in-memory state), and a repeatable-job breach-detection design over per-run delayed jobs. Phase 1 adds data-model.md (one additive refinement beyond doc06: SLARun.firstResponseBreachedAt), the admin/read contract, and six quickstart scenarios including a genuine process-restart boundary test for Constitution Principle VII. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.7 KiB
4.7 KiB
Quickstart: Validating SLA and Escalation
Prerequisites: migrations applied; at least one hierarchy node/agent/product set up per
006-support-organization's and 007-orchestration-assignment's own quickstarts, since this feature
starts an SLARun on a successful 007 assignment and escalation re-assigns through 007's engine.
Scenario 1 — policy definition and most-specific match (User Story 1)
- Create a global
SLAPolicy(productId: null, ...) and a second, product-scoped policy for the same product with tighter minutes. - Assign a ticket for that product (triggers Scenario 2's creation path).
- Expected: the
SLARunresolves the product-scoped policy, not the global one. - Delete the product-scoped policy's applicability (set
active: false). Assign a new ticket for the same product. Expected: falls back to the global policy.
Scenario 2 — calendar-aware due dates on assignment (User Story 2)
- Create a
BusinessCalendarwithworkingHoursonly Mon-Fri 09:00-17:00,timezone"America/New_York", and oneHolidaynext Monday. Attach it to anSLAPolicywithresolutionMinutes: 480(one working day). - Assign a ticket late on a Friday afternoon so that a naive
createdAt + 480minwould land on Saturday. - Expected:
resolutionDueAtlands the following Tuesday (Monday excluded as a holiday), never on the weekend. - Assign a ticket for a product/category/priority combination matching no active policy.
Expected: no
SLARunis created;GET /tickets/:ticketId/sla-runreturns404.
Scenario 3 — durable pause/resume across a process restart (User Story 3)
- Assign a ticket (Scenario 2), note
resolutionDueAt. - Transition the ticket to
WAITING_FOR_CUSTOMER. Expected:SLARun.statusbecomespaused,pausedAtset. - Restart the application process (rebuild
buildApp()fresh, simulating the restart the constitution's Principle VII requires surviving). - Wait a real interval, then transition the ticket out of
WAITING_FOR_CUSTOMER. Expected:SLARun.statusbecomesrunning; the newresolutionDueAtequals the original plus exactly the paused wall-clock duration — never reset to a fresh full duration.
Scenario 4 — durable breach detection (User Story 4)
- Assign a ticket against a policy with a very short
resolutionMinutes(e.g.1) and a 24/7 calendar (businessCalendarId: null). - Wait past
resolutionDueAtwithout resolving the ticket. - Expected: within one breach-detection job tick,
SLARun.statusbecomesbreached,breachedAtset. - Repeat, but resolve the ticket before
resolutionDueAtpasses. Expected:statusreachescompletedand is never later flipped tobreachedby a subsequent job tick. - Repeat, but pause the run before
resolutionDueAtpasses. Expected: the run is never markedbreachedwhile paused, even after the due instant passes.
Scenario 5 — breach-triggered escalation and scoped re-assignment (User Story 5)
- Create an
EscalationPolicyscoped to the ticket's product with an activeEscalationRule(triggerType: "resolution_breach",targetNodeIdset to a second hierarchy node with a different eligible agent). - Reach a
breachedrun (Scenario 4). Expected: exactly oneEscalationEventis created (ruleIdset,toNodeIdthe rule'stargetNodeId), and the ticket is reassigned to an agent eligible under that specific node — not re-resolved from the ticket's original context. - Repeat with no matching
EscalationRulefor the resolved policy. Expected: the run is still markedbreached; noEscalationEventis created.
Scenario 6 — manual escalation (User Story 6)
POST /tickets/:ticketId/escalatewith a validtargetNodeIdand a reason.- Expected: an
EscalationEventis created (ruleId: null,triggeredBythe calling actor), and the ticket is reassigned through the same scoped path as Scenario 5. - Repeat with a nonexistent
targetNodeId. Expected:404, noEscalationEventcreated. - Trigger a manual escalation on a ticket whose run is concurrently being auto-escalated by
Scenario 5's breach path. Expected: both
EscalationEventrows are recorded; the final assignment reflects 007's already-tested concurrency handling, not a corrupted double-write.
What "done" looks like
All six scenarios pass, and together they demonstrate every functional requirement and success
criterion in spec.md — including SC-002's explicit restart-survival requirement, which must be
verified by an actual fresh buildApp() in the middle of the test, not merely by asserting on
stored field values without ever exercising a real process boundary.