---
module: incident.incident-management
title: Incident Management
tab: Claude
order: 40
audience: bcos
---

## Ownership
- **Service:** `hiveops-incident` (Spring Boot, port **8080**, NGINX `/api/incident/`).
- **DB:** `hiveiq_incident` (Postgres). Env var **`DB_URL`** = single full JDBC URL (NOT split host/port/name).
- **Frontend view:** `hiveops-incident/frontend/src/components/Incident/IncidentList/IncidentList.svelte`; API client `lib/api.ts` (`incidentAPI`).

## Endpoints (METHOD path) — all under `/api`
| Method | Path | Role |
|--------|------|------|
| GET | `/api/incidents/paginated` | any auth |
| GET | `/api/incidents` · `/api/incidents/{id}` | any auth |
| GET | `/api/incidents/status/open` · `/api/incidents/severity/critical` | any auth |
| GET | `/api/incidents/atm/{atmId}` · `/api/incidents/device/{atmId}` (aliases) | any auth |
| PUT | `/api/incidents/{id}` | any auth |
| POST | `/api/incident-management` (create) | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/incident-management/{id}/assign` · `/reassign` · `/unassign` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/incident-management/{id}/transition` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/incident-management/{id}/notes` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/incident-management/bulk/update` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/incident-management/{id}/link` · `/group` | MSP_ADMIN/BCOS_ADMIN |
| DELETE | `/api/incident-management/{id}/link/{linkedId}` | MSP_ADMIN/BCOS_ADMIN |
| GET | `/api/incident-management/{id}/links` · `/audit-trail` · `/stats/summary` | any auth |
| GET | `/api/incident-management/technicians/available` | any auth |
| PUT | `/api/institution-keys/{key}/incident-mode` (set ownership mode, #221) | MSP_ADMIN/BCOS_ADMIN |

† Mutating rows list the `@PreAuthorize` gate. Since #221 that gate also admits `CUSTOMER`, and the real write decision is the per-institution **ownership mode** — see *Roles & per-institution ownership mode* below.

Bulk close body: `{ "incidentIds": ["1","2"], "newStatus": "CLOSED" }` (ids are strings).

## Roles & per-institution ownership mode (#221)
Reads: authenticated only. Mutations are gated in **two layers**:
1. `@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN','CUSTOMER')")` on every mutating endpoint.
2. Service-layer `IncidentService.assertWritable(incident)` / `assertCreatable(atmId)` then enforces the
   institution's **incident ownership mode** (`institution_keys.incident_management_mode`):

| Mode | BCOS_ADMIN | MSP_ADMIN | CUSTOMER (own institution) |
|------|-----------|-----------|----------------------------|
| `MSP_MANAGED` (default) | ✅ | ✅ | ❌ read-only |
| `CUSTOMER_MANAGED` | ✅ (override) | ❌ read-only | ✅ |

- Mode is resolved from the incident's institution **name** (`incident.getAtm().getInstitution()`), looked up in incident's own `institution_keys` copy; unknown institution ⇒ `MSP_MANAGED` (unchanged behaviour).
- Set it: `PUT /api/institution-keys/{key}/incident-mode` body `{"incidentManagementMode":"MSP_MANAGED"|"CUSTOMER_MANAGED"}` (MSP_ADMIN/BCOS_ADMIN; **upserts** incident's institution_keys row). Surfaced in the **MSP app** → Institution Keys → "Incident management".
- `IncidentDTO.institution` carries the institution name so the frontend can gate controls (hidden actions + a **Read-only** badge in the detail panel).
- The guard lives at the **controller layer only** — NOT in `saveIncident`/`createIncident`, which Kafka/journal auto-creation also uses, so auto-created incidents are unaffected.

## Tables (`hiveiq_incident`)
- `incidents`, `incident_events`, `incident_notes`, `workflow_transitions`, `atms`, `technicians`, `helpdesk_persons`, `device_summary` (Kafka mirror), `incident_embeddings`.

## Kafka topics
- **Produced:** `hiveops.incidents.created`, `hiveops.incidents.updated`, `hiveops.incidents.resolved` (`IncidentLifecycleProducer` / `IncidentLifecycleKafkaConfig`).
- **Consumed (auto-create):** `hiveops.device.events` (group `hiveops-incident-device-sync`), `hiveops.aria.threats` (group `hiveops-incident-aria`), `adoons.rule-suggestion.ready` (group `hiveops-incident-adoons`).
- WebSocket (STOMP): `/topic/incidents/status-changed`.

## Statuses
- `OPEN, ASSIGNED, IN_PROGRESS, RESOLVED, CLOSED, PARKED`.
- A device with any `PARKED` incident suppresses ALL auto-creation for that device (#219).

## Gotchas
- Transition requires a valid `workflow_transitions` edge; if `requiresAssignment`, assignee must be set first (else 400).
- Closing blocked by `assertNoUnfixedChildren()` if linked children unfixed.
- **Possible Fraud note-before-close is FRONTEND-only** (`IncidentList.svelte` fraud-close modal) — NOT enforced by the API.
- **MSP_ADMIN gets 403 on a `CUSTOMER_MANAGED` institution's incidents — this is intended** (#221), not a bug. Check the institution's `incident_management_mode`; only BCOS_ADMIN overrides it. Conversely a CUSTOMER *can* now write their own incidents when their institution is `CUSTOMER_MANAGED`.
- Agents never call this service; they go via **hiveops-agent-proxy** → `/api/internal/**` (`X-Internal-Secret`).
- `device_summary` is a read-only Kafka mirror from hiveops-devices; no reconciler → stale = fix upstream.
- `DB_URL` default points at a stale `hiveops_incident` DB — prod overrides to `hiveiq_incident`.

## Do NOT
- Do NOT add device/institution writes here — device data is owned by hiveops-devices.
- Do NOT assume the fraud-note rule protects direct API closes — it doesn't.
- Do NOT assume "MSP_ADMIN can always write" — since #221, a `CUSTOMER_MANAGED` institution makes MSP_ADMIN read-only (only BCOS_ADMIN overrides); and CUSTOMER can write when their institution is customer-managed.
- Do NOT move the write-mode guard into `saveIncident`/`createIncident` — it would 403 Kafka/journal auto-creation (no user role in context). Keep it at the controller layer.
- Do NOT split `DB_URL` into host/port/name.
- Do NOT create incidents for a parked device expecting auto-flow — parking silences it.
- Do NOT invent transition endpoints per-status (start/resolve/close); all go through one `POST /{id}/transition` with `newStatus`.
