docs← Back to article

Markdown for LLMs

nb-08 — Which edition applies and when the term expires

The source Markdown for this article. Copy it into your assistant or download it as a text file.

Download this articlePlain text ↗
# nb-08 — Which edition applies and when the term expires

*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/) →
[nb-08](/tutorials/northbridge/nb-08-edition-and-terms/)). All law
is fictional; every act, edition, fragment and calendar is synthetic and
unofficial. Engine `law 0.1.0`, semantics `law.core/0.2`, std `0.2.0`.*

## Situation

On 6 March 2026 the permits office sends Ann a notice: confirm your
address within two working days. Ann replies on 11 March — is she still
on time? Meanwhile a clerk re-opens a June 2025 file: residency
alone sufficed then, but since 1 January 2026 a registered vehicle is
also required.

Northbridge is a synthetic training town: none of this happened, no
office runs this code, and nothing here is valid law anywhere. Two
modeling questions carry the article: *which edition's text governs*
and *when exactly a term expires*. The answers use pinned editions and
anchored rules, three time axes, a deadline policy over a training
calendar, and a reusable term.

## Prerequisites

[nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/):
facts, one rule, one question, and `law test` as the way to check a
claim. [nb-03: Exceptions and conflicting rules](/tutorials/northbridge/nb-03-exceptions/):
strict versus defeasible rules and defeat.

This article adds time — validity windows on editions, effective
windows on rules, achievement windows on duties — over the same firing
logic.

## Example

Eight terms, each explained next to its first use. Start with the
clerk's first question: which text governs the file on her desk? The
**source** is the act itself (here, the training act "Northbridge
Residential Parking Rules"). An **edition** is one dated, frozen
version of that act. The 2025 and 2026 editions sit side by side;
their validity windows touch but do not overlap:

Excerpt from `demo.northbridge.sources`,
`packs/examples/language-demo/sources/package.law` (lines 19–33;
the `source` block above is cut):

```law
edition RULES_2025 of PARKING_RULES {
    language en;
    officiality unofficial;
    adopted @2024-12-01;
    in_force [@2025-01-01, @2026-01-01);
    materialization_status PINNED_OFFICIAL_BYTES;
}

edition RULES_2026_EN of PARKING_RULES {
    language en;
    officiality unofficial;
    adopted @2025-12-01;
    in_force [@2026-01-01, infinity);
    materialization_status PINNED_OFFICIAL_BYTES;
}
```

Within an edition, the clerk points at one sentence. A **fragment** is
a named slice of an edition's text — here Article 2 (`article/2`)
with a quoted sentence and a content hash; the 2025 sentence asks only
for residency, the 2026 one adds the registered vehicle. (A Russian
2026 edition exists, aligned as a reviewed translation.) An **anchor**
is the `@source(…)` line tying a rule to its fragment, so the reader
can walk from a firing rule back to its sentence:

Excerpt from the same package (lines 114–128; both rules in full):

```law
@source(PARKING_RULES_2025_ART2)
rule OldResidencyNotice strict {
    effective [@2025-01-01, @2026-01-01);
    for a: Applicant;
    when demo.northbridge.vocabulary::resident(a);
    then residency_notice(a);
}

@source(PARKING_RULES_2026_ART2_EN)
rule NewEligibilityNotice defeasible {
    effective [@2026-01-01, infinity);
    for a: Applicant;
    when demo.northbridge.vocabulary::resident(a) and demo.northbridge.vocabulary::vehicle_registered(a);
    then eligibility_notice(a);
}
```

Look at what each rule carries: two windows. The fragment says *what
the text is*; the `effective` window says *when the rule may fire*.
Their agreement here is the modeler's responsibility, not an engine
inference. The old rule is strict, the new one defeasible — the firing
logic from earlier lessons, now with dates attached.

The **time axes** decide which window matters. Every evaluation runs at
three moments — `legal_time` (whose law governs), `decision_time`
(when deciding), `knowledge_time` (what is known). The test profile
fixes all three plus timezone and counting policy:

Excerpt from `demo.northbridge.sources`,
`packs/examples/language-demo/sources/tests/sources.lawtest`
(lines 5–8; profile only):

```law
context profile CaseAxes {
    timezone "Asia/Almaty";
    deadline_policy WORKING_DAYS;
}
```

Ann's notice raises the second question: when does her two-day term
expire? A **deadline policy** names how day-counting works: when
counting starts, whether the window end is included, and where a term
landing on a day off rolls. A **calendar** supplies the working-day
facts the policy counts over. Northbridge's training calendar is thin
— weekdays, one conventional day off (9 March 2026), bounded to 2026
— and its metadata says it is not an official calendar:

Excerpt from `demo.northbridge.sources`,
`packs/examples/language-demo/sources/package.law` (lines 130–144;
calendar and policy in full):

```law
calendar NorthbridgeCalendar {
    timezone "Asia/Almaty";
    timezone_db "iana-tzdb@2026a";
    period [@2026-01-01, @2027-01-01);
    source PARKING_RULES;
    resource "resources/northbridge-calendar-2026.json";
    format "law.calendar/0.1";
    content_hash "sha256:344b43d6a1060fe139378ab2de8d30a85abab0d1584891c93cd8f38713ccd6ee";
}

deadline policy WORKING_DAYS {
    start_count next_day;
    include_end true;
    roll next_working_day;
}
```

Read the policy line by line. Counting starts the day *after* the
triggering event (`start_count next_day`), the window end counts as
inside (`include_end true`), and a term landing on a day off rolls
forward (`roll next_working_day`). The reply duty uses exactly this
arithmetic — two business days from receipt:

Excerpt from the same package (lines 152–155; goal block only —
surrounding rule lines cut):

```law
goal achievement {
    condition reply_filed(a);
    window [on, add_business_days(on, 2 business_day)];
}
```

Finally, the office reuses the same "due by when" shape for every
appeal. A **term** packages that shape into a reusable norm: from
which event the clock runs, what performance is due, the default
length, and named special cases that defeat the default. The
boundaries package teaches the same idea in calendar days, where no
policy or calendar is needed:

Excerpt from `demo.northbridge.boundaries`,
`packs/examples/language-demo/boundaries/package.law` (lines 17–22):

```law
term AppealWindow(a: Applicant, filed_on: Date) {
    from demo.northbridge.vocabulary::application_filed(a, filed_on);
    due appeal_decided(a);
    default 30 calendar_day;
    case FastTrack when urgent_appeal(a): 10 calendar_day;
}
```

The clock runs from the filing event, the due performance is the
appeal decision, the default length is 30 calendar days — and an
urgent appeal takes the named `FastTrack` case of 10 days instead.

## Command and result

The sources package is self-contained, so one command checks all six
claims:

```sh
law test packs/examples/language-demo/sources
```

Observed result (engine `law 0.1.0`):

```text
law test demo.northbridge.sources: мир demo.northbridge.sources, demo.northbridge.vocabulary
  ok   [demo.northbridge.sources] tests/sources.lawtest / 2026 edition: the rule is in force
  ok   [demo.northbridge.sources] tests/sources.lawtest / before entry into force the 2026 rule is silent
  ok   [demo.northbridge.sources] tests/sources.lawtest / 2025 edition: the old reading holds in its window
  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
  ok   [demo.northbridge.sources] tests/sources.lawtest / without a policy working days are not counted
итого: 6 проверено, 6 прошли, 0 не прошли, 0 не исполнены; код 0
```

What each test pins:

| Test | Setup | Expects |
|---|---|---|
| `2026 edition: the rule is in force` | `legal_time @2026-03-01`, resident + vehicle | `TRUE_ONLY`, `applied(NewEligibilityNotice)` |
| `before entry into force the 2026 rule is silent` | `legal_time @2025-06-01`, same facts | `NEITHER` — the rule exists but is not yet law |
| `2025 edition: the old reading holds in its window` | `legal_time @2025-06-01`, resident only | `TRUE_ONLY`, `applied(OldResidencyNotice)` |
| `working days: the term includes the window end` | notice received `@2026-03-06`, judged `@2026-03-11` | `position(ReplyOnTime, ACTIVE)` — the end day counts |
| `working days: overdue without performance` | same receipt, judged `@2026-03-12`, `not reply_filed` | `position(ReplyOnTime, VIOLATED)` |
| `without a policy working days are not counted` | same receipt, context without `deadline_policy` | `NON_EXECUTABLE` + `issue(MISSING_POLICY)` |

All six 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 is in
Russian: `итого: 6 проверено, 6 прошли, 0 не прошли, 0 не исполнены;
код 0` — 6 checked, 6 passed, 0 failed, 0 skipped, exit code 0.

The term machinery, in calendar days:

```sh
law test packs/examples/language-demo/boundaries
```

Observed result (engine `law 0.1.0`):

```text
law test demo.northbridge.boundaries: мир demo.northbridge.boundaries, demo.northbridge.calculations, demo.northbridge.vocabulary
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / default term — 30 calendar days
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / special case defeats the general term
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / defeated general term does not hold
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / timeless support under governs is no support
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / same fact without governs is support
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / facts group visible to the package world
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / duration step — exactly one day
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / declared unit tag resolves
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / derived tag resolves
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / snapshot without provisioning — loud refusal
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / add_legal_term steps over calendar working days
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / map leads the central zone to the high fee
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / map does not lead the outer zone to the high fee
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / record field projection opens the rule
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / small record yields nothing
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / role membership opens the rule
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / filing without role membership yields nothing
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / qualified call of an imported function
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / applicant fee check over two imports
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / urgent flag routes review past the queue
  ok   [demo.northbridge.boundaries] tests/boundaries.lawtest / non-urgent flag accelerates nothing
итого: 21 проверено, 21 прошли, 0 не прошли, 0 не исполнены; код 0
```

This article owns three of those twenty-one: the default term (filed
`@2026-02-01`, due `@2026-03-03`, `TRUE_ONLY`), the urgent special
case (same filing with `urgent_appeal`, due `@2026-02-11`), and the
defeated general term (for the urgent filing, `@2026-03-03` is
`FALSE_ONLY`: the special case refutes the default, it does not add a
second answer). The rest belong to other lessons; they are shown only
to prove the suite is green as a whole.

## Why this shape

The task is to decide *under which text* a case falls and *whether a
term has expired* — reproducibly, from pinned bytes and named counting
rules, not from a sense of "current law".

The source/edition/fragment/anchor chain makes the text behind every
firing rule inspectable. The time axes separate "which law governs"
from "when we decide". The policy plus calendar makes day-counting
explicit (start, inclusion, roll) instead of habit.

The six sources tests are the proof (windows select the rule; the end
counts; expiry flips `ACTIVE` to `VIOLATED`), plus the three term
tests (default 30 days, urgent 10, defeated default refuted) — all
executed by `law test`, not asserted in prose.

What is not proven: that two working days is the *right* reply term,
or that the 2026 vehicle requirement is good policy. The tests prove
the machine counts the declared term. Texts and terms are fictional
data, not legal advice.

<details>
<summary>Why not dates in conditions, or counting by hand?</summary>

Stuffing dates into rule conditions computes the same answers here but
scatters the edition boundary across every rule and leaves no text to
point at. Counting working days by hand in a function
([nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/) style) hides the choices
the policy declares. Conditions for case facts, editions for text
versions, policies for counting.

</details>

## Changed condition

Change one input: `legal_time @2026-03-01` → `@2025-06-01`, keeping
the same person and facts (resident, vehicle registered).

In March 2026 `eligibility_notice` is `TRUE_ONLY` via
`NewEligibilityNotice`. In June 2025 the same question is `NEITHER` —
the 2026 rule is silent before entry into force, and the engine says so
instead of borrowing tomorrow's law.

The old file is not empty, though: with residency alone,
`residency_notice` is `TRUE_ONLY` via `OldResidencyNotice` in its
`[@2025-01-01, @2026-01-01)` window. One date, three outcomes, and the
`legal_time` axis — not the reader's "now" — picks between them:
edition windows filter the rules before any fact is even read.

## Typical mistake

The mistake is keeping the business-day window but dropping the
`deadline_policy` from the context — "the calendar is right there in
the package, so counting should just work".

It does not. The last sources test does exactly this and gets
`NON_EXECUTABLE` with `issue(MISSING_POLICY)` instead of any position.
Without `start_count`, `include_end` and `roll` the window end is
undefined — 11 March could fall inside or outside the term depending on
unspoken habit — so the engine forces the missing choice into the
open.

The fix: `legal_time` without an edition window gives silence
(`NEITHER`); a business-day window without a policy gives refusal
(`MISSING_POLICY`). Both diagnose your model, not Ann: add the window,
or add the policy, and the real question becomes answerable.

## Limits

- **Refusals are profile facts, not language gaps.** `MISSING_POLICY`
  with `NON_EXECUTABLE` is what `law 0.1.0` / `law.core/0.2` do when
  working-day counting is asked for without a policy. The refusal is
  the specified behavior.
- **The calendar is a toy, honestly labeled.** One timezone, one
  bounded year, weekdays plus a single override (`2026-03-09` off),
  pinned by hash — and its own metadata says it is not an official
  calendar. Outside `[@2026-01-01, @2027-01-01)` there is nothing to
  count over.
- **Policies differ per package on purpose.** Sources count working
  days (`WORKING_DAYS`: next-day start, inclusive end, roll
  forward); boundaries count plain calendar days (`CALENDAR_COUNT`:
  no roll). A term in `calendar_day` needs no calendar at all —
  which is why the `AppealWindow` tests carry no policy drama.
- **Anchors do not verify text.** `@source(…)` records *which*
  fragment a rule claims to formalize; nothing checks the rule
  against the sentence. A misformalized rule would still fire — the
  anchor makes the mismatch findable, not impossible.

## Three levels

1. **Northbridge use** (this article): two editions, Article 2
   fragments, anchored rules, three-axis contexts, a working-day
   policy over a toy calendar, a two-business-day reply duty —
   verified by the six sources tests.
2. **Domain template:** freeze each text version as an edition with
   a touching-but-disjoint validity window, slice citable units as
   fragments, anchor every time-sensitive rule, drive applicability
   from `legal_time` alone, and count every term under a named
   policy (start, inclusion, roll) over a pinned calendar. Never let
   "now" leak into applicability; never count working days without
   a policy.
3. **Confirmed example elsewhere:** the reusable `term` in
   `demo.northbridge.boundaries` — `AppealWindow`, 30-day default
   defeated by a 10-day urgent case — verified by
   `law test packs/examples/language-demo/boundaries`
   (21 checked, 21 passed, 0 failed). The special case refutes the
   default (`FALSE_ONLY` at the default date), exactly as the 2026
   edition supersedes the 2025 reading. Confirmed external
   formalization: five-year prescription deadline (French Civil Code,
   prescription title) — package `fr.code_civil`,
   `corpus/laws/fr/code-civil/20-prescription-extinctive.law:122-129`,
   construct `add_calendar_period` step for year/month periods
   (`EcheanceCinqAns`: `add_calendar_period(depart, 5 calendar_year)`).
   Why this form fits: the period is built by calendar step, not
   measured in days, with the deadline policy carried alongside — the
   same "never count without a policy" template as this article.
   Evidence:
   `docs/research/constructs/20-deadline-calendar/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.

## Exercise

Without running the engine, predict, then check with `law test`:

1. `legal_time @2025-06-01`, Ann resident with a registered
   vehicle: what is `eligibility_notice(ann)`, and which rule fires,
   if any?
2. Notice received `@2026-03-06`, nothing filed, judged
   `@2026-03-11` versus `@2026-03-12`: which position for
   `ReplyOnTime` on each date, and why does one day change it?
3. Filed `@2026-02-01`, applicant urgent: what is
   `appeal_decided(ann, date)` on `@2026-02-11` and on `@2026-03-03`
   — and what defeats what?
4. Delete `deadline_policy WORKING_DAYS;` from `CaseAxes` and
   re-run the sources suite: which test changes status, to what,
   with which issue?

Predict first; run both suites; explain any miss in one sentence.
Checkable solution:
[solutions/nb-08-solutions.md](/tutorials/northbridge/solutions/nb-08-solutions/).

## Links

- Sources: `packs/examples/language-demo/sources/package.law`
  (editions, fragments, anchored rules, calendar, policy, reply duty)
- Texts: `packs/examples/language-demo/sources/sources/northbridge-rules-2025.txt`,
  `northbridge-rules-2026-en.txt`, `northbridge-rules-2026-ru.txt`
- Tests: `packs/examples/language-demo/sources/tests/sources.lawtest`
- Term source: `packs/examples/language-demo/boundaries/package.law`
  (`AppealWindow`, `CALENDAR_COUNT`)
- Term tests: `packs/examples/language-demo/boundaries/tests/boundaries.lawtest`
- Suite tour: `packs/examples/language-demo/README.md`
- Prerequisites: [nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/); [nb-03: Exceptions and conflicting rules](/tutorials/northbridge/nb-03-exceptions/);
  next: [nb-09: From permit to duties and powers](/tutorials/northbridge/nb-09-duties-powers/)