# Verification and support matrix ## Goal Say precisely what this section's deployment recipe was checked against — platforms, configurations, versions, and executable checks with expected results — and what turns the word "verified" from a compliment into a test outcome. ## Scope Two branches, one per server. The **serve branch** covers the section's main route — `law serve` with a journal on one Linux host, serving the known world with the kit fixture. The **MCP branch** covers `law-mcp-server` (crate `law-mcp`) as a sliced container image, built by the checkout's image build and checked by the profile checker. Nothing here covers SDKs or desktop builds — those have their own articles. ## Section vocabulary (binding) These four words mean the same in every Operate article. Existence of code alone never earns a stronger word. | Word | Meaning | |---|---| | Implemented | the mechanism exists in the named source revision | | Described | an instruction for it is published in this section | | Verified | the named configuration passed the listed checks and the run record is available | | Supported | the team commits to keep this scenario working inside the stated boundaries | ## Prerequisites Serve branch: - The reference kit (`kit/secure-deploy/` next to these articles, tracked in git with `MANIFEST.sha256`) at the pinned revision; tool `0.1.1`; the known world `packs/examples/cases/kompaniya` with the recorded pin. - A Linux host for checks S4–S10 (socket binds are required); checks S1–S3 run anywhere the `law` binary runs. MCP branch: - The image digest under test and the profile it claims (the profile's slice pin file present in the tree). - `docker`, `curl`, `jq`, `python3` on the checking host (operator tools). ## Steps 1. Pick the branch for the deployment under test and confirm its platform, configuration, and version cells below. Deviations under test must be named with the check run, or the run does not count as this matrix. 2. Run that branch's executable checks in order; each names its command and expected result. Stop at the first red cell and triage with [Troubleshooting](/operate/troubleshooting/). 3. Apply the recipe-verified conditions at the end before writing "verified" anywhere. ## Serve branch (main route) Platform cell: Linux x64 host (systemd-shaped layout per the kit; the served binary is the pinned `law` build). Configuration cells: loopback bind `127.0.0.1:8480`, token file set, journal directory set, known world `examples.cases.kompaniya@0.1.0`, workers `2`, call timeout 30 s. Version cells: tool `0.1.1`; world pin and `programHash` from the kit's `expected/serve-ask-expected.json`. Getting the kit: it lives in git next to these articles — no local overlay needed. The deployment record that qualifies a release names two values: the checkout revision and the kit digest. Fetch exactly that kit — not "current main" — and verify it before use. From any clone: ```bash CLONE= # any clone containing the revision below; every # git call names it via -C, so this sequence never # depends on the current directory REV= # full 40-hex, e.g. the S1 row below git -C "$CLONE" cat-file -t "$REV" >/dev/null || git -C "$CLONE" fetch origin git -C "$CLONE" cat-file -t "$REV" >/dev/null || { echo "revision not found" >&2; exit 1; } DEST=$(mktemp -d /tmp/kit-under-test.XXXXXX) || exit 1 git -C "$CLONE" archive --output="$DEST/kit.tar" "$REV" \ docs/operate/secure-deployment/kit || { echo "kit archive failed" >&2; exit 1; } tar -xf "$DEST/kit.tar" -C "$DEST" || { echo "kit extraction failed" >&2; exit 1; } KIT="$DEST/docs/operate/secure-deployment/kit" [ -f "$KIT/secure-deploy/MANIFEST.sha256" ] || { echo "kit layout unexpected" >&2; exit 1; } (cd "$KIT/secure-deploy" && shasum -a 256 -c MANIFEST.sha256) || exit 1 shasum -a 256 "$KIT/secure-deploy/MANIFEST.sha256" # must equal the digest in the deployment record (cd "$KIT" && sh tests/test-rotate-token.sh && sh tests/test-list-decisions.sh) || exit 1 ``` `git archive` takes the revision, not the worktree, so a dirty checkout cannot leak into the kit under test; the digest check then binds the extracted files to the recorded run. All paths derive from the computed `$KIT` root — no relative-`cd` chain to miscount. The helpers under `kit/bin/` travel in the same archive (outside `MANIFEST.sha256` scope, which covers `secure-deploy/` only) and are qualified by their committed suites, not by the digest. A run record filed in `expected/RUN-RECORD.md` at a new revision + digest is what turns S4–S10 from NOT-RUN to green. Executable checks (each names what was executed where): - S1. Local smoke (executed at revision `ab75e84c`, tool `0.1.1`): `law ask packs/examples/cases/kompaniya --query-json packs/examples/cases/kompaniya/queries/zaregistrirovana.json --out --format brief` → `TRUE_ONLY`, `resultHash sha256:b6094c8e…64487`. - S2. Fixture validity (executed): both `requests/serve-ask*.json` parse, and the stale-pin file differs from the positive one in `pin.resolutionHash` only. - S3. Pin provenance (executed): the fixture pin equals the world's `law.lock` `resolutionHash` and `name@version`. - S4. Start on the Linux host per the kit unit; `GET /readyz` → `200` after workers warm (needs the Linux run). - S5. `GET /v1/world` reports the fixture pin and `programHash` (needs the Linux run). - S6. Fixture ask through the ingress → `200`, served `resultHash` equals the recorded `sha256:b6094c8e…64487` (needs the Linux run). - S7. Stale-pin fixture → `409 SERVE_PIN_MISMATCH`; missing/wrong token → `401 SERVE_UNAUTHORIZED` (needs the Linux run). - S8. Restart: `/readyz` → `200` again with the same pin and `programHash` before ingress re-enable (needs the Linux run). - S9. Journal holds one fsynced `segment-*.jsonl` line per served decision; `law replay-record --world --journal --decision ` replays green (needs the Linux run). - S10. Kit `check-postflight.sh` green end to end (needs the Linux run). Limits that bound the branch: 1 MiB max body, 16 MiB max response, queue depth 64, 10 000 max assertions, no graceful drain (signals kill in-flight calls), one shared journal across generations with per-record pins. ## MCP branch Platform cell: `linux/amd64` only (builder `rust:1.94.1-alpine`). Any other architecture is not checked — do not report it as supported. Executable defaults (what the binary does with no flags or env): host `127.0.0.1`, port `8722`, profile `all`, no token, no call limit, journal on, journal body cap 64 KiB per side, journal socket `/run/systemd/journal/socket`. Effective configuration of the image under test (what M4–M8 run against — these override the defaults and the run record must name them): a sliced profile (a fixed non-`all` name; `all` is refused at build per M1), a token set (M8's `401` probe needs a tokened instance), an origin allowlist set to a fixed list (M8's `403` probe needs a disallowed `Origin` to exist), loopback-only publish per M4. Version cells: `serverInfo.version "0.1.0`; protocol revisions `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`; slice-pin schema `arxo.mcp-slice-pin/1`. Executable checks. Run M1–M3 on the build host, M4–M8 against the running image: - M1. `python3 /image_tree.py --out ` (the image-tree helper in the checkout's MCP deploy directory), then `--check-pin ` → exit `0` both (mismatch exits `1` with a path diff — real). Profile `all` is refused at build (real). - M2. The profile checker passes on the profile under test (real check). - M3. `law-mcp-server --help` → usage line on stderr, exit `2` (real quirk: help exits through the error path). - M4. Full launch with the effective configuration stated inline (no hidden entrypoint state — token, profile, and origin allowlist come from the env file the run record names): ```bash TOKEN=$(cat ) # 0600, run-record names it CID=$(docker run -d -p 127.0.0.1::8722 \ -e LAW_MCP_TOKEN="$TOKEN" \ -e LAW_MCP_PROFILE= \ -e LAW_MCP_ALLOWED_ORIGINS= \ ) PORT=$(docker port "$CID" 8722 | sed 's/.*://') ``` then poll `GET /healthz` on `127.0.0.1:$PORT` up to ~60 s → `200` with `"status":"ok"` and `"oracleGuard":"absent"` (real assertions; any other guard value means the slice composition is violated). The run record keeps `CID`, `PORT`, the digest, the profile, the allowlist, and the token-file reference; M5–M8 below all address `127.0.0.1:$PORT`. - M5. `GET /healthz` full shape → `200` JSON containing `status`, `pid`, `profile`, `verificationPending: 0`, `verified: 0`, `engineOwnsIr: true`, `oracleGuard: "absent"`, `driftScope: "semanticHash"`, `overrunCalls` (real route). - M6. Smoke `POST /mcp` `tools/call` `law_search` `{"query":"Vertrag"}` (real CI query) → `200`, `.result.content[0].text` present, `.result.isError != true` (real CI assertion). - M7. `law-mcp-server --healthcheck --port ` against the running instance → exit `0` (real; it dials `127.0.0.1`, `GET /healthz`, 4 s timeouts, and requires `HTTP/1.1 200` plus `"status":"ok"`). - M8. Negative probes (real statuses), each violating exactly one condition against the M4 instance on `127.0.0.1:$PORT` (valid token + allowed origin + known version unless the probe is about that field): missing/wrong token → `401`; disallowed `Origin` → `403`; unknown `mcp-protocol-version` on non-`initialize` → `400`; body over 1 MiB → `413`; `GET /mcp` → `405`; `POST /elsewhere` → `404`. Limits that bound the branch (implementation-limits, real): request `MAX_BODY` 1 MiB; journal default 64 KiB per side; answers-service 30 s / 32 MiB; healthcheck 4 s legs; no in-code connection, memory, or per-user caps; no worker cancellation. ## Expected result Serve branch: S1–S10 green on the pinned runtime + world; the run recorded with revision, pin, deviations, and date. MCP branch: all checks green on one pinned digest; the run recorded with digest, profile, deviations, and date. ## Failures and diagnostics - M4 orange past 60 s: image never healthy — `docker logs`, then Memory/Filesystem areas of [Troubleshooting](/operate/troubleshooting/). - M6 red while M4–M5 green: slice serves but the canon does not cover the probe query — wrong profile for the question, or a regressed slice; compare against the previous digest. - M8 wrong status: the instance is not the code this matrix describes (stale digest, patched build) — stop, re-pin, restart the matrix from M1. - S6 hash differs from the recorded `resultHash`: the served world is not the fixture world (drifted copy, wrong target) — stop, re-derive the pin from the served `law.lock`, restart from S3. - Any deviation from the configuration cells (different timeout, token, origins) without a record: the run is evidence for that deviation only, not for this matrix. ## Support boundaries - Supported, serve branch: Linux x64, pinned `law` build, known world served per [Deploy a private HTTP service](/operate/private-http-service/), upgraded per [Upgrades and compatibility](/operate/upgrades-compatibility/), backed up per [Backup and restore](/operate/backup-restore/), within the serve-branch limits. - Supported, MCP branch: `linux/amd64` sliced images of the profiles with committed slice pins, run per [Deploy a private MCP server](/operate/private-mcp-server/), sized per [Capacity and scaling](/operate/capacity-scaling/), within the MCP-branch limits. - A serve deployment counts as verified only if S1–S10 are green on the pinned runtime + world and the run record names the revision, pin, deviations, and date. An MCP deployment counts as verified only if M1–M8 are green on the pinned digest with the same record discipline. Drop any check and the word is "unverified", however healthy the instance looks. - "Secure" in this section means only the mechanisms named in its articles (token check, loopback default, public-bind refusal, origin allow-list, unprivileged image user, pinned bytes). It does not mean audited, certified, or penetration-tested. - Out of matrix: other architectures, unpinned builds, profile `all` in images, shared artifact directories, modified sources, and anything the checks above did not execute. ## Next step File the matrix run (revision/digest, pin/profile, deviations, green/red per check) with the release it qualifies; re-run it for every new build — a green matrix never transfers to another one.