Expand description
Enterprise audit trail (PR-5 of issue #487).
Every memory-mutation call site in the binary — HTTP handlers, MCP
tool dispatch, CLI write commands, and ai-memory boot — emits an
AuditEvent to a hash-chained, append-only JSON log when the
audit subsystem is enabled. The schema is stable, versioned, and
framework-agnostic (NOT bound to OCSF or CEF — see
docs/security/audit-schema.md). SIEMs ingest the lines as-is.
§Design properties
- Default-OFF for privacy. Operators opt in via
[audit] enabled = trueinconfig.toml(orAI_MEMORY_AUDIT_DIR=<dir>env var for one-off runs — seesrc/log_paths.rs::AUDIT_DIR_ENVfor the canonical name; the audit log file is written as<dir>/audit.log). - Hash-chained, tamper-evident. Each line carries a
prev_hashthat matches the prior line’sself_hash.ai-memory audit verifyrecomputes the chain and exits non-zero on mismatch. - Append-only OS hint. Best-effort
chflags(2)(BSD/macOS) orFS_IOC_SETFLAGSioctl (Linux). Documented as defense in depth; the chain is the load-bearing tamper-evidence. - Privacy by default. Audit captures
(memory_id, namespace, title, action, outcome, actor). Memory content is never emitted —redact_content = trueis the only supported mode in the v1 schema; the field is reserved inAuditTargetfor future compliance contexts that mandate content capture. - Per-process monotonic sequence, independent of the chain. Lets a SIEM detect dropped lines even before the chain check.
- No backpressure on the caller. Emission is synchronous (one
write per line so the chain is consistent across processes
concurrently appending — the file is opened with
O_APPEND), but failures inside emit are swallowed and logged viatracing. A broken audit pipeline never blocks a memory operation.
Modules§
- synthesis_
sources - #1558 batch 5 wave 3 — canonical
AuditActor::synthesis_sourceprovenance values. One spelling per value; every production writer (MCP dispatch, MCP store/delete tools, HTTP handlers, CLI crud/store/update) references these consts instead of scattering the literal. The vocabulary doc onsynthesis_sourceabove stays the narrative SSOT; this mod is the mechanical one.
Structs§
- Audit
Actor - Who performed the action.
- Audit
Auth - Authentication context for HTTP-originated events. Stdio (CLI / MCP) invocations omit this block entirely.
- Audit
Event - One audit event. The serialized form is one JSON object per line (NDJSON). Field order is stable for chain reproducibility.
- Audit
Sink - Initialised audit sink — writer handle protected by a mutex so the
chain head update + write are atomic across emission threads. The
writer is
dyn Write + Sendso tests can substitute an in-memoryVec<u8>for the productionFile. - Audit
Target - What was acted upon.
- Event
Builder - Builder for an audit event. Most call sites use one of the
convenience helpers ([
emit_store], [emit_recall], etc.) but the builder is public so unusual flows (consolidate-many, deferred import) can fill in custom targets. - Verify
Failure - Verify
Report - Outcome of
verify_chain.
Enums§
- Audit
Action - Canonical action vocabulary. Adding a variant is a non-breaking schema change; renaming or removing one IS breaking.
- Audit
Outcome - Outcome of the action.
- Verify
Failure Kind - Why the per-line audit-event hash chain (
AuditEventJSONL files underaudit/) failed to verify.
Constants§
- CHAIN_
HEAD_ PREV_ HASH - Sentinel
prev_hashfor the first line in a fresh chain. Hex-encoded 32-byte zero buffer — picked so a chain head is unambiguous on inspection. - SCHEMA_
VERSION - Stable schema version stamped on every emitted line. Bump only when
a field’s semantics change in a way SIEM parsers care about
(renaming, removing, or repurposing). Adding optional fields does
NOT bump the version. See
docs/security/audit-schema.md§Version policy for the full contract.
Functions§
- actor
- Construct an
AuditActorfrom an agent_id + synthesis source + optional scope. The synthesis source is informational metadata and MUST be one of the documented strings inAuditActor. - emit
- Write an event to the configured sink. No-op when audit is disabled.
Failures are logged via
tracing::error!and dropped — audit is never allowed to fail a memory operation. - init
- Initialise the audit sink. Called at most once per process from
init_from_config; subsequent calls replace the prior sink so test-only callers can swap targets. - init_
from_ config - Initialise the audit sink from a parsed
crate::config::AuditConfig. ReturnsOk(())whether or not audit is enabled — it is a no-op when disabled. - is_
enabled - Whether the audit subsystem is currently enabled. Cheap.
- resolve_
audit_ path - Resolve the audit log file path from the config, honouring the
user-mandated precedence ladder: CLI > env (
AI_MEMORY_AUDIT_DIR) - resolve_
audit_ path_ with_ override - Strict variant: takes an optional
--audit-diroverride, returns the resolved file path (withaudit.logappended when the input resolves to a directory) plus thecrate::log_paths::PathSourceused. - target_
memory - Construct an
AuditTargetfor a single memory. - target_
sweep - Construct an
AuditTargetfor a multi-row sweep operation. - verify_
chain - Walk an audit log file and verify the chain. Returns a structured
report; the binary’s
audit verifysubcommand turns this into an exit code. - verify_
chain_ from_ reader - Verify a chain from any
Readsource. Lets tests run against in-memory buffers without touching the filesystem.