RFC-019: Law End Dates (valid_to) and Expired-Reference Handling

AcceptedImplemented
Depends on
RFC-001 (YAML Schema Design Decisions), RFC-003 (Inversion of Control for Delegated Legislation), RFC-007 (Cross-Law Execution Model)
Required by
RFC-020 (Date-Aware Reference Resolution (as_of))

Context

Problem. Version selection filters on the lower bound only (valid_from <= calculation_date, newest wins - RFC-001 §5, RFC-003). There is no end date. So:

  • The most recent version of any law is treated as valid forever. A law can only “end” by being superseded - never by expiring or being repealed.
  • Real laws do end without a successor, in two ways:
    • intrinsic - the law’s own horizonbepaling: CEK23 “vervalt met ingang van 1 januari 2027” (art. 8.1 lid 2);
    • extrinsic - another instrument ends it: the Wet op de openluchtrecreatie was gefaseerd ingetrokken per 1 januari 2008; the Twm covid-19 measures vervielen per 20 mei 2022 after the Eerste Kamer rejected the goedkeuringswet for the fifth verlenging (17 mei 2022, dossier 36.042).
  • Dependents break silently (“verweesde regelingen”). A regulation whose delegation basis disappears can no longer have effect (old Aanwijzing 243: van rechtswege vervallen; since 2018 Aanwijzing 6.24 prescribes explicit mede-intrekking instead - Stb. 2018, 362 executes it to the letter). Example: the Tijdelijke regeling maatregelen covid-19 was vervallen per the same date as the Twm. In regelrecht’s source.regulation model this is a dangling reference: the engine keeps computing on no-longer-valid law - a silent wrong answer.

Scope:

  • valid_from/valid_to describe instrument validity (is the text in force?) - not the substantive scope a norm governs (e.g. CEK23 caps calendar-2023 deliveries), which stays in the rule’s own conditions and temporal metadata (RFC-001 §8).
  • Article repeal is out of scope: repealing a single article is an ordinary amendment, already expressed by version succession (new consolidation → new version of the same $id, newest-wins). See Alternatives.
  • Resolving an ended/changed law on purpose (eerbiedigende werking, statische verwijzing, ex tunc) is RFC-020 (date-aware resolution, as_of). RFC-019 covers only the global calculation_date.

Decision

Add a law-level valid_to, give version selection an inclusive upper bound, and report expired references honestly.

Grounding - the legislation this RFC is built on

InstrumentVindplaatsRole in this RFC
Subsidieregeling bekostiging plafond energietarieven kleinverbruikers 2023 (CEK23)BWBR0047628intrinsic end (art. 8.1 lid 2); worked example A; real corpus law
Wet op de openluchtrecreatieBWBR0006548 · EK 29.829extrinsic end: whole wet ingetrokken per 1-1-2008
Tijdelijke wet maatregelen covid-19 + Tijdelijke regelingBWBR0044337 · BWBR0044416the limits case (example B): KB-determined, per-provision end; verweesde regeling
Besluit Stb. 2018, 362 (Fokkerijbesluit/-regeling)stb-2018-362Aanwijzing 6.24 in practice: grondslag vervalt (art. 2) + explicit mede-intrekking (art. 3)
Wet open overheid art. 10.2fBWBR0045754open end date (“Hoofdstuk 6 vervalt bij koninklijk besluit”) - beyond the static boundary
Aanwijzingen voor de regelgeving 6.24/6.25 (old 243)BWBR0005730drafting conventions for vervallen/intrekking (besluit van de Minister-President, not substantive law)
BWB consolidation metadataeinddatum per toestandharvester source for valid_to

1. valid_to: instrument end date

$id: subsidieregeling_bekostiging_plafond_energietarieven_kleinverbruikers_2023 bwb_id: BWBR0047628 valid_from: '2022-12-15' valid_to: '2026-12-31' # art. 8.1 lid 2: "vervalt met ingang van 1 januari 2027" → laatst van kracht 31 dec 2026
  • Optional law-level field, mirroring valid_from (RFC-001 §5).
  • Inclusive: the last calendar day the law is in force, matching BWB’s einddatum (“geldend t/m”). A statutory “vervalt met ingang van D” is exclusive → valid_to = D − 1.
  • Only for genuine termination (the law vervalt or is ingetrokken):
    • not for ordinary succession - the next version’s valid_from already ends the old one;
    • a law repealed and replaced by a different $id still gets valid_to (newest-wins cannot end it).
  • Two provenance modes for the date:
    • intrinsic - the law’s own vervalbepaling (CEK23 art. 8.1 lid 2). The only mode RFC-019 implements.
    • extrinsic - the date lives in another instrument (intrekkingswet, verlengings-KB, or open: Woo art. 10.2f lid 1). A cross-law “terminates” mechanism that derives the date from that instrument is future work; until then the harvested literal valid_to is a denormalised value with the terminating instrument as source of record.
  • Boundary: validity is static metadata. This RFC never executes a law to decide whether another law is in force:
    • selection runs before execution, so derived validity has an ordering problem (you would have to execute a law to know which version is selectable);
    • a terminates-effect therefore binds early: at harvest time today (BWB already processed the terminating instrument into the final consolidation’s einddatum), at load/index time in the future mechanism - never at execution time (unlike overrides/implements, which resolve values, not validity);
    • load-time derivation matters for moving end dates: the Twm verlengings-KBs shifted the verval repeatedly; harvest-time literals would need a corpus rewrite on every change.
  • Harvester rule: write valid_to from BWB einddatum only when the consolidation is the final one and the einddatum is finite (≠ the 9999-12-31 “still in force” sentinel). An intermediate consolidation’s einddatum is mere succession.

2. Version selection gains an upper bound

  1. Among versions with valid_from <= calculation_date, select the newest (unchanged).
  2. If that version has a valid_to and calculation_date > valid_to: the law is not in force → return nothing. No fall-through to an older version - a repealed law does not resurrect a prior version.

In force iff valid_from <= calculation_date <= valid_to (inclusive). Additive: laws without valid_to behave exactly as today.

3. Expired references: honest diagnostics

A reference whose target is not in force resolves to nothing - and the engine reports only what the data proves (RFC-015):

CauseThe engine statesMust NOT state
$id absent from corpus”regeling <id> niet gevonden""geen grondslag”
all versions valid_from > calculation_date”geen versie van <id> van kracht op <datum> (nog niet in werking)“-
selected version valid_to < calculation_date”geen versie van <id> van kracht op <datum>; laatst geldig t/m <valid_to>""vervallen zonder opvolger”
  • Never “geen grondslag / zonder opvolger”: eerbiedigende werking, a statische verwijzing or an alternative grondslag may keep the law alive (→ RFC-020).
  • The resolver exposes a typed reason (NotFound / NotYetInForce / EndedOn(date)); execution maps it to typed errors (LawNotFound / LawNotYetInForce / LawEnded with reference date and valid_to).
  • Fatal vs. continue: a required reference that fails is an error (fail-fast, RFC-012); an optional one (required: false, RFC-003) yields a null/taint that propagates like UNTRANSLATABLE, recorded on the CrossLawReference trace node (RFC-007, RFC-013).

4. Receipt and conformance

  • The receipt’s loaded_regulations (RFC-013) gains valid_to next to valid_from: the full validity window.
  • A valid_to filtering case joins the Temporal conformance level (RFC-014).

Worked examples

A. Intrinsic end - Prijsplafond energie 2023 (CEK23, BWBR0047628)

Real corpus law, harvested from BWB: corpus/regulation/nl/ministeriele_regeling/subsidieregeling_bekostiging_plafond_energietarieven_kleinverbruikers_2023/2022-12-15.yaml. Modeled is what the articles literally vaststellen: plafondtarieven (art. 2.2), volumeplafonds (art. 2.3) and the vervaldatum (art. 8.1).

$id: subsidieregeling_bekostiging_plafond_energietarieven_kleinverbruikers_2023 regulatory_layer: MINISTERIELE_REGELING bwb_id: BWBR0047628 valid_from: '2022-12-15' valid_to: '2026-12-31' # art. 8.1 lid 2: "vervalt met ingang van 1 januari 2027" # (exclusive) → laatst van kracht 31 december 2026 (inclusive) articles: - number: '2.2' # plafondtarief - "wordt vastgesteld op: € 0,24755 per kWh" (lid 1) etc. machine_readable: execution: # euro-per-unit rates (EUR/kWh, EUR/m3(n), EUR/GJ), typed `number`: the corpus # `amount` convention is whole eurocents and type_spec has no tariff units yet output: - { name: plafondtarief_elektriciteit, type: number } - { name: plafondtarief_gas, type: number } - { name: plafondtarief_warmte, type: number } actions: - { output: plafondtarief_elektriciteit, value: 0.24755 } - { output: plafondtarief_gas, value: 0.85734 } - { output: plafondtarief_warmte, value: 47.38 } - number: '2.3' # volumeplafond - "wordt vastgesteld op: 2.900 kWh ... per jaar" etc. machine_readable: execution: output: - { name: volumeplafond_elektriciteit, type: number } # ... gas (1200), warmte (37.0) - number: '8.1' # slotbepaling machine_readable: execution: output: - { name: vervaldatum, type: date } # lid 2: "vervalt met ingang van 1 januari 2027" actions: - { output: vervaldatum, value: '2027-01-01' }
  • End date grounded in art. 8.1 lid 2, full text: “Deze regeling vervalt met ingang van 1 januari 2027, met dien verstande dat zij van toepassing blijft op subsidies die voor die datum zijn verstrekt.” Art. 8.1 outputs the vervaldatum verbatim (the grounded source); the top-level valid_to is the denormalised inclusive last day (vervaldatum − 1). Deriving it automatically from the article output is the static-metadata boundary of §1.
  • The staartzin is eerbiedigende werking - the RFC-020 bridge case: the regeling stays applicable to subsidies granted before 2027 even after its instrument end. valid_to correctly records the instrument end; §3 ensures the engine states only “no version in force on <datum>”, never “no longer applicable”. Resolving this regeling for a pre-2027 subsidie at a post-2026 date is exactly RFC-020’s as_of case.
  • Verified with the evaluate binary:
    • date: 2023-06-01plafondtarief_elektriciteit: 0.24755, volumeplafond_elektriciteit: 2900, vervaldatum: 2027-01-01;
    • date: 2027-06-01 → fails with the data fact: no version in force on 2027-06-01, last in force until 2026-12-31 (§2, §3).
  • Faithfulness: the regeling is a subsidie aan leveranciers (art. 2.1 lid 1); the supplier subsidy formulas of art. 3.1-3.5 (volume-capped verbruik × (contracttarief − plafondtarief), plus TUK, minus brutomarge-correctie) are not modeled - they need bijlage data and supplier-level aggregates. No article computes a consumer-facing “korting”. The cap’s substantive scope (calendar 2023) is separate from the instrument validity (text in force until 2027).

B. The limits case - dynamic, KB-determined, per-provision end (Twm)

The Tijdelijke wet maatregelen covid-19 shows what a static law-level valid_to deliberately does not cover (§1 boundary):

  • The end date is not in the law. Art. VIII lid 1: the inserted Hoofdstuk Va Wpg vervalt three months after entry into force; lid 3 lets a KB extend it - or designated parts - by at most three months at a time (“steeds ten hoogste drie maanden”). After repeated verlengings-KBs the measures lapsed per 20 mei 2022, when the goedkeuringswet for the fifth verlenging was rejected. Authoring valid_to: '2022-05-19' would be invented - not grounded in the Twm’s own text.
  • The verval is per-provision. Parts not extended lapsed separately (e.g. the avondklok-grondslag earlier); a single law-level date is too coarse.
  • The clean, grounded intrinsic case is CEK23 (example A): one literal vervaldatum for the whole regeling.

C. Expired dependent - orphaned reference

  • Regulation R reads an output of law B (source: { regulation: B, output: … }); B carries valid_to.
  • At a calculation_date after B’s end: B resolves to nothing, R yields no outcome, and execution reports “geen versie van B van kracht op <datum>; laatst geldig t/m <valid_to>” - instead of computing on the repealed B.
  • Tested end-to-end in features/einddatum.feature (test laws test_einddatum / test_einddatum_afnemer).

Testing

New cucumber-rs scenarios in features/einddatum.feature (test laws mirroring test_untranslatables):

  1. valid_to upper bound: resolves before and on the (inclusive) end date; fails with the data fact after it (no fall-through).
  2. expired cross-reference: dependent R references ended law B; after B’s valid_to, R yields no outcome and the error states the fact (§3).

Plus unit tests on the selection rule (SelectionReason cases) and, as follow-up, a valid_to case in the Temporal conformance level (RFC-014).

Why

Benefits

  • Expresses laws that end. Twm, CEK23 and WOR become representable; the latest version is no longer eternal.
  • No silent wrong answers. A reference to an ended law surfaces honestly.
  • Small and additive. One field + an upper bound + an honest diagnostic; laws without valid_to are unchanged.

Tradeoffs

  • Authoring discipline: valid_to only for genuine ends (mirroring RFC-003’s explicit valid_from convention for implementing regulations).
  • Receipt/conformance churn: one new field, one new case.

Alternatives Considered

  • valid_to on every version (mirroring the next version’s valid_from). Rejected: on ordinary succession the law text contains no end date - it would be invented, violating groundedness. Newest-wins already ends the old version implicitly.
  • Article-level valid_from/valid_to. Rejected: repealing a single article is an ordinary amendment, already expressed by version succession. Stb. 2018, 362 art. 2 (“Artikel 76 van de Gezondheids- en welzijnswet voor dieren vervalt met ingang van 1 november 2018”) materialises as the Gwwd’s 2018-11-01 consolidation with art. 76 as a “Vervallen” placeholder; a post-repeal lookup fails with the OutputNotFound data fact, while historical dates still resolve the older version.
  • Engine infers “repealed/no successor” and reports it. Rejected: violates RFC-015 (zero domain knowledge) and is unprovable - eerbiedigende werking or an alternative grondslag may keep the law alive.
  • Fall through to an older version once the latest has ended. Rejected: repeal does not resurrect a prior version; if an earlier version still applies, that is RFC-020’s explicit date-aware case, not an implicit fallback.

Implementation Notes

  • Schema: additive law-level valid_to; new minor schema version. Literal dates only (no #-references): the engine reads valid_to as static metadata, so a reference would be silently ignored.
  • Engine: ArticleBasedLaw.valid_to; inclusive upper bound + no-fall-through in select_version_for_date; typed SelectionReason mapped to the §3 errors; malformed calculation dates rejected up front (no selection bypass); receipt records valid_to.
  • Harvester: valid_to from BWB einddatum, only for a final consolidation with a finite einddatum.

References

  • RFC-001 YAML Schema · RFC-003 IoC/temporal filter · RFC-007 Cross-Law Execution · RFC-012 Untranslatables · RFC-013 Execution Provenance · RFC-014 Conformance · RFC-015 Engine Policy
  • RFC-020: date-aware reference resolution (as_of) - eerbiedigende werking, statische verwijzing, ex nunc/ex tunc, lex mitior
  • Legislation: see the Grounding table above. Aanwijzingen voor de regelgeving: official text BWBR0005730, deep links via kcbr.nl - 6.24 (vervallen uitvoeringsregelingen; explicit mede-intrekking since 2018), 6.25 (uitgewerkte regelingen), old 243 (pre-2018: van rechtswege vervallen)
  • Glossary of Dutch Legal Terms

RegelRecht

An exploration by Bureau Architectuur of the Dutch Ministry of the Interior into the possibilities of transparent, executable legislation.

Links

GitHub repository
How it works
Stay informed
Documentation

Contact

regelrecht@minbzk.nl

Part of

Bureau Architectuur
Ministry of the Interior and Kingdom Relations