Skip to main content

Incidents

Status: incidents are hidden from the app for now — they are off the sidebar and the command palette, and the Create incident alert action isn't offered for new rules. Existing incidents stay reachable by direct link, and the API below is unchanged. When incidents return to the navigation this page will say so.

A data-quality failure is an incident. Without incidents, Measured Cloud only fires outbound alerts when a run fails — the triage (who is on it, what the status is, when it was resolved) happened in some other tool. An incident is the first-class record that keeps that lifecycle inside the product.

Two model rows back it: an Incident is the triage record (status, severity, assignee, what it is about, notes); an IncidentEvent is one entry on its append-only timeline (a manual comment, or an auto-logged status change).

Every incident is about something. An incident opened by an alert is linked to that alert, and through it to the one execution run that fired it. An incident you open by hand is linked to a data asset or a rule. An incident with none of these can't be created.

Lifecycle​

open ───▶ acknowledged ───▶ resolved
  • open — the default on creation. An auto-opened incident starts here.
  • acknowledged — someone has picked it up. The transition stamps acknowledged_at.
  • resolved — done. The transition stamps resolved_at and is where you record a resolution note.

Every transition appends an IncidentEvent of kind status_change carrying {from, to}, so the timeline is a complete audit of how the incident moved.

Data model​

Incident (per-tenant):

ColumnWhat it stores
titleShort human label.
severityinfo / warning / critical — the same three-level ladder alerts use (#154 folded the old error tier up into critical).
statusopen / acknowledged / resolved.
assignee_user_id / assignee_emailWho owns it. assignee_email is denormalised so the list renders without a cross-schema join.
alert_idThe fired alert that opened it: one alert per (alert rule, execution run), so this names the run too. NULL for a manual incident.
data_asset_id / rule_idThe data asset or rule a manual incident is about. The database requires at least one of alert_id, data_asset_id and rule_id when an incident is created (ck_incidents_anchored). Deleting the asset or rule later clears the link but keeps the incident.
team_id / team_restrictedThe incident's own team: its run's team, or its asset's or rule's for a manual incident. As everywhere else, the team is a label unless team_restricted is on, in which case only that team sees the incident.
validation_result_idThe failed run that triggered it (NULL for a manual incident with no linked run; SET NULL on result delete so history survives).
rule_group_idThe validation whose run failed (the wire name for a validation). Used to dedupe: one open incident per validation. An asset's or a rule's Run is a run of the asset's default validation, so its incident links that validation.
checkpoint_idLegacy: the retired checkpoint whose run failed, on incidents opened before checkpoints were retired (and on a leftover checkpoint run that maps to no validation). Otherwise null.
root_cause / resolution_noteFree-text investigation + resolution notes.
sourcemanual (an operator escalated a failure), auto (opened by the workspace's default incident rule when a run failed), or alert (opened by any other alert rule whose action is Create incident — see #656).
created_at / acknowledged_at / resolved_atLifecycle timestamps.

IncidentEvent (per-tenant, append-only):

ColumnWhat it stores
kindcreated / status_change / comment / assigned.
user_id / user_emailThe author (NULL for system / auto entries). For a manual comment this is taken from the authenticated principal — never the request body — so one user can't post as another.
bodyThe comment text (NULL for a status-change entry).
metadata{from, to} for a status-change entry.

No column on either table is a secret, so — unlike an alert destination — nothing is encrypted at rest, and the API returns the rows verbatim.

Creation​

  • Manual. An operator opens an incident from the Incidents page: a title, a severity, the data asset it is about (and optionally one of that asset's rules), and an optional root cause. source = "manual". The incident takes the asset's or rule's team. Over the API, data_asset_id or rule_id is required (422 without one), and you must be able to see it (404 otherwise).
  • The default incident rule. Every workspace has a built-in alert rule that fires on every failed validation run, at any severity, and opens an incident (source = "auto", severity graded from the run). This covers runs of an asset or a single rule too, and direct-mode and agent-mode runs alike. Pause the rule on the Alerts page to stop these incidents. A passing run never opens one.
  • Alert action. Any other alert rule whose channel is Create incident opens an incident when it fires (source = "alert"), respecting the rule's validation scope, trigger, and severities. See Alerts → Create incident.

Both alert paths open the incident with the run's workspace and team and link it to the alert. They share one dedup: at most one open (or acknowledged) incident exists per validation, so a validation that keeps failing doesn't spawn duplicates. A repeat failure appends a timeline entry to the open incident instead.

Isolation​

Incident carries workspace_id and team_id and is enforced by the workspace- and team-isolation listener and by the database's row-level security policies, so a member of one workspace cannot read another workspace's incidents, and a team-restricted incident is visible only to its team. IncidentEvent has no workspace_id — it is reached only through its parent incident (the same shape as a result comment) and is org-isolated via the tenant schema's RLS.

Permissions​

ActionPermissionRoles
List / view / read timelinecan_view_incidentviewer, editor, admin, owner
Post a timeline commentcan_comment_incidentviewer, editor, admin, owner
Create / triage / resolve / deletecan_manage_incidenteditor, admin, owner

A viewer sees incidents and can add a timeline comment, but the triage controls (create / acknowledge / resolve / delete, plus the resolution-note field) are shown disabled, with a tooltip naming the role that can use them — mirroring the server-side gate, so a viewer can see what's possible but can't trigger it. The Comment button stays enabled for a viewer (it gates on can_comment_incident, which viewers hold). Commenting is a deliberately lower bar than triage, so a read-only stakeholder can leave context without being able to change an incident's state.

  • Alerts — the outbound notification an incident complements (an incident is the in-app triage record; an alert is the push to Slack/PagerDuty/etc.).
  • Validation results — the failed run an incident links to.
  • How-to: Manage incidents.