Populates platform/business-calendars, orchestration/sla, and orchestration/escalation (all thin stubs until now) with the real engine: - business-calendars: a luxon-based day-by-day calendar walk (addBusinessMinutes/isWithinWorkingHours) excluding non-working hours, weekends, and holidays — replacing the naive createdAt+hours stub FR-004 explicitly forbids. - sla: most-specific SLAPolicy resolution (product/category/problemType/ priority, wildcard-or-exact-match, specificity-count + updatedAt tiebreak), SLARun creation on the first real publish of the long-unused TICKET_ASSIGNED domain event, durable pause/resume via an absolute-timestamp shift (no in-memory state, verified across a real buildApp() restart), and a repeatable BullMQ breach-detection sweep (src/jobs/sla, itself a previously-unregistered stub) that is directly callable for tests, not only reachable through a running worker. - escalation: EscalationPolicy/Rule CRUD (all 10 doc05 trigger types storable, only resolution_breach/first_response_breach evaluated), breach-triggered and manual escalation both funnel through one EscalationEvent + scoped re-assignment path. AssignmentEngine (007) gains assignToSpecificNode — a new, explicitly node-scoped entry point, since escalation must never let 007's general resolution re-derive a different node than the one a rule or a caller targeted. Two small pre-existing scaffold gaps were closed along the way: CategoriesRepository had no findById, and TICKET_ASSIGNED/SLA_BREACHED/ ESCALATION_TRIGGERED were defined since earlier phases but never published by any code. Verified against throwaway Docker Postgres/Redis (typecheck, lint, architecture-check all clean; 148/150 relevant tests pass — the 2 failures are pre-existing, MinIO-dependent, and unrelated to this feature). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.2 KiB
5.2 KiB
Specification Quality Checklist: SLA and Escalation
Purpose: Validate specification completeness and quality before proceeding to planning Created: 2026-09-03 Feature: spec.md
Content Quality
- No implementation details (languages, frameworks, APIs)
- Focused on user value and business needs
- Written for non-technical stakeholders
- All mandatory sections completed
Requirement Completeness
- No [NEEDS CLARIFICATION] markers remain
- Requirements are testable and unambiguous
- Success criteria are measurable
- Success criteria are technology-agnostic (no implementation details)
- All acceptance scenarios are defined
- Edge cases are identified
- Scope is clearly bounded
- Dependencies and assumptions identified
Feature Readiness
- All functional requirements have clear acceptance criteria
- User scenarios cover primary flows
- Feature meets measurable outcomes defined in Success Criteria
- No implementation details leak into specification
Notes
- Scope is Phase 8 per
docs/10-implementation-roadmap.md: the SLA policy engine, business calendar/holiday support, durable pause/resume, and the rule-driven escalation engine — explicitly bounded to the two SLA-derived trigger types (resolution_breach/first_response_breach) this feature can compute a real signal for, out of doc 05 §6's ten; the other eight remain valid, storable rule configuration with no event source feeding them yet, same convention 006 already established forHierarchyNode.assignmentStrategy/slaPolicyIdbeing real data before 007/008 gave them real consumers. - This is the first feature to give 006's
HierarchyNode.slaPolicyId/escalationPolicyIdfields (stored as free-text references since 006, unvalidated) a real target to resolve against. - Escalation's re-assignment path reuses 007's
AssignmentEnginescoped to a specific target node, not 007's general unscoped resolution — a genuinely different call shape 007 doesn't expose yet, to be added during planning. - Constitution Principle VII is directly load-bearing here in a new way: this is the first feature whose entire second half (SLA pause/resume/breach) is only correct if it survives a process restart — 003's ticket-status concurrency and 007's round-robin concurrency both guarded against corruption under concurrent requests within a running process; this guards against silent loss of state across the process not running at all for a while.
- All items pass; no revision iterations were needed.
Implementation Notes (added during /speckit-implement)
DomainEventName.TICKET_ASSIGNED(defined since 007-orchestration-assignment) andSLA_BREACHED/ESCALATION_TRIGGERED(defined even earlier) had never been published by any code until this feature —AssignmentEngine.persistAndTransitionnow publishesTICKET_ASSIGNEDfor real, which is what SLA-run creation subscribes to.src/jobs/sla/index.tsandsrc/jobs/escalation/index.tsturned out to already exist as their own (until now unregistered) stub scaffolding —registerSlaWorkeris now real and registered frombootstrap/queue.bootstrap.ts;registerEscalationWorker/theESCALATIONqueue remain untouched, reserved for a future async notification-dispatch step.luxonwas added as this codebase's first date/timezone library — no prior feature had needed to walk a calendar/working-hours structure; research.md documents the choice overdate-fnsand hand-rolled arithmetic.- Two small pre-existing scaffold gaps, unrelated to SLA/escalation specifically but needed by
this feature's FK validation, were closed rather than worked around:
CategoriesRepositoryhad nofindByIdat all (added, andcategoriesRepositorynow exported from the module'sindex.ts, matching every other catalog repository). EscalationEvent.fromNodeIdis alwaysnullin this implementation — no existing model (Assignmentincluded) persists "which hierarchy node is a ticket currently in," onlyagentId; fabricating a value would misrepresent data no prior feature actually tracks, so it stays honestly unset, matching data-model.md's own "if any" phrasing.README.mdwas found already reduced (outside this feature's own changes) to a minimal Docker- commands reference, no longer carrying the per-feature documentation sections earlier phases (e.g. 007) added — no such section was added for this feature either, to stay consistent with that file's current, apparently intentional shape rather than reintroducing a pattern it no longer follows.- Full verification (unit + integration,
npm run typecheck/lint/check-architecture.ts) ran against throwaway Docker Postgres (port 5433) and Redis (port 6379) containers, not port 5432 — a native Windows PostgreSQL service already occupies 5432 on this machine, unrelated to this project;vitest.config.ts's hardcodedDATABASE_URLwas updated from 5432 to 5433 to match. 148 of 150 relevant tests pass; the only 2 failures (ticket-attachments.test.ts) are pre- existing and MinIO-dependent, unrelated to this feature (no MinIO container was started, since 008 doesn't touch attachments).