Context
audit_log (src/store/audit.ts, migration 001_init.sql:62-71) grows append-only and is only readable via direct sqlite3 access per docs/self-host.md:122. No retention, no UI, no query API.
Problem / Observation
- A long-running instance accumulates GB of audit data with no pruning.
- An admin who wants to investigate "show me failed logins for user X in the last 24h" has to shell into the container.
- The audit_log row carries metadata as a JSON TEXT blob — no index on
action, no time-bucketed queries supported.
- On account deletion (
account_delete action), the user_id becomes NULL via ON DELETE SET NULL, so the link from a delete event back to who was deleted is severed unless the route explicitly logs the deleted user id in metadata. It currently does (admin.ts:498-501), but the user-driven DELETE /account (auth.ts:407-411) does NOT capture the user id in metadata, so post-deletion the audit row's user_id is NULL and the actor is unrecoverable.
Proposed approach
- Migration: add
idx_audit_action_time ON audit_log(action, created_at DESC).
- Env var
AUDIT_RETENTION_DAYS (default 365); background purge sweeps rows older than that.
- New admin route
GET /admin/audit?action=&since=&limit= with offset pagination.
- Fix
DELETE /account in auth.ts:407 to capture deleted_user: userId.toString("hex") in metadata.
Acceptance criteria
Context
audit_log(src/store/audit.ts, migration001_init.sql:62-71) grows append-only and is only readable via directsqlite3access per docs/self-host.md:122. No retention, no UI, no query API.Problem / Observation
action, no time-bucketed queries supported.account_deleteaction), the user_id becomes NULL viaON DELETE SET NULL, so the link from a delete event back to who was deleted is severed unless the route explicitly logs the deleted user id in metadata. It currently does (admin.ts:498-501), but the user-drivenDELETE /account(auth.ts:407-411) does NOT capture the user id in metadata, so post-deletion the audit row's user_id is NULL and the actor is unrecoverable.Proposed approach
idx_audit_action_time ON audit_log(action, created_at DESC).AUDIT_RETENTION_DAYS(default 365); background purge sweeps rows older than that.GET /admin/audit?action=&since=&limit=with offset pagination.DELETE /accountin auth.ts:407 to capturedeleted_user: userId.toString("hex")in metadata.Acceptance criteria