Vetrix Docs

Audit Log

Vetrix records every security-sensitive and privileged operation in an immutable audit log. Entries are append-only — no application endpoint can delete or modify them.

Querying the audit log

Instance-wide (admin only)

GET /api/v1/admin/audit-log?limit=100&offset=0
Authorization: Bearer <admin-jwt>

Per-repository

GET /api/v1/repos/{owner}/{repo}/audit-log?limit=50
Authorization: Bearer <jwt>

Response:

[
  {
    "id": 4712,
    "actor_id": "550e8400-e29b-41d4-a716-446655440000",
    "action": "acl.grant",
    "resource": "repo:alice/myrepo",
    "ip_address": "203.0.113.42",
    "created_at": "2026-04-11T14:32:00Z"
  }
]

Query parameters

Parameter Default Description
limit 100 Maximum entries to return (max 500)
offset 0 Skip first N entries (pagination)

Single-event detail (admin only)

GET /api/v1/admin/audit-log/{id}
Authorization: Bearer <admin-jwt>

Returns the full auditLogEntryView (same fields as the list endpoint) for one event. {id} is the numeric BIGSERIAL primary key returned in the list response.

Status Body
200 { id, actor_id, actor_username, action, resource, resource_type, resource_id, ip_address, user_agent, details, created_at }
400 {"error":"invalid audit id"} for non-numeric or non-positive id
403 {"error":"admin access required"} for non-admin callers
404 {"error":"audit entry not found"}

Recorded actions

Action Trigger
repo.create Repository created
repo.delete Repository deleted
repo.push Code pushed to a repository
user.login Successful user login
user.login_failed Failed login attempt
user.logout User session ended
token.create Personal access token created
token.delete Personal access token revoked
totp.enabled TOTP 2FA enabled for a user
totp.disabled TOTP 2FA disabled
acl.grant Collaborator role granted on a repository
acl.revoke Collaborator role removed from a repository
admin.user_created Admin provisioned a new user account
admin.user_suspended Admin suspended a user account
admin.settings_changed Instance settings updated
pipeline.trigger CI/CD pipeline manually triggered
pipeline.job.hard_failed A pipeline job reached a hard (non-tolerated) failure
pipeline.job.soft_failed A pipeline job reached a soft (allow_failure) failure
repo.protected_branch.updated A protected-branch rule was created or updated (incl. the require_pipeline gate toggle)
repo.merge.pipeline_blocked A merge was refused by the required-pipeline gate
repo.mr.pipeline_blocked A merge-request open was refused by the required-pipeline gate
repo.push.pipeline_gate_blocked A push was rejected by the required-pipeline push gate
scan.trigger Security scan manually triggered
repo_transfer.preflight AccountTransfer preflight ran
repo_transfer.executing AccountTransfer began moving the repository
repo_transfer.completed AccountTransfer finished successfully
repo_transfer.failed AccountTransfer aborted

The four repo_transfer.* events form a chained record set. Each carries the following details fields:

Field preflight executing completed failed
actor_user_id required required required required
source_repo_id required required required required
target_user_id required required required required
manifest_digest optional (set when pg_dump succeeds) optional required optional
error n/a n/a n/a required

The catalog accepts these event types without an audit_log schema change — Action is a free-form string column. The hash-chain validator covers them identically to legacy actions.

Database schema

The audit_log table has no DELETE grant in application code:

SELECT id, actor_id, action, resource, ip_address, user_agent, details, created_at
FROM audit_log
ORDER BY created_at DESC
LIMIT 100;

Exporting

For compliance exports, query directly via psql or a read replica:

psql "$DATABASE_URL" -c "\copy (
  SELECT * FROM audit_log
  WHERE created_at >= '2026-01-01'
  ORDER BY created_at
) TO '/tmp/audit-2026.csv' WITH CSV HEADER"

Retention

Audit log entries are retained indefinitely by default. To implement a retention policy, run a scheduled database job — not an application delete:

-- Example: archive entries older than 2 years to a separate table.
INSERT INTO audit_log_archive SELECT * FROM audit_log WHERE created_at < NOW() - INTERVAL '2 years';
DELETE FROM audit_log WHERE created_at < NOW() - INTERVAL '2 years';