Version: 0.1.0
The audit system maintains an append-only, hash-chained ledger of all kernel operations. This specification defines the ledger structure, entry schema, and verification procedures.
The ledger MUST be append-only:
- Entries MUST NOT be modified after creation
- Entries MUST NOT be deleted
- New entries MUST be appended to the end
The ledger MUST be hash-chained:
- Each entry MUST include the hash of the previous entry
- The first entry MUST reference the genesis hash
- The chain MUST be verifiable through replay
The ledger MUST be deterministic:
- Same inputs MUST produce same hashes
- Serialization MUST use sorted keys
- Hash algorithm MUST be SHA-256
Every audit entry MUST contain:
| Field | Type | Description |
|---|---|---|
| prev_hash | string | SHA-256 hash of previous entry (64 hex) |
| entry_hash | string | SHA-256 hash of this entry (64 hex) |
| ts_ms | integer | Timestamp in milliseconds since epoch |
| request_id | string | Unique identifier of the request |
| actor | string | Actor who submitted the request |
| intent | string | Intent description from request |
| decision | Decision | Decision made (ALLOW, DENY, HALT) |
| state_from | KernelState | State before the operation |
| state_to | KernelState | State after the operation |
Audit entries MAY contain:
| Field | Type | Description |
|---|---|---|
| tool_name | string | Name of tool invoked, if any |
| params_hash | string | SHA-256 hash of parameters, if any |
| evidence_hash | string | SHA-256 hash of evidence, if any |
| error | string | Error message, if operation failed |
- prev_hash MUST be exactly 64 hexadecimal characters
- entry_hash MUST be exactly 64 hexadecimal characters
- ts_ms MUST be a non-negative integer
- request_id MUST be a non-empty string
- actor MUST be a non-empty string
The genesis hash is the prev_hash for the first entry:
genesis_hash = "0" * 64
Entry data MUST be serialized for hashing as follows:
{
"decision": "<decision_value>",
"error": <error_or_null>,
"evidence_hash": <hash_or_null>,
"intent": "<intent>",
"params_hash": <hash_or_null>,
"request_id": "<request_id>",
"state_from": "<state_from_value>",
"state_to": "<state_to_value>",
"tool_name": <name_or_null>,
"ts_ms": <timestamp>
}Keys MUST be sorted alphabetically. No whitespace between elements.
The entry hash MUST be computed as:
entry_data = serialize_deterministic(entry_fields)
combined = prev_hash + ":" + entry_data
entry_hash = sha256(combined.encode("utf-8")).hexdigest()
If params is present:
params_hash = sha256(serialize_deterministic(params).encode("utf-8")).hexdigest()
If evidence is present:
evidence_dict = {"evidence": evidence_string}
evidence_hash = sha256(serialize_deterministic(evidence_dict).encode("utf-8")).hexdigest()
An evidence bundle MUST contain:
| Field | Type | Description |
|---|---|---|
| ledger_entries | List[AuditEntry] | All entries in order |
| root_hash | string | Hash of last entry |
| exported_at_ms | integer | Export timestamp |
| kernel_id | string | Identifier of source kernel |
| variant | string | Variant of source kernel |
The root_hash MUST equal the entry_hash of the last entry in ledger_entries. For an empty ledger, root_hash MUST equal genesis_hash.
function replay_and_verify(entries, expected_root_hash):
prev_hash = genesis_hash
errors = []
for i, entry in enumerate(entries):
# Verify chain link
if entry.prev_hash != prev_hash:
errors.append(f"Entry {i}: prev_hash mismatch")
# Recompute entry hash
entry_data = serialize_entry_data(entry)
computed_hash = sha256(prev_hash + ":" + entry_data)
if computed_hash != entry.entry_hash:
errors.append(f"Entry {i}: entry_hash mismatch")
prev_hash = entry.entry_hash
# Verify root hash
if expected_root_hash and prev_hash != expected_root_hash:
errors.append("Root hash mismatch")
return len(errors) == 0, errors
A conforming verifier MUST:
- Check every entry in sequence
- Recompute every hash
- Report all errors found
- Not short-circuit on first error
Verification MUST return:
- Boolean indicating validity
- List of all errors found
- Number of entries verified
- Computed root hash
To append an entry:
- Compute params_hash if params present
- Compute evidence_hash if evidence present
- Serialize entry data
- Compute entry_hash using prev_hash and entry_data
- Create AuditEntry with all fields
- Append to entries list
- Update last_hash to entry_hash
To export evidence bundle:
- Copy all entries
- Get root_hash (last entry hash or genesis)
- Record export timestamp
- Include kernel_id and variant
- Return EvidenceBundle
To serialize ledger for storage:
- Convert each entry to dictionary
- Convert enum values to strings
- Serialize as JSON with sorted keys