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¶
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.