Skip to content
docs
Arxo ↗

Logs, audit trail, and decision journals

For LLMs10 sections

Separate the three records the deployment keeps — technical call log, evaluation audit trail, operator decision journal — and say for each what it holds, who reads it, and what happens when writing fails.

Component law-mcp over HTTP (stdio journals nothing, real). Scenario: a served tools/call and its afterlife in the logs. All call ids, hashes, and package names below are synthetic examples.

BranchCoverage in this article
MCP (law-mcp-server --http)call journal, audit fields, redaction list
law servedecision records live in article 02 (journal mode), Upgrades and compatibility, Rollback — shared journal, per-record pins
  • The journal switch state: enabled by default, LAW_MCP_LOG=0 disables recording (real, main.rs).
  • A journald socket at /run/systemd/journal/socket if native fields are wanted; otherwise records fall back to stderr JSON lines (real, journal.rs). LAW_MCP_LOG_SOCKET overrides the socket path (real, test seam).
  • journalctl access on the host to read native records (operator-policy-example reader; the server ships no reader).
  1. Distinguish the three records before configuring anything:
    • Technical call log: one journal record per call or refusal, written by the transport (reqlog.rs + journal.rs).
    • Evaluation audit trail: inside the answer itself — statuses, applied rules, provenance hashes, replayControl; replays via law_explain / law_inspect, not via the log.
    • Operator decision journal: human entries (who approved what publication, which retention ran). The engine keeps none; the operator keeps it as a file or ticket trail (operator-policy-example, no code writes it).
  2. Read a call record. Real fields (reqlog.rs, journal.rs, trace.rs): MESSAGE (http <method> <tool> ok|<code> <ms>ms), PRIORITY (6 ok / 3 failed / 4 refused or overrun-finished), SYSLOG_IDENTIFIER=law-mcp, MCP_TRANSPORT=http, MCP_PROFILE, MCP_CALL_ID (32 hex), MCP_PORT, MCP_CLIENT, MCP_METHOD, MCP_TOOL, MCP_OK, MCP_CODE, MCP_MS, MCP_BYTES_IN, MCP_BYTES_OUT, MCP_RPC_ID, hashes MCP_PROGRAM_HASH, MCP_CASE_HASH, MCP_RESULT_HASH, MCP_CODE_HASH, stages MCP_STAGES and MCP_MS_<STAGE>, bodies MCP_REQUEST/MCP_RESPONSE, and flags MCP_TRUNCATED / MCP_REDACTED. law_report additionally spreads MCP_REPORT_PACKAGE, MCP_REPORT_CATEGORY, MCP_REPORT_FRAGMENT, MCP_REPORT_PREDICATE, MCP_REPORT_CALL_ID (real).
  3. Know what enters the bodies. MCP_REQUEST/MCP_RESPONSE carry the raw JSON-RPC bytes capped at 64 KiB per side by default (LAW_MCP_LOG_MAX_BODY, 0 uncaps, real). Case facts ARE in the logged bytes when they fit — the journal is not a facts-free zone. Truncation is marked (MCP_TRUNCATED=request,response, real).
  4. Know what is redacted. Exactly three terminal keys are replaced with [REDACTED]: revokeSecret, LAW_ANSWERS_TOKEN, ANSWERS_INTERNAL_TOKEN, and the record gains MCP_REDACTED=answers-secret (real, reqlog.rs redact). Same-named keys in a tools/list schema keep their descriptions; only terminal values hide. Nothing else is redacted — notably, LAW_NEURAL_TOKEN is not on the redaction list, so keep it out of logged tool arguments (current gap, not a policy choice).
  5. Replay from the audit trail, not the log. The journal’s hashes (MCP_RESULT_HASH, MCP_CODE_HASH) plus the original question re-run through law_explain reproduce the proof chain; the server re-executes and refuses on hash mismatch (reference behavior of law_explain). GET /healthz leaves no journal record (real): health polls are invisible by design.
  6. Record operator decisions separately. Created-example entry: 2026-10-03T09:15Z op:salim prepared p_9f2 (24h), publish only after counsel sign-off; journal archived to w42. The format is the operator’s; the engine never reads it.
  • Every served call and every transport refusal has exactly one journal record, correlated across journald, OTLP spans, and the X-Law-Call-Id response header by the 32-hex call id (real, otlp.rs). An edge X-Request-Id is accepted into the record only if short (≤64) and alphanumeric (real, journal.rs).
  • A timed-out computation that later finishes emits a second record with MCP_CODE=OVERRUN_FINISHED and priority 4 (real, http.rs).
  • The answer bytes never depend on logging: disabling the journal changes no answer (real invariant, journal.rs header).
  • LAW_MCP_LOG_SOCKET=<created-example test socket> plus one call: the receiver gets a real datagram decodable field-by-field (real seam, covered by journal.rs tests).
  • A law_revoke_answer call journal line contains [REDACTED] and MCP_REDACTED=answers-secret, never the secret (real).
  • journalctl --namespace=law-mcp -u law-mcp@8722 MCP_TOOL=law_ask -o json --all (operator-policy-example unit name; field names real) lists only law_ask calls.
  • Journal write failure never fails the call: send fails → resend without bodies with MCP_TRUNCATED=oversize; no socket → stderr JSON line (real, Journal::emit). Silence in journald with lines on stderr means the socket path is wrong or journald is down.
  • OTLP receiver down or slow: the call never waits; spans queue (256, real) then drop, and the drop counter prints to stderr once per hundred (real, otlp.rs).
  • Missing MCP_RESULT_HASH on catalog/search answers is by construction (real, reqlog.rs): those tools carry no reproducibility provenance.
  • Access rights to records are the host’s: journald ACLs, file modes on the stderr capture, OTLP receiver auth. The server enforces no read policy on logs it already wrote.
  • With HTTP journaling on (default), bodies persist — so retention, backup, and cleanup of journal records follow the same case-data policy as the case directory itself (article 08): journald MaxRetentionSec/rotation, who can read the stderr capture and the OTLP store, what the backups include, and how records are purged. Turning journaling off stops future copies; it does not erase records already collected.
  • Integrity: records carry no signature or hash chain in code. Tamper evidence is the host’s (journald sealing/forwarding, append-only storage) — an operator-policy-example layer, not an engine guarantee. “Verified” in this article means only: hashes in the answer re-execute to the same bytes via law_explain.
  • The journal is not a backup (bodies truncate) and not the audit trail (replay needs the question + replayControl).

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.