Markdown for LLMs
nb-05 — Who counts as a suitable applicant
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# nb-05 — Who counts as a suitable applicant
*Northbridge course, intermediate ([nb-01](/tutorials/northbridge/nb-01-first-permit/) →
[nb-02](/tutorials/northbridge/nb-02-missing-fact/) → [nb-03](/tutorials/northbridge/nb-03-exceptions/) →
[nb-04](/tutorials/northbridge/nb-04-permit-fee/) → [nb-05](/tutorials/northbridge/nb-05-suitable-applicant/)).
Northbridge is synthetic: every act, district, household and
payment below is fictional and unofficial, and nothing here claims real
municipal deployment or legal validity. Engine `law 0.1.0`, semantics
`law.core/0.2`, std `0.2.0`.*
## Situation
The Northbridge permit office keeps a priority shortlist called
*suitable applicant*: a central-district resident from a large household
is offered the first free appointment. Meanwhile the cashier totals fee
payments, and the ledger is messy — Ann's three identical 10 EUR
instalments were recorded as three separate receipts.
Two questions reach the same desk: who belongs to the group, and what
was actually paid? This article turns both into Law DSL constructs: a
**definition** (a named shorthand for a condition), a
**classification** (a label attached when conditions hold), and
**aggregates** (`count` and `sum` folding a collection into one value) —
including the Set-versus-List pair that decides whether identical
payments are merged or added up.
## Prerequisites
[nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/):
facts, one rule, one question, and `law test` as the way to check a
claim. [nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/): `Money` values such
as `10 EUR`, and the shared constant `LARGE_HOUSEHOLD` (the integer `4`,
defined once in the calculations package and reused here).
Everything below either asks a *truth question* (does the label hold?)
or computes a *value* (how many? how much?) and stores it in a relation
— no duties, evidence, or time machinery is required.
## Minimal example
Excerpt 1 — from `packs/examples/language-demo/permits/package.law`
(lines 53–55; imports, relations and the other rules cut):
```law
definition central_resident(a: Applicant) exact {
when demo.northbridge.vocabulary::resident(a) and demo.northbridge.vocabulary::lives_in(a, demo.northbridge.vocabulary::central);
}
```
A **definition** gives a shorthand name to a condition. `exact` means
the name holds exactly when the body holds: resident *and* living in
the central district, nothing more, nothing less. In the code, `when`
carries the full condition — two facts about `a`, joined by `and` —
while the header names the shorthand.
Excerpt 2 — from the same file (lines 57–62; surrounding rules cut):
```law
rule LargeHousehold strict {
for a: Applicant;
when demo.northbridge.vocabulary::resident(a)
and count(collect m: Applicant where demo.northbridge.vocabulary::household_member(a, m)) >= demo.northbridge.calculations::LARGE_HOUSEHOLD;
then large_household(a);
}
```
`collect m: Applicant where ...` gathers every household member of `a`
into a **Set**: each distinct member appears once. `count(...)` is an
**aggregate** — it folds the whole collection into one number — and the
rule compares it against the shared threshold `4`. Follow the `when`
condition: the collection, the count, and the comparison with
`LARGE_HOUSEHOLD` all sit in that one clause.
Excerpt 3 — from the same file (lines 117–119; presumptions and
fictions cut):
```law
classification central_senior(a: Applicant) strict {
when central_resident(a) and large_household(a);
}
```
A **classification** attaches a group label when its body holds. Here it
stacks the two previous constructs: the definition plus the aggregate
rule. `central_senior` is the article's "suitable applicant". Its body
holds just two names — the shorthand and the rule conclusion do all the
work defined above.
Excerpt 4 — from the same file (lines 201–215; position rules cut):
```law
rule FeeTotalSet strict {
for a: Applicant;
for k: Integer;
for v: Money;
when fee_payment(a, k, v);
then fee_total_set(a, sum(collect w: Money, j: Integer where fee_payment(a, j, w)));
}
rule FeeTotalAll strict {
for a: Applicant;
for k: Integer;
for v: Money;
when fee_payment(a, k, v);
then fee_total_all(a, sum(collect all w: Money, j: Integer where fee_payment(a, j, w)));
}
```
Same three premises, same `sum` aggregate — one keyword apart.
`collect` builds a **Set** (identical entries merged);
`collect all` keeps every entry, duplicates included (List behavior).
Compare the two rules line by line: apart from their names, the
`collect` versus `collect all` inside `sum(...)` is the only
difference. The next section shows the observable result: `10 EUR`
versus `30 EUR` on identical input.
## Command and result
The permits package is checked with one command:
```sh
law test packs/examples/language-demo/permits
```
Observed result (engine `law 0.1.0`):
```text
law test demo.northbridge.permits: мир demo.northbridge.permits, demo.northbridge.calculations, demo.northbridge.vocabulary
ok [demo.northbridge.permits] tests/permits.lawtest / general rule: resident with a car
ok [demo.northbridge.permits] tests/permits.lawtest / unless removes the conclusion
ok [demo.northbridge.permits] tests/permits.lawtest / denial wins by priority
ok [demo.northbridge.permits] tests/permits.lawtest / without priority the conflict persists
ok [demo.northbridge.permits] tests/permits.lawtest / defeater removes support without negation
ok [demo.northbridge.permits] tests/permits.lawtest / definition: central resident
ok [demo.northbridge.permits] tests/permits.lawtest / aggregate: large household
ok [demo.northbridge.permits] tests/permits.lawtest / decision term: 30 days
ok [demo.northbridge.permits] tests/permits.lawtest / without a policy the term is not computed
ok [demo.northbridge.permits] tests/permits.lawtest / fee for three months
ok [demo.northbridge.permits] tests/permits.lawtest / positions: duty and prohibition active
ok [demo.northbridge.permits] tests/permits.lawtest / classification: central resident from a large household
ok [demo.northbridge.permits] tests/permits.lawtest / classification: not from the centre — no match
ok [demo.northbridge.permits] tests/permits.lawtest / presumption holds by default
ok [demo.northbridge.permits] tests/permits.lawtest / evidence rebuts the presumption
ok [demo.northbridge.permits] tests/permits.lawtest / fiction: notice deemed received
ok [demo.northbridge.permits] tests/permits.lawtest / constraint reports the violation
ok [demo.northbridge.permits] tests/permits.lawtest / set ignores duplicates: three times 10 — total 10
ok [demo.northbridge.permits] tests/permits.lawtest / list counts duplicates: three times 10 — total 30
ok [demo.northbridge.permits] tests/permits.lawtest / duty satisfied
ok [demo.northbridge.permits] tests/permits.lawtest / duty violated
ok [demo.northbridge.permits] tests/permits.lawtest / outcome unknown — no violation
ok [demo.northbridge.permits] tests/permits.lawtest / maintenance holds while the window is open
ok [demo.northbridge.permits] tests/permits.lawtest / maintenance violated by counterexample
ok [demo.northbridge.permits] tests/permits.lawtest / without a certificate the post-window outcome is unknown
ok [demo.northbridge.permits] tests/permits.lawtest / power exercised lawfully
ok [demo.northbridge.permits] tests/permits.lawtest / power without grounds does not operate
ok [demo.northbridge.permits] tests/permits.lawtest / liberty and immunity
итого: 28 проверено, 28 прошли, 0 не прошли, 0 не исполнены; код 0
```
The package also passes the static check:
```sh
law engine check packs/examples/language-demo/permits/package.law
```
```text
check OK: packs/examples/language-demo/permits/package.law
```
Six of the 28 tests carry this article:
| Test | Evaluates | Expects |
|---|---|---|
| `definition: central resident` | `central_resident(ann)` | `TRUE_ONLY` |
| `aggregate: large household` | `large_household(ann)`, 4 members | `TRUE_ONLY` |
| `classification: central resident from a large household` | `central_senior(ann)` | `TRUE_ONLY` |
| `classification: not from the centre — no match` | `central_senior(bob)`, outer zone | `NEITHER` |
| `set ignores duplicates: three times 10 — total 10` | `fee_total_set(ann, 10 EUR)` | `TRUE_ONLY` |
| `list counts duplicates: three times 10 — total 30` | `fee_total_all(ann, 30 EUR)` | `TRUE_ONLY` |
Ann is resident, lives in the centre, and has four recorded household
members (`m1`–`m4`), so the definition holds, the count reaches the
threshold of 4, and the classification fires. Bob lives in the outer
zone, so the classification body fails — and the answer is `NEITHER`
(no label derived), not a refusal (see
[nb-02: Why a missing fact is not a refusal](/tutorials/northbridge/nb-02-missing-fact/) for
the status meanings).
The last two rows decide the cashier's question: the same three receipts
total `10 EUR` under Set collection and `30 EUR` when duplicates are
kept. The "Changed condition" section unpacks why.
All 28 tests pass: every answer matched its expectation. The summary is
in Russian: 28 checked, 28 passed, 0 failed, 0 skipped, exit code 0.
The first line names the worlds under test (`мир` is "world"): the
permits package together with the calculations and vocabulary packages
it reads.
## Why this construct
The task is to decide group membership from combinations of conditions
(district plus household size), and to fold repeated records into totals
where duplicates may or may not count. A definition names one reusable
condition instead of repeating the conjunction in every rule; a
classification stacks such conditions into one group label; an aggregate
turns "how many / how much" into a value the rule can compare or store.
Each does exactly one of the three jobs.
Writing the district conjunction inline in every rule computes the same
truth but duplicates the condition — one district rename then touches
every rule. Writing the household-size check as four separate member
premises cannot express "at least four" without enumerating
combinations; `count` states the threshold directly. Totalling payments
with one shared receipt fact instead of three cannot distinguish "paid
once" from "paid three times".
The proof is the six tests above — each membership verdict and each
total is executed by `law test`, including the pair that differs only
in the collector keyword (`10 EUR` vs `30 EUR`). What they do not prove:
that central-district large households *deserve* priority, or that
three receipts mean three real payments. The tests prove the machine
classifies and totals the recorded facts as declared; they say nothing
about office policy or the honesty of the ledger. All persons and
payments are fictional data.
## Changed condition
Change one keyword: `collect` → `collect all`. The input is identical
in both tests — three `fee_payment` facts, each `10 EUR`, differing
only in receipt number (`1`, `2`, `3`):
- `fee_total_set(ann, 10 EUR)` is `TRUE_ONLY`: the Set collector merges
the three identical `10 EUR` entries into one, and `sum` folds a
single entry — total `10 EUR`.
- `fee_total_all(ann, 30 EUR)` is `TRUE_ONLY`: the duplicate-preserving
collector keeps all three entries, and `sum` adds `10 + 10 + 10` —
total `30 EUR`.
Same facts, same aggregate function, different collector, different
total — and the test names state the semantics outright ("set ignores
duplicates", "list counts duplicates").
## Typical mistake
Totalling repeatable payments with a plain `collect`. A newcomer copies
the household-count pattern (`count(collect ...)`, where Set semantics
is correct — four distinct members are four members) into the cashier's
sum. The observable consequence: three recorded 10 EUR instalments
total `10 EUR` instead of `30 EUR`, and the office would credit Ann one
third of what she paid.
The fix is to match the collector to the question. Set collection
answers "which distinct things exist?" (members, districts, categories)
— use plain `collect`. Adding up events that may repeat (payments,
deliveries, readings) needs `collect all`, or duplicates silently
vanish. When in doubt, write the pair as this package does — one rule
per collector — and let the two tests disagree loudly.
## Limits
- **Shorthands derive, they do not evidence.** A definition and a
classification restate given facts under a new name; neither creates
evidence nor overrides a missing fact. Bob's `NEITHER` is absence of
a label, not a verdict against him.
- **Aggregates need bound collections.** `count` and `sum` fold the
stated `where` selection — members of one applicant, payments of one
applicant — never the whole register. An unbound or wrongly scoped
selection folds the wrong rows, and no warning repairs the scope.
- **`sum` over `Money` keeps the currency.** `10 + 10 + 10 EUR` is
`30 EUR`, exactly (cf. [nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/));
mixed currencies do not silently merge.
- **Verified profile:** engine `law 0.1.0`, semantics `law.core/0.2`,
std `0.2.0`. Set-versus-duplicate-preserving behavior is a fact about
this implementation, not a claim about collection semantics in every
language.
## Exercise
Without running the engine, predict, then check with `law test`:
1. Ann is a central resident with four recorded household members.
What is the status of `truth(central_senior(ann))`, and which two
package elements must both hold for it?
2. Bob is a resident living in the outer zone. What is the status of
`truth(central_senior(bob))` — `TRUE_ONLY`, `FALSE_ONLY`,
`NEITHER`, or `BOTH` — and which test name confirms it?
3. Three receipts of `10 EUR` each: what does the Set collector total,
and what does the duplicate-preserving collector total? Which two
test names pin the two numbers?
4. Ann pays `10 EUR` twice (two receipts) instead of three times. What
would the two totals become, and why does the Set total stay
unchanged while the other moves?
Write down each prediction first; run the suite for questions 1–3 and
explain any miss in one sentence. Question 4 has no suite test — answer
it by reasoning from the Set-vs-`collect all` rule, then compare with
the checkable solution:
[the nb-05 solution](/tutorials/northbridge/solutions/nb-05-solutions/).
## Sources
- Teaching package: `packs/examples/language-demo/permits/package.law`
(definition `central_resident`, rule `LargeHousehold`, classification
`central_senior`, rules `FeeTotalSet`, `FeeTotalAll`; threshold
`LARGE_HOUSEHOLD` from
`packs/examples/language-demo/calculations/package.law`).
- Scenarios: `packs/examples/language-demo/permits/tests/permits.lawtest`
(`definition: central resident`, `aggregate: large household`,
`classification: central resident from a large household`,
`classification: not from the centre — no match`,
`set ignores duplicates: three times 10 — total 10`,
`list counts duplicates: three times 10 — total 30`).
- Level 1 — Northbridge use: this article (fictional priority shortlist
and fee ledger).
- Level 2 — domain template: when group membership combines a place
condition with a size threshold, name the place with a definition,
count the members with a Set aggregate, and stack both under one
classification; when totalling repeatable events, sum with
`collect all` and keep a Set-collector rule beside it wherever
distinctness (rather than volume) is the question.
- Level 3 — confirmed external formalization: guardian's transactions
for an incapacitated person (Civil Code of Kazakhstan, art. 26(2)) —
package `kz.corpus.civilcode`,
`corpus/laws/kz/codes/civil-code/02b-opeka-i-popechitelstvo.law:121-137`,
construct entitlement lowered to a conditional `power`
(`SovershatSdelkiOtImeniNedeesposobnogo` with holder/over/exercise/
effect/`valid_when`). A person-class consequence gated by conditions,
with every head variable bound in the rule body — the same
quantified-rule discipline as `LargeHousehold` and the fee-total
rules; and no duty to transact exists, so the entitlement is a
conditional power, not an obligation.
<details>
<summary>Evidence and scope of the external example</summary>
Documented in
`docs/research/constructs/14-claim-entitlement-correlatives/corpus-forms.en.md`,
section 2 (rated exemplary there). Verification confirms the named
construct at the cited lines only, by direct source read; it claims
nothing about deployment, runtime behavior, or legal correctness.
</details>