Audit log

Every state-changing surface in Covenant emits an AuditEvent to the append-only log at $COVENANT_HOME/audit/events.jsonl. The log is the ground truth — operators read it directly, covenant verify cross-checks it against the other state files, covenant audit verify checks the local hash-chain sidecar, and the covenant audit/recent route reads from the same file.

Event envelope

AuditEvent {
  id:           uuid,                  // unique per event
  timestamp_ms: u64,                   // unix milliseconds
  issuer:       AgentId,               // the daemon's local identity
  kind:         AuditKind              // tagged variant — see below
}

Variants

IntentDispatched

{
  "kind":           "intent_dispatched",
  "intent_id":      "uuid",
  "intent_text":    "…",
  "matched_agent":  "research@local" | null,
  "result_hash_hex": "…",
  "status":         "ok"
}

IntentIgnored

{
  "kind":            "intent_ignored",
  "intent_id":       "uuid",
  "intent_text":     "…",
  "matched_pattern": "**/*.pem"
}

CapabilityCheck

{
  "kind":              "capability_check",
  "agent_id":          "research@local" | "tool:echo",
  "required_actions":  ["tool.web_search"],
  "missing_actions":   [],
  "passed":            true
}

CapabilityGranted

{
  "kind":               "capability_granted",
  "subject_display":    "user@local",
  "action":             "tool.web_search",
  "granted_by_display": "user@local",
  "signature_b58":      "4qXP…8tF1"
}

Properties

  • Append-only during normal writes. The file is opened for append on event record. Operator-driven retention purge rewrites the retained rows and the sidecar together.
  • Locally chained. The daemon writes $COVENANT_HOME/audit/events.chain.jsonl with a SHA-256 hash chain over retained event rows.
  • One event per line. Compatible with tail -F, jq, and other JSONL-aware tooling.
  • Deterministic schema. Each variant serialises with a stable kind tag plus its payload. Adding new variants is a backward-compatible schema change.
  • Cross-checked. covenant verify runs four audits over a rolling window:
    • memory ↔ audit — every memory record has a matching IntentDispatched.
    • memory parent references — every parent id resolves in the memory store.
    • capability ↔ audit — every granted capability has a matching CapabilityGranted.
    • memory ↔ receipts — memory writes and settlement receipts pair by memory_record_id, with legacy count fallback.

Reading the log

Last few events

covenant audit recent --limit 5 --json
# Or via HTTP:
curl -s 127.0.0.1:8421/audit/recent?limit=5 | jq

Verify local chain

covenant audit verify
curl -s 127.0.0.1:8421/audit/verify \
  -H "Authorization: Bearer $COVENANT_OPERATOR_TOKEN" | jq

Filter for capability checks that failed

tail -F ~/.covenant/audit/events.jsonl \
  | jq -c 'select(.kind.kind == "capability_check" and .kind.passed == false)'

Find every dispatch for a specific agent

jq -c 'select(.kind.kind == "intent_dispatched"
              and .kind.matched_agent == "research@local")' \
  ~/.covenant/audit/events.jsonl

Trust model

The audit log is local. A user with write access to $COVENANT_HOME can rewrite history. The local hash-chain detects retained-row edits and sidecar mismatch after anchoring, and covenant verify surfaces cross-reference drift. This is not public signing or immutable storage.

Deployments where the operator is not the sole writer to the host should either sign individual events or stream the log to an append-only system with the appropriate trust model. Both approaches integrate against the existing AuditLog trait.

Related