Packages and cases
One session, two folders: a canon package holds the rules, a case package holds the situation. The canon below is a parcel tariff — up to 2 kg the fee is 500, above that 500 plus 150 for every kilogram over 2. Every terminal ran; every file is shown as written.
A canon package
Section titled “A canon package”law init with the package template creates law.toml and source.law:
$ law init parcel-tariff --name demo.parcel_tariff --template packageparcel-tariff: law.toml, source.lawThe rules go into source.law:
language "law.core" version "0.2";package demo.parcel_tariff version "0.1.0";namespace "urn:law:demo:parcel_tariff";
pub entity Shipment { label en unofficial "Shipment"; }
pub relation weight_kg(s: Shipment, kg: Decimal) kind empirical { label en unofficial "weight of the shipment, kg";}pub relation fee(s: Shipment, amount: Decimal) kind institutional { label en unofficial "postage under the tariff";}
rule BaseFee(s: Shipment, kg: Decimal) strict { label en official "up to and including 2 kg: base fee 500"; when weight_kg(s, kg); when kg <= 2.0; then fee(s, 500.0);}
rule HeavyFee(s: Shipment, kg: Decimal) strict { label en official "over 2 kg: 500 plus 150 for each kilogram above 2"; when weight_kg(s, kg); when kg > 2.0; then fee(s, 500.0 + (kg - 2.0) * 150.0);}Expectations are written from the tariff text before the run: 5 kg goes
to 500 + 3 × 150 = 950; exactly 2 kg stays at 500.
tests/tariff.lawtest:
language "law.core" version "0.2";package demo.parcel_tariff version "0.1.0";namespace "urn:law:demo:parcel_tariff";
test "urn:query:demo-parcel-tariff-t01-heavy" { given { context { legal_time @2026-09-25; decision_time @2026-09-25T00:00:00Z; knowledge_time @2026-09-25T00:00:00Z; timezone "UTC"; } assert "w": weight_kg(entity_ref("urn:case:box-1"), 5.0) { origin case_input; } } evaluate truth(fee(entity_ref("urn:case:box-1"), 950.0)); expect truth_status == TRUE_ONLY; expect evaluation_status == COMPUTED;}
test "urn:query:demo-parcel-tariff-t02-base" { given { context { legal_time @2026-09-25; decision_time @2026-09-25T00:00:00Z; knowledge_time @2026-09-25T00:00:00Z; timezone "UTC"; } assert "w": weight_kg(entity_ref("urn:case:box-2"), 2.0) { origin case_input; } } evaluate truth(fee(entity_ref("urn:case:box-2"), 500.0)); expect truth_status == TRUE_ONLY; expect evaluation_status == COMPUTED;}And law.toml gains the suite stanza:
[[tests]]family = "parcel_tariff"suites = ["tests/tariff.lawtest"]world = ["demo.parcel_tariff"]Check, fix, test
Section titled “Check, fix, test”The first draft carries a typo: line 23 of source.law calls the
relation weight_kgs. The static check reports it with a line, a column
and a stable code:
$ law engine check ../source.law:23:10: error LDC-E2102: predicate "weight_kgs" is not declared as a relation in this file(exit 1)The code LDC-E2102 names the fault — a predicate with no declaration — so an editor can react to the code rather than the wording. With the name corrected the check passes:
$ law engine check .check OK: .law engine lock writes law.lock:
$ law engine lock . --writewritten ./law.lockAnd law test runs the declared scenarios:
$ law testlaw test demo.parcel_tariff: world demo.parcel_tariff ok [parcel_tariff] tests/tariff.lawtest / urn:query:demo-parcel-tariff-t01-heavy ok [parcel_tariff] tests/tariff.lawtest / urn:query:demo-parcel-tariff-t02-basetotal: 2 checked, 2 passed, 0 failed, 0 not run; code 0A case under it
Section titled “A case under it”law pack puts the canon into one portable file:
$ law pack --out ../tariff.arxodemo.parcel_tariff → ../tariff.arxo: 6 files, 5 objects (12 KiB → 7 KiB after dedup → 6 KiB in the archive)containerHash sha256:829566fe4347…; capabilities: execute, rebuild, testA case package holds your situation; the canon arrives as a pinned dependency. A second folder, a second init:
$ cd .. && law init my-casemy-case: case package my_case, law.core language 0.2created: law.toml, package.law, main.lawcase, law.lockcase Main declared in main.lawcase and registered in law.toml; set the time axis by explicit inputnext: law add <package> → law install → law asklaw add pins the packed canon with no network:
$ cd my-case && law add ../tariff.arxodemo.parcel_tariff@0.1.0 ← ../tariff.arxo (containerHash sha256:829566fe4347…), registries: filefile ← /private<tmp>/my-case/.law/arxo-registry-77540/file (--registry)+ demo.parcel_tariff 0.1.0exports (3): Shipment, fee, weight_kgchanged: .law/transport.jsonchanged: deps/demo.parcel_tariff.lawir.jsonchanged: law.lockchanged: law.tomlchanged: package.lawThe case file sets the date axis and asserts one fact — a 5 kg shipment:
language "law.core" version "0.2";package my_case version "0.1.0";namespace "urn:law:my_case";
case Main { context { legal_time @2026-10-01; knowledge_time @2026-10-01T12:00:00Z; decision_time @2026-10-01T12:00:00Z; timezone "UTC"; } assert demo.parcel_tariff::weight_kg(entity_ref("urn:case:box-1"), 5.0) { origin case_input; }}The question asks the fee for that shipment:
evaluate truth(demo.parcel_tariff::fee(entity_ref("urn:case:box-1"), 950.0));law ask saves the request with the full evaluation document; piped
through jq the summary reads as a status with the result hash:
$ law ask --query "$Q" --query-id fee --format brief --out runs/fee | jq -r '.results[0].status, .resultHash'TRUE_ONLYsha256:0290d550984a…law eval recomputes the saved run and reaches the same hash:
$ law eval runs/fee | jq -r .resultHashsha256:0290d550984a…Where next
Section titled “Where next”- To write rules of your own: the Language topic, from a first rule to a package.
- To read the saved document field by field: How to read an answer and the Decision protocol.
- To see what a question sends: the Fact protocol.
- When the check fails on another code: Diagnostics.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.