# 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.
Why not dates in conditions, or counting by hand? 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.
## 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/)