docs← Back to article

Markdown for LLMs

Versioning

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

Download this articlePlain text ↗
# Versioning

An answer from the deadline example is reproducible only under the pins
that produced it. This page names the four version axes, the checklist
for moving any of them, and the rule for old captures: replay under old
pins, recompute under new ones, never overwrite.

```text
docs/build/examples/deadline-app
```

## The four axes

| Axis | Pinned where | Current value |
|---|---|---|
| SDK package | `package.json` | `@arxo/law 0.3.3`, `arxo 0.2.0` (Python) |
| Canon package | `package.json` | `@arxo/canon-bgb-fristen 0.1.5` |
| Canon model version | `openModel` in `src/runtime.ts` | `de.bgb.fristen@0.1.0` |
| Adapter and capture format | `src/to-case.ts`, `src/capture.ts` | `ADAPTER_VERSION 1.0.0`, `deadline-app.capture/1` |

Each axis moves for its own reason:

- **SDK package.** New engine or facade behavior. Answers keep their
  statuses and values across compatible SDK releases, but bytes may
  move: replays stay within one SDK.
- **Canon package.** New canon build: regenerated bindings, corrected
  metadata, repackaged engine files. The model version it carries may or
  may not move with it — check the package notes against the model pin.
- **Canon model version.** New formalization: changed rules, changed
  questions. A model move can change values and statuses, not just
  bytes.
- **Adapter and capture format.** New mapping from form fields to facts
  (`toFacts`, `toCaseInput`) or a new capture layout. The adapter
  version is stamped into every capture so a replay knows which mapping
  produced it.

The worked case pins all four: the packages above, the model
`de.bgb.fristen@0.1.0`, the adapter `1.0.0`, the capture format
`deadline-app.capture/1`, the time zone `Europe/Berlin`, and the policy
`urn:de:corpus:clir:bgb-fristen#BGB_FRISTEN_TAG`.

## What a capture records

A capture under `CAPTURE_FORMAT deadline-app.capture/1` carries its pins
with the answer, so a replay later still knows its terms:

- the form: `eventDate`, `durationDays`, `candidateEnd`, `legalTime`;
- the adapter version `ADAPTER_VERSION 1.0.0`;
- the SDK and canon package versions;
- the model version `de.bgb.fristen@0.1.0`, the time zone, and the policy;
- the answer: statuses, proof identifiers, the three hashes, and the
  canonical document as `documentBase64` with its `sha256`.

`verifyCaptureIntegrity` checks the bytes against the recorded hash
before `replayCapture` trusts them; either step fails closed.

## Upgrade checklist

1. Change one axis at a time and note the old and new values.
2. Reinstall from the lockfile (`npm ci`) so the tree matches the pins.
3. Run the full suite: `npm test`, the typecheck, and the Python checks.
   Read every failure as a behavior statement before touching code.
4. Compare the worked case: collect must still compute `2026-03-20`
   with `COMPUTED`, truth must still answer `TRUE_ONLY`, the wrong-date
   and partial cases must still answer `NEITHER`, and `whyNot` must
   still return 2 blockers — unless the upgrade note says otherwise, in
   which case the note is the new expectation and the suite is updated
   first.
5. If bytes moved but values did not, refresh the snapshots from runs
   you have read, one SDK at a time.
6. If the adapter moved, bump `ADAPTER_VERSION`, keep the old mapping
   readable for old captures, and add a regression case for the changed
   mapping.
7. If the capture format moved, bump `CAPTURE_FORMAT`, keep
   `verifyCaptureIntegrity` strict on both formats, and never rewrite
   old files into the new layout in place.

## Replay old, recompute new

- **Replay old.** To reproduce an old answer, restore its pins — SDK,
  canon package, model version, adapter — and run `replayCapture` (the
  `replay` command or the replay endpoint). Replay re-runs the stored
  query and compares the `result` hash; the checksum step separately
  verifies the stored document bytes. Neither step establishes
  byte-equality of the old and new documents, and neither checks the
  capture's metadata: restoring the recorded pins is the operator's
  job, because the code does not verify the installed environment
  against them.
- **Recompute new.** To answer under new pins, run `evaluate` again and
  store a **new** capture file. Never overwrite the old file: the old
  capture remains the record of what the old pins answered.

```text
old pins + old capture -> replayCapture  (must match)
new pins + same form   -> evaluate       (new file in captures/)
```

Cross-SDK replay is out of scope in both directions: a capture saved by
TypeScript is replayed by TypeScript, and a capture saved by Python is
replayed by Python. The statuses and values agree; the bytes do not.
Keep the old pins installed next to the new ones (separate checkouts or
separate machines) while old answers must stay reproducible during an
upgrade window.

## Next

- [Patterns: batch, offline, and questionnaire](/build/patterns/)