Audit logging — append-only per-object access log (#61) #66

Merged
Cordy merged 6 commits from feat/audit-log into main 2026-07-30 01:35:30 +00:00
Owner

Closes #61 — the procurement-critical public-sector feature (#59 derived).

internal/audit: JSONL month files on the data volume (audit.path, default /data/.cairn/audit), appended under a mutex, rotated monthly, pruned by audit.retainMonths (0 = keep forever), never modified after rotation. Failed operations are logged too (result = error string) — failed attempts are evidence. All methods nil-safe, so callers need no feature checks.

One storage decorator covers everything: placed UNDER the scope overlay (events record unambiguous backend paths — /spaces/records-unit/x for every reader) and ABOVE trash (a user delete is recorded as delete, not trash's internal rename). REST, WebDAV, TUS finalize, and public share downloads (empty user = unauthenticated) all flow through it. Listings/stats deliberately not audited — they'd bury the meaningful events. Usage capability forwarded (the encryption decorator taught us that lesson).

Extra events at their own chokepoints: share-create/share-revoke (with token), trash restore/purge.

Admin surface: GET /api/v1/admin/audit?user=&action=&path=&from=&to=&limit= — RFC3339 window, path prefix, newest first, bounded (default 500, max 5000). The raw JSONL files remain the export format for an auditor's own tooling.

Config: audit {enabled, path, retainMonths} — off by default, zero overhead when disabled.

Tests: append/query filters + ordering + limit + time window, retention pruning, decorator event emission (incl. list/stat exclusion and rename dest), nil-safety. gofmt/vet/build/test green on the runner.

Closes #61 — the procurement-critical public-sector feature (#59 derived). **`internal/audit`**: JSONL month files on the data volume (`audit.path`, default `/data/.cairn/audit`), appended under a mutex, rotated monthly, pruned by `audit.retainMonths` (0 = keep forever), never modified after rotation. Failed operations are logged too (`result` = error string) — failed attempts are evidence. All methods nil-safe, so callers need no feature checks. **One storage decorator covers everything**: placed UNDER the scope overlay (events record unambiguous backend paths — `/spaces/records-unit/x` for every reader) and ABOVE trash (a user delete is recorded as `delete`, not trash's internal rename). REST, WebDAV, TUS finalize, and public share downloads (empty user = unauthenticated) all flow through it. Listings/stats deliberately not audited — they'd bury the meaningful events. `Usage` capability forwarded (the encryption decorator taught us that lesson). **Extra events at their own chokepoints**: `share-create`/`share-revoke` (with token), trash `restore`/`purge`. **Admin surface**: `GET /api/v1/admin/audit?user=&action=&path=&from=&to=&limit=` — RFC3339 window, path prefix, newest first, bounded (default 500, max 5000). The raw JSONL files remain the export format for an auditor's own tooling. Config: `audit {enabled, path, retainMonths}` — off by default, zero overhead when disabled. Tests: append/query filters + ordering + limit + time window, retention pruning, decorator event emission (incl. list/stat exclusion and rename dest), nil-safety. gofmt/vet/build/test green on the runner.
Cordy merged commit e18351a8f7 into main 2026-07-30 01:35:30 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#66
No description provided.