SLA tracking

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.

On track Paused Breached
Two clocks

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.

Defaults
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.

Lifecycle

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.

Ticket created

Both clocks start. Due-ats stamped from the resolved policy (project override > org default). Auto-assignment fires per priority.

Status → awaiting_input / on_hold

Both clocks pause. Vendor wait counted into sla_vendor_wait_seconds, internal hold into sla_internal_hold_seconds.

Status leaves blocker state

Clocks resume. Due-ats shift forward by paused duration.

80% of clock elapsed

Fires fanoutSlaWarning — pre-breach warning gives the assignee time to act. Independent matrix event from the breach itself.

First non-system non-submitter comment

Response clock closes. sla_first_response_at stamped.

Ticket resolved

Resolve clock closes. Existing resolved_at reused — no schema churn.

Due passes (5-min scheduler tick)

Breached flag flips, breach timestamp stamped, escalation chain fires step-by-step on delay_minutes, in-app + immediate email fans out (bypasses digest).

Breach fanout

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.

MTD dashboard card

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.

MTD response breaches

Tickets whose response clock blew this month.

MTD resolve breaches

Tickets whose resolve clock blew this month.

Currently breached

Open tickets still over. Clickable → filtered ticket list.

Open with SLA clock

Active clocks, useful as a denominator.

Business hours

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.

Pre-breach warnings

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.

Escalation chains

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.

Auto-assignment

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.

Strategies
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.

Vendor vs internal

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.

Time-in-status

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.

Roadmap

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

Next

Webhook out on breach for Slack / Teams / PagerDuty. Today only the built-in fanout + escalation chain fires.

Holiday calendars

Next

Business-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

Next

Override 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.