Skip to content
docs
Arxo ↗

nb-13 — Repeatable norms without copying

For LLMs10 sections
← Course mapChapter 13 / 25 · Advanced

Northbridge is fictional. Every office, fine, notice and filer rule is synthetic and unofficial. No real municipal deployment or legal-validity claims. Verified profile: law 0.1.0, law.core/0.2.

The Northbridge permit desk keeps writing the same norm shape with different fillings: resale draws a fine unless an amnesty applies; a repeat late filing draws a fine, a first one only a notice; two visits mark a frequent filer. Hand copies drift — someone forgets the amnesty exception, or the priority that lets it win.

An expansion is the construct for this task: a rule template written once, with named params (parameters). Each expand block instantiates the template by binding every param, and the engine generates ordinary rules from it. No copies are maintained by hand.

You need nb-01: First permit: facts, a rule and a question for facts, strict rules, and law test as the way to check a claim. Below, TRUE_ONLY means “holds”, FALSE_ONLY means “provably does not hold”, and NEITHER means “no conclusion either way”. nb-03: Exceptions and conflicting rules showed defeasible rules — rules that hold unless defeated — and priorities, the named orders saying which rule wins a conflict. nb-04: The permit fee showed decision tables, an alternative way to compress repetitive norms.

The new words all describe the template machinery. The expansion is the template; each expand block is one instantiation. Its params are what an instance binds: binders, relations, values, lists, cases, integers, windows, options. Exports are the names an instance publishes, and each generated rule is one emit rule with a fixed stable identifier. Scope, dates, labels and sources flow from the expand site to the generated rules — that flow is metadata inheritance. Each word is explained again where it first appears below.

All fragments are excerpts from packs/examples/language-demo/templates/package.law (identifiers as written; surrounding relations and unrelated expansions cut).

Excerpt 1 — a whole template plus its instance (lines 28–42). The pair expansion takes three params — a subject binder (a placeholder for the quantified individual), a ground relation and a result relation — and emits one strict rule named self/ok, where self means “this instance”.

Arxo Law
expansion pair {
params { subject: binder; ground: relation(subject); result: relation(subject); }
exports { ok = self/ok; }
emit rule self/ok strict {
for subject; when ground(subject); then result(subject);
scope from self; effective from self; labels from self; source from self;
}
}
expand pair WorkerPermit {
label en unofficial "Workers in the city get a worker permit";
bind subject = a: Applicant;
ground = employed_in_city;
result = worker_permit;
}

Look at how the two halves divide the work: the template says how, the instance says what. The instance binds a real binder (a: Applicant) and two real relations to the three params. Look also at the last emit line — metadata inheritance: the generated rule takes scope, effective dates, labels and source from the expand site (self), so the answer carries the “Workers in the city” label.

Excerpt 2 — the sanction template head (lines 84–101): two binders, a value (option), a ground relation, a list of extra conditions, a result relation, and excluded — named exception cases with non-empty premise lists.

Arxo Law
expansion sanction {
params {
subject: binder;
matter: binder;
option: value;
ground: relation(subject, matter);
conditions: list<relation(subject, matter)> min 0;
result: relation(subject, matter, option);
excluded: cases { premises: list<relation(subject, matter)> min 1; };
}
exports { applicability = self/applicable; }
emit rule self/applicable defeasible {
for subject; for matter;
when ground(subject, matter);
for each p in conditions { when p(subject, matter); }
then result(subject, matter, option);
scope from self; effective from self; labels from self; source from self;
}

Look at the for each p in conditions line: it unrolls the list, so one template covers “ground plus conditions yields a sanction” for any arity-two relations. Look also at the emit head: the main rule is defeasible, because exceptions exist by design (next excerpt).

Excerpt 3 — the exception loop of the same template (lines 102–116). Per excluded case it emits two nodes: a defeater concluding not result(...), and a priority preferring the defeater over the main rule. key(c) names each node after its case; the case label is prefixed, falling back to the expand site.

Arxo Law
for each c in excluded {
emit rule self/excluded/key(c)/excludes defeasible {
for subject; for matter;
for each p in c.premises { when p(subject, matter); }
then not result(subject, matter, option);
scope from self; effective from self;
labels from c prefix "[excludes] "; source from c fallback self;
}
emit priority self/excluded/key(c)/priority {
prefer self/excluded/key(c)/excludes over self/applicable;
reason explicit_exception;
labels from c prefix "[priority] "; source from c fallback self;
}
}
}

Look at the emitted names: each carries key(c), so the case key becomes part of the stable identifier. The amnesty defeater is addressable as ResaleFine/excluded/Amnesty/excludes, its priority as ResaleFine/excluded/Amnesty/priority. Per excluded case, two nodes: a defeater concluding not result(...), and a priority preferring it over the main rule.

Excerpt 4 — the sanction instance (lines 118–131): ground trades_at, one condition (office_open), option Fine, the 2026 window, and the Amnesty case with its premise.

Arxo Law
expand sanction ResaleFine {
label en unofficial "Resale at an open office draws a fine unless exempt";
bind subject = a: Applicant;
bind matter = o: Office;
option = Fine;
ground = trades_at;
conditions = [office_open];
result = fined_for;
effective [@2026-01-01, @2027-01-01);
case excluded Amnesty {
label en unofficial "Amnesty exemption";
premises = [exempt_case];
}
}

Look at how much this one block fixes: the ground, the condition list, the option, the 2026 window, the labels, and the Amnesty case with its premise. Window, labels and exception all come from here — change the window in this block and all three generated nodes move together.

Excerpt 5 — the optional-premise template (lines 140–155). The cause param has type option<relation(subject)>: a relation or absent. if some cause as g adds the premise only when the instance passes one; with none the rule fires on the ground alone.

Arxo Law
expansion late_fee {
params {
subject: binder;
cause: option<relation(subject)>;
ground: relation(subject);
result: relation(subject);
}
exports { applicability = self/applicable; }
emit rule self/applicable strict {
for subject;
when ground(subject);
if some cause as g { when g(subject); }
then result(subject);
scope from self; effective from self; labels from self; source from self;
}
}

Look at the if some cause as g line: the extra premise appears only when the instance passes a relation. some versus none is chosen per instance, not per case — the same template produces both a “needs a record” rule and an “always fires” rule.

Excerpt 6 — the two instances (lines 157–171). WithRecord passes some repeat_late; FirstNotice passes none.

Arxo Law
expand late_fee WithRecord {
label en unofficial "late filing draws a fine on repeat";
bind subject = a: Applicant;
cause = some repeat_late;
ground = files_late;
result = late_fined;
}
expand late_fee FirstNotice {
label en unofficial "first late filing draws a notice";
bind subject = a: Applicant;
cause = none;
ground = files_late;
result = late_noticed;
}

Look at the cause lines: WithRecord passes some repeat_late, FirstNotice passes none. Each instance keeps its own label and result; the point of the pair is the cause premise — present in one generated rule, absent in the other.

Run the templates suite:

Terminal
law test packs/examples/language-demo/templates

Observed result (engine law 0.1.0):

Output
law test demo.northbridge.templates: мир demo.northbridge.templates, demo.northbridge.vocabulary
ok [demo.northbridge.templates] tests/templates.lawtest / pair: city worker
ok [demo.northbridge.templates] tests/templates.lawtest / threshold: two visits
ok [demo.northbridge.templates] tests/templates.lawtest / threshold not met
ok [demo.northbridge.templates] tests/templates.lawtest / sanction applies
ok [demo.northbridge.templates] tests/templates.lawtest / exception lifts the sanction
ok [demo.northbridge.templates] tests/templates.lawtest / some-premise without a record does not fire
ok [demo.northbridge.templates] tests/templates.lawtest / some-premise with a record fires
ok [demo.northbridge.templates] tests/templates.lawtest / none-premise always fires
итого: 8 проверено, 8 прошли, 0 не прошли, 0 не исполнены; код 0

All eight tests pass: each answer matched its expectation. That says the machine generates and applies the declared templates — it is not a ruling that Northbridge fines the right people. The first line names the world under test: the templates package together with the vocabulary package it is evaluated with. The summary line is in Russian and says that 8 tests were checked, 8 passed, none failed and none were left unexecuted, with exit code 0.

The package also passes the static check:

Terminal
law engine check packs/examples/language-demo/templates/package.law
Output
check OK: packs/examples/language-demo/templates/package.law

check OK means the package file is well-formed; the suite run above is what exercises its behavior.

What the decisive tests assert (evaluate truth(...) on generated rules):

TestSetupExpects
pair: city workeremployed in the cityworker_permit TRUE_ONLY
threshold: two visitson record, year set, two 2026 visitsfrequent_filer TRUE_ONLY
threshold not metsame, but one visitfrequent_filer NEITHER
sanction appliestrades at an open office, June 2026fined_for(..., Fine) TRUE_ONLY
exception lifts the sanctionsame plus exempt_casefined_for(..., Fine) FALSE_ONLY
some-premise without a record does not firefiles_late onlylate_fined NEITHER
some-premise with a record firesfiles_late plus repeat_latelate_fined TRUE_ONLY
none-premise always firesfiles_late onlylate_noticed TRUE_ONLY

Reading a generated rule back to its source works because every generated node carries a stable identifier plus an inherited label. The fine applicator is ResaleFine/applicable, its defeater is ResaleFine/excluded/Amnesty/excludes, and the repeat-fine rule is WithRecord/applicable. When a test reports TRUE_ONLY, the emit block with the matching self/... name is the exact rule that fired.

The task is to write each recurring norm shape once and instantiate it with different grounds, conditions, exceptions and thresholds, so a fix — a new exception, a moved window — is made in exactly one place. The repetitions differ in relations and values, not in arithmetic. An expansion binds relation names, binders, lists of premises, cases, integers, windows and options — the very things that vary between the instances. One construct covers all of them.

Copying rules by hand drifts: the amnesty priority is exactly the line a copy forgets. Decision tables from nb-04: The permit fee compress rows sharing one head, but they cannot emit a defeater plus a priority per case, nor switch a premise with some/none. Plain strict rules from nb-01: First permit: facts, a rule and a question cannot abstract over relation names at all.

The proof is the eight tests above: every generated rule — pair, threshold, sanction applicator, amnesty defeater, both late-fee variants — is executed by law test, not asserted in prose. The some/none trio is the sharpest proof: identical ground, three outcomes from the one differing premise. What the tests do not prove is that these are the right municipal norms. The tests prove the machine generates and applies the declared templates; no test can prove Northbridge fines the right people. All acts are fictional data, not legal advice.

Take some-premise without a record does not fire and add one fact: repeat_late(ann).

With files_late alone, truth(late_fined(ann)) is NEITHER — the WithRecord/applicable rule waits on the optional premise. Add the record and the twin test is TRUE_ONLY. The third test, none-premise always fires, is TRUE_ONLY in both setups: no optional premise to wait on. The same one-change logic holds for the sanction, where only exempt_case separates TRUE_ONLY from FALSE_ONLY, and for the threshold, where one visit is NEITHER and two visits are TRUE_ONLY.

A natural mistake is to assume the optional premise is satisfied: “Ann filed late, so the repeat fine applies.” It does not — the query is NEITHER with files_late on record. cause = some repeat_late adds an extra premise; some names what the option holds, it does not waive it.

The fix is to read the two spellings exactly: some X means “and X must also hold”, none means “no further premise”. The trio holds you to it — ground alone gives NEITHER for the some rule and TRUE_ONLY for the none rule.

Generation happens before evaluation. By the time law test runs, instances are ordinary rules. Params cannot be rebound at question time — an expand binds everything up front.

Identifiers are stable, not free text. The defeater’s address contains the case key (excluded/Amnesty/excludes); renaming the case renames the node. Quote the full Instance/... path in explanations, never just “the exception rule”.

Metadata flows downhill. Scope, effective dates, labels and sources come from the expand site (from self), with case labels prefixed ([excludes], [priority]) or falling back. An instance without a label generates working rules with poor explanations.

NEITHER is not FALSE_ONLY. A threshold not met, or a some premise unrecorded, yields NEITHER — nothing concluded. Only an operating defeater, like the amnesty case, yields FALSE_ONLY.

Verified profile: engine law 0.1.0, semantics law.core/0.2. The expansion vocabulary (params, exports, self/... identifiers, from self inheritance, some/none options) is a fact about this profile’s implementation, never a claim about the language in general.

Without running the engine, predict, then check with law test:

  1. For Ann with files_late only, then with files_late plus repeat_late: state truth(late_fined(ann)) and truth(late_noticed(ann)) in each setup. Which of the four answers changes, and which generated rule (WithRecord/applicable or FirstNotice/applicable) is responsible for the change?
  2. In sanction applies, which single added fact flips fined_for(ann, city-hall, Fine) from TRUE_ONLY to FALSE_ONLY, and what are the stable identifiers of the two generated nodes that produce the flip?
  3. The ResaleFine instance declares effective [@2026-01-01, @2027-01-01). Which generated nodes inherit that window, and what would a December 2027 trade report for fined_for — TRUE_ONLY, FALSE_ONLY, or NEITHER? Why?
  4. In threshold not met, why is the status NEITHER rather than FALSE_ONLY — and what would have to be added to the template family to ever report FALSE_ONLY for Bob?

Predict first; run the suite; explain any miss in one sentence. Checkable solution: full solution: predictions and checkable answers.

  • Source: packs/examples/language-demo/templates/package.law (expansions pair, visits_threshold, sanction, late_fee; instances WorkerPermit, FrequentFiler, ResaleFine, WithRecord, FirstNotice)
  • Tests: packs/examples/language-demo/templates/tests/templates.lawtest (the eight tests)
  • Language reference: docs/language/09-advanced-cheat-sheet.law.md (expansion definition with the pair example; the construct glossary entries for expansion, expand, params, exports, key, list, option, some), docs/language/06-testing-a-package.law.md (the law test reference)
  • Prerequisite: nb-01: First permit: facts, a rule and a question; next: nb-14: Why this answer and what would change it

Three levels:

  1. Northbridge use (this article): worker-permit pair, two-visit threshold, resale sanction with amnesty, repeat-versus-first late norms — verified by the eight tests above.
  2. Domain template: whenever one norm shape recurs, write one expansion with relation, list, case, integer, window and option params; emit the main rule plus one defeater-plus-priority per excluded case; bind metadata from the expand site; address generated rules by their Instance/... stable identifiers.
  3. Confirmed example elsewhere: the language reference’s own pair expansion at docs/language/09-advanced-cheat-sheet.law.md — the same template-and-instance shape this article scales up with lists, cases and options.
  4. Confirmed external formalization (corpus): offence qualification and liability (Administrative Offences Code of Kazakhstan, art. 229, legal-entity parts 1–2) — package kz.corpus.administrative_code, corpus/laws/kz/codes/administrative-code/83-insurance-payments.law:28-40: expand offence_liability Article229Part1LegalEntity with a bind/params block, instantiated per part so one expansion shape covers Art229 P1/P2.
Sources and scope of verification

The instance binds subject/act binders, qualifies/liability/actor relations, element lists, empty condition lists, and guilt = none. Evidence: the packs/expansions presumption+offence series, selected by inspection of repeated identical instantiations. The recorded verification confirms the construct’s presence at the cited lines only, by direct file read; it makes no claim about deployment, runtime behaviour, or legal correctness.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.