Skip to content

SLA policy & escalation

CondoPulse enforces one promise: reports get acknowledged on a clock. Each severity has a time-to-acknowledge target, and a periodic sweep escalates anything that blows past it - automatically, visibly, and on the record.

The SLA measures time to acknowledge: from the incident’s creation to its first transition past open (which stamps acknowledgedAt). It is not a time-to-fix guarantee - it’s the “someone has seen this” guarantee.

SeverityTargetEnv override
critical4 hoursSLA_HOURS_CRITICAL
high24 hoursSLA_HOURS_HIGH
medium72 hoursSLA_HOURS_MEDIUM
low168 hours (1 week)SLA_HOURS_LOW

Override any target by setting the env variable to a number of hours, e.g. SLA_HOURS_HIGH=12. Defaults apply when unset.

An incident past its target is breached: it shows an SLA breached badge in lists and on its detail page, sorts to the top of the /admin → Triage queue, and carries slaBreached: true in API payloads.

The badge appears as soon as the deadline passes; the sweep is the enforcement job that acts on breaches. Each run finds every incident that is still open past its acknowledgement target and, for each one:

  1. Bumps severity one level - medium → high, high → critical (max critical). This happens once per incident, ever: the bump is stamped (slaBumpedAt), so repeated sweep runs never re-escalate the same incident.
  2. Notifies people - email to board, manager, and admin users (via SMTP_URL; written to .data/outbox/*.eml.html when SMTP isn’t configured), and push notifications to the incident’s followers, including the reporter.
  3. Fires webhooks - the incident.sla_breached event goes to every active webhook (see Webhooks & n8n).
  4. Audits - an audit-log entry is recorded with the system actor.

Note the bump applies to open incidents - acknowledging on time is the whole game. Once an incident is acknowledged, the sweep leaves it alone.

Two equivalent entry points:

  • HTTP: POST /api/cron/sweep with Authorization: Bearer ${CRON_SECRET}
    • for any external scheduler hitting the running server.
  • CLI: npm run sweep from apps/web - connects to the database directly; the web server doesn’t need to be running.

Hourly is the sensible cadence (the shortest target is 4 hours):

# crontab -e - SLA sweep, hourly on the hour
0 * * * * curl -s -X POST -H "Authorization: Bearer dev-cron-secret" http://localhost:3000/api/cron/sweep

Replace dev-cron-secret with your real CRON_SECRET and the URL with your deployment’s base URL.

  1. Schedule Trigger node - Every Hour.
  2. HTTP Request node - Method POST, URL https://your-condopulse.example/api/cron/sweep, header Authorization: Bearer <CRON_SECRET>.

The endpoint responds when the run completes, so you can chain n8n nodes off the response (e.g. post to the board’s chat when a sweep escalates something).

Wrong secret → 401. With the seed data loaded there’s an open high-severity incident already past its 24-hour target: run the sweep and you should see it bump to critical, an email land in .data/outbox/, and a system-actor entry appear in /admin → Audit.

The sweep escalates; humans acknowledge. The working loop:

  1. Watch the Triage tab - /admin → Triage lists SLA-breached (and untriaged) incidents first with inline acknowledge controls.
  2. Acknowledge fast, even without a fix. Acknowledging stops the SLA clock and tells the reporter someone has seen it. “Acknowledged - vendor call booked Thursday” beats silence in every way the system measures.
  3. Treat the bump as real. A breached-medium is now high on purpose - it sat unseen for three days.
  4. Review breach counts monthly. The board report (/report?month=YYYY-MM) includes the month’s SLA breach count and list; recurring breaches are a staffing or process signal, not a software one.
  • The defaults (4/24/72/168) suit a building with a responsive manager. Tighten SLA_HOURS_HIGH if elevators dominate your incident mix; loosen SLA_HOURS_LOW if weeds-grade reports are drowning the queue.
  • Targets are read at sweep time, so changing an env variable affects the next run - no migration needed.
  • Don’t disable the sweep to avoid embarrassment. The badge still shows (it’s computed from timestamps, not from the sweep); the sweep is what turns the badge into notifications and escalation.