nb-24 — Neighbor constructs, nine mistakes and three decision tasks
Northbridge is fictional. All applicants, fines, fees, registers
and deadlines in this course are synthetic and unofficial.
Northbridge is not a deployed system and nothing here is a claim
about real law. Verified profile: law 0.1.0, language 0.2,
semantics law.core/0.2, std 0.2.0 (from law --version,
quoted in section 4).
1. Situation
Section titled “1. Situation”Mira audits Dana’s first solo file and finds six confusions between
constructs that look alike. Dana wrote unless where the file needed
a denial, a definition where it needed a classification, and a rule
where it needed a constraint. She also mixed up a function with a
decision table, a procedure with a stage, and three different ways
of getting residency onto the record.
The same file then fails in nine further ways. Six are quiet misuses: a set that swallows a duplicate payment, a register closed over the wrong domain, a swapped time axis, a document admitted on no grounds, a test that expects what the machine never said, and a mix of two numeric profiles. Three more come from picking the wrong side of a close pair: excepting an abbreviation, reporting a lifecycle as a timeless check, and approving on readiness with no recorded act.
Each pair below comes with the tests that tell the neighbors apart, and each mistake comes with a run that shows its consequence. Three decision tasks then ask you to choose the construct before running, on a senior-parking label, an unpaid fee, and a station approval.
Answers use the four truth statuses from
nb-01: First permit: facts, a rule and a question.
TRUE_ONLY means the statement is established, FALSE_ONLY means it
is denied, NEITHER means neither side is supported, and BOTH
means both sides are supported in conflict.
2. Prerequisites
Section titled “2. Prerequisites”You need nb-01: First permit: facts, a rule and a question:
facts, strict and defeasible rules, the four truth statuses, and
law test as the way to check a claim. You also need
nb-03: Exceptions and conflicting rules:
unless, priority and defeaters. This article turns that lesson
into a three-way distinguishing drill.
The payment pair builds on Money arithmetic from
nb-04: The permit fee. The register pair needs
closure, the explicit negative, and the silence outside a closed domain
from nb-06: When the register may stay silent.
Packages, pub visibility and explicit local imports come from
nb-18: Packages and composition, and
numeric kinds with loud refusals from
nb-23: Numbers and the standard library: every figure the 0.2 machine proves.
The sixth mistake reuses that lesson’s Money / Integer rejection.
New here: no new construct. This article compares constructs you already met, then catalogues nine ways a file that uses them fails its checks or silently misleads.
3. Minimal example
Section titled “3. Minimal example”Each excerpt below keeps byte-exact lines from the cited package file; whole unrelated rules are cut. Each label names the package, the path, the line range, and what was cut.
Pair 1 — unless removes, denial asserts, defeater only blocks.
Excerpt 1a — defeasible grant with an unless escape
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines 23–28;
neighboring rules cut).
rule PermitEligibility defeasible { for a: Applicant; when demo.northbridge.vocabulary::resident(a) and demo.northbridge.vocabulary::vehicle_registered(a); then permit_eligible(a); unless permit_suspended(a);}Ann is a resident driver, so the grant rule fires for her — until the
office suspends her permit. Look at the unless permit_suspended(a) line:
when that condition holds, the rule simply stops concluding. The suspension
takes the grant away and says nothing in its place; it does not assert
that Ann is ineligible.
Excerpt 1b — denial by negative inference plus its priority
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines 30–36
and 43–46; the badge rule between them cut).
rule FinesRefusal defeasible { for a: Applicant; when outstanding_fines(a); then not permit_eligible(a);}priority RefusalOverEligibility { prefer FinesRefusal over PermitEligibility; reason lex_specialis;}Ann is again a resident driver, but this time she has outstanding fines.
Look at the two things the excerpt keeps: then not permit_eligible(a)
asserts the negation, and the priority prefers FinesRefusal over
PermitEligibility. The fines do not just block the grant; they deny it,
and the priority makes that denial win the conflict.
Excerpt 1c — defeater: support removed, nothing asserted
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines 47–51;
surrounding rules cut).
rule FraudBlocksBadge defeater { for a: Applicant; when fraud_flag(a); defeat permit_eligible(a);}Bob holds a disability badge, so the badge rule would make him eligible —
but his file carries a fraud flag. Look at the defeat permit_eligible(a)
line: a defeater removes support without asserting anything. Fraud blocks
the badge path back to silence (NEITHER), not to denial (FALSE_ONLY).
Pair 2 — definition abbreviates, classification sorts.
Excerpt 2a — exact abbreviation (demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines 53–55;
surrounding 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);}Ann lives in the central zone, and the file needs a short name for that
combination. Look at the definition ... exact header: the name
central_resident is exactly its condition — resident plus lives centrally —
no more. Nothing in the file can except it; the abbreviation always means
the same condition.
Excerpt 2b — classification that may not match
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines
117–119; relations and neighboring constructs cut).
classification central_senior(a: Applicant) strict { when central_resident(a) and large_household(a);}Bob lives in the outer zone, so he is not a central resident. Look at the
classification ... strict header and its single when line: sorting checks
whether the case matches, and Bob matches no case. He stays NEITHER —
unsorted — where the definition would have been false; silence here means
“no match”, never “denied”.
Pair 3 — constraint reports, consequence rule derives.
Excerpt 3a — the report that leaves the fact standing
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines
133–138; position rules around it cut).
constraint IssuedNeedsFee(a: Applicant) { when permit_issued(a); require fee_paid(a); severity error; message "a permit is issued only after the fee is paid";}Ann’s permit is issued but the fee is unpaid, and the file says so
explicitly. Look at the when/require pair: when issuance holds, the fee
is required, and a missing fee raises an issue. The permit stays issued
(TRUE_ONLY), and the violation is reported beside it; the constraint
un-issues nothing.
Excerpt 3b — a rule that derives and detects nothing
(demo.northbridge.permits,
packs/examples/language-demo/permits/package.law, lines
201–207; the list-counting sibling rule 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)));}Ann paid the fee three times, and the ledger must total her payments.
Look at the then fee_total_set(...) line with its collect: the rule
derives a total from the payment facts. Totals are derived here; no unpaid
fee is ever reported, because a consequence rule detects nothing.
Pair 4 — function computes, decision table routes.
Excerpt 4a — one expression (demo.northbridge.calculations,
packs/examples/language-demo/calculations/package.law, line
11; decision tables below cut).
pub function permit_fee(months: Integer) -> Money = months * MONTHLY_RATE;Ann applies for three months, and the office multiplies by the monthly
rate. Look at the single expression after =: a function computes one value
from its inputs. Three months always cost thirty euro here; there are no
rows and no policy to consult.
Excerpt 4b — rows with a hit policy
(demo.northbridge.calculations,
packs/examples/language-demo/calculations/package.law, lines
13–18; the remaining tables cut).
pub decision LongTermDiscount(months: Integer) -> Decimal { table hit unique { when months >= 12 => 20 percent; otherwise => 0 percent; }}Ann applies for twelve months, and the office looks up her discount.
Look at the table hit unique header and its two rows: the table routes
inputs to outputs, and twelve months match exactly one row. The unique
policy would complain about overlaps instead of guessing; routing carries
a policy the function lacks.
Pair 5 — procedure moves on events, stage indexes rounds.
Excerpt 5a — states and the first region
(demo.northbridge.procedure,
packs/examples/language-demo/procedure/package.law, lines
47–53; the Tech region, joins and transitions cut).
procedure StationApproval(s: Station) { state Draft initial; state Filed; state Review parallel { region Docs { state DocsPending initial; state DocsOk terminal;A charging station waits for its documents while the file sits in review.
Look at the state DocsPending initial line inside the Docs region: the
case rests there until the DocsFiled event moves it. Nothing derives
anything; events move the case, and waiting is the point.
Excerpt 5b — rounds as derivation index
(demo.northbridge.allocation,
packs/examples/language-demo/allocation/package.law, lines
14–18; relations and seeding rules cut).
stage Allotment { index round: Integer from 1 to 2; bind shortlisted index 2; bind admitted index 2;}Seat allocation runs in two rounds, and the second round reads the closed
first one. Look at the index round: Integer from 1 to 2 line: the stage
indexes repeated derivation, and round 2 sees what round 1 fixed. There are
no events and no waiting here, only indexed derivation.
Pair 6 — explicit fact, evidence admission, closure.
Excerpt 6a — the closed register (demo.northbridge.register,
packs/examples/language-demo/register/package.law, lines
18–24; admission rules below cut).
closure ResidentsFile { predicate filed_resident; domain on_resident_file; snapshot "urn:snapshot:demo-northbridge-residents:2026"; complete_as_of @2026-01-01T00:00:00Z; derive_explicit_negative true;}Carl is on the residents file but has no record in it, and the office must
close that gap. Look at the domain and derive_explicit_negative true
lines: closure completes the register, so inside the domain absence becomes
denial (FALSE_ONLY). Outside the domain, silence stays silence.
Excerpt 6b — admission through five conjuncts
(demo.northbridge.register,
packs/examples/language-demo/register/package.law, lines
39–47; the evidence policy below cut).
rule Accept strict { for e: Text; for d: Text; for link: Text; when edge(e, d, link) and evidence_status(d, "verified") and authentic(d, "urn:demo:northbridge:verifier", "verified") and available(d) and current(d); then accepted(e);}A document arrives for Ann, and the office checks it against five conditions
before trusting it. Look at the long when with its five conjuncts: the edge,
verified status, pinned authenticity, availability and currency must all hold.
Drop authenticity and the document stays transport; admission needs every
conjunct, not just most of them.
Excerpt 6c — the plain asserted fact
(demo.northbridge.register,
packs/examples/language-demo/register/tests/register.lawtest,
lines 22–29; the remaining suite cut).
test "file record accepted" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } assert "filed-ann": filed_resident(entity_ref("urn:demo:northbridge:ann")) { origin case_input; } } evaluate truth(resident_admitted(entity_ref("urn:demo:northbridge:ann"))); expect truth_status == TRUE_ONLY;}Ann’s residency is simply asserted on the record, with no document to admit
and no register to walk. Look at the single assert line: one fact, entered
directly as case input. The test then asks about her admission and expects
TRUE_ONLY; the plain fact needs no policy and no closure.
Decision task 1 — definition vs classification: the senior-parking label
Section titled “Decision task 1 — definition vs classification: the senior-parking label”Northbridge reserves a senior-parking label for residents who live centrally in big families. Late in the audit, the office adds one demand: a fraud-flagged applicant must never read as a senior, even when the zone and family facts qualify them.
Requirements. R1: a qualifying applicant with no fraud flag reads TRUE_ONLY.
R2: a non-qualifying applicant reads non-true on a silent record. R3:
a qualifying applicant WITH a fraud flag must not read TRUE_ONLY.
Reader prompt. Pick definition ... exact or classification ... defeasible
for the label. Before running, predict the status of the non-qualifying
applicant under each model, and the status of the qualifying fraud-flagged
applicant under each model. Name the fact that discriminates.
Model A — exact abbreviation (Excerpt from
demo.northbridge.decisions,
packs/examples/language-demo/decisions/package.law, lines
23–25; the sibling definition and the vocabulary cut):
definition def_senior(a: Applicant) exact { when resident(a) and lives_central(a) and big_family(a);}Ann qualifies on zone and family, and the label abbreviates exactly that.
Look at the definition ... exact header: the name is its three-part
condition, and nothing in the file can except it. A fraud flag changes
nothing here, because the definition has no place to hang an exception.
Model B — defeasible sorting plus a defeater (Excerpt from
demo.northbridge.decisions,
packs/examples/language-demo/decisions/package.law, lines
32–40; the vocabulary cut):
classification cls_senior(a: Applicant) defeasible { when resident(a) and lives_central(a) and big_family(a);}
rule SeniorFraudBlock defeater { for a: Applicant; when fraud_flag(a); defeat cls_senior(a);}Ann qualifies on zone and family, so the sorting puts her in — until fraud
appears. Look at the classification ... defeasible header and the defeater
below it: the case sorts qualifying applicants in, and SeniorFraudBlock
takes that support away without asserting anything. Fraud returns the label
to silence, never to denial.
Minimal distinguishing scenario. Give both models identical facts for Ann: resident, central, big family, plus the fraud flag. The control does NOT discriminate: resident-only Bob on a silent record stays silent under both models, so only the fraud flag tells them apart.
Decision task 2 — constraint vs duty: the unpaid fee
Section titled “Decision task 2 — constraint vs duty: the unpaid fee”Ann’s permit is issued but the fee is unpaid, and the file says so explicitly. March still allows payment while April means breach, so the March read and the April read of the same facts must differ.
Requirements. R1: the unpaid issue is flagged on the record. R2: the March read (payment still possible) differs from the April read (window closed). R3: the issuance itself stays established on the record throughout.
Reader prompt. Pick a constraint that reports beside the standing fact, or
a rule deriving an achievement duty with a March window. Before running,
predict the issue set and the duty status at the March read and at the April
read under each model.
Model A — the report that leaves the fact standing (Excerpt from
demo.northbridge.decisions,
packs/examples/language-demo/decisions/package.law, lines
47–52; the duty twin cut):
constraint ConNeedsFee(a: Applicant) { when con_issued(a); require con_fee(a); severity error; message "a permit is issued only after the fee is paid";}Ann’s permit is issued and the fee is missing, and the report says so.
Look at the when/require pair: when issuance holds, the fee is required,
with no window and no lifecycle. The permit stays issued, and the violation
is reported beside it — identically in March and in April.
Model B — a duty with a March window (Excerpt from
demo.northbridge.decisions,
packs/examples/language-demo/decisions/package.law, lines
60–72; the constraint twin cut):
rule DutyToCollect strict { for a: Applicant; for o: Office; when duty_issued(a) and duty_office(o); then duty CollectFee { bearer a; beneficiary o; goal achievement { condition duty_fee(a); window [@2026-03-01, @2026-03-31]; } };}Ann’s issuance creates an obligation to pay within March. Look at the
window [@2026-03-01, @2026-03-31] line and the achievement goal: the duty
CollectFee carries a March window. The position lifecycle — ACTIVE, then
VIOLATED — carries the verdict, not an issue.
Minimal distinguishing scenario. Take issued Ann with explicit not fee_paid
and the office on file. Read the same record twice: once with
decision_time @2026-03-10 and once with @2026-04-10.
Decision task 3 — procedure vs stage: approval with and without acts
Section titled “Decision task 3 — procedure vs stage: approval with and without acts”A charging station is substantively ready: the file is complete and the nameplate is within limits. Two modelings compete: a procedure that moves the case through states only on recorded events, and a derivation that computes the outcome from the substantive facts over indexed rounds. The office asks what the file may say before anyone acts.
Requirements. R1: a complete, checked, decided file reads approved. R2: substantive readiness ALONE — no recorded act — must not read approved. R3: the model states where the case rests while it waits.
Reader prompt. Pick procedure (event-gated states) or stage (indexed
derivation rounds). Before running, predict what each model says about
approval with all substantive facts present and zero events asserted, and
where the waiting case rests in the procedure.
Model A — states moved by events (Excerpt from
demo.northbridge.decisions,
packs/examples/language-demo/decisions/package.law, lines
87–103; the event declarations cut):
procedure MiniApproval(s: Station) { state Draft initial; state Filed; state Approved terminal; transition File { from Draft; to Filed; on EvFiled; when file_complete(s); } transition Decide { from Filed; to Approved; on EvDecided; when nameplate_ok(s); }}The station is ready on paper, but no official has acted yet. Look at the
two transitions: File needs EvFiled plus a complete file, and Decide
needs EvDecided plus a sound nameplate. The case waits in a state until an
event moves it; readiness derives nothing by itself.
Model B — rounds as derivation index (Excerpt from
demo.northbridge.decisions_stage,
packs/examples/language-demo/decisions-stage/package.law,
stage lines 13–17; seeding and relations cut):
stage MiniAllotment { index round: Integer from 1 to 2; bind shortlisted index 2; bind admitted index 2;}Excerpt — the Admit rule
(packs/examples/language-demo/decisions-stage/package.law,
lines 32–39; seeding cut):
rule Admit strict { for a: Applicant; for p: Integer; for r: Integer; when supported(shortlisted(a, r)) and r < 2 and points(a, p) and p >= MIN_POINTS; then admitted(a, r + 1);}Ann has enough points, and the rounds do the rest. Look at the Admit rule:
round 2 reads the closed round 1 and derives admission from points. There are
no events anywhere in this world, so nothing can wait; the outcome follows
from the facts alone.
Minimal distinguishing scenario. Assert all substantive facts and zero
attempted_transition facts. The two worlds are separate by construction —
decisions-stage has no dependency on decisions — so the comparison is
about the input-to-outcome pattern on the same input shape, not about one
shared predicate.
Package map for the six pairs
Pairs 1–3 and the ledger come from demo.northbridge.permits; pair 4 from
demo.northbridge.calculations; pair 6 from demo.northbridge.register; pair 5
from demo.northbridge.procedure and demo.northbridge.allocation. The decision
tasks use demo.northbridge.decisions and demo.northbridge.decisions_stage.
4. Command and result
Section titled “4. Command and result”Tool versions first (five facts: tool, language, semantics, std, binary):
law --versionObserved (exit 0):
law 0.1.0семантика: law.core/0.2std для языка 0.2: 0.2.0хэш бинаря: sha256:78dea06ce928547e87bd9875a2556cc663def37cbb78c8b04aa0da57839c95d7The four lines carry five facts. law 0.1.0 is the tool; семантика
means semantics (law.core/0.2); std для языка 0.2: 0.2.0 names the
language (0.2) and its standard library (0.2.0); хэш бинаря is the
binary hash. These are the versions every run below assumes.
Pair 1–3 plus the payment ledger, one run of the permits world:
law test packs/examples/language-demo/permits 2>&1 | grep -E "unless|denial wins|defeater|definition|classification|constraint reports|duty violated|set ignores|list counts|итого"Observed (exit 0, suite green):
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 / defeater removes support without negation ok [demo.northbridge.permits] tests/permits.lawtest / definition: central resident 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 / 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 violatedитого: 28 проверено, 28 прошли, 0 не прошли, 0 не исполнены; код 0The ten ok lines cover pairs 1–3 and the payment ledger. The first three
tell unless (suspension back to silence), denial (fines to FALSE_ONLY)
and defeater (fraud back to silence) apart. The next three sort the central
resident and the senior household; then come the constraint report, the two
ledger totals, and the violated duty.
The summary итого: 28 проверено, 28 прошли, 0 не прошли, 0 не исполнены; код 0 means 28 checked, 28 passed, 0 failed, 0 unexecuted, exit code 0.
A passing test means the answer matched its expectation; it does not mean
every applicant in the file was granted.
Why the commands pipe through grep
Each law test run prints its full suite; the grep keeps only the lines that
tell the neighbors apart, plus the summary. The summary still reports the whole
suite (28 checked here, not just the ten lines shown).
Pair 4, the calculations world:
law test packs/examples/language-demo/calculations 2>&1 | grep -E "fee for three|discount for twelve|итого"Observed (exit 0, suite green):
ok [demo.northbridge.calculations] tests/calculations.lawtest / fee for three months ok [demo.northbridge.calculations] tests/calculations.lawtest / discount for twelve monthsитого: 9 проверено, 9 прошли, 0 не прошли, 0 не исполнены; код 0The two ok lines are the function and the table side by side. Three months
of fee compute to 30 EUR, and twelve months route to the 0.2 discount row.
The summary means 9 checked, 9 passed, 0 failed — the calculations world
is green.
Pair 6, the register world:
law test packs/examples/language-demo/register 2>&1 | grep -E "on file without|outside the domain|authenticity|policy|итого"Observed (exit 0, suite green):
ok [demo.northbridge.register] tests/register.lawtest / on file without a record — not a resident ok [demo.northbridge.register] tests/register.lawtest / outside the domain silence is silence ok [demo.northbridge.register] tests/register.lawtest / policy selected, authenticity pinned ok [demo.northbridge.register] tests/register.lawtest / without pinned authenticity support is rejected ok [demo.northbridge.register] tests/register.lawtest / without a policy support stays transport ok [demo.northbridge.register] tests/register.lawtest / bare assertion of the protected under a policyитого: 9 проверено, 9 прошли, 0 не прошли, 0 не исполнены; код 0The six ok lines walk the three residency routes. The on-file-but-recordless
case is denied by closure, while the outside-domain case stays silent. The
pinned document is admitted through the policy, and the three negative probes
confirm that dropping authenticity, dropping the policy, or asserting the
protected predicate bare all fail to admit.
The summary means 9 checked, 9 passed, 0 failed. For the case at hand, the
route determines the status: asserted fact and pinned document admit Ann,
closure denies Carl inside the domain, and Dana outside it stays NEITHER.
Pair 5, two worlds that never meet (allocation depends on register, never on procedure):
law test packs/examples/language-demo/procedure 2>&1 | grep -E "join waits|both regions ready|итого"Observed (exit 0, suite green):
ok [demo.northbridge.procedure] tests/procedure.lawtest / both regions ready — decision taken ok [demo.northbridge.procedure] tests/procedure.lawtest / join waits for both regionsитого: 6 проверено, 6 прошли, 0 не прошли, 0 не исполнены; код 0The procedure waits as designed: the join holds until both regions are ready, and the decision is taken only then. The summary means 6 checked, 6 passed — the waiting behavior, not a derivation, is what this world proves.
law test packs/examples/language-demo/allocation 2>&1 | grep -E "round 2 admits|insufficient|closed total|итого"Observed (exit 0, suite green):
ok [demo.northbridge.allocation] tests/allocation.lawtest / round 2 admits by points ok [demo.northbridge.allocation] tests/allocation.lawtest / insufficient points — no seat granted ok [demo.northbridge.allocation] tests/allocation.lawtest / closed total reads after the roundsитого: 5 проверено, 5 прошли, 0 не прошли, 0 не исполнены; код 0The allocation derives as designed: round 2 admits by points, insufficient points grant no seat, and the closed total reads after the rounds. The summary means 5 checked, 5 passed. Events move the first world; indexes derive the second — and the two never meet.
Decision tasks 1 and 2, the decisions world (both rival
models under distinct names, one run):
law test packs/examples/language-demo/decisions 2>&1 | grep -E "senior|fraud|negated|unpaid|duty|no report|итого"Observed (exit 0, suite green):
ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: defined senior on a silent record ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: classified senior on a silent record ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: fraud against the defined senior ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: fraud against the classified senior ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: qualifying senior without fraud, both models ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: definition over negated atom stays silent ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: issued but unpaid — march read ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: issued but unpaid — april read ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: duty world emits no report ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: fee duty active inside window ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: fee duty violated after windowитого: 14 проверено, 14 прошли, 0 не прошли, 0 не исполнены; код 0The eleven ok lines decide the first two tasks. For the senior label, both
models agree without fraud and on a silent record, but fraud leaves the
definition true while silencing the classification. For the unpaid fee, the
constraint flags both reads identically while the duty moves from ACTIVE
to VIOLATED, and the duty world emits no report.
The summary means 14 checked, 14 passed. Rival models run under distinct names in one world, so one run compares them without letting their heads compete.
Decision task 3, the event half in the same world:
law test packs/examples/language-demo/decisions 2>&1 | grep -E "without events|filed chain|итого"Observed (exit 0, suite green):
ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: complete file without events - no approval ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: complete file without events - still draft ok [demo.northbridge.decisions] tests/decisions.lawtest / probe: filed chain approvesитого: 14 проверено, 14 прошли, 0 не прошли, 0 не исполнены; код 0The three ok lines are a filtered view of the same 14-test suite. The complete
file without events reads no approval and still Draft; the filed chain with
both events approves. The summary repeats 14 checked, 14 passed, because the
grep shows three lines of one green run.
And the derivation half in its own world (no shared dependency, so no stage and no procedure ever meet):
law test packs/examples/language-demo/decisions-stageObserved (exit 0, suite green):
law test demo.northbridge.decisions_stage: мир demo.northbridge.decisions_stage ok [demo.northbridge.decisions_stage] tests/decisions-stage.lawtest / probe: admission derives without any event ok [demo.northbridge.decisions_stage] tests/decisions-stage.lawtest / probe: insufficient points stay outитого: 2 проверено, 2 прошли, 0 не прошли, 0 не исполнены; код 0The header мир demo.northbridge.decisions_stage names the world under test
(мир means world). Admission derives from points with no recorded act
anywhere, and insufficient points stay out. The summary means 2 checked,
2 passed — the derivation half answers from facts alone.
No single run covers both halves of task 3: the stage world has no dependency on the procedure world, so the comparison is about the input-to-outcome pattern, not one shared predicate.
The refused repair — bolting a defeater onto a defined head — fails statically, so it ships as a snippet, not a suite:
law engine check packs/examples/language-demo/decisions/evidence/snippets/def-head.law.txtObserved (exit 1):
packs/examples/language-demo/decisions/evidence/snippets/def-head.law.txt:13:12: error LDC-E4112: DEFEATER_WITHOUT_CANDIDATE: defeater "Block" атакует positive-голову "lab", но у неё нет defeasible-продюсера (§107.3; DECISION-0111 §2.1)The checker refuses the file before anything runs. LDC-E4112 with
DEFEATER_WITHOUT_CANDIDATE says the defeater attacks a positive head that
has no defeasible producer; the Russian clause says the same (атакует
attacks, но у неё нет but it has no). Exit 1 is the expected refusal, and
the snippet ships precisely because it cannot join a green suite.
5. Why this construct
Section titled “5. Why this construct”Each pair proves one behavioral difference. None proves the other side absent.
Pair 1 — unless, denial, defeater. Ann is suspended in one test, fined
in another, while Bob’s badge case carries a fraud flag. The suspended
resident ends NEITHER (unless removes the conclusion); the fined resident
ends FALSE_ONLY (denial wins by priority); the fraudulent badge holder
ends NEITHER again (defeater removes support without negation).
The two NEITHERs are not the same fact: suspension blocks one rule, fraud
defeats another, and only the denial asserts a negation that priority can
prefer. The pair does NOT prove fraud is worse than fines — it proves the
machine records three different outcomes for three different shapes.
Pair 2 — definition, classification. Ann lives centrally, and with four
household members she also sorts as a central senior. central_resident
fires for her (definition: central resident); central_senior fires for her
too, but matches nothing for Bob from the outer zone (classification: not from the centre — no match, NEITHER). The definition would have been false
for Bob; the classification is silent instead.
The pair does NOT prove classifications never deny — it proves non-match here
is silence, not denial. Decision task 1 decides the auditor’s file: the
defeasible classification wins. It meets R1 (both models TRUE_ONLY without
fraud), R2 (both NEITHER on a silent record) and R3 (fraud-flagged Ann reads
NEITHER under cls_senior but stays TRUE_ONLY under def_senior).
The definition fits where the name is a pure abbreviation with no exceptions
ever, such as central_resident itself. The attempted repair — the same
defeater on the defined head — is refused with LDC-E4112 because a defined
head has no defeasible producer. This does NOT prove every sorting needs
defeasibility — it proves only that on these facts the exception requires
a defeasible producer.
Pair 3 — constraint, consequence rule. Ann’s permit is issued but unpaid,
while the office misses its own deadline. The issued-but-unpaid permit stays
TRUE_ONLY and carries CONSTRAINT_VIOLATED (constraint reports the violation); the late office is VIOLATED on its duty (duty violated). The
constraint never un-issues the permit; the duty never derives a payment.
The pair does NOT prove every requirement wants a constraint — it proves
reporting and deriving are different jobs. Decision task 2 decides the
March/April file: the duty rule wins. It meets R1 through the lifecycle
(ACTIVE, then VIOLATED), R2 (the two reads differ) and R3 (issuance stays
TRUE_ONLY, and the no report probe confirms no issue is emitted). The
constraint meets R1 and R3 but fails R2: March and April read identically.
The constraint fits where the requirement is a timeless invariant with no grace period — a static “these two may not co-occur” check. This does NOT prove duties subsume constraints — it proves only that a lifecycle requirement needs a construct that has one.
Pair 4 — function, decision table. Ann pays for three months and asks for
the twelve-month discount. permit_fee(3) is 30 EUR (fee for three months); LongTermDiscount(12) is 0.2 (discount for twelve months). The
input shape is the same but the machinery differs: the function always
computes, while the table routes and its unique policy polices overlaps.
The pair does NOT prove tables compute more — it proves routing carries a policy the function lacks.
Pair 5 — procedure, stage. A station waits for both review regions, while
a seat allocation closes its rounds. The station waits for both regions
(join waits for both regions) and decides only when both are ready (both regions ready — decision taken); the allotment admits by points in round 2
(round 2 admits by points) and reads the closed total afterwards (closed total reads after the rounds). Events move the first; indexes derive
the second.
The pair does NOT prove stages are simpler — it proves waiting and indexing
answer different questions. Decision task 3 decides the untouched file: the
procedure wins. It meets R1 (the filed chain approves), R2 (the complete file
without events reads Approved: NEITHER) and R3 (the case rests in Draft: TRUE_ONLY). The stage meets R1 in its own world but fails R2: substantive
facts alone derive admitted(ann, 2): TRUE_ONLY with no recorded act anywhere.
The stage fits where the outcome is pure computation over closed rounds with no human acts to record. This does NOT prove procedures are always safer — it proves only that waiting for acts needs a construct that records acts.
Pair 6 — explicit fact, evidence admission, closure. Ann reaches the
record twice: once by direct assertion, once by pinned document. The asserted
record admits her (file record accepted, via Excerpt 6c); the pinned document
admits her through the policy (policy selected, authenticity pinned). The
on-file-but-recordless Carl is denied (on file without a record — not a resident, FALSE_ONLY by closure); Dana, outside the domain, is silence
(outside the domain silence is silence, NEITHER).
One predicate, four routes, three different statuses. The set does NOT prove documents are unnecessary — it proves each route has its own preconditions.
6. Changed condition
Section titled “6. Changed condition”Ann is a resident driver and the grant fires for her. Add one hold to her
file and the grant goes silent.
Excerpt — the defeating fact (demo.northbridge.capstone,
packs/examples/language-demo/capstone/tests/capstone.lawtest,
lines 25–34; the granting sibling test cut).
test "a hold defeats the grant" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } assert "resident-ann": demo.northbridge.vocabulary::resident(entity_ref("urn:demo:northbridge:ann")) { origin case_input; } assert "vehicle-ann": demo.northbridge.vocabulary::vehicle_registered(entity_ref("urn:demo:northbridge:ann")) { origin case_input; } assert "hold-ann": capstone_hold(entity_ref("urn:demo:northbridge:ann")) { origin case_input; } } evaluate truth(capstone_grant(entity_ref("urn:demo:northbridge:ann"))); expect truth_status == NEITHER;}Look at the three assert lines: resident, vehicle, and the added hold-ann.
That hold is the only difference from the granting test. Readiness still holds,
but the grant does not.
law test packs/examples/language-demo/capstone 2>&1 | grep -E "hold|итого"Observed (exit 0, suite green):
ok [demo.northbridge.capstone] tests/capstone.lawtest / resident driver is granted without a hold ok [demo.northbridge.capstone] tests/capstone.lawtest / a hold defeats the grantитого: 9 проверено, 9 прошли, 0 не прошли, 0 не исполнены; код 0The change is one fact: hold-ann. The result is a flip from TRUE_ONLY in
the granting test to NEITHER here, while readiness is untouched. The hold
defeats the grant without denying it, so the case reads silence — not refusal.
The summary means 9 checked, 9 passed; both lines pass because each answer
matched its expectation.
7. Typical mistake
Section titled “7. Typical mistake”Nine mistakes, each with the run that shows it. Each entry names the mistake, the observed consequence, and the fix.
Mistake 1 — billing from the set. Ann pays 10 EUR three times, and Dana
totals from the set. The mistake is billing from fee_total_set: three
payments of 10 EUR collapse to a set total of 10, not 30.
The section 4 permits run shows the consequence: set ignores duplicates: three times 10 — total 10 against list counts duplicates: three times 10 — total 30.
Bill from the set and the customer is undercharged by 20 EUR with every test
green. Fix: bill from fee_total_all, which counts each payment.
Mistake 2 — closing the register over the wrong domain. Dana has no record,
and Dana’s absence is what the domain line decides. The mistake is putting her
on the domain: her absence then becomes FALSE_ONLY, while outside it stays
NEITHER.
The section 4 register run shows the consequence: on file without a record — not a resident against outside the domain silence is silence. Carl inside
the domain is denied (FALSE_ONLY); Dana outside it is silence (NEITHER).
Fix: close the register over exactly the domain the file promises — the domain
line is the whole difference.
Mistake 3 — reading at the wrong time axis. The office reads Ann’s reply term,
and the axis it reads on decides the verdict. The mistake is evaluating at
knowledge time: the sources pair decides on decision_time, with the same
knowledge_time (@2027-12-31) in both tests.
The 11 March file is ACTIVE (working days: the term includes the window end)
while the 12 March file without performance is VIOLATED (working days: overdue without performance). Evaluate at knowledge time and the 11 March file
looks overdue a year early. Fix: read the lifecycle at decision_time.
Reproduced by the run below:
law test packs/examples/language-demo/sources 2>&1 | grep -E "window end|overdue|итого"Observed (exit 0, suite green):
ok [demo.northbridge.sources] tests/sources.lawtest / working days: the term includes the window end ok [demo.northbridge.sources] tests/sources.lawtest / working days: overdue without performanceитого: 6 проверено, 6 прошли, 0 не прошли, 0 не исполнены; код 0The two ok lines differ only by decision day: 11 March still inside the window,
12 March past it without performance. The summary means 6 checked, 6 passed.
For the case at hand, the axis is the verdict — knowledge time sees a year-old
file, decision time sees whether the window has closed.
Mistake 4 — admitting a document on no grounds. Ann’s document arrives, and
Dana drops the authenticity conjunct from Excerpt 6b. The mistake is admitting
on four conjuncts out of five: without authenticity, accepted never derives.
The section 4 register run shows the consequence: without pinned authenticity support is rejected and without a policy support stays transport. Transport
in, transport out, no admission. Fix: keep all five conjuncts and the policy —
admission needs every one of them.
Mistake 5 — expecting what the machine never said. Ann paid once, but the rule
needs two payments. The mistake is a test that demands TRUE_ONLY from one
payment: it claims what the machine never said. This test is meant to fail.
Reproduced by a scratch probe under /tmp (writes stay inside /tmp):
mkdir -p /tmp/nb24-m5 && cat > /tmp/nb24-m5/probe.law <<'EOF'language "law.core" version "0.2";package probe version "0.1.0";namespace "urn:probe";relation paid(a: Text, n: Integer) kind empirical;relation ready(a: Text) kind institutional;rule Ready strict { for a: Text; when paid(a, 1) and paid(a, 2); then ready(a);}EOFcat > /tmp/nb24-m5/probe.lawtest <<'EOF'language "law.core" version "0.2";package probe version "0.1.0";namespace "urn:probe";test "ready with one payment only" { given { context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; } assert "p1": paid("ann", 1) { origin case_input; } } evaluate truth(ready("ann")); expect truth_status == TRUE_ONLY;}EOFlaw engine test /tmp/nb24-m5/probe.lawtest --program /tmp/nb24-m5/probe.law 2>&1Observed (expected FAIL, exit 1):
test FAIL: ready with one payment only truth_status == TRUE_ONLY: в документе NEITHERlawc test: 0/1 тестов прошлиThe test fails loudly, as designed. test FAIL: ready with one payment only
names the failing test; в документе NEITHER means the document holds
NEITHER (в документе means in the document); lawc test: 0/1 тестов прошли
means 0 of 1 tests passed. Silence is not confirmation — one payment leaves
ready at NEITHER, not TRUE_ONLY. Fix: expect NEITHER, or supply both
payments before expecting TRUE_ONLY.
Mistake 6 — mixing numeric profiles. Dana divides 1000 KZT by 3. The mistake
is the quiet mix: Money divided by an Integer is refused at check time.
Standalone mix — full content of mix.law: the profile mix the checker refuses
(expected refusal, LDC-E2108). This file is meant to be refused. The divisor
must be Decimal.
language "law.core" version "0.2";package probe version "0.1.0";namespace "urn:probe";pure function quarter() -> Money = 1000 KZT / 3;Look at the -> Money promise and the / 3 divisor: the arrow promises
Money, but the divisor is an Integer. The kind discipline refuses the file
before anything runs; money has no quiet integer division.
Reproduced by checking the scratch probe. Save the block above as
/tmp/nb24-m6/mix.law first (create the directory if needed), then run:
law engine check /tmp/nb24-m6/mix.law 2>&1Observed (expected refusal, exit 1):
/tmp/nb24-m6/mix.law:4:36: error LDC-E2108: «/»: §58 не называет делимым Money на Integer — виды делимых перечислены поимённо (errata E-0020)The checker refuses the file with LDC-E2108: §58 does not name Money
divided by Integer among its dividend kinds (не называет делимым means
does not name as dividend; kinds are listed by name, with errata E-0020).
Exit 1 is the expected refusal — no quiet integer division of money happens,
because the file never runs. Fix: use a Decimal divisor.
Mistake 7 — excepting an abbreviation. The senior label is defined exactly,
and the fraud exception arrives late with nowhere to attach. The mistake is
bolting the defeater onto the defined head: it is refused statically with
LDC-E4112, no defeasible producer.
The section 4 refusal run shows the consequence: the snippet fails law engine check with exit 1, while the classified twin absorbs the same defeater and the
suite stays green. Fix: re-sort the label as a defeasible classification before
fraud can take support away.
Mistake 8 — reporting a lifecycle as a timeless invariant. Ann’s permit is issued but unpaid, and the file must tell March from April. The mistake is using a constraint: it flags the issue but cannot record that the window has closed — both reads carry the identical issue with no before/after in the report.
The section 4 march/april runs show the consequence: the march read and april read probes each carry their issue, while the duty lifecycle moves from ACTIVE
to VIOLATED. Pick the constraint and the grace period exists only in the office
manual, never in the record. Fix: use a duty with a window when the requirement
has a lifecycle.
Mistake 9 — approving on readiness with no act. The station is substantively
ready, and Dana treats that readiness as approval. The mistake is deriving the
verdict from facts alone: substantive facts derive admitted(ann, 2) in the
stage world, approving a station no official ever touched.
The section 4 runs show the consequence: the decisions-stage run (admission derives without any event) against the decisions event-half run (complete file without events — no approval, still draft). Same input shape, opposite
verdicts, because only the procedure records acts. Fix: use a procedure with
event-gated states when approval needs a recorded act.
8. Limits
Section titled “8. Limits”Neighbors share shapes, never semantics. Swapping one for the other compiles — except the numeric mix, which is refused — and changes the recorded outcome.
The unless/denial/defeater three-way, the definition/classification
silence-vs-denial split, and the closure-domain boundary are properties of this
build’s four-valued evaluation, not language-wide necessity claims. Verified
profile: law 0.1.0, law.core/0.2, std 0.2.0. Refusals (LDC-E2108 and
LDC-E4112) are implementation facts of this profile. Nothing here covers real
registers, real deadlines, or real evidence law.
The decision tasks keep rival models apart by construction. Tasks 1–2 run under
distinct predicate names inside demo.northbridge.decisions, and task 3 splits
the procedure from the stage into two worlds with no shared dependency. Rival
heads never compete on one predicate, and no single run covers both worlds.
Window readings are decision_time reads of one record, not re-executions. The
audit_hold relation exists for the exercise only: no rule names it, so it can
neither sort nor defeat until you write the rule that does.
9. Exercise
Section titled “9. Exercise”Bob holds a badge and carries fines, lives in the outer zone, and pays the fee
three times at 10 EUR. For demo.northbridge.permits, predict the truth status
of permit_eligible(Bob), the truth status of central_senior(Bob), and the two
ledger totals fee_total_set / fee_total_all for his three payments. Then check
each prediction against the suite’s tests.
Checkable expectation: name all four values before running. All four are confirmed
by law test packs/examples/language-demo/permits with 28 passed and 0 failed.
Solution lives ONLY in solutions/nb-24-solutions.md.
Decision exercise 1 — the hold nobody wrote. Cora is resident, central, big
family — and carries audit_hold, which no rule in decisions mentions.
Predict def_senior(cora) and cls_senior(cora) before running, then check both
on a scratch copy of the package. Copy the package under /tmp, add one probe,
and run law test on the copy; the shipped suite stays untouched.
Then argue, in one paragraph each: which construct fits a temporary hold that must
never deny the underlying label, and what would have to change for Cora to read
NEITHER on cls_senior.
Decision exercise 2 — the window opening. Keep the issued-but-unpaid Ann and read
the duty world at decision_time @2026-03-01 (the window opening) instead of
March 10. Predict the duty status and check it on a scratch copy. Explain in one
paragraph why this reading differs — or does not differ — from the March 10 read.
Then state which construct you would keep if the office abolished the window and demanded same-day payment, and why.
Decision exercise 3 — the half-filed station. Assert EvFiled for the complete
station but no EvDecided, and predict the truth status of Draft, Filed and
Approved before running. Check on a scratch copy: copy the filed chain probe
and drop the second event. Explain in one paragraph where the case rests now and
which transition is still gated.
Then state what a round-3 extension of MiniAllotment could never express about
this waiting.
10. Sources
Section titled “10. Sources”Three levels, separated. In Northbridge, the pairs decide Ann’s eligibility, fines, fraud, household size and the three-times-10 ledger; the decision tasks decide the senior label, the unpaid fee, and the station approval. As domain templates, each pair names its reusable shape: blocking vs denying vs defeating, abbreviating vs sorting, reporting vs deriving, computing vs routing, waiting on events vs indexing rounds, and the three residency routes.
Full sources: the packages
permits package,
calculations package,
register package,
procedure package,
allocation package,
sources package,
capstone package,
decisions package,
decisions-stage package
and their tests. The scratch probes in section 7 are self-contained under /tmp
and need no source.
- Unless/denial/defeater, definition/classification, constraint, set-vs-list
ledger →
demo.northbridge.permits: Ann’s eligibility, fines, fraud, household size and the three-times-10 ledger. - Function and decision-table routing →
demo.northbridge.calculations. - Closure, admission conjuncts, explicit facts →
demo.northbridge.register; time axes and policies →demo.northbridge.sources; events and joins →demo.northbridge.procedure; indexed rounds →demo.northbridge.allocation. - Rival-model decision tasks →
demo.northbridge.decisions(tasks 1–2 and the procedure half of task 3) anddemo.northbridge.decisions_stage(the stage half of task 3, a separate world by construction). The refused defeater-on-definition repair is pinned asdecisions/evidence/snippets/def-head.law.txtwith its expectedLDC-E4112.
No external formalization is claimed for the pair semantics. The four-valued outcomes are this build’s evaluation behavior on the cited fixtures, and this build executes the 0.2 line only.
Scope of the recorded runs
Each recorded run confirms behavior on its cited fixture under the verified profile. It does not establish deployment, legal validity, or behavior on other inputs.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.