Ctrl K
ComunidadGuías…

docs/operations/incident-response.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Incident response

DOMINUS Cloud runs multi-tenant production data. When something breaks, the
difference between a ten-minute and a two-hour incident is usually whether
the responder can answer three questions immediately: when did it start, what
is affected, and which request do I look at.

This runbook is the answer to "what do I do now". It is intentionally short.
A long runbook is not read during an incident.

## Before anything else: find the request

Every response carries an `X-Request-Id` header and every error body carries
`requestId` (v1.2.0). They are the same value, and every log line emitted
while that request was in flight carries it, because a pino mixin reads it
from the AsyncLocalStorage the request context created.

```bash
# Everything that happened for one reported failure
grep '"requestId":"<id-from-the-ticket>"' /var/log/dominus/*.log
```

If a customer reports "the score page is down", ask for the id in the
response, not a screenshot. When there is no id (a page that never loaded),
fall back to the time window.

## Severity ladder

| Severity | Definition | Examples | Response |
|---|---|---|---|
| **SEV-1** | Data loss, cross-tenant leak, or money wrong | A tenant sees another tenant's domains; a Stripe charge is applied twice; a backup restore fails | All hands, immediately. Freeze the deploy. No fixes under pressure — restore. |
| **SEV-2** | The service is down or unusable for some tenants | API 5xx; the worker stops dequeuing; DNS consensus is degraded for everyone | Respond now. Post visible status within 15 min. |
| **SEV-3** | Degraded but working | One provider leg failing; slow runs; single tenant cannot sign in | Respond within the SLA window. |
| **SEV-4** | Cosmetic or cosmetic-plus | Chart label, broken link, a font | Normal queue. |

The only severity worth arguing about is SEV-1, and the rule is simple:
**if a tenant's data may be wrong, exposed or unrecoverably gone, it is
SEV-1** regardless of how healthy the dashboards look. A green
`/api/health` does not mean a tenant is isolated.

## First 5 minutes

1. **Stop the bleeding, not the cause.** Roll back before you diagnose.
   The deploy pipeline migrates before it rolls (ADR-0061), so a rollback
   does not undo schema changes; if a migration is implicated, go to the
   recovery runbook in [RTO/RPO](rto-rpo.md) instead.
2. **Read `/api/health`.** It reports DNS provider health, quorum state and
   whether consensus is degraded. DNS degradation is the most common cause
   of "verdicts changed without us deploying anything".
3. **Read Grafana** (`deploy/grafana/`): API error rate, stage errors, stuck
   queue, dead letters, provider error rate. Alertmanager routes these; the
   rules live in `deploy/prometheus/rules.yml`.
4. **Check the queue.** A stuck `job_queue` looks identical to an outage
   because runs never complete. Dead letters are the place to look.
5. **Write down the timeline as you go.** Every action with a timestamp.
   Reconstructing it afterwards from logs is what turns a 30-minute incident
   into a 3-hour one.

## DNS consensus incidents

A large share of "it stopped reporting domains as available" reports are the
consensus gate working correctly (ADR-0039, ADR-0002). Before escalating:

- A run marked `degraded` is a partial answer, and the CLI exits non-zero
  on purpose. This is the intended fail-closed signal, not a bug.
- If the consensus provider is misconfigured the system is meant to fail
  fast at startup rather than serve unverifiable verdicts.
- A verdict downgraded to `Unknown` means a resolver leg could not be
  verified. That is correct behaviour.

Do not "fix" this by loosening consensus thresholds during an incident.

## Escalation

1. The on-call engineer named in the rotation.
2. The platform owner (maintainer of `deploy/`).
3. For billing anomalies, the Stripe owner — check the Stripe dashboard
   against `job_queue` dead letters before assuming our side is wrong.

External escalation is only required for SEV-1. For a cross-tenant leak or a
payment error, GDPR notification obligations start running immediately:
involve the platform owner at once, not at the end of the incident.

## The status page

The severity ladder promises a visible status for SEV-2 within fifteen
minutes, and a status nobody can read is not a status. The status page is
the operator's own hosted page (a hosted provider, not the app node: the
app node is the thing that breaks) and it is linked from the public site
and from the incident emails.

What it has to carry, in this order:

| Field | Why it is there |
|---|---|
| Current state | `operational`, `degraded`, `partial outage`, `major outage` |
| What is affected | the API, the pipeline runs, the dashboard, the automation |
| Start time | the question every customer asks first |
| The last update | a status that stops being updated is worse than none |
| The incident reference | the post-mortem link, when it exists |

Two rules that the severity ladder depends on:

- **Update it on a schedule even when nothing changed.** A customer who
  checks the page every five minutes and sees a timestamp that moved knows
  somebody is awake.
- **Do not post a cause before it is known.** "Under investigation" is a
  status; a guess is not.

The external probe ([external-monitoring.md](external-monitoring.md)) is
the source for the current state: it measures the same thing the status
page claims, so the two cannot drift apart silently.

## After it is over

Within 48 hours, a post-mortem with:

- Timeline, from first signal to resolution.
- Impact, stated in tenants affected and duration.
- Root cause, distinguished from trigger — "the migration was slow" is a
  trigger, "no timeout on the migration step" is a cause.
- What we will change, each with an owner and an issue number. A
  post-mortem without a dated action item is a record, not a process.

Post-mortems are blameless. A responder who broke production is not the
finding; the fact that it was possible is.

## What this document deliberately does not cover

- The recovery runbook for data loss, which lives in
  [RTO/RPO](rto-rpo.md) because it has different tools and different owners.
- The routine operations commands (`dominus maintenance backup`, `vacuum`,
  `prune`), documented in [ADR-0022](../adr/0022-backup-and-operations.md).
- Specific alert thresholds, which live in
  `deploy/prometheus/rules.yml` so that changing them is a reviewed change
  rather than a documentation edit.