> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentvault.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# Immutable Audit Logs

> BLAKE2b hash-chained audit trails with tamper detection and forensic querying.

# Immutable Audit Logs

Because autonomous agents operate without constant human supervision, post-incident forensics are critical. AgentVault maintains cryptographically chained audit trails that make log tampering mathematically detectable.

***

## Hash Chain Design

Every audit event references the hash of the previous event, forming a per-tenant chain:

```
genesis → evt_1 → evt_2 → evt_3 → ... → evt_N
   │         │        │        │
   └─hash──► └─hash─► └─hash─► └─hash──►
```

### Hash Computation

Each event hash covers: previous hash, timestamp, trace context, event body, and participants. It excludes: `observed_timestamp`, delivery metadata, and the `hash_chain` block itself.

```python theme={null}
import hashlib, json

def compute_event_hash(event: dict, previous_hash: str) -> str:
    hash_input = {
        "previous_hash": previous_hash,
        "timestamp": event["timestamp"],
        "trace_id": event["trace_id"],
        "span_id": event["span_id"],
        "body": event["body"],
        "sender": event["attributes"].get("av.sender.entity_id"),
        "recipient": event["attributes"].get("av.recipient.entity_id"),
        "sequence_number": event["hash_chain"]["sequence_number"]
    }
    canonical = json.dumps(hash_input, sort_keys=True, separators=(",", ":"))
    return "sha256:" + hashlib.sha256(canonical.encode()).hexdigest()
```

### Chain Properties

| Property         | Implementation                                                   |
| ---------------- | ---------------------------------------------------------------- |
| **Algorithm**    | SHA-256 (for hash chain linking) + BLAKE2b (for content hashing) |
| **Partitioning** | Per-tenant — each tenant has its own independent chain           |
| **Ordering**     | Strictly sequential by `sequence_number` within a tenant         |
| **Genesis**      | `sha256(b"agentvault_genesis_v1")` — deterministic and auditable |

***

## Traceparent Chaining

Agent actions are linked via W3C TraceContext `traceparent` headers:

```
00-{trace_id}-{span_id}-{trace_flags}
```

* **trace\_id** — deterministically derived from conversation/room ID
* **span\_id** — unique per message exchange
* **trace\_flags** — always `01` (sampled) for audit events

This enables full trace reconstruction: given a trace ID, you can reconstruct the entire conversation's audit trail across all participants.

***

## Event Types

| Event                | Severity | Description                                 |
| -------------------- | -------- | ------------------------------------------- |
| `message_delivered`  | INFO     | Message successfully delivered to recipient |
| `message_read`       | INFO     | Recipient acknowledged message              |
| `agent_connected`    | INFO     | Agent WebSocket connected                   |
| `agent_disconnected` | INFO     | Agent WebSocket disconnected                |
| `decision_made`      | INFO     | Owner resolved a decision request           |
| `policy_evaluated`   | INFO     | Policy pipeline evaluated a request         |
| `action_executed`    | INFO     | Agent executed a confirmed action           |
| `error`              | ERROR    | An error occurred during processing         |
| `security_violation` | FATAL    | Security policy violated                    |

***

## Chain Verification

### API Verification

```bash theme={null}
POST /api/v1/audit/verify
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "tenant_id": "tnt_uuid",
  "from_sequence": 1,
  "to_sequence": 10847
}
```

```json theme={null}
{
  "valid": true,
  "events_verified": 10847,
  "first_hash": "sha256:...",
  "last_hash": "sha256:...",
  "verified_at": "2026-03-18T15:00:00Z"
}
```

### Programmatic Verification

```python theme={null}
def verify_chain(events: list[dict]) -> tuple[bool, int | None]:
    """Returns (True, None) if valid or (False, break_index) if tampered."""
    for i, event in enumerate(events):
        expected_previous = GENESIS_HASH if i == 0 else events[i-1]["hash_chain"]["event_hash"]
        if event["hash_chain"]["previous_hash"] != expected_previous:
            return (False, i)
        recomputed = compute_event_hash(event, expected_previous)
        if event["hash_chain"]["event_hash"] != recomputed:
            return (False, i)
    return (True, None)
```

If verification fails, the break index identifies exactly which event was tampered with.

***

## Querying Audit Trails

### By Conversation/Room

```bash theme={null}
GET /api/v1/audit/trace/{trace_id}
```

Returns all audit events for a conversation, ordered by sequence number.

### By Time Range

```bash theme={null}
GET /api/v1/audit/tenant?since=2026-03-01T00:00:00Z&until=2026-03-18T00:00:00Z&severity_min=17
```

The `severity_min=17` filter returns only ERROR and above.

### By Entity

```bash theme={null}
GET /api/v1/audit/entity/{entity_id}?limit=100
```

Returns the most recent audit events involving a specific agent or user.

***

## Storage & Retention

* **Primary storage:** PostgreSQL with JSONB columns for flexibility and queryability
* **Indexes:** Optimized for chain traversal, trace lookup, time-range queries, and severity filtering
* **GIN indexes** on `body` and `attributes` for full-text JSONB search
* **Retention:** Configurable per tenant (default: 90 days, enterprise: unlimited)
* **No external stack required:** The audit system runs entirely on PostgreSQL — no ClickHouse, Elasticsearch, or external observability infrastructure needed

***

## OTel Export

Audit events are natively OTel-compatible. When OTel export is enabled, events are dual-written:

1. **PostgreSQL** — primary storage with hash chain integrity
2. **OTLP endpoint** — exported to any OTel-compatible backend (Splunk, Datadog, Grafana)

The OTel export requires zero schema changes because the audit event structure already conforms to the OTel LogRecord data model. See the [OTel Data Model](/standards/otel-data-model) for details.
