docs← Back to article

Markdown for LLMs

The lab: parcels fixtures tour

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

Download this articlePlain text ↗
# The lab: parcels fixtures tour

Five small fictional packages form a parcel registry that never
existed: parcels, owners, registration, fees, appeals, and one worked
case. The domain is invented on purpose, so no training sentence can
be mistaken for law. Every command below runs against these fixtures,
and every expected result on this page was recorded by running the
tool.

Environment: the public `law-v0.1.1` release on `PATH`, commands run
from the extracted bundle root (`corpus-lab/`), input files from the
lab archive. One route also needs a source checkout; its section says
so.

## Choose a route

The lab supports three routes. Each one stands on its own; start
with the first run if this is your first visit.

| Route | What the reader gets | Sections |
|---|---|---|
| First run | five initial packages, their tests, three questions on the case, and how to read the answers | [Before you start](#before-you-start), [Run the world](#run-the-world), [Ask the case](#ask-the-case) |
| Changing the corpus | publishing a version, changing the fee rate, migrating the shared vocabulary | [Versions, hands-on](#versions-hands-on), [Stage A](#stage-a--a-meaning-change-with-the-same-signature), [Stage B](#stage-b--a-shared-vocabulary-interface-change) |
| Structure and sources | structural queries, pinning, and extra checks; **requires a source checkout** (I) for `query` and `gen` | [Read the structure](#read-the-structure), [Provenance and shape](#provenance-and-shape) |

[Negative cases](#negative-cases) lists the expected no-answers of all
three routes, and [Templates](#templates) holds the copy-paste aids the
second route uses. Complete transcripts for every command are in the
[worked solutions](/corpus/lab/solutions/).

## Before you start

The install routes on the [Install](/cli/install/) page deliver the
binary only: a package manager, a setup script, or a release archive,
each carrying the same release bytes. None of them carries the tour
fixtures. The fixtures, templates, and check scripts ship as one
self-contained archive instead — start from an empty directory:

```shell
mkdir -p ~/corpus-lab-run && cd ~/corpus-lab-run
curl -O https://docs.arxo.io/build/corpus-lab-bundle.tar.gz
curl -O https://docs.arxo.io/build/corpus-lab-bundle.tar.gz.sha256
shasum -a 256 -c corpus-lab-bundle.tar.gz.sha256
tar xzf corpus-lab-bundle.tar.gz
shasum -a 256 -c corpus-lab/MANIFEST.sha256
cd corpus-lab
ls -d fixtures/parcels-*
mkdir -p /tmp/lab-work
```

The first check confirms the download. The manifest check confirms
every extracted file. Run it from the directory that holds
`corpus-lab/`, because the manifest paths carry that prefix. Run it
without a pipe, so that a failure exit is not lost. The listing names
ten fixture directories: the five without a version suffix are the
first run; the suffixed ones are later revisions, introduced in the
exercise that uses them. Everything the tour writes lands under
`/tmp/lab-work`; the fixtures stay unchanged. Every fixture path in
the tour is relative to the bundle root (`fixtures/...`).

Then confirm the build:

```shell
law version
```

Expected: the first line reads `law 0.1.1`. This means the public
release the transcripts were recorded on is the one on your `PATH`.
The full block, with the per-platform binary hash, is under
[Reproduction details](#reproduction-details).

Do not substitute the checkout `./law` launcher for the tour. It
builds newer source whose 0.2.6 numerals disagree with these 0.2.4
fixtures, and the fee scenario fails there. The checkout is needed
only by the two sections of the third route, and each of them names
its own prerequisites.

## The fixtures

The first run uses five packages:

| fixture | package | version | role |
|---|---|---|---|
| parcels-iface | labparcels.iface | 0.1.0 | shared vocabulary: parcels, owners, the holding link, the kind list |
| parcels-registry | labparcels.registry | 0.1.0 | the act: two pinned articles plus the rules that apply them |
| parcels-fees | labparcels.fees | 0.2.0 | first consumer: the fee each registered parcel owes |
| parcels-appeals | labparcels.appeals | 0.1.0 | second consumer: whether an appeal arrived inside its window |
| parcels-case1 | labparcels.case1 | 0.1.0 | the worked case: one parcel, three questions, recorded answers |

All five declare the same language line and the explicit import style.
The fees package sits at 0.2.0 while its neighbours sit at 0.1.0, so
the world mixes versions from the start; the
[versions exercise](#versions-hands-on) turns that into hands-on work.
The other five fixture directories are later revisions of the same
packages: [Stage A](#stage-a--a-meaning-change-with-the-same-signature)
and [Stage B](#stage-b--a-shared-vocabulary-interface-change) introduce
them.

## Run the world

Each fixture ships its scenarios and its pinned world, so `law test`
passes with no setup. Run the five in dependency order.

```shell
law test fixtures/parcels-iface
```

Expected last line: `total: 2 checked, 2 passed, 0 failed, 0 not run; code 0`.
The shared vocabulary holds on its own, with no dependencies.

```shell
law test fixtures/parcels-registry
```

Expected last line: `total: 4 checked, 4 passed, 0 failed, 0 not run; code 0`.
The act works over the shared vocabulary: registration and transfer,
each with one case where nothing should follow.

```shell
law test fixtures/parcels-fees
law test fixtures/parcels-appeals
```

Expected last lines: `total: 5 checked, 5 passed, 0 failed, 0 not run; code 0`
for the fees and `total: 3 checked, 3 passed, 0 failed, 0 not run; code 0`
for the appeals. Both consumers read the act through their pins.

```shell
law test fixtures/parcels-case1
```

Expected last line: `total: 4 checked, 4 passed, 0 failed, 0 not run; code 0`.
The whole five-package world is consistent, and the case can be asked.

Several scenarios expect silence rather than a verdict: a stranger's
holding, a missing kind, a late filing, a wrong amount. Silence there
is the correct behaviour. The full transcripts, with every scenario
name, are the [first worked solution](/corpus/lab/solutions/#scenario-transcripts),
and [why each silence holds](/corpus/lab/solutions/#why-each-silence-holds)
explains each one.

Background reading on packages and worlds lives under
[several packages and a world](/tutorials/packages/) and
[packages and cases](/cli/packages-cases/).

## Ask the case

The parcels-case1 fixture pins one matter: Amy holds parcel p1, the
parcel is residential, Amy asks to transfer it to Bob, and an appeal
arrives on day 25 of a window running from day 10 to day 40. Three
question files ship with the fixture; each asks whether one conclusion
holds.

```shell
law ask fixtures/parcels-case1 --case ParcelTransfer --query-json fixtures/parcels-case1/queries/fee-answered.json --format text
```

Expected, in the answer section (`## Ответ`) of the recorded transcript:

```text
Вычисление завершено.
Установлено, что «fee_due» (parcel: `p1`, сумма: 100); обратное не установлено.
```

The answer shell speaks Russian while the fixture words stay English.
The first line says the evaluation completed. The second line says the
fee of 100 for p1 is established and its opposite is not. The grounds
below it lead from the rule `FeeResidential` to the registration rule,
which cites article 1 of the pinned edition, and then to the two case
facts it used.

```shell
law ask fixtures/parcels-case1 --case ParcelTransfer --query-json fixtures/parcels-case1/queries/transfer-answered.json --format text
```

Expected, in the answer section:

```text
Вычисление завершено.
Установлено, что «transfer_granted» (parcel: `p1`, from: `amy`, to: `bob`); обратное не установлено.
```

The transfer from Amy to Bob is established. Its first ground cites
article 2; the registration step under it cites article 1.

```shell
law ask fixtures/parcels-case1 --case ParcelTransfer --query-json fixtures/parcels-case1/queries/appeal-answered.json --format text
```

Expected, in the answer section:

```text
Вычисление завершено.
Установлено, что «appeal_timely» (parcel: `p1`); обратное не установлено.
```

The appeal is timely: the grounds name the filing on day 25 and the
window from day 10 to day 40 as case facts.

All three conclusions are established, and each proof cites the case
facts and pinned articles it used. The complete records, with the
footer hashes and a reading guide, are in the
[answers solution](/corpus/lab/solutions/#answers). One question file
needs a warning: the amount travels in the compiled Integer shape, and
the solutions show why that spelling is required.

How a case package is put together is covered in
[a package in the corpus](/tutorials/package/).


## Read the structure

**Requires a source checkout** (I). Prerequisites for this section
only: a full repository checkout, commands run from the repository
root with the `./law` launcher. The public composition is smaller
than the checkout one: it has no `query` and no `gen` command. To
confirm that boundary on the public build:

```shell
law query --list 2>&1 | head -n 1
law gen pinning 2>&1 | head -n 1
```

```text
law: unknown command query
law: unknown command gen
```

LawQL questions read the formalization itself rather than any matter.
The tour uses the catalogued questions; list the catalog first:

```shell
./law query --list
```

Twenty-two entries answer: six named questions and sixteen checks.
Five questions below run against the fixtures; each names its target
package explicitly, so the snapshot stays inside the lab world.

Who derives the registered flag:

```shell
./law query producers --project docs/corpus/lab/fixtures/parcels-registry --param predicate=urn:law:lab:parcels:registry#registered --param pkg=labparcels.registry
```

One row: the strict registration rule, with its anchor on the first
article.

Who reads the holding link, across the whole lab world:

```shell
./law query readers --project docs/corpus/lab/fixtures/parcels-case1 --param predicate=urn:law:lab:parcels:iface#owns --param pkg='labparcels.*'
```

Two rows: both registry rules, each with the article behind it.

What the registration rule leans on, and how the parcel type resolves:

```shell
./law query anchors-of --project docs/corpus/lab/fixtures/parcels-registry --param node=urn:law:lab:parcels:registry#RegistrationComplete --param pkg=labparcels.registry
./law query type-step --project docs/corpus/lab/fixtures/parcels-registry --param type=urn:law:lab:parcels:registry#Parcel --param pkg=labparcels.registry
```

One anchor row pointing at the first article; one alias step from the
registry parcel type to the shared parcel type.

What the case package pulls in:

```shell
./law query imports-of --project docs/corpus/lab/fixtures/parcels-case1 --param pkg=labparcels.case1
```

Four rows, one per lab dependency. The full rows are recorded with the
structure solutions. The reference for the question language is the
[LawQL page](/lawql/).

## Provenance and shape

The registry fixture pins two invented articles and derives both of
them through rules. Four readings confirm the chain from text to
machine. The first two **require a source checkout** (I): full
repository, `./law` from the repository root. The public composition
has no `gen`, and its audit cannot see the lint catalog. First, the
pinning check, which compares the fragment block in the sources
file against the pinned bytes:

```shell
./law gen pinning docs/corpus/lab/fixtures/parcels-registry
```

A tick and two articles answer. Second, the audit over the same
fixture, which reports both articles executable, the text share they
cover, and sixteen structural checks with no findings:

```shell
./law audit docs/corpus/lab/fixtures/parcels-registry
```

Third, the import-block check on the registry package. The package
keeps its source model and its rules in two files, and the rules file
names the fragments it leans on in a block the tool maintains. Confirm
the block is canonical with the per-package form of the command, which
is the only form to use inside a shared tree:

```shell
law fix imports fixtures/parcels-registry --check
```

Fourth, the manifest and lock readings on any fixture, plus the
offline install, which confirms the pinned files already match the
lock without touching the network:

```shell
law engine manifest fixtures/parcels-registry/law.toml
law engine lock fixtures/parcels-registry --check
law install --offline --project fixtures/parcels-registry
```

The pinning drill is described in
[pin the act, edition, and publication](/recipes/i-sources/pin-publication/),
and the package mechanics in
[N. Package, context, snapshots, case, result, and test](/recipes/n-package/).

## Versions, hands-on

Everything so far was read-only. This part writes, so it works on
scratch copies outside the bundle and leaves the fixtures pristine.
The goal: publish the lab packages to a throwaway registry, watch the
currency check pass, author a new registry revision, watch the check
flag it, and move one consumer across.

Build the registry from the fixtures. Pack each package, then publish
each artifact under one registry name:

```shell
law pack --out /tmp/lab-work/artifacts/iface --project fixtures/parcels-iface
law pack --out /tmp/lab-work/artifacts/registry --project fixtures/parcels-registry
law pack --out /tmp/lab-work/artifacts/fees --project fixtures/parcels-fees
law pack --out /tmp/lab-work/artifacts/appeals --project fixtures/parcels-appeals
law pack --out /tmp/lab-work/artifacts/case1 --project fixtures/parcels-case1
law publish /tmp/lab-work/artifacts/iface --registry lab=/tmp/lab-work/registry
law publish /tmp/lab-work/artifacts/registry --registry lab=/tmp/lab-work/registry
law publish /tmp/lab-work/artifacts/fees --registry lab=/tmp/lab-work/registry
law publish /tmp/lab-work/artifacts/appeals --registry lab=/tmp/lab-work/registry
law publish /tmp/lab-work/artifacts/case1 --registry lab=/tmp/lab-work/registry
```

Check currency against that registry. Both pins report up to date:

```shell
law outdated --project fixtures/parcels-fees --registry lab=/tmp/lab-work/registry
```

Now author revision 0.2.0 of the registry package on a scratch copy:
copy the parcels-registry fixture aside, raise its version in the
manifest, in both law files, and in the four scenario files, add one
small derived rule, and confirm its four scenarios still pass on the
copied pins with no rewiring step. Pack and publish the revision into
the same registry. Currency now flags the new revision and exits
nonzero; the solutions record the exact lines.

Move a scratch copy of the fees consumer across. First preview:

```shell
law update 'labparcels.registry@0.2.0' --project /tmp/lab-work/fees-upd --registry lab=/tmp/lab-work/registry --dry-run
```

Then run the same command without the preview flag, and run the
consumer scenarios plus the currency check again: five passing
scenarios, both pins up to date. Asking for a revision that was never
published is refused; the solutions show that refusal too.

Version rules and release practice are covered in
[versions](/protocols/versions/).

## Stage A — a meaning change with the same signature

This stage uses one more fixture, a later revision of the fee
consumer:

| fixture | package | version | role |
|---|---|---|---|
| parcels-fees-0.3.0 | labparcels.fees | 0.3.0 | the residential rate moves, the signature stays |

The fee revision at 0.3.0 moves the residential rate from 100 to 120.
Nothing else changes: the same predicate, the same argument shapes,
the same commercial and garden rates. Confirm the revision passes on
its own over the original world:

```shell
law test fixtures/parcels-fees-0.3.0
```

Expected last line: `total: 5 checked, 5 passed, 0 failed, 0 not run; code 0`.
The revision is consistent by itself; the stage below shows what it
changes for the case. The rule that moves:

```law fixture="parcels-fees-0.3.0/package.law"
rule FeeResidential strict {
    for p: Parcel;
    for k: ParcelKind;
    when labparcels.registry::registered(p) and labparcels.registry::kind_of(p, k) and k == labparcels.iface::residential;
    then fee_due(p, 120);
}
```

Work on scratch copies under a stage directory, with its own
throwaway registry holding the original five states plus the fee
revision:

```shell
mkdir -p /tmp/lab-work/stage-a/artifacts /tmp/lab-work/stage-a/answers
law pack --out /tmp/lab-work/stage-a/artifacts/iface --project fixtures/parcels-iface
law pack --out /tmp/lab-work/stage-a/artifacts/registry --project fixtures/parcels-registry
law pack --out /tmp/lab-work/stage-a/artifacts/fees --project fixtures/parcels-fees
law pack --out /tmp/lab-work/stage-a/artifacts/appeals --project fixtures/parcels-appeals
law pack --out /tmp/lab-work/stage-a/artifacts/case1 --project fixtures/parcels-case1
law pack --out /tmp/lab-work/stage-a/artifacts/fees-0.3.0 --project fixtures/parcels-fees-0.3.0
law publish /tmp/lab-work/stage-a/artifacts/iface --registry lab=/tmp/lab-work/stage-a/registry
law publish /tmp/lab-work/stage-a/artifacts/registry --registry lab=/tmp/lab-work/stage-a/registry
law publish /tmp/lab-work/stage-a/artifacts/fees --registry lab=/tmp/lab-work/stage-a/registry
law publish /tmp/lab-work/stage-a/artifacts/appeals --registry lab=/tmp/lab-work/stage-a/registry
law publish /tmp/lab-work/stage-a/artifacts/case1 --registry lab=/tmp/lab-work/stage-a/registry
law publish /tmp/lab-work/stage-a/artifacts/fees-0.3.0 --registry lab=/tmp/lab-work/stage-a/registry
cp -r fixtures/parcels-case1 /tmp/lab-work/stage-a/case1
```

Before moving anything, save the three old answers and replay the
fee answer against its saved inputs. The replay reaches the same
hash, which is the point: old inputs, old answer, no drift.

```shell
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/fee-answered.json --format text --out /tmp/lab-work/stage-a/answers/fee-answered | tail -n 1
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/transfer-answered.json --format text --out /tmp/lab-work/stage-a/answers/transfer-answered | tail -n 1
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/appeal-answered.json --format text --out /tmp/lab-work/stage-a/answers/appeal-answered | tail -n 1
law eval /tmp/lab-work/stage-a/answers/fee-answered | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['resultHash'], d['results'][0]['truthStatus'], d['results'][0]['evaluationStatus'])"
```

Now predict the move before running it. After the case crosses to
the fee revision, the old residential amount goes silent, the new
amount is established, and the other two verdicts stand while their
hashes move with the changed program bytes:

| question | before | after |
|---|---|---|
| residential fee of 100 | established | silent |
| residential fee of 120 | silent | established |
| transfer from Amy to Bob | established | established, new hash |
| timely appeal | established | established, new hash |

Move the scratch case across, confirm the four predictions, and run
both consumers plus the case. The case run breaks exactly where
predicted — its fee expectation still names the old amount — so move
that expectation to the new amount and rerun until every test passes:

```shell
law update 'labparcels.fees@0.3.0' --project /tmp/lab-work/stage-a/case1 --registry lab=/tmp/lab-work/stage-a/registry --dry-run
law update 'labparcels.fees@0.3.0' --project /tmp/lab-work/stage-a/case1 --registry lab=/tmp/lab-work/stage-a/registry
law test /tmp/lab-work/stage-a/case1
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/fee-answered.json --format text | grep -E '^(Установлено|Не установлено|Вычисление)|resultHash'
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query 'evaluate truth(labparcels.fees::fee_due(entity_ref("urn:lab:parcel:p1"), 120));' --query-id fee-120 --format text | grep -E '^(Установлено|Не установлено|Вычисление)|resultHash'
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/transfer-answered.json --format text | grep -E '^(Установлено|Не установлено)|resultHash'
law ask /tmp/lab-work/stage-a/case1 --case ParcelTransfer --query-json /tmp/lab-work/stage-a/case1/queries/appeal-answered.json --format text | grep -E '^(Установлено|Не установлено)|resultHash'
perl -pi -e 's/fee_due\(entity_ref\("urn:lab:parcel:p1"\), 100\)/fee_due(entity_ref("urn:lab:parcel:p1"), 120)/' /tmp/lab-work/stage-a/case1/tests/01-worked-parcel.lawtest
law test /tmp/lab-work/stage-a/case1 | tail -n 1
law test fixtures/parcels-fees-0.3.0 | tail -n 1
law test fixtures/parcels-appeals | tail -n 1
```

The `grep` filters keep the Russian answer lines `Установлено`
(established), `Не установлено` (not established), and `Вычисление`
(evaluation), plus the `resultHash` footer; the
[answer glossary](/corpus/lab/solutions/#answers) translates every
recurring phrase.

The solutions record every line: the saved footers, the replay, the
preview and the move, the predicted break, the four verdicts after
the move, and the passing runs. The change template for a meaning
shift is the [shared dependency
change](/build/corpus-lab-templates/shared-dependency-change.md) aid.

## Stage B — a shared-vocabulary interface change

This stage uses four more fixtures, the renamed line:

| fixture | package | version | role |
|---|---|---|---|
| parcels-iface-0.2.0 | labparcels.iface | 0.2.0 | the shared vocabulary with the holding link renamed |
| parcels-registry-0.3.0 | labparcels.registry | 0.3.0 | the act migrated to the renamed link |
| parcels-fees-0.4.0 | labparcels.fees | 0.4.0 | the fee consumer on the renamed line, original rates |
| parcels-appeals-0.2.0 | labparcels.appeals | 0.2.0 | the appeal consumer on the renamed line |

The renamed line renames the holding link of the shared vocabulary
from `owns` to `holds`, keeping its shape: same owner and parcel
arguments, same key. The registry, fee, and appeal revisions follow
the rename; rates and windows stay at their original values. Stage A
and Stage B are alternative futures that branch from the original
states, not a single timeline, so each can be run on its own.
Confirm the four revisions pass:

```shell
law test fixtures/parcels-iface-0.2.0
law test fixtures/parcels-registry-0.3.0
law test fixtures/parcels-fees-0.4.0
law test fixtures/parcels-appeals-0.2.0
```

Expect 2, 4, 5, and 3 passing scenarios, the same totals as the
original states: the rename keeps every scenario. The full transcripts
are with the [scenario solutions](/corpus/lab/solutions/#scenario-transcripts).
The renamed declaration:

```law fixture="parcels-iface-0.2.0/package.law"
pub relation holds(owner: Owner, parcel: Parcel) kind institutional {
    key(parcel);
    label en unofficial "owner holds the parcel";
}
```

Build a second stage directory whose registry holds the original
five states plus the four renamed revisions:

```shell
mkdir -p /tmp/lab-work/stage-b/artifacts
law pack --out /tmp/lab-work/stage-b/artifacts/iface --project fixtures/parcels-iface
law pack --out /tmp/lab-work/stage-b/artifacts/registry --project fixtures/parcels-registry
law pack --out /tmp/lab-work/stage-b/artifacts/fees --project fixtures/parcels-fees
law pack --out /tmp/lab-work/stage-b/artifacts/appeals --project fixtures/parcels-appeals
law pack --out /tmp/lab-work/stage-b/artifacts/case1 --project fixtures/parcels-case1
law pack --out /tmp/lab-work/stage-b/artifacts/iface-0.2.0 --project fixtures/parcels-iface-0.2.0
law pack --out /tmp/lab-work/stage-b/artifacts/registry-0.3.0 --project fixtures/parcels-registry-0.3.0
law pack --out /tmp/lab-work/stage-b/artifacts/fees-0.4.0 --project fixtures/parcels-fees-0.4.0
law pack --out /tmp/lab-work/stage-b/artifacts/appeals-0.2.0 --project fixtures/parcels-appeals-0.2.0
law publish /tmp/lab-work/stage-b/artifacts/iface --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/registry --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/fees --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/appeals --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/case1 --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/iface-0.2.0 --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/registry-0.3.0 --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/fees-0.4.0 --registry lab=/tmp/lab-work/stage-b/registry
law publish /tmp/lab-work/stage-b/artifacts/appeals-0.2.0 --registry lab=/tmp/lab-work/stage-b/registry
cp -r fixtures/parcels-registry /tmp/lab-work/stage-b/reg-old
cp -r fixtures/parcels-case1 /tmp/lab-work/stage-b/case1-holds
cp -r fixtures/parcels-fees /tmp/lab-work/stage-b/fees-conflict
```

First the break. Moving the unmigrated act under the renamed
vocabulary is refused before anything is written: the refusal names
the missing word at both rules that read it and lists the new word
among the exports. A second refusal follows on the fee consumer —
moving one of two coupled pins alone is a version conflict — which
is why the migration below rewires pins together instead of moving
them one by one:

```shell
law update 'labparcels.iface@0.2.0' --project /tmp/lab-work/stage-b/reg-old --registry lab=/tmp/lab-work/stage-b/registry --dry-run
law update 'labparcels.iface@0.2.0' --project /tmp/lab-work/stage-b/fees-conflict --registry lab=/tmp/lab-work/stage-b/registry --dry-run
```

Then the migration. Rename the holding link in the scratch case
file and its scenarios, strip the four pins to a bare header,
re-pin against the renamed revisions, and restore the body with the
new import lines:

```shell
perl -pi -e 's/labparcels\.iface::owns\(/labparcels.iface::holds(/g' /tmp/lab-work/stage-b/case1-holds/cases/parcel-transfer.lawcase /tmp/lab-work/stage-b/case1-holds/tests/01-worked-parcel.lawtest
cp /tmp/lab-work/stage-b/case1-holds/package.law /tmp/lab-work/stage-b/case1-body.law
python3 -c "t=open('/tmp/lab-work/stage-b/case1-holds/law.toml').read(); t=t.split('[dependencies]')[0].rstrip()+'\n'; open('/tmp/lab-work/stage-b/case1-holds/law.toml','w').write(t); h=open('/tmp/lab-work/stage-b/case1-holds/package.law').read().split('\n'); open('/tmp/lab-work/stage-b/case1-holds/package.law','w').write('\n'.join(h[:3])+'\n')"
rm -rf /tmp/lab-work/stage-b/case1-holds/deps /tmp/lab-work/stage-b/case1-holds/law.lock /tmp/lab-work/stage-b/case1-holds/.law
law add 'labparcels.iface@0.2.0' --project /tmp/lab-work/stage-b/case1-holds --registry lab=/tmp/lab-work/stage-b/registry --from lab
law add 'labparcels.registry@0.3.0' --project /tmp/lab-work/stage-b/case1-holds --registry lab=/tmp/lab-work/stage-b/registry --from lab
law add 'labparcels.fees@0.4.0' --project /tmp/lab-work/stage-b/case1-holds --registry lab=/tmp/lab-work/stage-b/registry --from lab
law add 'labparcels.appeals@0.2.0' --project /tmp/lab-work/stage-b/case1-holds --registry lab=/tmp/lab-work/stage-b/registry --from lab
perl -pi -e 's/import labparcels\.iface version "0.1.0"/import labparcels.iface version "0.2.0"/; s/import labparcels\.registry version "0.1.0"/import labparcels.registry version "0.3.0"/; s/import labparcels\.fees version "0.2.0"/import labparcels.fees version "0.4.0"/; s/import labparcels\.appeals version "0.1.0"/import labparcels.appeals version "0.2.0"/' /tmp/lab-work/stage-b/case1-body.law
cp /tmp/lab-work/stage-b/case1-body.law /tmp/lab-work/stage-b/case1-holds/package.law
law test /tmp/lab-work/stage-b/case1-holds
```

Every test of the recomposed world passes, and all three case questions are
established again — including the residential fee of 100, which is
the point of the stage: the words changed, the meaning held. The
failing moves, the four dependency wires, and the three answers are
recorded with the solutions. The interface side of the change
template is the [shared dependency
change](/build/corpus-lab-templates/shared-dependency-change.md) aid alongside the
[public vocabulary
contract](/build/corpus-lab-templates/public-vocabulary-contract.md) promises the
rename had to renegotiate.

## Negative cases

The lab ships its no-answers alongside its answers. Each item below is
exercised by the fixtures and explained in the solutions:

| case | expected behaviour |
|---|---|
| a stranger's holding | the vocabulary stays silent |
| a holding without a recorded kind | registration stays silent |
| a transfer asked by a non-owner | the grant stays silent |
| a kind without a holding | no fee is due |
| the wrong amount for a kind | the fee is not established |
| a filing past the window end | timeliness is not established |
| a filing without registration | timeliness is not established |
| a currency check with a newer revision published | the pin is flagged, nonzero exit |
| an update to a revision that was never published | the command is refused |
| a fragment line edited by hand | the pinning check fails, and the diff names the line |
| the old amount after the rate change | the old fee stays unestablished; updating the expectation fixes the test, not the model |
| a consumer moved under renamed words without migration | the update is refused and names the missing word |
| moving one of two coupled pins | refused as a version conflict; re-pin together |

## Templates

The lab ships six copy-paste templates that the tour uses at each
decision point. They are documentation aids, not engine input files:

- [Corpus charter](/build/corpus-lab-templates/corpus-charter.md) — scope, sources,
  owners, and release criteria of a corpus area.
- [Public vocabulary contract](/build/corpus-lab-templates/public-vocabulary-contract.md) —
  the stability promises a shared vocabulary makes to consumers.
- [Vocabulary decision record](/build/corpus-lab-templates/vocabulary-decision-record.md) —
  one naming decision with context, options, and effect.
- [Shared dependency change](/build/corpus-lab-templates/shared-dependency-change.md) —
  interface and meaning changes with consumer checks.
- [Corpus review checklist](/build/corpus-lab-templates/review-checklist.md) — the checks
  each change passes before release.
- [Release checklist](/build/corpus-lab-templates/release-checklist.md) — composition,
  passports, reports, and replay for a release.

Each template also ships one filled example on lab material, with pasted
engine outputs recorded by running the tool:

- [Filled corpus charter](/build/corpus-lab-templates/filled/corpus-charter.md) — the parcel
  area, its package set, and the verification runs on record.
- [Filled public vocabulary contract](/build/corpus-lab-templates/filled/public-vocabulary-contract.md) —
  the stability promises of the shared parcel words.
- [Filled vocabulary decision record](/build/corpus-lab-templates/filled/vocabulary-decision-record.md) —
  why the parcel kind is a closed three-member enum.
- [Filled shared dependency change](/build/corpus-lab-templates/filled/shared-dependency-change.md) —
  moving the fees consumer across a registry revision.
- [Filled corpus review checklist](/build/corpus-lab-templates/filled/review-checklist.md) — the
  registry review with its evidence fences.
- [Filled release checklist](/build/corpus-lab-templates/filled/release-checklist.md) — the
  registry release with its publication line.

## Reproduction details

<details>
<summary>Toolchain, hashes, and full transcripts</summary>

Transcripts on this page and in the solutions quote standard output.
Compare verdicts and proof shape when you repeat the tour. The long
hashes in answer footers match whenever the inputs match.

Pinned tool: the public `law-v0.1.1` release, observed through
`law version` on macOS arm64:

```text
law 0.1.1
semantics: law.core/0.2
std for language 0.2: 0.2.0
lawql: lawql/1 (queryResult 0.1)
binary hash: sha256:cfa7c17232f2dc594e665dbbf2ad3c154675797ef4be07ef7d758c9abfb80a28
```

The first line is the tool version; the last line hashes the bytes
behind it, so record the whole block with your run. Linux x64
reports `sha256:be8b4fbb…` instead; both hashes are pinned in the
bundle's `PINNED.md`, which also records the source revision of the
archive.

The checksum file pins the archive:

```text
d274971f1618db195ea795803e9fd968c4d366903acd807ff9f2a573d0e33d0b  corpus-lab-bundle.tar.gz
```

Full transcripts for every command of the tour, including the
fixture listing and the version check, are in the
[worked solutions](/corpus/lab/solutions/#before-you-start).

</details>

## Further reading

- [A package in the corpus](/tutorials/package/)
- [N. Package, context, snapshots, case, result, and test](/recipes/n-package/)
- [LawQL](/lawql/)
- [Several packages and a world](/tutorials/packages/)
- [Packages and cases](/cli/packages-cases/)
- [Pin the act, edition, and publication](/recipes/i-sources/pin-publication/)
- [Versions](/protocols/versions/)

Worked solutions with complete transcripts: [solutions](/corpus/lab/solutions/).