# Trace specification v1

## Encoding and chaining

A run is UTF-8 JSONL with one event per line and no blank lines. Objects use sorted keys, UTF-8 without ASCII escaping, separators `,` and `:`, and reject NaN/infinity. The hash is:

```
sha256(canonical_json({v,seq,ts,run_id,event,data}) + ascii(prev))
```

The first `prev` is `sha256:` followed by 64 zeroes. Sequence numbers start at 1. `run.end` is terminal. Version 1 event names are frozen; new event names may be added, existing meanings may not change.

## Blob payloads

Each event data field is canonicalized independently. A value whose canonical encoding exceeds 4096 bytes is stored as canonical JSON in `blobs/<first-two-hex>/<full-hex>`. Its replacement is exactly `{"blob":"sha256:<hex>"}`. Reads resolve no blobs by default but always read and validate every referenced blob. Identical bytes therefore share one file across all runs.

## Merkle root

Leaves are event-hash bytes in trace order. Each parent is `sha256(left || right)`; an odd final node is duplicated at that level. The empty root is `sha256(b"")`. `run.end.data.merkle_root` covers every event before `run.end`, avoiding a self-referential hash. Full reads recompute the root and validate the entire chain. Comparing an already loaded root with a trusted external anchor is O(1); proving integrity of previously unread bytes necessarily requires O(n) reads.

## Compaction

`compact.jsonl` is a separate append-only physical view. A `snapshot` event spans sequence 1..N and contains:

- `first_seq` and `last_seq`;
- a blob holding the complete original prefix events;
- the original terminal Merkle root;
- an application-defined restorable `state` value.

The physical tail is re-chained after the snapshot and receives a new physical terminal root. Logical reads restore the original prefix and recompute the original tail chain, including the original terminal root. The resulting logical JSONL must be byte-identical to the original. `latest_snapshot` exposes state and only the physical tail for callers that can restore application state and skip prefix work.

## Defined read failures

- `TraceCorrupted`: malformed JSON, invalid schema, sequence/prev/hash mismatch, events after termination, Merkle mismatch, or missing/corrupt blobs.
- `TraceExhausted`: a missing/empty trace or a valid prefix without `run.end`.
- `DivergenceError`: replay is valid but a request fingerprint differs from the recording.
