Skip to content

Audit Logs

Doable keeps two independent audit trails that together cover privileged admin activity and integration call activity.

Table What it tracks Who can view
admin_audit_log Every action a platform admin takes on the audit surface (conversation search, session view, stats, log reads) Platform admins only
connector_audit Every integration/MCP call made through the connector proxy: status, latency, user Platform admins; workspace-scoped data visible to workspace owners through the connector proxy API

What gets recorded

admin_audit_log: Platform admin actions

Recorded whenever a platform admin touches the audit or operations surface:

Action string Trigger
audit.conversations.search Admin searches the conversation list (/admin/audit)
audit.conversation.view Admin opens a specific AI session transcript
audit.messages.search Admin performs a full-text message search
audit.actions.search Admin views the admin action log itself
audit.stats.view Admin loads aggregate usage statistics
view_project_logs Admin reads a project's runtime log output

The act of auditing is itself audited

Every read on the audit surface writes a new admin_audit_log row. Admins cannot browse conversations without leaving a trace.

connector_audit: Integration and MCP proxy calls

Written on every request through the connector proxy, regardless of outcome:

Status Meaning
ok Call succeeded
denied Blocked by tool-scoping, rate limit, allowlist, or missing connection
error Upstream integration returned an error

Fields captured

admin_audit_log

Field Type Description
id bigserial Auto-incrementing primary key
ts timestamptz Event timestamp (server clock)
actor_id uuid Platform admin who triggered the action
actor_email text Email at time of action (denormalized)
actor_role text Always platform_admin
action text Dot-separated action string (see table above)
resource_type text session, message, user, workspace, project, project_runtime
resource_id text Free-form identifier, usually a UUID
target_user_id uuid Subject user, if applicable
target_workspace_id uuid Subject workspace, if applicable
target_project_id uuid Subject project, if applicable
details jsonb Structured context: filters used, result counts, query parameters
client_ip inet Resolved from cf-connecting-ip, then x-forwarded-for, then socket
user_agent text Browser or tool making the request

connector_audit

Field Type Description
id uuid Row identifier
project_id uuid Project the proxy call was made on behalf of
integration text Integration slug (e.g. github, mcp)
action text Tool or action name
user_id uuid Calling user (if authenticated; null for service-key calls)
status text ok, denied, or error
duration_ms int End-to-end call latency
ts timestamptz Event timestamp

Where to view

Conversation audit (/admin/audit)

Go to Admin, Audit (platform admins only). The page shows:

  • Aggregate stats: total sessions, messages, users; 24-hour and 7-day message counts
  • A searchable conversation list filtered by user ID, workspace ID, project ID, time range, and message content substring
  • Click any row to read the full session transcript at /admin/audit/<sessionId>

Admin action log (/admin/audit/actions)

Click Admin action log in the top-right of the audit page. Filter by actor ID, action type, and time range to see who searched or viewed what, and when.


Retention

Neither table has a scheduled cleanup job in the current schema. Records accumulate indefinitely. To control growth:

  • Partition admin_audit_log by month using PostgreSQL declarative partitioning and drop old partitions.
  • Archive rows older than your retention policy to cold storage before deletion.
  • connector_audit is indexed on (project_id, ts DESC); queries stay fast even at large volume, but disk growth is unbounded without pruning.

Operators are expected to handle retention according to their own compliance requirements.


Exporting

There is no built-in CSV or JSON export button in the current UI. To export programmatically:

-- Export admin action log for a date range
COPY (
  SELECT * FROM admin_audit_log
  WHERE ts >= '2025-01-01' AND ts < '2025-02-01'
  ORDER BY ts DESC
) TO '/tmp/admin_audit_jan.csv' WITH CSV HEADER;

-- Export connector audit for a project
COPY (
  SELECT * FROM connector_audit
  WHERE project_id = '<uuid>' ORDER BY ts DESC
) TO '/tmp/connector_audit.csv' WITH CSV HEADER;

Run these as the postgres superuser or a role with pg_write_server_files.


Trust model

  • Append-only by design. The migration comment in 059_admin_audit.sql is explicit: "No UPDATE/DELETE policy; operators rotate by partitioning or archiving the table, never by mutating rows." There is no API endpoint that deletes or modifies audit rows.
  • Admin read access, no delete. Platform admins can query and filter logs through the UI. No route exposes DELETE or UPDATE on either audit table.
  • Audit failures are non-blocking. recordAdminAction and the connector audit() function are fire-and-forget; a database hiccup will not fail the admin request, but the missed row is logged to stdout ([admin-audit] insert failed).

What is NOT recorded

  • Request or response bodies of end-user AI messages (content is stored in ai_messages, not duplicated into the audit log).
  • Full SQL queries: only structured metadata (action, resource IDs, filters) is captured.
  • Ephemeral session state such as WebSocket connection metadata or in-memory rate-limit counters.
  • Actions taken by non-admin users (workspace member operations, project file edits, chat turns). Those are covered by the application data model, not the audit log.

Workspace owners

The admin_audit_log surface is platform-admin only; workspace owners have no UI access to it and the API routes are guarded by isPlatformAdmin checks.

connector_audit rows are project-scoped. Workspace owners can query this table for their own projects via direct database access if they self-host, but there is no dedicated workspace-owner UI for it in the current release.


See also