Skip to content

Audit chain

A tamper-evident log of every decision, verifiable in the inbox and offline.

Every decision, approval, policy change and team change is appended to one hash chain in Postgres. Each entry’s hash covers every column of the entry plus the previous entry’s hash:

Output
hash(n) = base64url(sha256(hash(n-1) + "|" + canonicalJson(entry n)))
hash(0) = "GENESIS"

Editing, deleting or reordering an entry in the middle breaks every hash after it.

  • Activity lists every decision. Filter by agent, person, tool, verdict or rule, and open a run to see its plan, policy version and each call’s outcome.
  • Audit verifies the chain on the server and lists flagged entries.

Members see only their own runs.

An admin can export the whole chain and verify it with the CLI, using the same hashing code as the server:

Terminal
curl -b cookies.txt -o audit.jsonl \
  "https://intentlock.acme.dev/api/v1/audit?format=jsonl"
intentlock audit verify --file audit.jsonl

The result is either ok with a count, or the first broken sequence number and why. A member’s export holds only their own entries, which is not a complete chain.

The chain detects edits, deletions in the middle and reordering. Deleting the newest entries is not detectable from the chain alone; keep regular exports if that matters to you.

In hybrid mode the client decides locally and writes every decision to an outbox file, chained with its own hashes, before the tool runs. A background worker uploads batches to the server, which appends them to the same chain. On upload the server:

  • checks the client chain, and flags edited entries (chain_break) and missing ones (gap);
  • re-runs check() for each decision with its own signed token, and flags any decision looser than the policy (decision_mismatch);
  • compares the decision count in each report with what it received, and flags a truncated tail (truncated).

Flagged entries are stored, never dropped, and shown in the inbox.