---
module: incident.incident-tracker
title: Incident Tracker
tab: Claude
order: 40
audience: dev
---

> Terse act-without-guessing reference. Confirmed against `hiveops-incident` code 2026-07-01.

## Owner
- Service: **hiveops-incident** (backend port 8080, NGINX `/api/incident/`). DB: **hiveiq_incident** (env `DB_URL` = full JDBC URL).
- Frontend: `hiveops-incident/frontend/src/components/IncidentTracker/IncidentTracker.svelte` (Vite+Svelte SPA, state-routed via `currentView`).
- The tracker is **read-only + client-side aggregated**. No tracker endpoint/table/topic exists.

## Endpoints the tracker calls (all GET, all via NGINX `/api/incident/` → base `/api`)
| Method + path | Params used | Handler |
|---------------|-------------|---------|
| GET `/api/device-summary` | — | `DeviceSummaryController.getAll` |
| GET `/api/incidents/paginated` | `status=OPEN,ASSIGNED,IN_PROGRESS&size=1000&groupId` | `IncidentController.getIncidentsPaginated` |
| GET `/api/incidents/paginated` | `status=RESOLVED,CLOSED&size=500&sort=updatedAt,desc&groupId` | same |

## Drill-down (View Incidents button)
- Dispatches `viewAtmIncidents { atmId: <integer FK>, statuses }` into `IncidentList` — filter is client-side, not a new endpoint.

## Tables
| Table | R/W here | Key |
|-------|----------|-----|
| `incidents` | read | join key = `atmName` (string), `atmId` = integer FK |
| `device_summary` | read | key = `atm_id` (string, natural key) |

## Kafka (feeds `device_summary`, not the tracker directly)
| Topic | Consumer / group |
|-------|------------------|
| `hiveops.device.events` | `DeviceEventConsumer` / `hiveops-incident-device-sync` |

Constants in `config/DeviceEventKafkaConfig`: `TOPIC`, `CONSUMER_GROUP`.

## Roles
- Viewing tracker: **any valid JWT**. Read endpoints have **no `@PreAuthorize`**; scoped by `InstitutionContext.resolveScope()`:
  - CUSTOMER / MSP_ADMIN → scoped to JWT-granted institution keys.
  - BCOS_ADMIN / ADMIN / QDS_ADMIN / USER → `null` = all institutions.
- Mutations (create/assign/transition/bulk): `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`.

## Gotchas
- Row join is **client-side** on `incident.atmName == device_summary.atm_id`. No match → stub row, blank location. Blank location = **stale `device_summary` mirror** (devices didn't publish; no reconciler).
- `groupId` filter applies to **incidents only**, not `device-summary` (per code comment).
- Board shows only ATMs with active incident, or resolved/closed when the toggle is on.
- Pipeline stage & severity are computed in JS (`Math.min` stage over active; severity precedence CRITICAL>HIGH>MEDIUM>LOW). To change a row, fix the `incidents` record.
- Silent caps: active `size=1000`, closed `size=500`, no UI pagination → truncates on very large fleets.
- Auto-refresh 60s (`setInterval`), no websocket. Lifecycle topics `hiveops.incidents.created/updated/resolved` are produced but **not consumed** by the tracker.
- `DB_URL` hardcoded default is a stale `hiveops_incident` on 192.168.200.103 — prod overrides to `hiveiq_incident`.

## Do NOT
- Do NOT look for a `/api/incident-tracker` endpoint or a `tracker` table — none exist.
- Do NOT expect the tracker to update `device_summary` — it's read-only; devices owns device data.
- Do NOT assume it consumes `hiveops.incidents.*` — it re-polls REST.
- Do NOT add a write to `incidents`/`device_summary` from this view — mutations belong to incident-management endpoints ([platform.service-ownership]).
- Do NOT call these agent-facing; agents never hit incident directly (agent-proxy → `/api/internal/**`).
- Do NOT report the tracker as "AI" — hiveops-ai is branded **Adoons**; the tracker has no AI.
