InfraGuard

Problem Resolution (Incident Cases)

An incident case workflow that tracks outages and errors through register -> investigating -> cause_identified -> resolved -> closed, recording direct/root cause, fix, and preventive action.

Overview — what it does

Problem Resolution is the incident case workflow for the Service role area. Register any outage, error, or degradation as a single case and drive it through the status pipeline register → investigating → cause identified → resolved → closed, capturing investigation, cause, and action at each step. It doubles as your post-mortem and preventive-action ledger.

All data lives in one table, infr_incident, and this screen handles only rows where area='service'. The Security Problem Resolution screen shares the same table with area='security' — identical schema, separate board.

Each case carries four cause/action fields modeled on a formal post-mortem report:

Field Meaning Example
direct_cause The surface trigger of the outage mysql connection limit exceeded (Too many connections)
root_cause The underlying reason that produced the surface cause a 5-minute cron script never returned connections → leak → limit exhausted
resolution Action that restored service during the incident cleared connections, restarted process, raised the limit for immediate recovery
action_taken Permanent fix to prevent recurrence isolated dedicated account with MAX_USER_CONNECTIONS, patched the script, added a monitoring alert

This screen was renamed from the former "SLA & Uptime" menu (namespace sla → resolution). The uptime-target KPI (uptime_target) is retained.


When to use


Layout

KPI strip

The top KPIs are computed across all area='service' cases.

KPI How it is computed
Total (total) Count of service cases
Active (active) Sum of open + investigating + cause_identified
Resolved (resolved) Sum of resolved + closed
MTTR (mttr_min) Average of (resolved − occurred) in minutes, only for cases that have BOTH occurred_at and resolved_at
Cumulative downtime (downtime_min) Plain sum of each case's downtime_min field (minutes)
Uptime target (uptime_target) Operations Policy sla_uptime_target (default 99.9%)

To get accurate MTTR, enter an occurred-at time (occurred_at) on the case and let the transition to Resolved stamp resolved_at automatically. A case with no occurred-at is excluded from the MTTR average.

Case board

Cases are listed as cards/rows, sorted server-side (PHP, to avoid raw SQL in the controller) by this priority:

  1. Status rank: open → investigating → cause_identified → resolved → closed
  2. Severity rank: crit → warn → info
  3. Newest id first

So active, severe, recent cases float to the top.

Each card shows the source badge (manual / Pulse / alert / agency / quarantine / detection-promoted), severity, status, project name, occurred time, downtime, and event count.

Detail modal

Opening a case shows the Report on the left (summary · direct cause · root cause · resolution · preventive action) and the Timeline on the right (status changes · notes), with pipeline transition buttons at the top.


How to use

1) Register a case

[Register case] → modal form. On create the case is stored with source='manual', area='service', status='open'; detected_at is stamped server-side (now()) and a created event is appended to the timeline.

Fields and validation limits:

Field Required Limit
title ✔ max 200 chars
severity info/warn/crit — empty defaults to warn (Operations Policy incident_default_severity)
project_id must exist in infr_project
service_id must exist in infr_service
url max 255 chars
occurred_at date
summary max 4000 chars
direct_cause / root_cause / resolution / action_taken max 4000 chars each
downtime_min integer, ≥ 0
impact max 191 chars

2) Status transition (pipeline)

In the detail modal, clicking a next-step button opens a step transition dialog. Each step explains "what to do in this step" and lets you fill in the report fields relevant to that step. Only the fields you send are updated, so you can enrich causes without changing status, or beef up the report only.

Step Code What to do in this step
Register open Return the case to intake (before investigation)
Investigating investigating Confirm symptoms, scope the impact, write the summary
Cause identified cause_identified Determine and record the direct cause and its root (background) cause
Resolved resolved Record the recovery method applied and the preventive action
Closed closed Verify the fix and prevention are in place, then close the case

Transition rules:

Dates render via toLocaleString bound to the active UI locale (window.__locale), so month/time display correctly even in EN mode.

3) Collaboration notes

Adding a note in the detail modal appends a kind='note' event to the timeline (notes are consolidated into infr_incident_event — there is no separate note table). Body max 4000 chars.

4) Delete

Deleting a case removes the incident and all its linked infr_incident_event rows together. This cannot be undone.

5) Auto-intake (Pulse / exception reporter)

When an external Laravel app pushes its exceptions to the incident ingestion API (POST /api/v1/incidents), IncidentIngestService handles them and auto-enters them on this board:

Keys and integration code are managed in Administration ▸ API Keys.


Data sources

Table Role
infr_incident The case itself. This screen queries only area='service'. Migration 000719 (+ 000723 ingest fields · 000731 area · 000732 source expansion)
infr_incident_event Timeline (append-only). kind = created/note/action/status_change/cause/resolution/alert_added. Uses created_at only (migration 000720)
infr_project Project dropdown · card project name
infr_service Service dropdown
infr_audit_log Audit trail for create/update/transition/delete

Key infr_incident columns: source, area, title, severity, status, project_id, service_id, url, occurred_at, detected_at, resolved_at, closed_at, summary, direct_cause, root_cause, resolution, action_taken, downtime_min, impact, fingerprint, occurrence_count, last_occurred_at, api_key_id, created_by, updated_by.

Defaults come from Operations Policy OpsPolicy::slaDefaults():

sla_uptime_target         = 99.9   # SLA target uptime (%)
incident_default_severity = warn   # default severity on register (info|warn|crit)

Routes:

GET  /service/resolution              index (board)
GET  /service/resolution/live         live  (JSON polling refresh)
POST /service/resolution/save         register / update a case
POST /service/resolution/transition   status transition (+ per-step report / closed_at)
POST /service/resolution/note         add a collaboration note
POST /service/resolution/delete       delete a case

Access

Action Required level
View board (index/live) Authenticated user (infr.auth, level ≥ 1)
Register / update / transition / note / delete level ≥ 2

Every write (register/update/transition/delete) is recorded in infr_audit_log as incident.create / incident.update / incident.transition / incident.delete, capturing the actor and before/after status. See RBAC levels for the level model.


Troubleshooting & FAQ

Q. MTTR shows blank / no value. A case must have BOTH an occurred-at time (occurred_at) and a resolved-at time (resolved_at) to be included in the MTTR average. Confirm you entered occurred-at and that transitioning to Resolved stamped resolved_at.

Q. Cumulative downtime shows 0. The downtime KPI is a plain sum of each case's downtime_min field. You must enter downtime (minutes) on the case; it is not auto-derived from occurred/resolved times.

Q. A Pulse case is not showing up. Ingested cases have source='pulse' and leave area unset, so they default to service and appear here. If one is still missing, check whether (1) the API key is valid, and (2) the same fingerprint was deduped into an already-open case (only occurrence_count incremented). Security incidents appear on Security Problem Resolution (area='security'), not this board.

Q. Can I set the closed time in the past? Yes. Enter the desired time in the closed-at (flatpickr) field of the closed transition dialog and that value is saved. Leave it blank to use the server's current time (now()).

Q. I want to enrich causes without changing status. The transition dialog updates only the fields you send. Transitioning to the same status while filling report fields updates the report without a status-change event (or edit the case directly).

Q. Can a deleted case be restored? No. Delete removes the case and all its timeline events and cannot be undone. The delete action itself is recorded in the audit log.


Related

Problem resolution — the incident board
Problem resolution — the incident board