feat(api): GET /api/agent/audit — the user's agent write history #349

Merged
james merged 1 commit from 348-audit-log-api into main 2026-06-29 11:45:51 +00:00
Owner

First half of epic #47's "audit log + undo" linked ticket: the read surface over the already-captured trail. The commit path writes one append-only audit_events row per applied proposal with before/after snapshots (#51, migration 023), but there was no way to list them — so the PWA can't show "what Carol changed." Undo is deliberately a separate follow-up (reversal semantics need their own design: conflict detection, re-creating ids, child rows, whether undo is itself audited).

What's in it

  • GET /api/agent/audit — cursor-paginated (newest first) list of the authenticated user's audit events → AuditEventDto (id, entityType, entityId, action, tool, before, after decoded from JSON to structured values — null on create/delete, proposalId, createdAt).
  • Per-user scoped — a user only ever sees their own trail.
  • AuditEventsRepository.listPageByUser — keyset pagination on (created_at, id), mirroring the conversations list; the existing listByUser (returns all) stays for internal callers/tests.
  • zod DTO, registered in the OpenAPI spec; api-client types regenerated (both drift gates green).

Verification (run locally on this branch)

  • typecheck ✓ · lint
  • test against both engines (ephemeral Postgres): 1217 passed, 0 skipped (+6: a dual-engine listPageByUser pagination test + 4 route tests — 401, decoded before/after newest-first, cross-user isolation, next_cursor paging)
  • @carol/api-client test ✓ 40 passed · openapi:check ✓ · openapi:coverage 117 → 118 · api-client check ✓ up to date
  • semgrep (CI pack set) ✓ 0 findings on the new files

Out of scope (follow-ups)

  • UndoPOST /api/agent/audit/{id}/undo. Own ticket + design.
  • The @carol/api-client hook + history UI.

Part of epic #47.

Closes #348

🤖 Generated with Claude Code

First half of epic #47's "audit log + undo" linked ticket: the **read** surface over the already-captured trail. The commit path writes one append-only `audit_events` row per applied proposal with before/after snapshots (#51, migration 023), but there was no way to list them — so the PWA can't show "what Carol changed." Undo is deliberately a **separate follow-up** (reversal semantics need their own design: conflict detection, re-creating ids, child rows, whether undo is itself audited). ## What's in it - **`GET /api/agent/audit`** — cursor-paginated (newest first) list of the authenticated user's audit events → `AuditEventDto` (`id`, `entityType`, `entityId`, `action`, `tool`, `before`, `after` decoded from JSON to structured values — null on create/delete, `proposalId`, `createdAt`). - **Per-user scoped** — a user only ever sees their own trail. - **`AuditEventsRepository.listPageByUser`** — keyset pagination on `(created_at, id)`, mirroring the conversations list; the existing `listByUser` (returns all) stays for internal callers/tests. - zod DTO, registered in the OpenAPI spec; api-client types regenerated (both drift gates green). ## Verification (run locally on this branch) - `typecheck` ✓ · `lint` ✓ - `test` against **both engines** (ephemeral Postgres): **1217 passed, 0 skipped** (+6: a dual-engine `listPageByUser` pagination test + 4 route tests — 401, decoded before/after newest-first, cross-user isolation, `next_cursor` paging) - `@carol/api-client test` ✓ 40 passed · `openapi:check` ✓ · `openapi:coverage` 117 → **118** · api-client `check` ✓ up to date - semgrep (CI pack set) ✓ 0 findings on the new files ## Out of scope (follow-ups) - **Undo** — `POST /api/agent/audit/{id}/undo`. Own ticket + design. - The `@carol/api-client` hook + history UI. Part of epic #47. Closes #348 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(api): GET /api/agent/audit — the user's agent write history
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 11s
PR / Static analysis (pull_request) Successful in 4m25s
PR / OpenAPI (pull_request) Successful in 8m11s
PR / Lint (pull_request) Successful in 8m18s
PR / Typecheck (pull_request) Successful in 8m31s
PR / Test (postgres) (pull_request) Successful in 8m42s
PR / Client (web export smoke) (pull_request) Successful in 8m47s
PR / Build (pull_request) Successful in 8m55s
PR / Test (sqlite) (pull_request) Successful in 8m47s
PR / pnpm audit (pull_request) Successful in 4m41s
PR / Package age policy (soft) (pull_request) Successful in 41s
PR / OSV-Scanner (pull_request) Successful in 1m14s
Secrets / gitleaks (pull_request) Successful in 39s
PR / E2E (Playwright) (pull_request) Successful in 9m49s
PR / Coverage (soft) (pull_request) Successful in 2m0s
PR / Trivy (image) (pull_request) Successful in 2m26s
6b1798eede
The commit path already records an append-only audit_events row per
applied proposal with before/after snapshots (#51, migration 023), but
there was no way to read them back. Add the list endpoint so the PWA can
show "what Carol changed" (and, later, offer undo).

- AuditEventsRepository.listPageByUser: cursor-paginated newest-first
  page on the (created_at, id) keyset, mirroring the conversations list.
  The existing listByUser (returns all) stays for internal callers/tests.
- lib/dto/audit.ts: AuditEventDto — decodes the before/after JSON
  snapshots to structured values (null on create/delete).
- GET /api/agent/audit: per-user scoped (a user only sees their own
  trail), cursor-paginated, registered in the OpenAPI spec; api-client
  types regenerated.

Undo is a separate follow-up (reversal semantics need their own design).

Closes #348

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 79.3% ≥ 50%
Branches 71.1% ⚠️ ≥ 75%
Functions 80.3% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 79.3% ✅ | ≥ 50% | | Branches | 71.1% ⚠️ | ≥ 75% | | Functions | 80.3% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 7b95d97563 into main 2026-06-29 11:45:51 +00:00
james deleted branch 348-audit-log-api 2026-06-29 11:45:52 +00:00
Sign in to join this conversation.
No description provided.