Skip to content
docs
Arxo ↗

nb-06 — When the register may stay silent

For LLMs10 sections
← Course mapChapter 06 / 25 · Intermediate

Northbridge course, intermediate (nb-05 → nb-06 → nb-07). All law is fictional; every register, snapshot and document in this series is synthetic and unofficial. Engine law 0.1.0, semantics law.core/0.2, std 0.2.0. Northbridge is a synthetic training story: nothing here describes a real deployment or makes any legally valid claim.

A Northbridge clerk checks two files. Carl’s file says he is on the residents’ file index, but holds no residency record for him. Dana’s file holds nothing at all — she is not even on the index. The clerk asks the same question twice: “is this person a filed resident?” For Carl the answer is no. For Dana the answer is: we cannot say.

Both answers are silences, but different ones. Carl’s file is complete: the register guarantees that everything filed by 1 January 2026 is in it, so a missing record means the filing never happened. Dana stands outside the register’s coverage entirely, so the same missing record means nothing at all.

This article teaches the closure construct that draws that line: a full domain, a pinned snapshot, and the key rule that keeps conflicting entries visible.

nb-02: Why a missing fact is not a refusal taught the four truth statuses (TRUE_ONLY, FALSE_ONLY, BOTH, NEITHER): by default, silence yields NEITHER. nb-05: Who counts as a suitable applicant introduced the applicant relations from the vocabulary package.

This article adds one idea: an explicit, scoped declaration that turns some of that silence into a derived negative.

Excerpt from packs/examples/language-demo/register/package.law (the closure block, lines 17–23; rule bodies and the evidence pipeline 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;
}

Five lines, five ideas — each term explained at first use. closure ResidentsFile names a completeness claim over one predicate: filed_resident. The register says “we know everything about who filed”.

domain on_resident_file bounds that claim. Completeness applies only to applicants satisfying on_resident_file — the file index. Anyone off the index is outside the closure, and silence about them stays silence.

snapshot pins which register copy the claim is about (urn:snapshot:demo-northbridge-residents:2026). The claim is tied to that frozen copy, not to “the register” in the abstract.

complete_as_of pins when: the copy is complete for everything filed up to @2026-01-01T00:00:00Z.

derive_explicit_negative true says what silence inside the domain becomes: a domain member with no record derives the explicit negation — a real FALSE_ONLY, not a NEITHER.

Excerpt from packs/examples/language-demo/vocabulary/package.law (line 21; surrounding relations cut):

Arxo Law
pub relation application_filed(a: Applicant, on: Date) kind empirical { key(a); }

key(a) declares a functional dependency: one applicant, one filing date. Asserting two dates for Ann does not silently overwrite — the engine keeps the fact and raises a KEY_CONFLICT issue, so the conflict stays visible in the record.

Excerpt from packs/examples/language-demo/register/tests/register.lawtest (the closure pair, lines 5–20; remaining seven tests cut):

Arxo Law
test "on file without a record — not a resident" {
given {
context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; }
assert "file-carl": on_resident_file(entity_ref("urn:demo:northbridge:carl")) { origin case_input; }
}
evaluate truth(filed_resident(entity_ref("urn:demo:northbridge:carl")));
expect truth_status == FALSE_ONLY;
}
test "outside the domain silence is silence" {
given {
context { legal_time @2026-03-01; decision_time @2026-03-01T09:00:00Z; knowledge_time @2026-03-01T09:00:00Z; timezone "UTC"; }
}
evaluate truth(filed_resident(entity_ref("urn:demo:northbridge:dana")));
expect truth_status == NEITHER;
}

Read the two tests side by side. Carl’s test asserts one fact — he is on the index — and asks about filed_resident: the answer is FALSE_ONLY. Dana’s test asserts nothing at all and asks the same question: the answer is NEITHER. Same missing record, different coverage, different answer.

The register package tests against its own world plus the vocabulary (world pinned in suite.json), so one command checks everything:

Terminal
law test packs/examples/language-demo/register

Observed result (engine law 0.1.0):

Output
law test demo.northbridge.register: мир demo.northbridge.register, demo.northbridge.vocabulary
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 / file record accepted
ok [demo.northbridge.register] tests/register.lawtest / key conflict: two filing dates
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
ok [demo.northbridge.register] tests/register.lawtest / refuting document
итого: 9 проверено, 9 прошли, 0 не прошли, 0 не исполнены; код 0

The package also passes the static check:

Terminal
law engine check packs/examples/language-demo/register/package.law
Output
check OK: packs/examples/language-demo/register/package.law

Four of the nine tests carry this article:

TestSetupExpects
on file without a record — not a residentCarl on the index, no recordFALSE_ONLY
outside the domain silence is silenceDana off the indexNEITHER
file record acceptedAnn’s filed_resident assertedTRUE_ONLY via AdmitFromFile
key conflict: two filing datesAnn filed on two datesTRUE_ONLY + issue(KEY_CONFLICT)

All nine tests pass: each answer matched its expectation. A passing test is not a ruling in anyone’s favour — it only says the engine’s answer agreed with the test’s expect line.

The summary line is in Russian: итого: 9 проверено, 9 прошли, 0 не прошли, 0 не исполнены; код 0 — 9 checked, 9 passed, 0 failed, 0 skipped, exit code 0. The first line names the test world (мир): the register package plus the vocabulary it reads.

The key-conflict test deserves a close read. The queried literal (application_filed on the first date) is still TRUE_ONLY — the engine deletes neither entry — but the run attaches a KEY_CONFLICT issue. The conflict is reported, not resolved and not hidden.

The task is to let the register say “no” when its coverage justifies it, without letting that “no” leak to people the register never covered.

A closure writes the coverage down — predicate, domain, snapshot id, as-of time — so the negative is derived from stated completeness, with the scope inspectable in the source. Carl’s FALSE_ONLY cites the closure; it is not a clerk’s assumption.

The nine tests are the proof. The closure pair pins the inside/outside-domain split, the conflict test pins the visible issue, and the remaining five tests (the evidence pipeline, covered in nb-07: A document is not yet a proven fact) confirm the closure changes nothing outside its predicate.

What is not proven: that the register copy is actually complete. The tests prove the engine derives negatives exactly inside the declared domain and snapshot; no test can prove the clerks filed everything by the as-of date. Completeness is a declared claim, not a verified fact.

Why not a default rule?

A default rule (“everyone without a record is not a resident”) would give Carl’s answer too, but it would also answer for Dana — wrongly, since the register never covered her. Keeping NEITHER as the default and closing only the indexed domain (the default-silence discipline of nb-02: Why a missing fact is not a refusal plus one explicit boundary) keeps Dana’s silence silent.

Likewise, without key(a) the two filing dates would coexist quietly; the key turns a data-entry collision into an observable issue.

Change one condition: put Dana on the index. Add to her test’s given block:

Sketch — one line to insert into the test’s given block, not runnable alone; the flipped outcome is quoted below.

Arxo Law
assert "file-dana": on_resident_file(entity_ref("urn:demo:northbridge:dana")) { origin case_input; }

Her filed_resident question flips from NEITHER to FALSE_ONLY — she becomes Carl. Nothing about Dana’s records changed (there still are none); only her coverage changed, and coverage decides whether silence speaks.

The reverse also holds. Asserting a record for Carl (filed_resident for Carl) flips his answer from FALSE_ONLY to TRUE_ONLY by the assertion itself — an actual record beats the closure’s negative — and resident_admitted follows via AdmitFromFile. A record decides what is said.

The mistake is reading the closure as universal: “Dana has no record, so she is not a resident.”

The observed consequence is a status the author did not expect: truth(filed_resident(dana)) stays NEITHER, and any downstream rule waiting for a derived not filed_resident(dana) never fires.

The fix is not to argue with the engine but to check coverage: is Dana in on_resident_file? If she should be, assert the index fact — and she becomes FALSE_ONLY, honestly. If the register genuinely does not cover her, NEITHER is the correct finding: report it and complete the file instead of inventing a denial.

  • One predicate, one domain, one snapshot. The closure speaks only about filed_resident, only for index members, only as of the pinned copy and time. A second register (vehicles, fines) needs its own closure; the engine does not generalize coverage across predicates.
  • Declared, not verified. complete_as_of records when the copy claims completeness; the engine trusts the declaration. A late filing missing from the copy still derives a negative — the failure mode of every closed file, stated plainly.
  • Keys report, they do not resolve. KEY_CONFLICT flags the double filing and both entries survive; choosing the correct date is a human correction to the file, not an engine inference.
  • Verified profile: engine law 0.1.0, semantics law.core/0.2. Refusals or behaviors outside this profile are implementation facts, never language-wide inability claims.

Dana’s file starts exactly as in outside the domain silence is silence (nothing asserted). Without running the engine, predict, then check with law test:

  1. truth(filed_resident(dana)) after asserting only on_resident_file(dana) — status?
  2. truth(resident_admitted(dana)) after asserting only on_resident_file(dana) — status, and which rule (if any) fires?
  3. truth(filed_resident(dana)) after asserting both on_resident_file(dana) and filed_resident(dana) — status?
  4. In key conflict: two filing dates, what would change if application_filed had no key(a) — the TRUE_ONLY, the KEY_CONFLICT issue, or both?

Write each prediction down first; run the suite; explain any miss in one sentence. Checkable solution: solutions/nb-06-solutions.md.

Three levels, separated:

  1. Northbridge use (this article, verified): the ResidentsFile closure and the key(a) on application_filed, verified by the nine tests above (law test packs/examples/language-demo/register: 9 checked, 9 passed).
  2. Domain template: for any “is X on file?” question, declare the coverage as a closure — predicate, membership relation as domain, snapshot id plus as-of time — with derive_explicit_negative true only where the file genuinely claims completeness; declare functional dependencies with key(...) so collisions surface as issues instead of silent overwrites.
  3. Confirmed example elsewhere: budget-linked benefit parameter (Kazakhstan Social Code, 2026 financial year) — package kz.corpus.socialcode, corpus/laws/kz/codes/social-code/00-package.law:4978-4989, construct effective [start, end) finite rule window (AddressedAssistanceMciFromBudget2026 holds only inside [@2026-01-01, @2027-01-01)). Why this form fits: scope is declared, not assumed — the rule switches off by date rather than by package edit, the same explicit-bounds discipline as the closure’s domain-plus-moment coverage. Evidence: docs/research/constructs/19-time/corpus-forms.en.md section 1 (rated exemplary there). Limit of verification: presence of the named construct at the cited lines only, confirmed by direct source read; no claim about deployment, runtime behavior, or legal correctness.
Cited, not claimed: the tariff snapshot

The same package holds a tariff function with snapshot_from context.tariffs, which pins external values to a snapshot the same way. It has no scenario in this suite, so it is cited, not claimed.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.