Audit log & non-repudiation
Every sensitive operation appends an immutable row to audit_logs. Recording
is fire-and-forget: an audit failure never breaks the request that triggered it
(lib/audit.ts → recordAudit, mirroring the stats pattern).
What is recorded
Section titled “What is recorded”Each row captures the actor, the resource, the request’s IP and
user-agent, free-form metadata (old/new values on mutations), and an
immutable created_at. The audited actions (SPECS §10.3):
auth.login · auth.logout · auth.password_resetproject.create · project.update · project.deleteintegration.create · integration.update · integration.revokeapi_key.create · api_key.revokeplan.create · plan.update · plan.delete · plan.assignuser.create · user.update · user.delete · user.plan_changefile.upload · file.update · file.delete · file.downloadstats.read · audit.readThe actor model
Section titled “The actor model”actor_type is one of admin | project | integration (a DB CHECK):
- an admin session →
admin,actor_id= the admin user id; - a project session →
project,actor_id= the project id; - an integration key, or a user/space token (both integration-delegated),
→
integration, with the specific credential inactor_key_id.
user_id names the end-user the action concerns; project_id scopes the row to
a tenant (null for admin-global actions).
Querying
Section titled “Querying”Both endpoints are read-only and newest-first with cursor pagination
(limit 1–100, default 50; cursor). Filters: action, user_id,
actor_type, from / to (ISO timestamps).
GET /project/audit— a project reads its own logs. Owner-only (a project session; integration/user/space tokens are rejected). Scoped to the session’s project.GET /admin/audit— the super admin reads all logs, plus an extraproject_idfilter. Admin-gated.
# a project's own recent file activitycurl "$BASE/project/audit?action=file.upload&limit=20" -H "Authorization: Bearer <project-jwt>"
# admin, one project, a date windowcurl "$BASE/admin/audit?project_id=<uuid>&from=2026-08-01T00:00:00Z" --cookie "session=<admin>"The read itself records an audit.read entry.
Retention & purge
Section titled “Retention & purge”The retention window lives in system_settings under audit_retention_days
(default 365). A daily interval in the API entrypoint purges rows older than
the window.
GET /admin/settings/audit-retention # → { retention_days: 365 }PUT /admin/settings/audit-retention # { "retention_days": 730 }