Audit & Evidence
"No protected production write without durable evidence." AXIS writes decision evidence before execution and result evidence after; the audit trail is a hash-linked append-only WAL with a JSONL projection.
Hash chain
event_hash and previous_hash; a change in any earlier record invalidates all later hashes.Event categories
| Category | Examples |
|---|---|
| Request / decision | request_received, decision_recorded, approval_created, approval_resolved, approval_expired |
| Execution | execution_dispatched, execution_success, execution_failure, execution_state_unknown |
| Policy lifecycle | policy_loaded, policy_validated, policy_activated, policy_rollback, policy_candidate_created, policy_candidate_rejected |
| System | health_fail, rate_limit_hit, oversized_request, startup, shutdown |
Each event carries event_id, event_hash, previous_hash, timestamp, event_type, policy_version, operation, fingerprint, decision, actor, app, tenant, env, session_id, approval_id, and context-specific fields (reason codes, error codes, WAL seq, evidence bundle reference, fallback reason).
Storage layout
- WAL (
AUDIT_WAL_PATH, default./audit.wal): canonical append-only evidence with durability flags (fsync_required: trueby default). - JSONL projection (
AUDIT_LOG_PATH, default./audit.log): human-readable projection; not canonical. The security model requires that deleting the JSONL does not affect verification or WAL continuity. - Derived index V1 (
audit_index_v1.json): read-model index, safe to delete and rebuild; WAL remains canonical. - Proof of Prior Continuity is validated at startup; corruption is surfaced, never silently repaired.
Verification
# verify evidence integrity for the whole chain
curl -sS http://localhost:6543/evidence/verify
# per-audit-file verification
curl -sS -X POST http://localhost:6543/audit/verify \
-H "Content-Type: application/json" \
-d '{"audit_file": "./audit.wal"}'
# range evidence export (Evidence Bundle V1)
curl -sS -X POST http://localhost:6543/audit/evidence \
-H "Content-Type: application/json" \
-d '{"start_seq": 1, "end_seq": 100, "reason": "demo"}'
| Endpoint | Checks |
|---|---|
GET /evidence/verify | WAL existence, read lock, header, event count, per-event hash recompute, previous_hash linkage, seq continuity, chain start, trailing bytes. Returns status: ok / corruption_detected / partial. |
POST /audit/verify | Same checks on a specified audit file (WAL or JSONL with indices). |
POST /audit/evidence | Range export as bundle_type: axis.evidence_bundle.v1 with first/last seq, first/last event hash, payload count; optional Ed25519 signature field when signing is enabled. Verify with verify_ed25519 or local public-key verification; fetch public key from GET /audit/evidence/signing-public-key. |
Optional evidence signing
Evidence bundles can be Ed25519-signed when configured (AXIS_EVIDENCE_SIGNING_ENABLED=true plus key paths). Signing is optional in v0.6–v0.9 and is not part of the core integrity guarantee; the hash chain is the primary protection. Private keys must be protected; public keys are exposed for local verification.
Guarantees and limits
- Evidence is written before any protected write executes; "no protected production write without durable evidence."
- Hash chain detects tampering; it is not an external tamper-proof ledger, consensus, attestation, or key management service.
- Local review is the v1 guarantee: records are trusted for local review, not multi-party distributed audit finality.
- Runtime logs (
GET /logs) are operational visibility only — not durable proof; the Control Plane never fabricates log rows in real mode. - Identity fields are unverified claims; signed evidence does not prove identity claims are true.
- PostgreSQL does not log per-request decision evidence; AXIS evidence is the local audit trail.
Keep WAL and JSONL on the same volume for local review; run /evidence/verify after restarts or migrations and include a copy of the WAL in evidence bundles.
Related: Request Lifecycle · Security Model · API Reference