# Limits and operations

## Explicit events are incomplete by design

AuditLog records only events that the application writes. It cannot recover a
missing event from a changed row. SQL consoles, migrations, ETL jobs,
replication workers, and forgotten code paths can change data without producing
an event.

AuditLog provides semantic audit logging, not exhaustive database provenance.

## Atomicity stops at the database boundary

A domain write and event are atomic when the caller places both in one
transaction on the same database. Writes to another database or external
service cannot join that transaction.

AuditLog does not publish events. If an application must deliver messages,
write a separate outbox row in the same transaction and let an outbox worker
handle delivery and retries.

## Time and order have narrow meanings

PostgreSQL sets `occurred_at` with `transaction_timestamp()`. It is the
transaction start time, not insertion time, commit time, or the exact time a
person acted.

All events written in one transaction share that timestamp. Queries use the
event UUID as a deterministic display tie-breaker, but UUID order does not show
insertion or causal order. Use `causation_id` to record a direct cause.

A correlation ID groups work into one request or business operation. It does
not prove that one event caused another.

## Snapshots can become sensitive or stale

Actor labels and reasons are snapshots, not live references. Later changes do
not update them. They can contain personal or sensitive data and may need their
own access and retention policy.

JSON metadata weakens schema guarantees. AuditLog does not enforce a metadata
size limit. The application must set and enforce that limit and store only
queryable facts. Do not store complete row images, credentials, access tokens,
or unbounded payloads.

AuditLog canonicalizes metadata through JSON before insertion. Returned
metadata therefore uses string keys and JSON primitive values. Application code
must not rely on atom keys or Elixir-specific nested values surviving the
boundary.

## Index maintenance adds write and storage cost

Every insert updates the indexes created by `AuditLog.Migration` and consumes
storage. Measure that cost at the application's actual event rate. Inspect the
migration SQL when an exact index inventory matters.

Do not add broad JSON, action, reason, or causation indexes without a measured
query that needs them.

## Append-only means ordinary DML only

An `ENABLE ALWAYS` trigger rejects `UPDATE` and `DELETE`, including DML from a
logical-replication apply worker running with
`session_replication_role = replica`.

This is not tamper evidence. A table owner or superuser can disable or replace
the trigger, use `TRUNCATE`, alter or drop the table, or edit physical backups.
Database administration remains more powerful than the trigger.

The trigger also blocks ordinary cleanup. Retention must use conspicuous,
reviewed maintenance DDL. AuditLog exposes no runtime deletion function and
no hidden bypass.

Calling `AuditLog.Migration.down/1` drops the event table and permanently
removes its contents. Use it for migration rollback only when that data loss is
acceptable.

## Retention, restore, and maintenance need runbooks

Before production use, define:

- who may read actor labels, reasons, and metadata;
- how long each class of event must remain;
- how backups and restores preserve the table and `ALWAYS` trigger state;
- who must review, test, and record destructive retention work;
- who must verify the trigger after replication or restore, and how; and
- who must stop application traffic during destructive maintenance, and how.

Removing events changes the audit record permanently. Test retention and
restore procedures against a copy that reproduces the production data volume
and PostgreSQL configuration before relying on them.
