The Episodic Ledger and Sigchain¶
The episodic ledger is the canonical truth of every AI decision. It is append-only, cryptographically signed, and hash-chained. Nothing can be removed or modified after it is written.
The 18-field AuditEvent¶
Every entry in the ledger is an AuditEvent with exactly 18 fields:
| Field | Type | Description |
|---|---|---|
event_id |
str | UUID v7 (time-ordered) |
episode_id |
str | Groups related events into an "episode" |
sequence |
int | Monotonically increasing, per-chain (starts at 1) |
event_type |
str | e.g. "ingest.accepted", "acme.billing.credit-issued" |
schema_version |
str | Always "1.0" in this release |
valid_from |
str | ISO 8601 — when the event became valid |
valid_to |
str | None | ISO 8601 — optional validity end |
system_time |
int | Hybrid Logical Clock timestamp (nanoseconds) |
causation_id |
str | None | audit_id of the event that caused this one |
correlation_id |
str | None | Trace correlation across multiple events |
actor |
str | Who performed the operation (required, non-empty) |
trace_id |
str | None | OpenTelemetry trace ID |
span_id |
str | None | OpenTelemetry span ID |
payload |
dict | The operation's data (what was ingested, queried, etc.) |
payload_hash |
str | SHA3-256 of the canonical JSON payload |
prior_hash |
str | SHA3-256 hash of all fields of the previous event |
signature |
str | Ed25519 signature over all fields except signature |
signer_key_id |
str | UUID of the Ed25519 private key that signed this event |
How the chain works¶
Each event includes the hash of the previous event. Modifying any event breaks the chain from that point forward, making tampering detectable.
Genesis hash = SHA3-256("aevum:genesis")
↓
Event 1: prior_hash = genesis_hash
payload_hash = SHA3-256(event1.payload)
signature = Ed25519(all fields except signature)
chain_hash_1 = SHA3-256(all fields except signature)
↓
Event 2: prior_hash = chain_hash_1
...
↓
Event N: prior_hash = chain_hash_{N-1}
...
To verify that the chain is intact from genesis to the current state:
intact = engine.verify_sigchain()
# True = every event is unmodified and correctly linked
# False = tampering detected or signing key changed without rotation
The audit_id format¶
Every OutputEnvelope includes an audit_id that identifies the ledger entry:
urn:aevum:audit:0196f2a1-1234-7abc-8def-0123456789ab
^ ^
| UUID v7 (time-ordered, ~1ms resolution)
Namespace prefix (frozen invariant)
UUID v7 is time-ordered, which means audit IDs sort chronologically.
Verifying the chain¶
engine = Engine()
# ... operations ...
# Full chain verification
intact = engine.verify_sigchain()
if not intact:
print("WARNING: sigchain integrity check failed")
verify_sigchain() re-verifies:
1. That prior_hash in each event matches the computed hash of the previous event
2. That payload_hash in each event matches the SHA3-256 of the actual payload
3. That the Ed25519 signature in each event is valid over the signing fields
All three checks must pass for the chain to be considered intact.
What tamper detection looks like¶
If a ledger entry is modified after writing, verify_sigchain() returns False.
The check fails at the first inconsistency. To identify which event was tampered:
events = engine.get_ledger_entries()
for e in events:
print(e["sequence"], e["audit_id"], e["event_type"])
# The last valid event is the one before the chain breaks
If you are using a persistent backend, check for direct database modifications.
The signing key (signer_key_id) should not change between events unless a
deliberate key rotation was performed.
The "episode" terminology¶
An episode is a group of related audit events that together represent a complete AI decision or workflow. For example:
episode_id: "ep-billing-INV-001"
event 1: ingest.accepted — invoice data ingested
event 2: query.accepted — billing status retrieved
event 3: review.created — credit approval requested
event 4: review.approved — credit approved by manager
event 5: commit.accepted — acme.billing.credit-issued recorded
event 6: commit.accepted — action.outcome.ok recorded
To group events into an episode, pass episode_id to each function call:
ep = "ep-billing-INV-001"
engine.ingest(..., episode_id=ep)
engine.query(..., episode_id=ep)
engine.commit(..., episode_id=ep)
Episodes make it possible to replay an entire workflow (not just a single event) and to understand the full context of any past decision.
Hybrid Logical Clock¶
The system_time field uses a Hybrid Logical Clock (HLC), not wall time.
HLC advances monotonically even if the system clock is adjusted, preventing
sequence-ordering anomalies in distributed deployments.
The valid_from field uses wall time (ISO 8601) for human-readable timestamps.
The system_time field uses HLC nanoseconds for ordering guarantees.
Kernel-Reserved Event Types¶
The kernel writes the following event types to the sigchain. Application code MUST NOT use these prefixes:
| Event type | When written |
|---|---|
ingest.accepted |
Successful ingest through the governed membrane |
ingest.rejected |
Ingest denied by policy or consent |
query.accepted |
Graph traversal executed |
barrier.triggered |
Any unconditional barrier fired |
complication.installed |
Complication registered |
complication.approved |
Complication moved to ACTIVE |
complication.suspended |
Complication suspended by admin |
review.created |
review() gate opened |
review.approved |
Human approved a pending review |
review.vetoed |
Human vetoed (or deadline passed) |
context.stale |
commit() rejected stale witness |
session.start |
Kernel startup |
commit.accepted |
Manual commit appended |
replay.started |
Replay of a past decision begun |
Complication outcome events use the action.outcome.* prefix
(Section 11.6 of the specification). Application events should
use a namespaced prefix: publisher.category.name