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.
Applies to
Section titled “Applies to”| Branch | Coverage in this article |
|---|---|
MCP (law-mcp-server --http) | call journal, audit fields, redaction list |
law serve | decision records live in article 02 (journal mode), Upgrades and compatibility, Rollback — shared journal, per-record pins |
Prerequisites
Section titled “Prerequisites”- The journal switch state: enabled by default,
LAW_MCP_LOG=0disables recording (real,main.rs). - A journald socket at
/run/systemd/journal/socketif native fields are wanted; otherwise records fall back to stderr JSON lines (real,journal.rs).LAW_MCP_LOG_SOCKEToverrides the socket path (real, test seam). journalctlaccess on the host to read native records (operator-policy-example reader; the server ships no reader).
- 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 vialaw_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).
- Technical call log: one journal record per call or refusal,
written by the transport (
- Read a call record. Real fields (
reqlog.rs,journal.rs,trace.rs):MESSAGE(http <method> <tool> ok|<code> <ms>ms),PRIORITY(6ok /3failed /4refused 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, hashesMCP_PROGRAM_HASH,MCP_CASE_HASH,MCP_RESULT_HASH,MCP_CODE_HASH, stagesMCP_STAGESandMCP_MS_<STAGE>, bodiesMCP_REQUEST/MCP_RESPONSE, and flagsMCP_TRUNCATED/MCP_REDACTED.law_reportadditionally spreadsMCP_REPORT_PACKAGE,MCP_REPORT_CATEGORY,MCP_REPORT_FRAGMENT,MCP_REPORT_PREDICATE,MCP_REPORT_CALL_ID(real). - Know what enters the bodies.
MCP_REQUEST/MCP_RESPONSEcarry the raw JSON-RPC bytes capped at 64 KiB per side by default (LAW_MCP_LOG_MAX_BODY,0uncaps, 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). - Know what is redacted. Exactly three terminal keys are replaced
with
[REDACTED]:revokeSecret,LAW_ANSWERS_TOKEN,ANSWERS_INTERNAL_TOKEN, and the record gainsMCP_REDACTED=answers-secret(real,reqlog.rsredact). Same-named keys in atools/listschema keep their descriptions; only terminal values hide. Nothing else is redacted — notably,LAW_NEURAL_TOKENis not on the redaction list, so keep it out of logged tool arguments (current gap, not a policy choice). - Replay from the audit trail, not the log. The journal’s hashes
(
MCP_RESULT_HASH,MCP_CODE_HASH) plus the original question re-run throughlaw_explainreproduce the proof chain; the server re-executes and refuses on hash mismatch (reference behavior oflaw_explain).GET /healthzleaves no journal record (real): health polls are invisible by design. - 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.
Expected result
Section titled “Expected result”- Every served call and every transport refusal has exactly one
journal record, correlated across journald, OTLP spans, and the
X-Law-Call-Idresponse header by the 32-hex call id (real,otlp.rs). An edgeX-Request-Idis 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_FINISHEDand priority4(real,http.rs). - The answer bytes never depend on logging: disabling the journal
changes no answer (real invariant,
journal.rsheader).
Result check
Section titled “Result check”LAW_MCP_LOG_SOCKET=<created-example test socket>plus one call: the receiver gets a real datagram decodable field-by-field (real seam, covered byjournal.rstests).- A
law_revoke_answercall journal line contains[REDACTED]andMCP_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 onlylaw_askcalls.
Failures and diagnostics
Section titled “Failures and diagnostics”- 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_HASHon catalog/search answers is by construction (real,reqlog.rs): those tools carry no reproducibility provenance.
Support boundaries
Section titled “Support boundaries”- 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).
Next step
Section titled “Next step”- Article 11 (Resource limits and cancellation) for the timeout/overrun mechanics behind
OVERRUN_FINISHED; article 12 (Health and observability) for OTLP span contents and the health endpoint.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.