Audit Log
The ledger_events table in the causet database is Causet’s audit log. Every state-changing action produces a ledger event. The ledger is append-only — events are never updated or deleted through application code paths.
Note: For the observability angle on the audit log (forensics, replay, debugging), see Audit Events. This page focuses on compliance, access, and retention.
What the audit log records
Every ledger event contains:
| Field | Description | Example |
|---|---|---|
event_id | Unique event identifier | evt_abc123 |
event_type | The event name | ARTIST_FOLLOWED |
entity_id | The affected entity | user-1 |
entity_type | The entity type | user |
payload | Full event payload (JSONB) | {"user_id": "user-1", "artist_id": "artist-1"} |
correlation_id | Links to the intent that caused this | corr-xyz789 |
fork_id | Deployment environment | production |
platform_id | Platform | my-platform |
application_id | Application | concert-app |
created_at | Committed timestamp (microseconds) | 2026-06-25 20:00:00.123456 |
Who changed what, when
The standard audit query pattern:
-- All changes to a specific entity
SELECT
event_id,
event_type,
entity_id,
payload,
correlation_id,
created_at
FROM causet.ledger_events
WHERE entity_id = 'user-1'
AND platform_id = 'my-platform'
AND application_id = 'concert-app'
AND fork_id = 'production'
ORDER BY created_at ASC;-- All events in a time range for a specific event type
SELECT
event_id,
entity_id,
payload,
created_at
FROM causet.ledger_events
WHERE event_type = 'ARTIST_FOLLOWED'
AND fork_id = 'production'
AND created_at BETWEEN '2026-06-01' AND '2026-06-30'
ORDER BY created_at DESC;-- Trace a complete action (find all events from one intent)
SELECT
event_type,
entity_id,
payload,
created_at
FROM causet.ledger_events
WHERE correlation_id = 'corr-xyz789';Compliance audit queries
User activity report
-- All actions performed by a user in the last 90 days
SELECT
event_type,
payload->>'artist_id' AS artist_id,
created_at
FROM causet.ledger_events
WHERE entity_id = 'user-1'
AND entity_type = 'user'
AND fork_id = 'production'
AND created_at >= NOW() - INTERVAL '90 days'
ORDER BY created_at DESC;Event type frequency report
-- Event counts by type for a date range
SELECT
event_type,
COUNT(*) AS event_count,
MIN(created_at) AS first_seen,
MAX(created_at) AS last_seen
FROM causet.ledger_events
WHERE fork_id = 'production'
AND created_at >= '2026-01-01'
GROUP BY event_type
ORDER BY event_count DESC;Building audit projections
For compliance queries with a fixed shape (e.g., “all follow/unfollow actions by user”), create a projection table:
# projections/audit.projections.causet
projections:
follow_audit_log:
source_events: [ARTIST_FOLLOWED, ARTIST_UNFOLLOWED]
target:
table: follow_audit_log
primary_key: [event_id]
fields:
event_id: TEXT
user_id: TEXT
artist_id: TEXT
action: TEXT
occurred_at: BIGINT
derive:
event_id: event.event_id
user_id: event.user_id
artist_id: event.artist_id
action: event.event_type
occurred_at: event.ts
mutations:
ARTIST_FOLLOWED: { op: upsert }
ARTIST_UNFOLLOWED: { op: upsert }This materializes a purpose-built audit table that:
- Is indexed for fast compliance queries
- Has a specific schema for audit report generation
- Can be queried without joining against the full
ledger_eventstable
Query it through the query service:
queries:
follow_audit:
from: follow_audit_log
where:
user_id: { eq: input.user_id }
order_by:
occurred_at: desc
input:
user_id: { type: string, required: true }Immutability
The ledger is append-only by application design:
causet-runtime-servicecontains noUPDATEorDELETEqueries againstledger_events- The database schema has
NOT NULLconstraints on critical fields - Sequence numbers and timestamps create a detectable record if rows are manually modified or deleted
For deployments where database-level immutability is required, add PostgreSQL row-level security:
-- Prevent anyone (including superusers) from updating or deleting ledger events
-- via the application database user
REVOKE UPDATE, DELETE ON causet.ledger_events FROM causet_runtime;
REVOKE UPDATE, DELETE ON causet.ledger_events FROM causet_worker;This does not prevent a database administrator from modifying rows, but limits the blast radius of application-layer vulnerabilities.
Retention policy
Ledger events accumulate without bound by default. Define a retention policy based on your compliance requirements:
| Compliance framework | Typical retention |
|---|---|
| GDPR (EU) | As long as necessary for purpose; delete when no longer needed |
| SOX (financial) | 7 years |
| HIPAA (healthcare) | 6 years |
| PCI DSS | 1 year minimum |
Implement retention as a scheduled job that archives old events to cold storage before deleting:
# 1. Export events older than 2 years to S3 Glacier
aws s3 cp \
<(psql $DB -c "COPY (SELECT * FROM causet.ledger_events WHERE created_at < NOW() - INTERVAL '2 years') TO STDOUT CSV HEADER") \
s3://causet-audit-archive/ledger-events-$(date +%Y%m%d).csv
# 2. Verify archive integrity
# 3. Delete from live table
psql $DB -c "DELETE FROM causet.ledger_events WHERE created_at < NOW() - INTERVAL '2 years'"Warning: Deleting ledger events breaks the ability to replay entity history for the deleted period. Do this only after archiving and only for data outside your operational retention window.
Access control for audit data
Restrict direct access to the ledger_events table to:
- The
causet_runtimedatabase user (application writes) - A dedicated
causet_auditread-only user for compliance queries - Database administrators (logged access)
Do not expose raw ledger access to application users. Build projection-based audit reports with appropriate access control through the query service.