Markdown for LLMs
nb-06 — When the register may stay silent
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# 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.
<details>
<summary>Why not a default rule?</summary>
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.
</details>
## 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.
<details>
<summary>Cited, not claimed: the tariff snapshot</summary>
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.
</details>
- 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/)