Audit
Read-only listing over the workspace's append-only audit log. Every mutating admin route (POST / PATCH / DELETE) writes one AuditEntry row capturing the actor principal, action verb, target entity, and a JSON-safe before/after snapshot. This endpoint is the authoritative "what happened on my workspace" surface — dashboards and SOC exports read it, and incident response pivots from it into /admin/inference via inference_log_id.
The router lives at packages/shared/audit_routes.py. The persistence layer is AuditLogRepository (packages/shared/audit.py:58).
Endpoints
| Method | Path | Auth | Summary |
|---|---|---|---|
| GET | /admin/audit | Bearer | Workspace-scoped audit listing, ordered by ts DESC. |
GET /admin/audit
Query parameters (all optional, all AND-composed):
| Param | Type | Notes |
|---|---|---|
action | string | Exact match (e.g. adapter.activate). |
entity | string | Exact match (e.g. adapter:adp_…). |
actor | string | Exact-match on the actor_principal column. |
since | ISO-8601 datetime | Inclusive UTC lower bound. |
until | ISO-8601 datetime | Inclusive UTC upper bound. |
limit | int (1–500) | Default 100. |
offset | int (≥0) | Default 0. |
Workspace is always taken from the bearer principal — there is no query string for it.
Response shape:
{
"entries": [
{
"id": "aud_01HF…",
"workspace_id": "default",
"actor_principal": "operator",
"action": "adapter.activate",
"entity": "adapter:adp_01HF…",
"before": null,
"after": {
"model_id": "llama3.2:3b",
"adapter_id": "adp_01HF…",
"swap_ms": 412.3
},
"ts": "2026-05-01T12:00:00Z",
"inference_log_id": null
}
],
"total": 1,
"limit": 100,
"offset": 0
}Rows are ordered by ts DESC so the most recent action comes first. total reflects only the page payload size — there is no full-table count for v0; pagination uses limit/offset.
curl -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
"http://localhost:8000/admin/audit?action=adapter.activate&since=2026-04-01T00:00:00Z"Operator notes
- Workspace fence. Every row is filtered by the principal's
workspace_id; cross-tenant existence does not leak. See workspace fence. - Append-only. There is no
DELETEroute by design. Retention is the operator's responsibility — drop rows from theaudit_logstable directly when policy demands it. - Cross-layer correlation. When the audited action followed a logged inference,
inference_log_idlinks back to/admin/inference/{id}(added in migration0016_cross_layer_correlation). 503causes.app.state.audit_repois wired in lifespan; the route returns503with"AuditLogRepository not initialised"when the lifespan did not complete.- Best-effort writes. Mutating admin routes write audit rows in fire-and-forget fashion; a transient audit-store outage never blocks the originating call. The corollary: a missing audit row is not proof a mutation did not happen — also check
/admin/inferenceand the relevant resource state.