Skip to main content

An append-only, hash-chained SQLite evidence log for LLM systems, with deterministic replay.

Project description

incident-ledger

An append-only, hash-chained SQLite evidence log for LLM systems — with deterministic replay. Pure Python standard library, no dependencies, runs anywhere.

When an AI system takes an action (issues a refund, moves money, files a ticket), "we have logs" is not the same as having evidence. incident-ledger makes an inference log into evidence by enforcing two properties in code, not prose:

  • Contemporaneity (P2) — each row is written at inference time, stamped with the wall-clock moment it happened. The only mutation is append: there is no update and no delete.
  • Tamper-evidence — each row's entry_hash is a SHA-256 over its content and the previous row's hash. Altering or deleting any earlier row — even by editing the SQLite file directly — breaks the chain, and verify() reports the exact seq where it broke.
  • Reproducibility (P3) — a sealed decision can be reconstructed into a replay bundle (pinned model version, seed, exact context) and rerun, with an honest boundary: exact match at temperature 0, or a pre-registered variance tolerance above it, and an explicit not-replayable verdict when no seed was captured.

This is the minimal reference implementation accompanying the paper "Implementing Agentic-AI Accountability: A Minimal, From-Scratch Reference Stack Mapped to the IMDA MGF, EU AI Act, NIST, and ISO/IEC 42001" — see CITATION.cff. It implements the logging / audit-trail control common to those frameworks. It is deliberately small: every line is meant to be read.

Install

pip install incident-ledger

Or from source:

git clone https://github.com/MaqAnquor/incident-ledger
cd incident-ledger
pip install -e .

Requires Python ≥ 3.10. No third-party dependencies.

Quickstart

from incident_ledger import IncidentLedger

# Open on a file for a durable ledger (":memory:" for tests).
ledger = IncidentLedger("evidence.db")

entry = ledger.append(
    query="Is order #9871244 refundable?",
    answer="Yes — $487 refund issued.",
    confidence=0.91,
    model_version="refund-clf-v3@sha-9c1a2f",   # pin the exact model; never "latest"
    context_snapshot="Policy R-12: refunds within 30 days of delivery.",
    sources=("policy://refunds/R-12", "order://9871244"),
    disclosed_limits=("partial-refund case untested",),
    downstream_action="refund.issue",
    trace={"sampling": {"seed": 482991, "temperature": 0.0}},
)

# The chain is intact...
assert ledger.verify().ok

# ...and tampering is detectable, even done directly against the file.
import sqlite3
raw = sqlite3.connect("evidence.db")
raw.execute("UPDATE ledger SET answer = 'No refund' WHERE seq = 1")
raw.commit(); raw.close()

result = IncidentLedger("evidence.db").verify()
assert not result.ok
print(result.broken_seq, result.reason)   # 1  "altered content at seq 1: seal does not recompute"

Replay a sealed decision

from incident_ledger import replay_from_ledger

# Reconstruct the bundle from a sealed row and rerun it. The attempt itself
# is sealed back into the ledger as a new contemporaneous entry.
outcome = replay_from_ledger(ledger, seq=1)
print(outcome.mode, outcome.reproduced)   # "deterministic" True

A row sealed with no captured seed is reported not-replayable rather than silently treated as deterministic — a gap you disclose, not one you paper over.

API

Symbol Purpose
IncidentLedger(path=":memory:", *, now=time.time) The store. append(...), verify(), get(seq), entries(), head_hash, len().
LedgerEntry One immutable sealed row; payload(), recompute_hash().
VerificationResult Verdict of verify(): ok, entries_checked, broken_seq, reason.
ReplayBundle Everything needed to rerun one decision: model version, seed, context, temperature, prompt.
replay(bundle, ...) / replay_from_ledger(ledger, seq, ...) Rerun a bundle, or reconstruct one from a sealed row and rerun it.
ReplayResult mode (deterministic | bounded-variance | pin-mismatch | not-replayable), reproduced, agreement.

Measured overhead

Tamper-evidence is cheap. On a modern laptop (file-backed SQLite, one synchronous commit per append), over 1,000 appends:

Metric Value
Mean append latency ~0.47 ms (p95 ~0.60 ms)
Storage per 1,000 inferences ~4.0 MB (payload-dominated)
Full-chain verify() over 1,000 rows ~31 ms
Hash-chain overhead vs. a plain-insert baseline under 0.1 ms per append (within run-to-run variance)

The hash chain adds far less than the SQLite commit already costs. Regenerate the numbers on your own machine:

python benchmarks/ledger_overhead.py --n 1000 --reps 3

Figures are measured on a single machine, single thread, one payload shape and one durability setting; they do not extrapolate to production write-concurrency or a different retention policy.

Tests

pip install pytest
pytest -q

Citing

If you use this software, please cite both the software and the paper — see CITATION.cff.

Both DOIs are reserved and not yet resolvable — they begin resolving when the Zenodo records are published. If a link above 404s, the deposit has not landed yet.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

incident_ledger-1.0.0.tar.gz (22.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

incident_ledger-1.0.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file incident_ledger-1.0.0.tar.gz.

File metadata

  • Download URL: incident_ledger-1.0.0.tar.gz
  • Upload date:
  • Size: 22.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for incident_ledger-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1e98a1204f9f57ea6b36e8a796738a33878572e49188cdcbaf95aa5cb734b150
MD5 683e5d88badfe27e91e36936dd789a85
BLAKE2b-256 1fe1d4f1befbee4c46b31aa34c5191173ad4c6d81e146621a6a7b964ed8e2613

See more details on using hashes here.

File details

Details for the file incident_ledger-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for incident_ledger-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c78e2f408da66bea8e3ef9357baa456897bcc3d7ccb041d5c2996b1d5a7100c0
MD5 406cf3ff194a18e0408dd465781c4817
BLAKE2b-256 33b6c85cc70702bcf6cb0685a40b32c5613c76c4286f5ed394d353bb54d3e9d2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page