Audit log — Article 12 record-keeping

⚠️ Not legal advice. Engineering documentation of a technical control.

Article 12 requires automatic, retained logging over the system's lifetime. AuditLogSink adds tamper-evidence on top of the runtime's durable sinks.

How the hash chain works

Each record carries record_hash = sha256(prev_hash || canonical_json(record)), where canonical_json is sorted-key, whitespace-free JSON. The genesis record links to prev_hash = "0" * 64. Any after-the-fact edit, deletion, or reorder changes a hash and breaks the chain:

from techrevati.runtime.compliance import AuditLogSink, SqliteAuditBackend

sink = AuditLogSink(SqliteAuditBackend("audit.db"))
# ... attach to AgentSession(audit_log=sink) or via the kit ...
result = sink.verify_chain()
assert result.valid                  # False + first_bad_sequence on tampering

AuditLogSink implements both EventSink (emit) and UsageSink (record), so a session writes lifecycle events and per-turn usage into one chain. On construction it resumes from the persisted tip, so the chain survives restarts.

Optional HMAC envelope

Pass signing_key=... to attach a detached HMAC over each record_hash. The key is never written to storage; verification with the wrong key (or none) fails:

sink = AuditLogSink(SqliteAuditBackend("audit.db"), signing_key=hsm_key)
sink.verify_chain(signing_key=hsm_key).valid

Retention & export

Threat model — tamper-evident, not tamper-proof

An adversary with write access to the backing store can rewrite the entire chain (recomputing every prev_hash) and produce an internally consistent forgery. The HMAC envelope raises the bar — the forger also needs the key. Real defenses are deployer infrastructure, not implemented here: