Python and Claude · 4 of 6
The chain.
Every action the agent takes is one JSON line that carries the hash of the line before it. Edit one in place and the verifier names it.
Why Python holds this
An audit log that the agent can edit is a diary. The chain makes it evidence. Each entry hashes its own fields together with the previous entry's hash for the same merchant, so a row edited in place, deleted, or reordered breaks every later link, and verify walks the entries in order and names the first broken one. Appends for one merchant are serialized in Postgres with an advisory lock around read-previous-and-insert, so concurrent appends cannot fork the chain.
Session ids never enter the table. An entry carries the first sixteen hex characters of the SHA-256 of the session id, so a row can be correlated but never replayed. The digest idea is the reference's; the chain, the verifier, and the lock are mine. The construction is Haber and Stornetta's from 1991, applied to logs by Schneier and Kelsey in 1999.
The code
skoor_harness/audit.py, merchant agent harness. The chain is private code today; publishing it with its tests is on the list.
GENESIS_HASH = "0" * 64
def session_digest(session_id: str | None) -> str | None:
"""What an entry stores for a session: sha256(session_id)[:16], never the id."""
if not session_id:
return None
return hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:16]
def canonical_json(value: Any) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
def entry_hash(entry: AuditEntry, previous_hash: str) -> str:
body = canonical_json({**entry.fields(), "previous_hash": previous_hash})
return hashlib.sha256(body.encode("utf-8")).hexdigest()
# Appends for one merchant run inside:
# SELECT pg_advisory_xact_lock(hashtext(%(merchant_id)s));
# SELECT entry_hash FROM skoor.audit_log WHERE merchant_id = %s ORDER BY seq DESC LIMIT 1;
# INSERT INTO skoor.audit_log (...) VALUES (...);
# so two writers cannot both read the same previous hash and fork the chain.
What it does not stop
Anything, by itself. An audit log is not a control. It is what lets a person find out what happened and prove it, which is the condition for trusting every other control. And a chain is tamper-evident only relative to its head: publish the head somewhere the agent cannot write, or the whole chain can be rewritten together.