Audit log¶
Document type: Normative.
Scope: commands-owned AccessSummary, AuditRow, AuditLog,
transactional command-outbox projection, and GetAuditHistory.
1. Two evidence planes¶
The commands family exposes one append-only analytical table with two distinct inputs:
- Actor-aware dispatcher calls produce bounded advisory access evidence.
- Durable command state transitions produce transactional outbox events.
The command ledger, tick manifest, artifact index, evaluation receipt, and other family records remain authoritative for their outcomes. The analytical audit table is a query projection; it never replaces those records.
Trusted CommandDispatcher.apply and defer calls do not fabricate an actor
or authorization row. Actor-aware apply_as, defer_as, batch, and spawn
entry points attempt bounded access evidence. GetAuditHistory is a registered
commands-owned, direct-only read that calls AuditLog.query.
2. Append-only invariant¶
AuditLog has no delete or drop operation. Destroying a world, cleaning up a
rollout fork, cancelling commands, or removing a live registry binding does not
delete analytical rows. Durable rows remain queryable after the live world is
gone.
Rows live in the dedicated Iceberg table audit_rows. They are not part of
the Lance world store and are not stored in the control catalog. A managed
deployment supplies the shared storage session and an explicit audit
StorageConfig; table isolation does not require a second mutable session.
3. Bounded access evidence¶
An AccessSummary contains:
- operation;
- actor identity;
- optional world identity;
- decision (
allowedordenied); - outcome (
succeeded,failed,denied,rejected, orqueued); and - a small metadata mapping.
The canonical encoded summary is limited to 4096 bytes. The dispatcher accepts only an allowlist of routing and count keys, bounded scalar strings, finite numbers, booleans, and null. It does not copy component values, storage configuration, credentials, callbacks, repository diffs, validator output, critic findings, arbitrary results, or exception text.
The ordering for an actor-aware call is role preauthorization, exact availability, bounded coordinate resolution, quota policy, primary effect, then evidence. A role denial happens before world resolution. Direct-only or trusted-only rejection happens before scheduler persistence.
Access evidence is advisory. Failure to summarize, encode, or append it cannot replace the primary success or failure and cannot invite an unsafe retry of an already-applied operation.
4. Bounded buffering and backpressure¶
AuditLog.record converts accepted access evidence into AuditRow values and
buffers at most one configured batch (128 rows by default). Reaching the
threshold, flush, query, and shutdown append the current batch through
StorageService.
If a full batch cannot flush, the log retries that same batch before accepting
another row. A repeated failure rejects the incoming row with
AuditBackpressureError, retains the original bounded batch, and increments
rejected_rows. It never hides an outage behind an unbounded process-memory
queue.
AuditBackpressureError implements the public AvailabilityError contract.
The dispatcher suppresses advisory recording failures after the primary
decision; a host that directly invokes the log may still classify the error.
Concurrent writers rely on Iceberg's optimistic append/retry behavior through
StorageService. They do not create process-local audit forks.
5. Transactional outbox projection¶
Durable admission, retry/dead-letter/rejection, cancellation, and applied settlement append outbox events in the same control-authority transaction as the state transition.
Projection:
- reads available outbox events, optionally for one world;
- flushes already-accepted access rows;
- appends the event rows directly, without placing them in the bounded access buffer; and
- acknowledges the source watermark only after the append succeeds.
A crash after append but before acknowledgement may replay an event.
audit_id is the outbox event identity and query-time deduplication produces
one analytical row. If append fails, the source remains authoritative and
retryable.
The composition root retains durable world-to-catalog coordinates. History reads and shutdown can therefore project events for discovered or resumed worlds even when the current process did not admit their commands.
6. Row schema¶
AuditRow is defined in archetype.commands.models with:
audit_idcommand_idworld_idactor_idcommand_typestatuspayload_jsonaccepted_atapplied_at- nullable
idempotency_key
Application-scoped access rows have no fabricated world identity. A
world-filtered query therefore does not include an application-scoped
create_world decision.
7. Query contract¶
AuditLog.query is read-only and supports:
world_idactor_ididempotency_keystatuslimit
It projects available outbox rows, flushes access rows, reads lazily through
Daft, deduplicates by audit_id, applies predicates, and orders by
(accepted_at, audit_id). A positive limit selects the newest rows before
restoring ascending order. Zero returns an empty frame; a negative limit is
invalid.
tick_range remains a validated compatibility input because existing runtime
and HTTP history methods expose it. It has no filtering effect until an
explicit row-schema migration adds a tick field.
Compaction and snapshot-retention policy are storage-maintenance concerns outside this contract.
Executable contracts¶
tests/commands/test_audit_projection_contracts.pytests/commands/test_scheduler_audit_contracts.pytests/commands/test_dispatch_policy_contracts.pytests/integration/test_fork_destroy_contracts.py