docs← Back to article

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.

Download this articlePlain text ↗
# 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.