# nb-06 — When the register may stay silent *Northbridge course, intermediate ([nb-05](/tutorials/northbridge/nb-05-suitable-applicant/) → [nb-06](/tutorials/northbridge/nb-06-register-silence/) → [nb-07](/tutorials/northbridge/nb-07-document-vs-fact/)). 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.* ## Situation 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. ## Prerequisites [nb-02: Why a missing fact is not a refusal](/tutorials/northbridge/nb-02-missing-fact/) taught the four truth statuses (`TRUE_ONLY`, `FALSE_ONLY`, `BOTH`, `NEITHER`): by default, silence yields `NEITHER`. [nb-05: Who counts as a suitable applicant](/tutorials/northbridge/nb-05-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. ## Minimal example Excerpt from `packs/examples/language-demo/register/package.law` (the closure block, lines 17–23; rule bodies and the evidence pipeline cut): ```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): ```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): ```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. ## Command and result The register package tests against its own world plus the vocabulary (world pinned in `suite.json`), so one command checks everything: ```sh law test packs/examples/language-demo/register ``` Observed result (engine `law 0.1.0`): ```text 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: ```sh law engine check packs/examples/language-demo/register/package.law ``` ```text check OK: packs/examples/language-demo/register/package.law ``` Four of the nine tests carry this article: | Test | Setup | Expects | |---|---|---| | `on file without a record — not a resident` | Carl on the index, no record | `FALSE_ONLY` | | `outside the domain silence is silence` | Dana off the index | `NEITHER` | | `file record accepted` | Ann's `filed_resident` asserted | `TRUE_ONLY` via `AdmitFromFile` | | `key conflict: two filing dates` | Ann filed on two dates | `TRUE_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. ## Why this construct 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](/tutorials/northbridge/nb-07-document-vs-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](/tutorials/northbridge/nb-02-missing-fact/) 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.
## Changed condition 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. ```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. ## Typical mistake 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. ## Limits - **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. ## Exercise 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](/tutorials/northbridge/solutions/nb-06-solutions/). ## Sources 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.
- Source: `packs/examples/language-demo/register/package.law` (closure lines 17–23, key in `packs/examples/language-demo/vocabulary/package.law` line 21) - Tests: `packs/examples/language-demo/register/tests/register.lawtest` - Suite tour: `packs/examples/language-demo/README.md` - Prerequisite: [nb-05: Who counts as a suitable applicant](/tutorials/northbridge/nb-05-suitable-applicant/); next: [nb-07: A document is not yet a proven fact](/tutorials/northbridge/nb-07-document-vs-fact/)