Incidents
Incidents with a severity, a status timeline, affected components and postmortems, and a public status page that anyone can read through one function that returns only public fields.
incidents is for running an outage and telling customers about it. You open
an incident with a severity and the components it affects, post timeline
updates as it moves from investigating to resolved, and write a postmortem
afterwards. Each organization can publish one status page: the public
components with their status and daily uptime, and the public incident
updates. Visitors read it without signing in.
better-supabase add incidentsimport { createIncidents } from "better-supabase/blocks/incidents";The module requires tenant and access. With
notifications the commander is notified when an
incident opens, with outbox it records the events, and with audit every
change is audited. The choices behind the anonymous surface are in
ADR 0084.
Tables
| Table | Holds |
|---|---|
status_components | The services a status page reports on: a name, a group, whether it is public, and its current status |
status_component_history | One row per status a component held, with its start and end, for the uptime history |
incidents | The title, description, severity, status, whether it is public, the commander, and the start and end |
incident_updates | The timeline: each update's status, text and whether it is public |
incident_impacts | The components an incident affects and how |
incident_postmortems | One postmortem per incident: summary, impact, root cause, resolution and action items |
status_pages | One page per organization, with a slug unique across organizations |
Members with incidents.read read every table. anon has no privileges on
any of them.
Components
const incidents = createIncidents({ transport });
const api = await incidents
.saveComponent(organizationId, { name: "API", group: "Platform" })
.orThrow();
await incidents
.saveComponent(organizationId, { name: "Primary database", public: false })
.orThrow();A component is public unless public is false. Its status is never set by
hand: it is the worst impact of the open incidents on it (maintenance,
degraded, partial_outage, major_outage), and operational when none is
open. Every change is kept in the history the uptime is read from. Components
and the status page need incidents.manage.
Run an incident
const incident = await incidents
.open(organizationId, {
title: "Elevated API errors",
severity: "sev2",
public: true,
commanderId,
message: "We are looking into elevated error rates.",
impacts: [{ componentId: api.id, impact: "partial_outage" }],
})
.orThrow();
await incidents
.postUpdate(incident.id, {
status: "identified",
body: "A replica fell behind; failing over.",
public: false,
})
.orThrow();
await incidents
.postUpdate(incident.id, { status: "resolved", body: "Resolved." })
.orThrow();An incident starts investigating with severity sev3 unless you pick one,
and is private unless public is true. message becomes the first
timeline update. Each postUpdate adds to the timeline and moves the
incident to the update's status (investigating, identified, monitoring
or resolved); resolving sets resolvedAt and returns its components to
operational. An update is public when the incident is, unless it says
otherwise. setImpacts replaces the affected components. Opening, updating,
posting and writing the postmortem need incidents.write.
Postmortems
await incidents
.savePostmortem(incident.id, {
summary: "Replica lag caused failed writes.",
rootCause: "A replica on an undersized disk",
actionItems: [{ title: "Alert on replica lag", ownerId }],
})
.orThrow();
await incidents.publishPostmortem(incident.id).orThrow();A postmortem can be drafted at any time and is published once the incident
is resolved, with incidents.manage. The status page shows only its summary.
Status page
await incidents
.saveStatusPage(organizationId, {
slug: "acme",
title: "Acme status",
published: true,
})
.orThrow();
const page = await incidents.publicStatusPage("acme", 30).orThrow();publicStatusPage(slug, days) works with the publishable key, signed in or
not. It returns undefined unless the page is published. Otherwise it
returns the page title and description, the overall status, each public
component with its status, uptimePercent and one entry per day (1 to 90
days, default 90) with its worst status and minutes of outage, and the public
incidents that are open or resolved in the window. Each incident carries its
title, severity, status, start and end, the public components it affects, its
public updates (newest first) and the summary of a published postmortem.
Descriptions, commanders, private updates, private components, root causes,
action items and ids of organizations and members never appear.
publicStatusPage is the only function anon may call. The pgTAP test in
supabase/tests/database/incidents.test.sql checks that, and that a private
incident never shows on the page.
| Method | Returns |
|---|---|
saveComponent(organizationId, input) | the component |
deleteComponent(componentId) | its id |
components(organizationId) | the components in order |
open(organizationId, input) | the incident |
update(incidentId, changes) | the incident |
postUpdate(incidentId, input) | the update |
setImpacts(incidentId, impacts) | the incident |
get(incidentId) | the incident with its timeline and postmortem |
list(organizationId, options) | the incidents, newest first |
savePostmortem(incidentId, input) | the postmortem |
publishPostmortem(incidentId) | the published postmortem |
saveStatusPage(organizationId, input) | the page |
statusPage(organizationId) | the organization's page, or undefined |
publicStatusPage(slug, days) | the public page, or undefined |
Errors
| Hint | Cause |
|---|---|
INCIDENTS_FORBIDDEN | the caller lacks incidents.read, .write or .manage |
INCIDENTS_INVALID | a field is invalid, such as a slug that isn't 2 to 63 lowercase letters, digits and dashes |
INCIDENT_NOT_FOUND | no incident with this id in an organization the caller reads |
STATUS_COMPONENT_NOT_FOUND | no component with this id |
POSTMORTEM_NOT_FOUND | the incident has no postmortem |
INCIDENT_NOT_RESOLVED | the postmortem of an unresolved incident can't be published |
STATUS_PAGE_SLUG_TAKEN | another organization uses this slug |
Events
incident.opened, incident.updated, incident.resolved,
postmortem.published and status_component.changed. The
events page lists their payloads.
Last updated on
Controls
Compliance frameworks and controls with evidence files and notes, recurring evidence tasks with assignees and an overdue state, versioned policies that members acknowledge, and a status rollup per framework.
Chat SDK
Run Chat SDK bots on Supabase with a Postgres state adapter, an inbox adapter for the in-app widget, and helpers that record Slack, WhatsApp and SMS messages in the inbox.