Two clocks. Fair pauses. Loud breaches.
Response + resolve targets per priority, with per-project overrides. Pre-breach warnings at 80%. Clocks pause on blocker statuses and count only business hours. Escalation chains fire step-by-step on breach. Auto-assignment picks an agent at create time so the clock starts on the right person.
Response. Resolve.
Every ticket carries a response clock (closes when someone other than the submitter posts a non-system comment) and a resolve clock (closes when the ticket reaches a resolved state). Both stamp due-ats at create from the policy resolved for that priority.
Org defaults live in sla_policies keyed on priority. Project overrides key on priority + project — a project override always beats the org default.
| Priority | Response | Resolve |
|---|---|---|
| P1 | 30 min | 4 hrs |
| P2 | 1 hr | 8 hrs |
| P3 | 4 hrs | 24 hrs |
| P4 | 8 hrs | 72 hrs |
| P5 | 1 day | 7 days |
Tune in Admin → SLA policies.
Pause when the blame shifts.
A status carrying the awaiting_input or on_hold semantic tag pauses both clocks — vendor / customer wait time stops counting against you. Resume on transition out shifts due-ats forward by the paused duration.
Both clocks start. Due-ats stamped from the resolved policy (project override > org default). Auto-assignment fires per priority.
Both clocks pause. Vendor wait counted into sla_vendor_wait_seconds, internal hold into sla_internal_hold_seconds.
Clocks resume. Due-ats shift forward by paused duration.
Fires fanoutSlaWarning — pre-breach warning gives the assignee time to act. Independent matrix event from the breach itself.
Response clock closes. sla_first_response_at stamped.
Resolve clock closes. Existing resolved_at reused — no schema churn.
Breached flag flips, breach timestamp stamped, escalation chain fires step-by-step on delay_minutes, in-app + immediate email fans out (bypasses digest).
Loud. Bypasses digests.
A 5-minute scheduler flips the breached flag, stamps the breach timestamp, and fans out via fanoutSlaBreach — in-app notification + immediate email to assignee, followers, and submitter (deduped). Breaches are action-required, so the email always sends instantly regardless of digest cadence.
Month-to-date at a glance.
Dashboard card with four stat tiles plus a per-project breakdown table when breaches exist. Admin / Manager see all projects; Submitter / Viewer see only their member projects. All-clear state collapses to one line.
Tickets whose response clock blew this month.
Tickets whose resolve clock blew this month.
Open tickets still over. Clickable → filtered ticket list.
Active clocks, useful as a denominator.
Clocks count working hours only.
business_hours_policies carries a timezone, day mask, and start/end times — per-project or org default. Plugs into the SLA clock via addBusinessMinutes().
Friday 4pm + 4hr response target now lands Monday 12pm, not Saturday 8pm. Seeded Mon–Fri 9–5 Central; admin can add per-project overrides for customer-specific hours.
Soft nudge at 80%.
Every policy gets a warning_threshold_percent (default 80) that fires fanoutSlaWarning before the actual breach.
Independent matrix event from the breach itself — users can opt out of warnings without losing breach pages, or vice versa.
Step-by-step on four triggers.
Per-policy step lists fire on four triggers. Each step has a delay_minutes grace period and an action: notify user / notify role / reassign user / reassign role. Project-scoped steps run additively alongside org defaults — a per-project nudge can coexist with an org-wide "page the on-call" step. Each step fires once per ticket.
warning_response 80% of response clock — pre-breach nudge.
warning_resolve 80% of resolve clock — pre-breach nudge.
breach_response Response clock blew. Loud fanout + step chain.
breach_resolve Resolve clock blew. Loud fanout + step chain.
Priority operators (= / < / <= / > / >=) apply on the match — one step can cover P1 + P2 with <= 2 instead of duplicating rows.
Pick an agent at create time.
Per-priority assignment policies pick an agent the moment a ticket lands so the clock starts on a named human, not an empty assignee. Eligible agents come from a per-project Agent flag on project_members — a Tech can be an Agent on Project A but not Project B without a role change.
Priority operators apply here too — one row <= 2 → round_robin covers the high-touch tier.
round_robin Cycle through the pool in order. Cursor advances atomically via UPDATE…RETURNING so concurrent inserts don't double-pick.
case_load Pick whoever has fewest open tickets right now. Ties break by user_id ASC.
specific_user Always assign to a fixed user.
Are we slow, or is our vendor slow?
The awaiting_input and on_hold semantic tags split into two counters — sla_vendor_wait_seconds (waiting on external party) and sla_internal_hold_seconds (we blocked ourselves).
The dashboard SLA card renders a stacked bar across both. Answers the question without parsing audit logs.
Per-status durations, window-pickable.
/api/sla/time-in-status aggregates per-status durations from the audit log using LEAD window functions. Surfaced on the dashboard with a 7 / 30 / 90 day window picker — find the status that always eats your clock.
What's next.
v0.6.0 shipped clocks + pauses + breach fanout + dashboard. v0.7.0 added pre-breach warnings, business hours, escalation chains, auto-assignment, and vendor-vs-internal reporting. Queue below.
Custom breach destinations
NextWebhook out on breach for Slack / Teams / PagerDuty. Today only the built-in fanout + escalation chain fires.
Holiday calendars
NextBusiness-hours policies skip weekends today. Per-region holiday lists layer on top so federal / regional holidays don't burn SLA clock.
Per-customer SLA contracts
NextOverride a policy by company (not just project) so MSP customer-tier SLAs map directly to the contract that pays for them.
Tracked in ROADMAP.md. Open an issue if you want a specific beat prioritized.
Tune your SLA policies.
Admin → SLA policies. Pick org defaults, layer project-specific overrides, watch the dashboard card light up at month-end.