Metadata-Version: 2.4
Name: arcaeon-audit
Version: 0.1.4
Summary: Tamper-evident, audit-ready logs for AI agents — ISO 42001 / SOC 2 / EU AI Act evidence. Prove what your agent did.
Author: Arcaeon
License: MIT
Project-URL: Homepage, https://arcaeon.io
Project-URL: Source, https://github.com/dan8433-user/arcaeon-audit
Keywords: ai,agent,audit,audit-log,tamper-evident,provenance,eu-ai-act,article-12,compliance,hash-chain,observability
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: arcaeon-ledger>=0.5.3

# arcaeon-audit

**Tamper-evident, audit-ready logs for AI agents.**
*Observability shows you what your agent did. This lets you **prove** it wasn't altered —
the evidence your ISO 42001 / SOC 2 auditor (and, by 2027, the EU AI Act) asks for.*

```bash
pip install arcaeon-audit
```

## Why this exists

If you ship an AI agent into anything regulated or enterprise-sold, you're already
being asked to **keep records of what it did and produce them on demand** — by your
**SOC 2** auditor, by **ISO/IEC 42001** (Annex A.6.2.8: keep AI event logs across the
lifecycle), and by the security/AI-governance questionnaires your enterprise customers
send *today*. The **EU AI Act, Article 12** adds a hard tamper-evident-logging
mandate for high-risk systems from **2 December 2027** (Digital Omnibus, Reg. (EU)
2026/1744; penalties to €35M or 7% of revenue).

Almost all of that forces you to *keep* records. Almost none of it is satisfied by a
plain log file — anyone with write access can edit, delete, or reorder a past record
and nothing shows. What auditors and regulators want is an **append-only,
integrity-protected** record you can hand over and have *independently verified*.
That integrity layer — the provable part — is exactly what this gives you.

A normal log file doesn't meet the bar: anyone with write access can edit,
delete, or reorder a past record and nothing shows. The regulators' expectation
is an **append-only, integrity-protected** record — in practice, a hash chain.

`arcaeon-audit` is the small, boring, correct layer that gives you exactly that,
in two lines and a folder. It wraps [`arcaeon-ledger`](https://pypi.org/project/arcaeon-ledger/)
(a hash-chained append-only log, zero heavy deps) with the event vocabulary and
the **regulator-ready export** that Article 12 asks for.

## Use

```python
from arcaeon_audit import AuditLog

log = AuditLog("agent-audit.jsonl", system_id="triage-agent-v3", provider="Acme AI")

log.record(event="system_start", agent="triage-agent-v3")
log.record(event="input",  agent="triage-agent-v3", inputs={"patient_msg": "chest pain"})
log.record(event="decision", agent="triage-agent-v3", decision="escalate",
           outputs={"routed_to": "ER", "priority": 1}, capability_version="v2")

log.verify().ok            # True — any edit to history would make this False
log.export_bundle("audit-export/")   # regulator-ready folder
```

`export_bundle()` writes a self-verifying folder:

| file | what it is |
|------|------------|
| `records.jsonl` | the full hash-chained audit log, verbatim |
| `integrity.json` | verdict — `chain_ok`, `truncation_checked`, `truncation_ok`, row count, exact first break if any, and a `witness` block naming the witness's `kind` / `identifier` / `independence` |
| `manifest.json` | system id, provider, period covered, counts by event type |
| `ARTICLE_12_SUMMARY.md` | human-readable mapping to Article 12's requirements |
| `witness.json` | the external-witness pin + truncation verdict + its nature (only when a witness is consulted) |

The bundle is **self-verifying**: `records.jsonl` is hash-chained, so anyone can
re-run `arcaeon-ledger`'s `verify_file()` and reproduce `integrity.json`. Tamper
evidence does not depend on trusting this tool or its author — that's the point.

## Truncation, and why a chain alone can't catch it

A hash chain proves nobody **edited or reordered** your records. It provably
**cannot** prove nobody **truncated** them: delete the most recent rows and the
surviving prefix still chains clean. So a log truncated *before* export would earn
a clean pass from chain verification alone — the one gap the integrity check can't
close by itself.

The fix is an **external witness**: an outside record of your log's head
`(rows, chain)` at a point in time. Pin to it on a cadence; a later truncation has
*fewer* rows than the witness saw, and is caught. `export_bundle()` cross-checks
against the witness when one is configured, and the bundle's verdict distinguishes:

- **`PASS`** — chain intact **and** the witnessed prefix matches the witness: no
  truncation *up to the witnessed head*. The only verdict that claims completeness —
  but only **through the last pin**, and only if the witness is controlled
  **independently** of whoever can write the log. Records written *after* the last
  pin are not covered (pin close to export), and a witness an attacker can also
  rewrite proves nothing.
- **`VERIFIED_MODULO_TRUNCATION`** — chain intact, but **no witness was consulted**,
  so truncation was *not* checked. Honest non-proof, never a silent clean pass.
- **`TRUNCATION_DETECTED` / `REWRITE_DETECTED`** — the witness caught missing or
  re-minted history.

**Judge independence yourself.** A witness only proves completeness if it is
controlled *independently* of whoever can write the log. So `integrity.json` names
*what the witness was*: a `witness` block with `kind` (`local_file` / `remote_url` /
`opentimestamps` / `none`), its `identifier`, and an honest `independence` label
(`self_asserted` / `externally_verifiable` / `none`). The reference `WitnessStore` is
a **local file** — same control domain as the log — so it is labelled `self_asserted`,
**not** independent: a PASS backed by it is self-attested, and the block says so
plainly. The point is to let a regulator *see* the independence question, not to claim
an independence we don't have. (Bundles from ≤0.1.3 predate this block; `witness_nature_of()`
reads them back as `kind: unknown`.)

```python
from arcaeon_ledger.witness import WitnessStore, publish_head
from arcaeon_ledger import Ledger

store = WitnessStore("witness.jsonl")            # or a hosted witness endpoint
publish_head(store, "acme/triage-v3", Ledger("agent-audit.jsonl"))   # pin, on a cadence
log.export_bundle("audit-export/", witness=store, witness_namespace="acme/triage-v3")
```

## CLI

```bash
arcaeon-audit verify  agent-audit.jsonl
arcaeon-audit pin     agent-audit.jsonl witness.jsonl acme/triage-v3
arcaeon-audit export  agent-audit.jsonl audit-export/ --system-id triage-v3 --provider "Acme AI" \
                      --witness witness.jsonl --namespace acme/triage-v3
```

## What this is and isn't

**Is:** an engineering control that produces automatic, tamper-evident, exportable
records — the integrity + export primitive Article 12 leans on.

**Isn't:** legal advice, and not compliance-in-a-box on its own. Article 12
compliance also depends on **what** you choose to log and your broader obligations
under the Act. This tool gives you the hard part (provable integrity + a clean
export); the coverage is yours to define.

## How it works

Every record is hash-chained: `chain = sha256(prev_chain + canonical(row))`. Edit,
delete, or reorder any record and every later link breaks; `verify()` names the
exact row. Records also carry an `authority` block (principal + capability version)
so "was this edited?" sharpens to "was this edited **and** was the writer authorized?"

MIT licensed. Built by [Arcaeon](https://arcaeon.io) — the evidence layer for AI.
