Files
support_backend/src/modules/orchestration/escalation/routes/escalation.routes.ts
T
saqib mirandClaude Sonnet 5 9357f03e1d feat: implement SLA and escalation (008)
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>
2026-09-03 13:02:05 +05:30

38 lines
1.3 KiB
TypeScript

import { FastifyInstance } from 'fastify';
import { escalationController } from '../controller';
/** contracts/sla-escalation-contract.md: every route gated by fastify.authenticate (known
* limitation inherited from 002-007). */
export async function escalationRoutes(fastify: FastifyInstance): Promise<void> {
fastify.post(
'/admin/escalation-policies',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.createPolicy(req, reply),
);
fastify.get(
'/admin/escalation-policies',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.listPolicies(req, reply),
);
fastify.post(
'/admin/escalation-policies/:id/rules',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.createRule(req, reply),
);
fastify.patch(
'/admin/escalation-policies/:id/rules/:ruleId',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.updateRule(req, reply),
);
fastify.delete(
'/admin/escalation-policies/:id/rules/:ruleId',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.deleteRule(req, reply),
);
fastify.post(
'/tickets/:ticketId/escalate',
{ preHandler: fastify.authenticate },
(req, reply) => escalationController.escalateManually(req, reply),
);
}