nb-05 — Who counts as a suitable applicant
Northbridge course, intermediate (nb-01 →
nb-02 → nb-03 →
nb-04 → nb-05).
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
Section titled “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
Section titled “Prerequisites”nb-01: First permit: facts, a rule and a question:
facts, one rule, one question, and law test as the way to check a
claim. nb-04: The 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
Section titled “Minimal example”Excerpt 1 — from packs/examples/language-demo/permits/package.law
(lines 53–55; imports, relations and the other rules cut):
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):
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):
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):
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
Section titled “Command and result”The permits package is checked with one command:
law test packs/examples/language-demo/permitsObserved result (engine law 0.1.0):
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 не исполнены; код 0The package also passes the static check:
law engine check packs/examples/language-demo/permits/package.lawcheck OK: packs/examples/language-demo/permits/package.lawSix 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 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
Section titled “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
Section titled “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)isTRUE_ONLY: the Set collector merges the three identical10 EURentries into one, andsumfolds a single entry — total10 EUR.fee_total_all(ann, 30 EUR)isTRUE_ONLY: the duplicate-preserving collector keeps all three entries, andsumadds10 + 10 + 10— total30 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
Section titled “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
Section titled “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
NEITHERis absence of a label, not a verdict against him. - Aggregates need bound collections.
countandsumfold the statedwhereselection — 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. sumoverMoneykeeps the currency.10 + 10 + 10 EURis30 EUR, exactly (cf. nb-04: The permit fee); mixed currencies do not silently merge.- Verified profile: engine
law 0.1.0, semanticslaw.core/0.2, std0.2.0. Set-versus-duplicate-preserving behavior is a fact about this implementation, not a claim about collection semantics in every language.
Exercise
Section titled “Exercise”Without running the engine, predict, then check with law test:
- 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? - Bob is a resident living in the outer zone. What is the status of
truth(central_senior(bob))—TRUE_ONLY,FALSE_ONLY,NEITHER, orBOTH— and which test name confirms it? - Three receipts of
10 EUReach: what does the Set collector total, and what does the duplicate-preserving collector total? Which two test names pin the two numbers? - Ann pays
10 EURtwice (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.
Sources
Section titled “Sources”- Teaching package:
packs/examples/language-demo/permits/package.law(definitioncentral_resident, ruleLargeHousehold, classificationcentral_senior, rulesFeeTotalSet,FeeTotalAll; thresholdLARGE_HOUSEHOLDfrompacks/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 alland 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 conditionalpower(SovershatSdelkiOtImeniNedeesposobnogowith 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 asLargeHouseholdand the fee-total rules; and no duty to transact exists, so the entitlement is a conditional power, not an obligation.
Evidence and scope of the external example
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.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.