Every workspace ships with an immutable audit trail at /app/audit. Admin and Owner roles can browse, filter, and inspect entries; Editor and Viewer are blocked.

What gets logged

The application writes to audit_logs for any action that materially changes workspace state or surfaces compliance exposure. Examples include:

Action vocabulary

The application writes the following audit actions today. Filter on the Action toolbar field to surface a specific slug (substring match — agent shows every agent.* row).

When an action fires from a context with no authenticated user (queue worker, scheduled command, webhook callback), the row's after.system_origin field carries a marker: console, webhook_or_queue, or background. Use this to distinguish "system did this" from "we forgot to capture the actor".

Each row carries: action slug, entity type / id, actor user (or System when triggered by a queue job), originating IP, user agent, and a before / after JSON snapshot.

Filtering

The toolbar accepts:

Filters compose; pagination respects the active filter set.

Retention

Audit rows are pruned daily via the scheduled command audit:prune --days=365. Default retention is one year — long enough to investigate "what changed two quarters ago?" but bounded so the table doesn't grow without limit. Customers with stricter compliance requirements (SOC2, HIPAA, long-tail GDPR holds) should export their audit log via the artisan command on their own cadence before this prune fires. Override the window by passing a different --days value in your routes/console.php.

# Dry-run: report what would be pruned, change nothing.
php artisan audit:prune --days=365 --dry-run

# Force a longer retention window.
php artisan audit:prune --days=730

Why no edit / delete

Audit rows are append-only. There is no UI to edit a row, and the database has no updated_at column on audit_logs. This is intentional — a compliance log that can be rewritten is not a compliance log.

Performance

The table is indexed on (workspace_id, created_at) and (entity_type, entity_id). A workspace with tens of thousands of rows still serves the first page in under 50ms. If you need to bulk-export, query the table directly via the workspace:audit-export Artisan command (planned) rather than scraping the UI.