---
module: devices.config-audit
title: Config Audit
tab: Claude
order: 40
audience: dev
---

> Terse agent reference. All facts below verified in `hiveops-devices` source (2026-07-01).

## Ownership

- **Service:** `hiveops-devices` (backend port **8096**; SPA `devices.bcos.cloud` port 5177).
- **DB:** `hiveiq_devices` (Postgres, separate DB VM — ProxyJump to reach).
- **Kafka:** none for this feature. Do not look for a topic.
- **Frontend:** `frontend/src/components/Settings/ConfigAudit.svelte`; API in `lib/api.ts` → `institutionKeyAPI.getConfigAudit`.

## Endpoints (backend-relative; base = `VITE_API_URL` / `http://localhost:8096`)

| Method | Path | Auth | Notes |
|--------|------|------|-------|
| `GET` | `/api/institution-keys/config-audit?institutionKey={k}&page={n}&size={s}` | authenticated, **no `@PreAuthorize`** | Paged log; diffs computed on read; default size 50 |
| `PUT` | `/api/institution-keys/{key}/agent-properties` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | The ONLY thing that creates an audit row |
| `GET` | `/api/institution-keys` (→ `/accessible`) | authenticated, scoped | Dropdown source |

## Tables

- `institution_config_audit` — the log. Cols: `id, institution_key, changed_by, changed_at, previous_config(TEXT), new_config(TEXT)`. Indexes `(institution_key)`, `(changed_at DESC)`. Migration `V7`.
- `institution_agent_properties` — the audited config (`hiveops_config`, `ej_config`).
- `institution_keys` — key→name resolution.

## Roles / scoping (`InstitutionContext.resolveScope()`)

- `null` (unrestricted): `BCOS_ADMIN`, `ADMIN`, `QDS_ADMIN`, `USER` → `institutionKey` param honored (blank = all).
- scoped list: `CUSTOMER`, `MSP_ADMIN` → forced to `scopedKeys.get(0)`; **param ignored**.

## Gotchas

- Audit row is a **side effect of `PUT …/agent-properties`** — there is no create/delete audit endpoint.
- **Diffs are NOT stored.** Only full `previous_config`/`new_config` JSON snapshots are; `InstitutionKeyService.computeDiff` regenerates the field diff per read.
- Only `hiveopsConfig` is snapshotted in the audit; `ejConfig` changes are saved to props but **not** captured in the audit diff.
- First save → `previous_config = null` → UI badge "Initial save", all fields `from = null`.
- `changed_by` = SecurityContext principal name, else literal `"system"`.
- Scoped user (esp. multi-institution `MSP_ADMIN`) sees **only their first institution key**; dropdown filter is a no-op for them.
- `computeDiff` swallows JSON parse errors (warn log) → can return empty/partial diff silently.
- `institution_key` column is `VARCHAR(10)`.

## Do NOT

- Do **not** claim Config Audit publishes/consumes Kafka — it does not.
- Do **not** claim the diff is persisted — it is computed on read.
- Do **not** assume `MSP_ADMIN` can filter across institutions here — backend pins to the first scoped key.
- Do **not** look for this data in `hiveops-incident` — it lives in `hiveops-devices` / `hiveiq_devices`.
- Do **not** invent an "add/edit/delete audit" API — the log is append-only via config saves.
- Do **not** run `docker exec hiveiq-postgres` on the services VM — Postgres is on the separate DB VM.
