Audit Events

In Causet, the event ledger is your audit log. Every state change in the system results in a committed event in ledger_events. This record is append-only, covers all state changes by construction, and does not require you to build or maintain a separate audit system.


What the ledger records

Every event appended to the ledger contains:

FieldDescription
event_idUnique identifier for this event
event_typeThe event name (e.g., ARTIST_FOLLOWED)
entity_idThe entity this event applies to
entity_typeThe entity type (e.g., user)
payloadThe full event payload (as JSONB)
fork_idThe fork (environment) this event belongs to
platform_idThe platform
application_idThe application
correlation_idLinks this event to the intent that caused it
created_atTimestamp (microseconds) when the event was committed

Why the ledger is a better audit trail

Compared to a manually maintained audit table:

It cannot be accidentally omitted. The runtime emits events as part of rule evaluation — there is no code path that changes state without producing an event. A manual audit table depends on developers remembering to write audit rows in every code path.

It cannot be retroactively modified. Events are appended, never updated. The ledger_events table has no UPDATE or DELETE paths in the application. A separate audit table can be corrupted or cleared by a developer with DB access.

It covers all state changes. Every action that changes entity state goes through the runtime and produces events. Partial state changes are impossible — the rules engine is atomic.

It is queryable. The ledger is a PostgreSQL table. You can query it with standard SQL for specific entities, time ranges, event types, or correlation IDs.


Querying audit history

-- All events for a specific user
SELECT event_type, entity_id, payload, created_at
FROM causet.ledger_events
WHERE entity_id = 'user-1'
  AND fork_id = 'production'
ORDER BY created_at ASC;
 
-- All ARTIST_FOLLOWED events in the last 24 hours
SELECT event_id, entity_id, payload, created_at
FROM causet.ledger_events
WHERE event_type = 'ARTIST_FOLLOWED'
  AND fork_id = 'production'
  AND created_at >= NOW() - INTERVAL '24 hours'
ORDER BY created_at DESC;
 
-- Trace what caused a specific state change (by correlation ID)
SELECT event_type, entity_id, payload, created_at
FROM causet.ledger_events
WHERE correlation_id = 'corr-abc123';

Audit projections

For specific compliance query shapes, create a projection table that materializes the audit data you need:

# projections/audit.projections.causet
projections:
  artist_follow_audit:
    source_events: [ARTIST_FOLLOWED, ARTIST_UNFOLLOWED]
    target:
      table: artist_follow_audit
      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 materialized view is optimized for audit queries (indexed, specific shape) while the underlying ledger remains the authoritative append-only record.


Replay for forensics

To understand the state of an entity at any point in time, replay events up to a specific timestamp:

-- All events for user-1 before a specific timestamp
SELECT event_type, payload
FROM causet.ledger_events
WHERE entity_id = 'user-1'
  AND created_at < '2026-06-25 18:00:00'
ORDER BY created_at ASC;

Apply these events in order to reconstruct entity state at that point. This is the same mechanism the runtime uses internally — entity state is always derived from events, never stored independently.


Immutability guarantees

The ledger is append-only by application design, not by database constraint. In practice:

  • The causet-runtime-service has no UPDATE or DELETE query paths for ledger_events
  • Database-level constraints (NOT NULL, foreign keys) protect data integrity
  • Row-level security or PostgreSQL publication can be configured to prevent manual tampering if required for compliance

Note: Causet does not provide cryptographic signing of ledger events. The ledger is tamper-evident in the sense that modifying historical events would require direct database access and would leave detectable gaps in sequence numbers and timestamps. For cryptographic immutability guarantees (e.g., for regulated industries), integrate a separate ledger technology like a blockchain or immutable audit log service.


PII in audit logs

The ledger is immutable — events cannot be deleted. This creates tension with GDPR right-to-erasure obligations.

If event payloads contain PII, deletion is not possible without corrupting the audit trail. Design your events to contain opaque IDs rather than PII values.

See PII Handling for specific strategies (cryptographic erasure, pseudonymization).


Retention

Ledger events accumulate indefinitely by default. For long-running applications, the ledger_events table grows continuously. Implement a retention policy based on your compliance requirements:

-- Archive events older than 2 years to cold storage, then delete
-- (implement this as a scheduled job, not a manual operation)
DELETE FROM causet.ledger_events
WHERE created_at < NOW() - INTERVAL '2 years'
  AND fork_id = 'production';

Warning: Deleting events breaks the ability to replay entity history for those events. Only delete events after archiving them to cold storage (e.g., S3 Glacier) and verifying the archive is complete.