Skip to content

SCRUM-238 — Dashboard merged audit trail

Plan ref: DSH-C11 (docs/11-admin-plane-plan.md). Stacked on SCRUM-237.

What exists

GET /api/admin/dashboard/audit (viewer): source (config | admin-auth | both), actor, from, to, limit (1..200, default 50), cursor.

Piece Notes
internal/auditsrc one client per feed (/api/admin/config/audit, /admin-auth/audit), forwarding only the caller's own bearer token; timeout, 4 MiB cap, strict decode. Upstream 401/403 → same status to the caller
Merge pages from each feed fetched concurrently; k-way merge by at desc (tie: source name, then id desc); a feed whose page runs out mid-merge is refilled so ordering holds across page boundaries
Cursor base64url JSON {"config":{"cursor":…,"last_id":…},"admin-auth":{…}}; resume refetches from the feed's keyset cursor and drops ids ≥ last_id (already returned). "done":true for an exhausted feed
Degradation one feed failing → 200 with the other's entries and "degraded":["…"]; both → 502
Config DASHBOARD_CONFIG_URL (http://config:8080), DASHBOARD_ADMIN_AUTH_URL (http://admin-auth:8080)

The Dashboard never touches another service's database.

How to verify

cd services/dashboard
gofmt -l . && go vet ./... && go test -race -count=3 ./...

Tests: interleaving; walking with limits 1–7 over random feeds with frequent equal timestamps returns every entry exactly once in global order; rows inserted at a feed's head between pages are neither repeated nor allowed to hide others; token forwarded verbatim with no other header leaking; 403 propagated; one feed down → degraded; both down → 502; source filter; validation; through the server.

Results at time of writing

  • gofmt, go vet, go test -race -count=3 (10 packages): pass.

How it was built

DeepSeek run scoped (Landlock) to services/dashboard (510 s, ~81k output tokens, reasoning effort low). Claude review: the cursor resumed a feed as "refetch this page and skip n entries". On a feed's head page (no upstream cursor yet), audit rows written between two requests shift that page, so later pages repeated or lost entries. Resume is now by the last returned id (the feeds are id-ordered with keyset cursors). The test fake used index cursors and random ids, unlike the real feeds; it now pages by id with a keyset cursor. The new regression test fails without the fix and passes with it.