Skip to content
docs
Arxo ↗

nb-24 — Neighbor constructs, nine mistakes and three decision tasks

For LLMs10 sections
← Course mapChapter 24 / 25 · Modeling practice

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).

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.

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.

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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo 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)));
}

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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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).

Arxo Law
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):

Arxo Law
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):

Arxo Law
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):

Arxo Law
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):

Arxo Law
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):

Arxo Law
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):

Arxo Law
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):

Arxo Law
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.

Tool versions first (five facts: tool, language, semantics, std, binary):

Terminal
law --version

Observed (exit 0):

Output
law 0.1.0
семантика: law.core/0.2
std для языка 0.2: 0.2.0
хэш бинаря: sha256:78dea06ce928547e87bd9875a2556cc663def37cbb78c8b04aa0da57839c95d7

The 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:

Terminal
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):

Output
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 не исполнены; код 0

The 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:

Terminal
law test packs/examples/language-demo/calculations 2>&1 | grep -E "fee for three|discount for twelve|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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:

Terminal
law test packs/examples/language-demo/register 2>&1 | grep -E "on file without|outside the domain|authenticity|policy|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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):

Terminal
law test packs/examples/language-demo/procedure 2>&1 | grep -E "join waits|both regions ready|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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.

Terminal
law test packs/examples/language-demo/allocation 2>&1 | grep -E "round 2 admits|insufficient|closed total|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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):

Terminal
law test packs/examples/language-demo/decisions 2>&1 | grep -E "senior|fraud|negated|unpaid|duty|no report|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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:

Terminal
law test packs/examples/language-demo/decisions 2>&1 | grep -E "without events|filed chain|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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):

Terminal
law test packs/examples/language-demo/decisions-stage

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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:

Terminal
law engine check packs/examples/language-demo/decisions/evidence/snippets/def-head.law.txt

Observed (exit 1):

Output
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.

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.

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).

Arxo Law
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.

Terminal
law test packs/examples/language-demo/capstone 2>&1 | grep -E "hold|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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.

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:

Terminal
law test packs/examples/language-demo/sources 2>&1 | grep -E "window end|overdue|итого"

Observed (exit 0, suite green):

Output
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 не исполнены; код 0

The 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):

Terminal
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);
}
EOF
cat > /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;
}
EOF
law engine test /tmp/nb24-m5/probe.lawtest --program /tmp/nb24-m5/probe.law 2>&1

Observed (expected FAIL, exit 1):

Output
test FAIL: ready with one payment only
truth_status == TRUE_ONLY: в документе NEITHER
lawc 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.

Arxo Law
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:

Terminal
law engine check /tmp/nb24-m6/mix.law 2>&1

Observed (expected refusal, exit 1):

Output
/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.

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.

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.

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) and demo.northbridge.decisions_stage (the stage half of task 3, a separate world by construction). The refused defeater-on-definition repair is pinned as decisions/evidence/snippets/def-head.law.txt with its expected LDC-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.