Markdown for LLMs
Data flow, storage, and retention
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Data flow, storage, and retention
## Goal
Trace every byte a question touches: where questions, facts,
documents, drafts, and answers travel, where each copy rests, who can
read it, and when it disappears.
## Scope
Component `law-mcp` (MCP host; default profile `all`, default
`--port 8722`, default bind `127.0.0.1`). Scenario: one `tools/call`
round trip over stdio or HTTP, plus the optional Lens publication and
neural-search legs. All case names, hashes, and hostnames below are
synthetic examples.
## Applies to
| Branch | Coverage in this article |
|---|---|
| MCP (`law-mcp-server`) | full data-flow trace incl. answers/neural/OTLP legs |
| `law serve` | not covered — serve state is the world + the shared decision journal, see [Deploy a private HTTP service](/operate/private-http-service/), [Backup and restore](/operate/backup-restore/) |
## Prerequisites
- The server root (`--root` / `LAW_MCP_ROOT`, real), or a checkout the
server can find by its two markers — the profiles directory and
the compiled-corpus directory (real root discovery).
- Know which legs are enabled: HTTP (`--http`), call journal (on by
default, `LAW_MCP_LOG=0` disables), OTLP (`LAW_MCP_OTLP_URL`),
Lens answers (`LAW_ANSWERS_URL` + `LAW_ANSWERS_TOKEN`), neural
appendix (`LAW_NEURAL_URL` + `LAW_NEURAL_TOKEN`). All real
(`law-mcp/src/main.rs`, `publication.rs`,
`reference/search.rs`).
## Steps
1. Send the question. Over stdio the request is one JSON line on
stdin; over HTTP it is `POST /mcp` with a single JSON-RPC object
(real: batches are refused, `http.rs`). The request carries the
tool name, the predicate with arguments, and the case facts with
provenance.
2. The server reads the corpus and the case. Reads are confined to the
root: a case package `<root>/<package>/` must contain `law.toml`
(real, `execution.rs`), and a path that canonicalizes outside the
root is refused (`case_ask.outside_root`, real). Symlink escapes
are refused, not followed.
3. The engine evaluates in memory. Pure evaluation writes no files:
the answer document (statuses, applied rules, provenance hashes
`programHash`/`caseHash`/`resultHash`/`codeHash`) is built in the
call thread and returned in the response (real). The writing tool
classes — `editing` (case packages), `drafting` (author-tree
files) — are the exception, listed in the
[effects matrix](/operate/tool-profiles-input-policy/#effects-the-authoritative-matrix).
4. Optional leg A — call journal. On the HTTP transport only (stdio
never journals, real, `reqlog.rs`), each call's primary record
goes to the journald native socket
`/run/systemd/journal/socket`, or — if the socket is absent — as
one JSON line to stderr (real, `journal.rs`). A late-finishing
call adds a second record for the same call id
(`OVERRUN_FINISHED`), so journal reads correlate by id rather
than counting lines (see [Logs, audit, decision journals](/operate/logs-audit-decision-journals/)).
Bodies are capped at 64 KiB per side by default
(`LAW_MCP_LOG_MAX_BODY`, `0` uncaps, real).
5. Optional leg B — Lens answers. The four publication tools
(`law_capture_answer`, `law_prepare_answer`,
`law_publish_answer`, `law_revoke_answer`, real,
`publication.rs`) forward to `POST {LAW_ANSWERS_URL}/internal/mcp/call`
with schema `law.answers.mcp-call/0.1` (real). Without
`LAW_ANSWERS_URL` and a non-empty token the tools answer
`ANSWERS_UNAVAILABLE` and nothing leaves the host.
6. Optional leg C — neural appendix. `law_search` posts the query text
plus the lexical `taken` candidates (kind/id/point, no case facts)
to `{LAW_NEURAL_URL}/v1/appendix` (real, `reference/search.rs`).
Without `LAW_NEURAL_URL` the leg reports `active:false` and the
answer stays purely lexical.
7. Optional export — `--export-neural-input PATH` writes the search
index input document to exactly that path (real, `main.rs`).
Together with the `editing`/`drafting` tool writes and per-call
temp workbench files, this is the complete file-write surface —
see the
[effects matrix](/operate/tool-profiles-input-policy/#effects-the-authoritative-matrix)
for the authoritative list.
## Expected result
- The client holds the full answer document in the response. The
MCP host has no separate persistent answer store and keeps no
long-lived response object after the call returns (real) — but
with journaling on (the HTTP default), request and response
bodies, including case facts, are persisted in the log
infrastructure (journald, stderr capture, or the external
collector) and must be handled as case data: same access
rights, retention, backup, and cleanup rules as the case
directory itself. Audit the whole deployment, not just the
absence of a database inside the process.
- Case inputs live where the operator put them and where the
deployment copies them: the case directory (`law.toml`,
`law.lock`, `queries/*.json`, `.lawcase` files), the client
that sent the facts, and — when journaling is on — the journal
records described below.
- Journal records live in the host journal (journald) or the
process stderr stream — see article 09 for fields and redaction.
- A prepared Lens document is a non-public frozen document held by
the answers service for 24 hours (reference behavior of
`law_prepare_answer`); publishing returns a URL plus a separate
revoke secret, and revoking closes HTML, API, and download. The
MCP host stores none of these.
## Result check
- With the journal on stderr, one successful call produces one
primary JSON line containing `MCP_TOOL` and `MCP_CALL_ID` (real
field names). Line count is NOT a call count: a late-finishing
computation may add an `OVERRUN_FINISHED` record for the same
call, a journal-write failure can drop a record while the call
itself succeeds, an oversized datagram is resent truncated
(content lost, record survives), and transport failures produce
no call record at all. Correlate by `MCP_CALL_ID` and event
type; see article 09. The guarantees of the MCP call log and
the `law serve` decision journal differ — do not treat either
as a backup or as an audit-completeness proof on its own.
- With `LAW_ANSWERS_URL` unset, `law_prepare_answer` returns
`isError:true` with code `ANSWERS_UNAVAILABLE` (real).
- With `LAW_NEURAL_URL` unset, `law_search` carries
`neural.active:false` (real).
- `GET /healthz` is never journaled (real, `reqlog.rs`): the
balancer's polls leave no case-data trace.
## Failures and diagnostics
- `413` on `POST /mcp`: request body over the 1 MiB implementation
limit (`MAX_BODY`, real). Reduce the request only in ways that
preserve its meaning: use the supported references to a prepared
case, remove genuinely redundant content, or split independent
objects. Do NOT split one logically connected case into separate
computations without established equivalence: `A ∧ B → C`
evaluated on `{A, B}` yields `C`, while two separate runs on
`{A}` and `{B}` may not — and in models with exceptions and
conflicts the effect of splitting is even less obvious.
- `ANSWERS_TOO_LARGE`: Lens response over 32 MiB (real,
`publication.rs`). `BODY_SIZE` from other services: response over
4 MiB (real, `service_client.rs`).
- `SERVICE_CONFIG` with `https_required`: a service URL that is not
HTTPS to a non-loopback host (real). Fix the URL; there is no
bypass flag.
- Oversized journal datagram: resent without bodies with
`MCP_TRUNCATED=oversize` (real). The record survives; the content
does not — do not treat the journal as a backup.
## Support boundaries
- Retention of journal records is the host's policy (journald
`MaxRetentionSec`, log rotation): the server sets no TTL and runs
no cleanup job. Retention of prepared/published Lens documents is
the answers service's behavior (24 hours frozen, explicit revoke).
- Backups cover the inputs the operator owns: case directories,
`law.toml`/`law.lock`, and the journal archive. There is no server
database to dump.
- The MCP host never deletes caller data: cleanup means the
operator's retention policy plus `law_revoke_answer` for published
documents.
- "Secure" in this article means only: confined reads (root
containment), no silent copies (stdio journals nothing; each
network leg needs an explicit URL + token), and named storage
places. It does not mean encryption at rest — that is the host's
disk policy.
## Next step
- Article 09 ([Logs, audit trail, and decision journals](/operate/logs-audit-decision-journals/)) for what exactly lands in each journal record and what
is redacted; article 10 ([Runtime filesystem isolation](/operate/runtime-filesystem-isolation/)) for directory rights and the
Arxo-vs-external-network boundary in full.