Skip to content

ADR-0003: Audit/v1 Stream Design

Status: Accepted (2026-05-17)
Author: Operator

Context

The policy/v1 gate (PR #9) evaluates Allow/Deny/Prompt decisions for networked sinks. Operators require visibility into these decisions for compliance verification and debugging. Prior to this work, no structured audit logging mechanism existed.

Problem Statement

  • Policy enforcement happens silently; operators cannot verify what decisions were made or why.
  • Compliance auditors cannot demonstrate to external parties that policy is working as intended.
  • Debugging policy bugs requires source-level tracing; post-hoc log analysis is not possible.

Decision

Implement an append-only JSONL audit stream at ~/.vox/audit.jsonl with the following properties:

Storage & Permissions

  • Location: ~/.vox/audit.jsonl (relative to user home, not project root)
  • Mode: 0600 (read/write for owner only)
  • Format: JSONL (one JSON object per line)
  • Thread-safe writes via mutex

Schema

{
  "timestamp": "2026-05-17T14:32:18Z",
  "kind": "policy_decision",
  "sink": "llm-anthropic",
  "verdict": "allowed",
  "reason": "",
  "mode": "selective"
}

Field value reference: - kind: always "policy_decision" in v1 - verdict: "allowed" or "denied" - reason: denial reason constant ("air-gap-mode", "explicit-deny", "consent-declined", "headless-prompt-required") or "" when allowed - mode: active policy mode ("air-gap", "permissive", "selective")

Schema versioning: future changes are gated via a schema_version field (1 reserved for baseline; migration tooling tracked in bd blackrim-vox-1vb).

Rotation Policy

  • Size: 16 MB per file (rotates to audit-<RFC3339-timestamp>-<ns>.jsonl)
  • Age: 30 days (archives older than 30 days)
  • Retention: Keep 5 most recent files
  • Deletion beyond retention is silent (no backup/trash)

Default Behavior

  • NullStream (no-op) is the default when no sink is configured
  • Preserves existing test behavior (no audit side effects in tests unless explicitly enabled)
  • Can be toggled on/off via config flag [audit] enabled = true|false

Consequences

Positive

  • Operators can query audit trail: vox audit tail, grep verdict=deny audit.jsonl, etc.
  • Compliance auditors can demonstrate policy enforcement post-hoc (non-repudiation).
  • Disk growth is bounded by rotation; no unbounded file growth.
  • Schema versioning allows future extensions without breaking existing deployments.

Risks & Mitigations

  • Disk space: Rotation + retention bounds the footprint. On typical usage (~10 decisions/hour), 5 files of 16 MB each absorbs ~750 days of logs.
  • Performance: Mutex + append is O(1); serialization latency is <1 ms per entry. Negligible impact on policy evaluation (~40 ms baseline).
  • Schema changes: Breaking changes require migration tooling. Plan: vox audit migrate subcommand for bulk rewriting old entries (filed as bd dbl).
  • Privacy: Home directory storage is per-user; shared systems see separate audit trails per user (no cross-user data).

Alternatives Considered

Syslog / Journald

Rejected. Platform-specific (Linux journald, macOS log, BSD syslog). Vox targets Linux, macOS, and Windows; syslog adds platform abstraction overhead. Also makes grepping from CI/CD pipelines harder (must use platform-specific query tools). Local file is simpler and more portable.

Prometheus Events / Metrics

Rejected. Prometheus is a metrics scraper, not an event log. Cannot preserve historical decisions (point-in-time gauge overwrite). Overkill for a CLI tool without infrastructure assumptions.

Structured Database (SQLite)

Rejected. Adds a runtime dependency and complexity for write-heavy, read-light workload. JSONL + grep is sufficient and easier to ship as a single-binary tool. Database migrations introduce failure modes (locked DB, schema mismatch across binary versions).

Cloud Audit Sinks (CloudTrail, GCP Logging, etc.)

Rejected. Out of scope for vox 1.x; adds dependency on cloud provider choice. Local audit trail is a prerequisite. Cloud sinks can be layered on top later (via log-shipping tooling).

Unresolved Questions

  1. Aggregation across runs: For high-volume sinks, a single audit file across many policy decisions may grow quickly. Long-term archival strategy (S3, log-shipping) is deferred to a future enhancement.

  2. Decryption/signing: Audit entries are stored as plaintext. HMAC signing for tamper detection is tracked as blackrim-vox-2rf (post-1.0).

  3. Structured query API: vox audit tail and grep are sufficient for MVP. A structured query interface (e.g., vox audit query --since 7d --verdict deny) is deferred.

  • blackrim-vox-457 (this ADR)
  • blackrim-vox-1vb (schema migration tooling)
  • dbl (vox audit migrate subcommand)
  • blackrim-vox-2rf (HMAC signing)