Backup and restore
For LLMs9 sections
Capture everything needed to rebuild a working deployment on a clean machine, and prove the restore works before it is needed.
Component law CLI deployment: project files, bundles, journals, keys,
and serve configuration; tool 0.1.1. Scenario: scheduled backups plus
a restore drill on a clean machine. Paths and schedules are synthetic
created-examples.
Prerequisites
Section titled “Prerequisites”- A backup store the deployment host can write to but cannot silently
rewrite (operator-policy-example: append-only object prefix
s3://example-backups/law/— created-example location). - The production inventory: project dir, bundle files,
--journaldir, signing/registry keys, serve invocation, trusted-key configuration. - A clean drill machine with the pinned
lawversion installed (see Install and trust artifacts).
- Back up, as one labeled set (created-example label
bk-2026-10-03):- project sources:
law.toml,law.lock,.lawfiles, queries,pinning.toml/*.deontic-window.tomlinputs if used; - release bundles: every deployed
.arxofile; - decision journals: the full
--journaldirectories; - saved evaluations:
law ask --outdirectories kept as replay corpus; - keys: signing key files and registry credentials — separately encrypted (see step 3);
- serve configuration: the exact
law serveinvocation, port,--token-filereference,trustedKeys, registryID=LOCATIONmap.
- project sources:
- Copy consistency: journals are append-only during serve and each
decision is served only after its record is fsync’d (real serve
behavior). Quiesce writers with this ordered sequence — fencing
the port alone is not enough, because already-accepted calls keep
running and writing after accept stops:
- stop intake (disable the ingress route);
- stop the process and confirm the PID is gone (
systemctl stopthen check). There is no graceful drain: in-flight calls are killed, and only decisions completed before the kill have fsynced records — which is exactly the consistent set to copy; - confirm no writer holds the journal open (
lsof +D <journal>empty, or the process table shows no server); - copy or snapshot the volume;
- resume serving. If the backup includes mutable case packages, their external writers (editors, authoring tools) must be quiesced the same way. Never back up a journal dir with a plain recursive copy while the server appends to it.
- Data + secret protection: bundles and journals are integrity-checked
by hash/signature at restore (see Result check); key files keep mode
0600(reallaw key newbehavior) and travel encrypted under a key the backup store never sees (operator-policy-example: age/PGP envelope — created-example tooling). - Record a manifest for the set: filename, bytes,
sha256per file,law version --json, bundle--verifylines (created-example manifest format; the hashes themselves are the check) — plus a replay map: for each distinct (recorded pin, executor identity) pair found in the journal, which saved world directory AND which runtime executable replay it. The pin alone is NOT the group key: a runtime-only upgrade keeps the pin while the executable changes, so one pin routinely spans two executors (decision A on E1, decision B on E2) that must not share a replay row without a tested compatibility statement. Replay’s guarantee is conditional by construction: the same world (pin), the same input, and a compatible executor (real,replay.rsheader) — the tool pin-checks the world but does not verify executor compatibility itself. Each record carries its executor identity inengine: {host, version, binaryDigest}plus an execution receipt (implementation: law-serve-core/<version>, real,record.rs); copy that identity per (pin, executor) group into the replay map. The rule is explicit: a group replays only under its named executor (same version + binary digest), or under a version covered by a tested compatibility statement recorded in the manifest. Without either, keep the old binary with the set — “the currentlawreplays everything” is neither assumed nor denied here. A shared journal normally spans several (pin, executor) pairs, so the manifest must say which worlds and which executors cover which records. It must also declare the contract: full-history replay (every pair has its world AND its executor saved) or current-generation-only (older pairs restore as evidence but have no saved world to replay against). - Restore drill on the clean machine: install
lawfrom pinned supply, restore the set, then verify in this order (all real commands):law inspect <bundle> --verify --trust <key>,law testover the restored project,law eval <saved-calculation-dir>for saved calculations, then the grouped journal replay from step 7 (never one unscopedreplay-recordover a multi-generation journal). - Start
law serveon the drill machine with the restored world + journal copy and confirmGET /healthz,GET /readyz, andGET /v1/worldanswer as in production. - Saved-decision replay, grouped by (recorded pin, executor
identity).
law replay-recordre-executes journal entries on the loaded world and comparesresultHash/outcomeHashper entry (real) — but it pin-checks every record against that world first (replay.rs: record/world/pinversus the loaded pin), so a record from another generation reportsPIN_MISMATCHinstead of replaying. Keep the journal whole and split only the selection:- list decision ids per (pin, executor) group from the
restored segments (real record fields
decisionId,world.pin,engine) with the torn-tail-safe kit helper — never a plainjqover the segments, which prints partial output and exits5on the crash state the reader itself tolerates:kit/bin/arxo-list-decisions <journal-copy>(stdout TSV:decisionId,world,resolutionHash,engineVersion,binaryDigest, in segment order). Every newline-terminated line must be a valid record or the helper refuses naming file and offset; trailing bytes without a newline are accepted ONLY in the last segment and reported asTORN_TAIL(file, offset, length). The helper is read-only; a server restart instead cuts the tail and logs it, so run the listing on the backup copy before any restart consumes the evidence; - for each (pin, executor) group, pick the saved world AND the
named executor binary the manifest replay map records for
that pair and run
<that-law> replay-record --world <that-world> --journal <dir> --decision <id>…for the group’s ids. A group replayed under a different, undeclared executor proves nothing about the backup — executor identity is part of the acceptance, not a footnote. After a runtime-only upgrade this means two replay runs for one pin: pre-upgrade ids under the old binary, post-upgrade ids under the new one; - confirm the groups cover every id the manifest claims for the contract (full history, or the current generation plus older evidence kept without replay worlds — see step 4). A clean grouped replay is the restore’s acceptance proof.
- list decision ids per (pin, executor) group from the
restored segments (real record fields
Expected result
Section titled “Expected result”- Every manifest hash matches; every check command named in the
manifest exits
0; served endpoints answer with the production pin. - The drill machine can answer the replay corpus identically to the backup source.
Result check
Section titled “Result check”- Byte check: recompute
sha256over restored files vs the manifest. - Semantic check: the grouped replay from step 7 reports matching
hashes for every entry of every covered (pin, executor) group,
and the union of the groups equals the manifest’s claimed
coverage; spot-check
law case diffbetween a production evaluation and the drill’s re-execution — expect an empty diff. - Any mismatch fails the drill; do not “accept with notes”.
Failures and diagnostics
Section titled “Failures and diagnostics”- Hash mismatch on restore: storage or transfer corruption (or a tampered set); re-pull from the append-only store, investigate, do not serve from the mismatched copy.
law replay-recordmismatch on an entry: first distinguishPIN_MISMATCH(the entry belongs to another (pin, executor) group — replay it against the world AND the executor binary the manifest names for its recorded pair) from a hash mismatch on the right world, which means world drift (wrong bundle or lock), the wrong executor for a runtime-only split group, or journal truncation.- Permission errors on keys (
--key,--token-file): restore preserved the wrong mode/owner; keys must be readable only by the service account (operator-policy-example: dedicatedlawsvcuser — created-example name). - Partial journal copy (server was appending): date the set as inconsistent and retake it quiesced; a torn journal is not repaired by retrying the copy.
TORN_TAILfrom the listing helper after a crash (not a quiesce failure): expected and acceptable — the tail bytes were never a confirmed decision. Keep the backup bytes as taken (evidence), record file/offset/length in the drill report, and let the drill restart cut the tail with its log line. AnyREFUSALinstead fails the drill: investigate the named file and offset, do not hand-edit the segment into parseability.
Support boundaries
Section titled “Support boundaries”- Supported: hash/signature verification, scenario tests, saved-decision replay as restore acceptance.
- Reference setting: store location, labels, schedule (created-example: daily sets, 30-day retention), and the service account are operator-policy-examples.
- “Verified” in this article means only “hashes match and replays are clean” — it does not attest the normative correctness of the canon.
Next step
Section titled “Next step”Schedule the drill cadence in the operations calendar; for undoing a bad switch rather than a lost host, see Rollback.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.