# nb-13 — Repeatable norms without copying *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`.* ## Situation 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. ## Prerequisites You need [nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/) 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](/tutorials/northbridge/nb-03-exceptions/) showed defeasible rules — rules that hold unless defeated — and priorities, the named orders saying which rule wins a conflict. [nb-04: The permit fee](/tutorials/northbridge/nb-04-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. ## Minimal example 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". ```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. ```law expansion sanction { params { subject: binder; matter: binder; option: value; ground: relation(subject, matter); conditions: list min 0; result: relation(subject, matter, option); excluded: cases { premises: list 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. ```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. ```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`: 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. ```law expansion late_fee { params { subject: binder; cause: option; 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`. ```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. ## Command and result Run the templates suite: ```sh law test packs/examples/language-demo/templates ``` Observed result (engine `law 0.1.0`): ```text 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: ```sh law engine check packs/examples/language-demo/templates/package.law ``` ```text 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): | Test | Setup | Expects | |---|---|---| | `pair: city worker` | employed in the city | `worker_permit` TRUE_ONLY | | `threshold: two visits` | on record, year set, two 2026 visits | `frequent_filer` TRUE_ONLY | | `threshold not met` | same, but one visit | `frequent_filer` NEITHER | | `sanction applies` | trades at an open office, June 2026 | `fined_for(..., Fine)` TRUE_ONLY | | `exception lifts the sanction` | same plus `exempt_case` | `fined_for(..., Fine)` FALSE_ONLY | | `some-premise without a record does not fire` | `files_late` only | `late_fined` NEITHER | | `some-premise with a record fires` | `files_late` plus `repeat_late` | `late_fined` TRUE_ONLY | | `none-premise always fires` | `files_late` only | `late_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. ## Why this construct 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](/tutorials/northbridge/nb-04-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](/tutorials/northbridge/nb-01-first-permit/) 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. ## Changed condition 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. ## Typical mistake 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. ## Limits 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. ## Exercise 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](/tutorials/northbridge/solutions/nb-13-solutions/). ## Sources - 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](/tutorials/northbridge/nb-01-first-permit/); next: [nb-14: Why this answer and what would change it](/tutorials/northbridge/nb-14-why-this-answer/) 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.