# ai-audit-trail — full documentation

This file is the concatenation of all documentation pages, intended for ingestion by LLM tools (Cursor `@docs`, Anthropic Skills, Context7, etc.). Generated by `scripts/build_llms_full.py`.



---


<!-- source: docs/index.md -->

# ai-audit-trail

**Prove what your AI did, why, and that nobody changed the record.**

`ai-audit-trail` is a Python library producing tamper-evident
[Decision Receipts](concepts.md) for every decision your AI system makes.

- **Ed25519** signatures over canonical JSON (RFC 8785 equivalent)
- **SHA-256 hash chain** per tenant — any retroactive edit is detected
- **ISO 42001 / NIST AI RMF / EU AI Act** [compliance crosswalks](compliance.md) built in
- **Drop-in adapters** for [FastAPI, LangChain, OpenAI, Anthropic](integrations.md)
- **KMS support** for HashiCorp Vault, AWS KMS, AWS Secrets Manager
- **Offline verification** — auditors verify a ZIP bundle without runtime access to your systems
- **Pure Python**, MIT, no native build steps, 234+ tests, mypy --strict, 0 errors

```bash
pip install ai-audit-trail
```

[Quickstart →](quickstart.md){ .md-button .md-button--primary }
[Compliance mappings →](compliance.md){ .md-button }
[GitHub →](https://github.com/sundsoffice-tech/ai-audit-trail){ .md-button }

---

## Why this exists

The **EU AI Act** becomes mandatory for high-risk AI systems in **August 2026**.
It requires tamper-evident logs proving every decision was made correctly
([Article 12](compliance.md)). Most teams are solving this
with normal application logging — which is neither tamper-evident nor legally
defensible in an audit.

`ai-audit-trail` closes this gap with **cryptographic receipts** that any
auditor can verify offline, without accessing your production systems. The same
principle as a blockchain — without the blockchain overhead, the SaaS
dependency, or the vendor lock-in.

## Who this is for

- **Regulated AI teams** (FinTech, HealthTech, LegalTech, InsurTech) who must
  prove compliance to internal or external auditors.
- **Enterprise platform teams** deploying LLM agents with tool access who need
  per-tool-call provenance.
- **Security and compliance officers** who need audit-ready evidence packages.
- **Developers** who want `pip install` and three lines of code, not a
  platform migration.

## Shared responsibility

`ai-audit-trail` provides the **technical building blocks** that *support*
ISO 42001, NIST AI RMF, and EU AI Act compliance. It does not, by itself,
guarantee regulatory compliance — compliance is an organisational obligation
that extends beyond any single software component. See the
[Shared Responsibility Model](https://github.com/sundsoffice-tech/ai-audit-trail#shared-responsibility-model)
in the repository for the boundary between what the library guarantees and
what your organisation must do.



---


<!-- source: docs/quickstart.md -->

# Quickstart

## Install

```bash
pip install ai-audit-trail
```

Optional integrations are extras — install only what you use:

```bash
pip install "ai-audit-trail[fastapi]"     # FastAPI / Starlette middleware
pip install "ai-audit-trail[langchain]"   # LangChain callback handler
pip install "ai-audit-trail[openai]"      # AuditedOpenAI proxy client
pip install "ai-audit-trail[anthropic]"   # AuditedAnthropic proxy client
pip install "ai-audit-trail[postgres]"    # PostgresColdBackend
pip install "ai-audit-trail[s3]"          # S3ArchiveBackend
pip install "ai-audit-trail[vault]"       # HashiCorp Vault KeyProvider
pip install "ai-audit-trail[aws-kms]"     # AWS KMS / Secrets Manager
pip install "ai-audit-trail[all]"         # everything
```

## Generate a signing key

```bash
ai-audit gen-key
```

Stores nothing — prints the hex-encoded Ed25519 seed to stdout. Capture it in
your secret store (Vault, AWS Secrets Manager, an environment variable, …)
and pass it to the library at startup.

## Hello, audit trail

```python
from ai_audit import (
    AuditConfig, init_audit_config,
    ReceiptCollector, ReceiptStore,
    verify_chain, get_verify_key_hex,
)

# Configure once at startup
init_audit_config(AuditConfig.from_env())   # reads AI_AUDIT_SIGNING_KEY
store = ReceiptStore()

# Wrap each request / decision
collector = ReceiptCollector(trace_id="req-1", tenant_id="acme")
collector.set_input("What's the weather?")
collector.add_check("safety", score=0.05, threshold=0.5)
collector.set_output("I'm a chatbot, I can't check live weather.")
collector.set_action("allow")
collector.emit(store)
collector.cleanup()

# Verify the chain (e.g. before handing data to an auditor)
result = verify_chain(store.get_by_tenant("acme"), get_verify_key_hex())
assert result.valid
```

## FastAPI middleware

```python
from fastapi import FastAPI
from ai_audit import AuditConfig, init_audit_config, ReceiptStore
from ai_audit.integrations.fastapi import AuditMiddleware

init_audit_config(AuditConfig.from_env())
store = ReceiptStore()

app = FastAPI()
app.add_middleware(
    AuditMiddleware,
    store=store,
    tenant_id="acme",
    path_prefix="/v1/ai/",   # only audit AI endpoints
)
```

Every request to `/v1/ai/*` now produces one `DecisionReceipt`, sealed,
chained, and ready for offline verification.

## Verify a bundle offline

After exporting an evidence package via `export_evidence_package(bundle.zip)`,
auditors can verify it without any access to your systems:

```bash
ai-audit verify bundle.zip
```

Returns `PASS` (chain intact, signatures valid) or `FAIL` with the index of
the first inconsistent receipt.

## Production checklist

1. Set a persistent signing key:
   ```bash
   export AI_AUDIT_SIGNING_KEY="$(ai-audit gen-key --quiet)"
   export AI_AUDIT_ENV=production
   ```
2. Pick a [storage backend](https://github.com/sundsoffice-tech/ai-audit-trail#storage-backends)
   (Redis, Postgres, S3, …) — the in-memory default is dev-only.
3. Pick a [KMS provider](https://github.com/sundsoffice-tech/ai-audit-trail#kms-providers)
   if your security model requires it.
4. Decide on a [PII redaction policy](https://github.com/sundsoffice-tech/ai-audit-trail#pii)
   before sealing — once a receipt is sealed, the input/output is hashed.



---


<!-- source: docs/concepts.md -->

# Concepts

## Decision Receipt

A `DecisionReceipt` captures **one** security-relevant decision your AI system
made. The receipt is the unit of audit evidence.

Each receipt contains:

| Field | Purpose |
| --- | --- |
| `receipt_id` | Stable ID for cross-referencing across systems |
| `timestamp` | UTC ISO 8601, set at construction |
| `trace_id` / `session_id` | Correlate to your application's request IDs |
| `tenant_id` | Multi-tenant scope identifier (no semantics imposed) |
| `input_c14n` | SHA-256 of the canonicalised input (NFKC + lowercase) |
| `output_hash` | SHA-256 of the model's output |
| `state_digest` | Hash of security-relevant context |
| `checks` | Ordered list of `CheckRecord`s (safety, supervisor, routing, …) |
| `action` | `allow` / `reject` / `fail_retry` / `cache_hit` / `escalate` / `bypass` |
| `model_id` / `config_digest` | Model provenance |
| `prev_receipt_hash` | Chain linkage — points at the previous receipt in the same tenant |
| `receipt_hash` | SHA-256 self-hash over canonical JSON (excluding `receipt_hash` and `signature`) |
| `signature` | Ed25519 signature over the same canonical bytes |

## Hash chain

Every emitted receipt for a tenant carries `prev_receipt_hash` pointing at the
previous receipt's `receipt_hash`. The chain is **append-only**:

- Inserting a receipt **between** two existing ones changes downstream
  `prev_receipt_hash` values, which changes downstream `receipt_hash` values,
  which invalidates the Ed25519 signatures.
- Editing the content of an old receipt changes its `receipt_hash`, which
  invalidates the signature directly *and* breaks every chain link after it.
- Deleting an old receipt leaves a dangling `prev_receipt_hash` reference in
  the next receipt — `verify_chain` reports the gap.

Verification is O(N) by Ed25519 signature checks plus O(N) hash equalities.

## Merkle batch sealing

For high-throughput pipelines (10k+ receipts per second) per-receipt chaining
becomes a write hotspot. `MerkleBatcher` accumulates receipts and emits one
RFC-6962-style `BatchSeal` per batch — only the batch *roots* are chained.
Verification cost drops to O(log N) per batch via inclusion proofs.

## Ed25519 signatures

Each `seal()` computes a deterministic JSON canonicalisation of the receipt
(`orjson.OPT_SORT_KEYS`, RFC 8785-equivalent) and signs the resulting bytes
with libsodium's Ed25519 (via PyNaCl). The same canonical bytes are also
SHA-256-hashed for `receipt_hash`, so the signature and the chain linkage both
witness the exact same payload.

The signing key is loaded via the active `KeyProvider`. The default loads the
seed from `AuditConfig(signing_key_hex=...)`; production deployments inject
[`VaultKeyProvider`, `AWSKMSKeyProvider`, or `AWSSecretsManagerKeyProvider`](
https://github.com/sundsoffice-tech/ai-audit-trail#kms-providers).

## TOCTOU safety

`ReceiptCollector.emit()` calls `ReceiptStore.atomic_seal_and_append()` which
acquires a per-tenant `threading.Lock` for the *seal + chain-tip update*
critical section. Concurrent emits for the same tenant cannot interleave to
produce a forked chain.

## Evidence package

`export_evidence_package(receipts, "bundle.zip")` produces a self-contained ZIP
with:

- `receipts.jsonl` — one JSON object per receipt
- `chain_metadata.json` — first/last hashes, count, public key
- `public_key.hex` — the Ed25519 verify key
- `verify.py` — a standalone, dependency-light verifier script
- `manifest.json` — Ed25519-signed file manifest with SHA-256 of every other file

The auditor verifies offline:

```bash
ai-audit verify bundle.zip
```

No access to your production systems is required.



---


<!-- source: docs/integrations.md -->

# Integrations

Each integration lives in its own optional submodule and pulls in its own
optional dependencies. Importing `ai_audit.integrations` itself does **not**
load any optional package.

## FastAPI / Starlette

```python
from fastapi import FastAPI
from ai_audit import AuditConfig, init_audit_config, ReceiptStore
from ai_audit.integrations.fastapi import AuditMiddleware

init_audit_config(AuditConfig.from_env())
store = ReceiptStore()

app = FastAPI()
app.add_middleware(
    AuditMiddleware,
    store=store,
    tenant_id="acme",
    path_prefix="/v1/ai/",        # only audit AI endpoints
    capture_body=True,            # include request body in input hash
    max_body_bytes=64 * 1024,
    trace_header="x-trace-id",    # propagate from upstream
    session_header="x-session-id",
)
```

The middleware emits one `DecisionReceipt` per matched request, mapping HTTP
status codes to actions:

- `2xx` / `3xx` → `ALLOW`
- `4xx` / `5xx` → `REJECT`
- exception during `call_next` → `FAIL_RETRY`

Install: `pip install "ai-audit-trail[fastapi]"`

## LangChain

```python
from langchain_openai import ChatOpenAI
from ai_audit import AuditConfig, init_audit_config, ReceiptStore
from ai_audit.integrations.langchain import AuditCallbackHandler

init_audit_config(AuditConfig.from_env())
store = ReceiptStore()

handler = AuditCallbackHandler(store=store, tenant_id="acme")
llm = ChatOpenAI(callbacks=[handler])
```

One receipt per LLM call (`on_llm_start` / `on_llm_end` / `on_llm_error`).
Errors map to `FAIL_RETRY`.

Install: `pip install "ai-audit-trail[langchain]"`

## OpenAI SDK

```python
from ai_audit.integrations.openai import AuditedOpenAI

client = AuditedOpenAI(store=store, tenant_id="acme")
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hello"}],
)
```

`finish_reason` maps to actions:

- `content_filter` → `REJECT`
- `length` → `ALLOW` + `truncated_by_length` reason code
- `stop` (and others) → `ALLOW`

For manual control over the receipt:

```python
from ai_audit.integrations.openai import emit_chat_completion_receipt

response = openai_client.chat.completions.create(...)
emit_chat_completion_receipt(
    store, tenant_id="acme", model="gpt-4o-mini",
    messages=messages, response=response,
)
```

Install: `pip install "ai-audit-trail[openai]"`

## Anthropic SDK

```python
from ai_audit.integrations.anthropic import AuditedAnthropic

client = AuditedAnthropic(store=store, tenant_id="acme")
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
```

`stop_reason` maps to actions:

- `refusal` → `REJECT`
- `max_tokens` → `ALLOW` + `truncated_by_length` reason code
- `end_turn` (and others) → `ALLOW`

Install: `pip install "ai-audit-trail[anthropic]"`

## Build your own

Every integration is ~100 lines of glue using the same three primitives:

```python
from ai_audit import ReceiptCollector, ReceiptStore

collector = ReceiptCollector(trace_id=..., tenant_id=...)
collector.set_input(prompt_text)
collector.set_output(response_text)
collector.set_action("allow")
collector.emit(store)
collector.cleanup()
```

There is no framework coupling — `tenant_id`, `trace_id`, and `session_id`
are opaque strings to the protocol.



---


<!-- source: docs/compliance.md -->

# Compliance crosswalks

`ai-audit-trail` ships with formal mappings between Decision Receipt fields
and three regulatory frameworks. The mappings are deterministic — given a
list of receipts, the library returns coverage statistics per control.

```python
from ai_audit import build_crosswalk, nist_function_map, build_compliance_summary

receipts = store.get_by_tenant("acme")

# Per-control coverage
crosswalk = build_crosswalk(receipts)
for c in crosswalk:
    print(f"{c.framework} {c.control_id}: {c.status}  ({c.coverage:.0%})")

# NIST AI RMF function-level rollup
nist = nist_function_map(receipts)
print(nist)  # {"GOVERN": "PASS", "MAP": "PASS", "MEASURE": "PASS", "MANAGE": "PASS"}

# SPRT-certified summary with reject_rate, allow_rate, action_distribution
summary = build_compliance_summary(receipts)
print(f"Reject rate: {summary.reject_rate:.1%}  Status: {summary.sprt_status}")
```

## ISO/IEC 42001

| Control | What it covers | Receipt fields used |
| --- | --- | --- |
| **A.6.2.8** | Logging of AI system activities | timestamp, trace_id, action, checks |
| **A.7.5** | Data provenance | input_c14n, state_digest, model_id |
| **A.6.2.6** | AI system performance evaluation | checks (scores, thresholds, fired) |
| **A.8.4** | AI system output controls | output_hash, action, reason_codes |
| **A.5.3** | AI risk assessment | reason_codes, NIST tags, escalation actions |

## NIST AI RMF (1.0)

| Function | Receipt fields used |
| --- | --- |
| **GOVERN** | config_digest, NIST tags, signing-key provenance |
| **MAP** | model_id, checks (context identification) |
| **MEASURE** | scores, SPRT certification, drift detection |
| **MANAGE** | action, reason_codes, escalation chain |

`nist_function_map()` returns a dict of function → status (`PASS` / `MONITORING` / `FLAGGED`)
with per-function coverage percentage.

## EU AI Act

The library covers articles primarily concerned with operational record-keeping:

### EU AI Act Art. 9 — Risk management system

Receipts surface ongoing risk evidence: the `checks` list includes the safety,
fairness, and supervisor evaluations applied at decision time. Aggregating
over a tenant with `build_compliance_summary` produces the running reject
rate that Art. 9 expects to be monitored continuously.

### EU AI Act Art. 12 — Automatic recording of events

Article 12 requires automatic, traceable, *tamper-evident* event logs. This
is the article most other logging tools fail. Each `DecisionReceipt` is:

- automatic (emitted from middleware / collector, not manual)
- traceable (signed `trace_id` and `session_id`)
- tamper-evident (Ed25519 signature + SHA-256 hash chain)

### EU AI Act Art. 13 — Transparency / instructions for use

`config_digest` and `model_id` provide cryptographic anchors for the model
configuration in effect at decision time, so transparency disclosures can be
matched to specific receipts.

### EU AI Act Art. 17 — Quality management system

The `ComplianceReportGenerator` produces per-period reports binding receipts
to ISO 42001 / NIST AI RMF controls — the QMS evidence trail.

### EU AI Act Art. 18 — Documentation retention

Evidence-package export plus the 30-day default Redis TTL plus the optional
`PostgresColdBackend` / `S3ArchiveBackend` cover the 10-year retention horizon
that Art. 18 mandates for high-risk systems.

## What this is *not*

A library cannot deliver compliance on its own. `ai-audit-trail` provides the
**technical record-keeping primitives** (Art. 12 / A.6.2.8 / GOVERN) plus
*supporting* evidence for several adjacent controls. It does not:

- replace your AI risk assessment process (Art. 9 — organisational)
- replace your data-governance and conformity-assessment work (Art. 10 / 43)
- replace human oversight (Art. 14 — operational)
- substitute legal advice or constitute a notified body opinion

See the
[Shared Responsibility Model](https://github.com/sundsoffice-tech/ai-audit-trail#shared-responsibility-model)
for the boundary between what the library guarantees and what your organisation
must do.



---


<!-- source: docs/cli.md -->

# Command-line interface

After install, the CLI is callable directly as `ai-audit`. The
`python -m ai_audit ...` form remains supported as an equivalent.

## `ai-audit gen-key`

Generate a fresh Ed25519 signing key (32-byte hex seed) plus the matching
public verify key. Stores nothing — you decide where to persist it.

```console
$ ai-audit gen-key
# Ed25519 signing key (KEEP SECRET)
AI_AUDIT_SIGNING_KEY=d58251819896733b749755e52a6ca32d3161c028ce71ae0faa941aabbcf1b70d

# Public verification key (safe to share / commit)
AI_AUDIT_VERIFY_KEY=445e2ee195915c1aa07e8516e9e6b62f6699ff873dcc9c29f82033a0db8a01f4

# Usage:
#   export AI_AUDIT_SIGNING_KEY=<seed>
#   export AI_AUDIT_ENV=production
#   from ai_audit import AuditConfig, init_audit_config
#   init_audit_config(AuditConfig.from_env())
```

For scripting / piping, use `--quiet` to print only the seed:

```console
$ ai-audit gen-key --quiet
d58251819896733b749755e52a6ca32d3161c028ce71ae0faa941aabbcf1b70d

$ export AI_AUDIT_SIGNING_KEY="$(ai-audit gen-key --quiet)"
```

## `ai-audit verify`

Verify an evidence-package ZIP **offline**, without any access to the
production system that produced it. Used by external auditors.

```console
$ ai-audit verify bundle.zip
PASS: bundle.zip - all checks passed
```

`PASS` means: the manifest is signed correctly, every file's SHA-256 matches
the manifest, every receipt's Ed25519 signature is valid against the embedded
public key, and the hash chain is intact end to end. `FAIL` reports the index
of the first failing receipt and the failure mode.

Add `-v` for verbose output:

```console
$ ai-audit verify bundle.zip -v
Verifying: bundle.zip
PASS: bundle.zip - all checks passed
```

## `ai-audit info`

Print the package version and a couple of runtime self-checks. Useful in bug
reports and CI.

```console
$ ai-audit info
ai-audit-trail 0.4.3
Python: 3.12.10
PyNaCl: available
```
