The audit log is the second architectural commitment that makes closegate defensible to your external auditor. It does three things differently from the typical application audit log:
1. Append-only at the database layer
The audit_events table has two SQLite triggers attached:
CREATE TRIGGER audit_no_update
BEFORE UPDATE ON audit_events
BEGIN
SELECT RAISE(ABORT, 'audit_events is append-only');
END;
CREATE TRIGGER audit_no_delete
BEFORE DELETE ON audit_events
BEGIN
SELECT RAISE(ABORT, 'audit_events is append-only');
END;
The mutation refusal happens at the database. An insider with
code-write access to the application layer can't bypass it. An ops engineer
with raw SQL access can't bypass it either — the trigger fires regardless
of the connection. Only INSERT succeeds.
2. Every event carries verbatim policy clause text
When the policy gate fires, the event row includes:
- event_type:
OK/POLICY_VIOLATION/ESCALATION_PENDING/ etc. - actor_id:
human:<id>/llm:<session>/engine:<process> - policy_version: git commit hash of
policy.yamlat decision time - clause_text: the exact words of the rule from
policy.yaml - clause_pointer: JSON-pointer (RFC 6901) into the parsed policy structure
- match_id / transition_id: the workflow entity
- amount_usd, entity_id, accounts: the materiality / SoD inputs
- timestamp_utc: monotonic via
gate_now_utc() - hash_prev, hash_self: hash chain for tamper detection
Your auditor reads clause_text verbatim into the PBC bundle.
Your engineer greps for clause_pointer in logs. Both are the
same string of words.
3. Hash-chained for replay-detection
Each row computes hash_self = sha256(prev_hash + canonical_event_json).
The chain detects any after-the-fact insertion or row-level rewrite that
bypasses the triggers (which is hard, but not impossible at the OS level).
Run closegate-engine audit verify on a clean checkout against
your recon.db. It exits 0 on intact, 2 on broken — used as a CI smoke
test on the SOC 2 monitoring loop.
Querying the audit log
From the CLI:
# Recent events
closegate-engine audit tail -n 50
# Time-bounded
closegate-engine audit tail --since 2026-03-01 --until 2026-03-31
# By actor / event type
closegate-engine audit tail --actor "human:alice@example.com"
closegate-engine audit tail --event-type POLICY_VIOLATION
# Tamper-check
closegate-engine audit verify From the MCP query_audit tool (T0, read-only — your AI agent can use it freely):
query_audit(
since="2026-03-01T00:00:00Z",
until="2026-03-31T23:59:59Z",
event_type="POLICY_VIOLATION",
match_id="m-abc-123"
) The PBC bundle
For SOC 2 Type 2 or SOX walkthroughs, the bundle is produced by:
closegate-engine audit-evidence-export \
--since 2026-01-01 --until 2026-03-31 \
--out evidence-2026-Q1.zip Seven files: audit sample, actor registry, dead-letters, policy versions, eval runs, sweeper runs, README. Reproducible from any clean checkout. Full walkthrough in For auditors.
What this is not
- Not a SIEM. The audit log is the book-of-record for state-changing decisions. Pipe to your SIEM for cross-system correlation; the audit log is the source-of-truth.
- Not a write-ahead log. The WAL is for crash recovery; the audit log is for audit attribution. Both can coexist; they record different things.
- Not a generic event store. The schema is finance-specific (clause text, materiality, SoD fields). For non-finance event sourcing, use a different store.
Adjacent reading
- The policy gate — what writes to the audit log
- Compliance mappings — how the audit log satisfies SOC 2 CC4.1 + PCAOB AS 1215 + SOX 404 evidence requirements
- For auditors — the 60-minute walkthrough