docs← Back to article

Markdown for LLMs

Upgrades and compatibility

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# Upgrades and compatibility

## Goal

Move a deployment from one pinned state to another — runtime, canon
packages, configuration, or data formats — with the change reviewed
before traffic switches and the prior state kept bootable.

## Scope

Component `law` CLI + pinned world (`law.toml`, `law.lock`, `.arxo`
bundle, serve configuration), tool `0.1.1`. Scenario: a planned upgrade
with a staging host and a production host. All versions and paths are
synthetic created-examples.

## Prerequisites

- The current production pin set recorded: `law version --json` output,
  the production `law.lock`, the production bundle filename + hashes,
  and the serve invocation (world dir, journal dir, port).
- A staging host that can reach the registries (for `update`) or holds
  the new bundle (for offline hosts).
- A replay corpus: saved `law ask --out <dir>` evaluation directories
  and/or a decision journal for the current world.

## Steps

1. Classify the change and upgrade each layer separately, in this order
   (operator-policy-example order; the tool does not enforce it):
   (a) configuration, (b) canon packages (pins), (c) data formats,
   (d) runtime binary. Never move two layers in one switch.
2. Canon packages: on staging, inspect drift without touching the lock
   (real): `law outdated --project <dir>` — "what is pinned, what is
   published, whether the pin bytes match; the lock is untouched".
3. Apply the pin move as a preview first (real flags):
   `law update <name>[@<version>] --project <dir> --dry-run`, then with
   `--replay <dir>` to replay saved evaluations against the new closure.
4. Rebuild and re-verify the bundle (real):
   `law pack --project <dir> --out staging-N.arxo`, then
   `law inspect staging-N.arxo --verify --trust <key>`.
5. Run the package's declared scenarios over the new lock world (real):
   `law test` (flags: `--family`, `--file`, `--grep`, `--json`).
6. Expected-change review: diff old vs new evaluations (real):
   `law case diff <evaluation A> <evaluation B>` over `ask --out`
   directories (or `.arxo` answer sets). Every diff hunk must map to an
   intended normative change in the change record; an unmapped hunk
   blocks the switch.
7. Runtime binary: install the new `law` on staging only, re-run steps
   5–6, and compare `law version --json` (`tool`, `semantics`, `std`,
   `binaryHash`) old vs new in the record.
8. Pre-switch checks on production: confirm the current journal is
   durable, the current bundle is intact (`law inspect --verify`), and
   `GET /readyz` is `200`.
9. Switch serve traffic by repointing the world symlink and letting the
   server follow it (real flag): run `law serve` with `--watch-world`,
   which "watches the `--world` symlink target change and warms the new
   world"; then flip the symlink. Alternatively stop, repoint, and start
   (simpler; brief downtime). Either way the `--journal` directory does
   not change: the journal is shared across generations and new
   records simply carry the new pin. Mixed-pin journals are the
   normal post-switch state, and rollback selects records by pin
   (see [Rollback](/operate/rollback/)).
10. Keep the prior bundle, prior `law.lock`, and prior binary path
    untouched on the host until the new state is accepted (see Result
    check). Do not garbage-collect during the observation window.

## Expected result

- Staging `law test` green; `law case diff` contains only intended,
  recorded hunks.
- After the switch, `GET /v1/world` (real endpoint: the active pin)
  reports the new pin, and `GET /readyz` returns `200`.
- Prior bundle + lock + binary still present on disk.

## Result check

- Historical replay: re-run the replay corpus over the saved data
  (`law eval <dir>` for saved calculations;
  `law replay-record --world <dir> --journal <dir> --decision <id>`
  for journal entries, checking `resultHash`/`outcomeHash` per entry)
  and confirm the outcomes equal the staging-reviewed outcomes. This
  proves the saved state still reproduces — not that production
  serves it. Records are pin-checked against the loaded world, so
  replay each entry against the world its pin names — and under
  the executor the backup replay map names for its (pin,
  executor) group (see [Backup and restore](/operate/backup-restore/)):
  the tool does not verify executor compatibility itself, so a
  runtime move without a tested compatibility statement keeps the
  old binary as the replay executor for pre-move records — same
  pin, different group.
- Deployment check: send the control question through the real
  production ingress with the pre-approved new pin and compare the
  served decision (pin, `resultHash`, outcome) with the
  staging-reviewed expectation. Only this proves which generation
  actually answers the external address after the switch.
- Fresh-key canary (mandatory after any runtime move): the control
  question MUST use a new `Idempotency-Key` per run — resending a
  previously used key replays the journaled decision instead of
  exercising the new executable, so a fixed smoke key would
  "pass" without touching the new compute path. Confirm the new
  record's `engine` identity (version + `binaryDigest`) names the
  deployed runtime. The same-key repeat is a separate check that
  proves idempotent replay (same `decisionId`, no second record),
  never fresh computation.
- Acceptance checkpoint (operator-policy-example): N consecutive clean hours
  (created-example: 24) with zero unexpected diffs before the prior
  bundle may be archived.

## Failures and diagnostics

- `law update` without `--dry-run` surprises: re-read the help — the
  preview flags exist precisely for this; restore `law.lock` from
  version control and redo as `--dry-run` + `--replay`.
- New `law test` failures after a runtime-only move: the binary changed
  semantics-sensitive behavior; keep the old binary path and treat this
  as a runtime regression, not a canon problem.
- `/readyz` not `200` after a `--watch-world` flip: the new world did
  not warm on all workers; flip the symlink back (see [Rollback](/operate/rollback/))
  and inspect server stderr / `--log-socket` output.
- `--replay` mismatches with no `law case diff` mapping: stop; the
  closure moved further than the change record claims.

## Support boundaries

- Supported: per-layer preview and diff commands named above; symlink
  world switch with `--watch-world`.
- Reference setting: the layer order, observation window, and acceptance
  checkpoint are operator-policy-examples, not tool defaults.
- Out of scope: database migrations for host-side mutable data and
  multi-host rolling upgrades are not tool behavior; see rollback limits
  in [Rollback](/operate/rollback/).

## Next step

After acceptance, archive (do not delete) the prior bundle with its
hashes; if the switch must be undone, follow [Rollback](/operate/rollback/).