Skip to content

docs: add RFC-020 date-aware reference resolution (as_of)#767

Merged
tdjager merged 8 commits into
mainfrom
docs/rfc-020-date-aware-resolution
Jun 12, 2026
Merged

docs: add RFC-020 date-aware reference resolution (as_of)#767
tdjager merged 8 commits into
mainfrom
docs/rfc-020-date-aware-resolution

Conversation

@tdjager

@tdjager tdjager commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

RFC-020 — concept 2 of the temporal-validity split. Where RFC-019 (#766) adds a law end date relative to the global calculation_date, RFC-020 lets a reference be resolved at a different date and have that date propagate down its subtree.

Stacked on #766. Base branch is docs/rfc-019-temporal-validity so RFC-020's [RFC-019] links resolve in the docs link-check. The diff here is only rfc-020.md. When #766 merges, GitHub retargets this PR to main.

What it proposes

  • as_of on a cross-law reference / open-term resolution. Default = dynamic (follows calculation_date, the hoofdregel). as_of: <date|$param> resolves the referenced law (and its subtree) at that date — modeling statische verwijzing, ex tunc, and eerbiedigende werking. Propagation: inherited down the subtree, innermost wins. The resolved date is recorded per reference in trace/receipt (RFC-013).
  • Applicable date is computed by law, not engine heuristic: the Awb (heroverweging art. 7:11) and/or the specific law's overgangsrecht. The ex-nunc reading is jurisprudential (standard arrest ABRvS Greenpeace 28-10-2020, ECLI:NL:RVS:2020:2571; Steenwijkerland ECLI:NL:RVS:2016:3388; exception-side anchor Berg en Dal ECLI:NL:RVS:2018:610) and the overgangsrecht default is onmiddellijke werking (Aanwijzing 5.61) — so those defaults live in Engine Policy, overridable per reference.
  • Multiple dates per beoordeling are codified: lex mitior (art. 5:46 lid 4 Awb; ABRvS 21-05-2025 ECLI:NL:RVS:2025:2223; ABRvS 30-08-2023 ECLI:NL:RVS:2023:3291; CBb 06-07-2021 ECLI:NL:CBB:2021:700) — proving as_of must be per-reference. With four faithfulness boundaries: mandatory & offender-favourable, punitive-only, only on gewijzigd inzicht van de wetgever, selector-in-law (MIN/IF, the engine never optimizes over dates).
  • Three temporal axes named: instrument validity (RFC-019) vs substantive scope vs applicable-date selection.

Grounding

Every claim verified against the official sources: Awb art. 7:11 lid 1 and 5:46 lid 4 quoted verbatim (wetten.overheid.nl), Aanwijzingen 3.47 / 5.60 / 5.61 / 5.64 (BWBR0005730, status as drafting guidelines noted), and all six jurisprudence anchors pinned to their ECLIs on rechtspraak.nl. Worked YAML per mechanism (ex nunc/ex tunc, statische verwijzing, lex mitior) — all examples stay within the existing operation grammar; only as_of itself is new.

Review round processed

  • Example C (lex mitior) rewritten to two inputs + MIN over variables (the earlier shape put source-objects inside operation values, which the schema does not allow).
  • ECLIs added for all six anchors; CBb date corrected to 6 juli 2021; ABRvS 30-08-2023 disambiguated to ECLI:NL:RVS:2023:3291 (a same-day lex-certa ruling exists).
  • Fourth lex-mitior boundary (gewijzigd inzicht van de wetgever, per ABRvS 21-05-2025).
  • "zoals dat luidde op " attributed to the statutory formula (Wet IB 2001 art. 10bis.1), not to Aanwijzing 3.47's own wording.
  • Example A marked illustrative (vreemdelingenwet_2000 has no verblijfsvergunning_verleend output yet; Awb 7:11 not yet in the corpus).
  • Implementation notes: as_of in memoization/cycle-detection keys; source is additionalProperties: false so a new minor schema version is needed; selection failures reported relative to the resolved date.

Status

Draft — design discussion. Implementation follows once the open points (form of the Engine Policy default; reusable lex-mitior pattern) are settled, after RFC-019 lands.

@tdjager tdjager force-pushed the docs/rfc-019-temporal-validity branch from 1faea2c to 0d1a2a4 Compare June 11, 2026 13:15
tdjager added a commit that referenced this pull request Jun 11, 2026
…f to RFC-022

Review discussion on #766 surfaced that single-article repeal (Stb. 2018,
362 art. 2 / art. 76 Gwwd) deserved its own RFC rather than a scope-note
paragraph. RFC-020 now documents the succession model: a repealed article
is a new version of the same $id (BWB consolidation -> harvester version
file -> newest-wins), no article-level valid_to exists or is needed, and a
post-repeal lookup fails with the OutputNotFound data fact.

Since RFC-020 merges with this PR, the date-aware resolution RFC (as_of,
#767) is renumbered to RFC-022 (021 is claimed by #796); all references in
RFC-019 and the engine doc comment follow.
@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from 7ecd905 to e3ef970 Compare June 11, 2026 14:12
@tdjager tdjager changed the title docs: add RFC-020 date-aware reference resolution (as_of) docs: add RFC-022 date-aware reference resolution (as_of) Jun 11, 2026
tdjager added a commit that referenced this pull request Jun 11, 2026
… entry

On reflection, article repeal contains no decision of its own: like any
amendment it is enacted by another instrument and is already fully expressed
by version succession (new consolidation -> new version of the same $id ->
newest-wins). A standalone RFC that decides nothing dilutes the register, so
rfc-020.md is removed and its substance lands where the question is actually
asked: the scope note (succession + the file-per-consolidation-date layout)
and a rejected alternative 'Article-level valid_from/valid_to' with the
Stb. 2018, 362 / art. 76 Gwwd grounding. The as_of follow-up reclaims number
RFC-020 (renumber reverted in #767).
@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from e3ef970 to 47599a3 Compare June 11, 2026 19:07
@tdjager tdjager changed the title docs: add RFC-022 date-aware reference resolution (as_of) docs: add RFC-020 date-aware reference resolution (as_of) Jun 11, 2026
@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from 47599a3 to 923ee33 Compare June 12, 2026 07:12
tdjager added a commit that referenced this pull request Jun 12, 2026
…f to RFC-022

Review discussion on #766 surfaced that single-article repeal (Stb. 2018,
362 art. 2 / art. 76 Gwwd) deserved its own RFC rather than a scope-note
paragraph. RFC-020 now documents the succession model: a repealed article
is a new version of the same $id (BWB consolidation -> harvester version
file -> newest-wins), no article-level valid_to exists or is needed, and a
post-repeal lookup fails with the OutputNotFound data fact.

Since RFC-020 merges with this PR, the date-aware resolution RFC (as_of,
#767) is renumbered to RFC-022 (021 is claimed by #796); all references in
RFC-019 and the engine doc comment follow.
tdjager added a commit that referenced this pull request Jun 12, 2026
… entry

On reflection, article repeal contains no decision of its own: like any
amendment it is enacted by another instrument and is already fully expressed
by version succession (new consolidation -> new version of the same $id ->
newest-wins). A standalone RFC that decides nothing dilutes the register, so
rfc-020.md is removed and its substance lands where the question is actually
asked: the scope note (succession + the file-per-consolidation-date layout)
and a rejected alternative 'Article-level valid_from/valid_to' with the
Stb. 2018, 362 / art. 76 Gwwd grounding. The as_of follow-up reclaims number
RFC-020 (renumber reverted in #767).
@tdjager tdjager force-pushed the docs/rfc-019-temporal-validity branch from c87c6c2 to f00727d Compare June 12, 2026 08:11
@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from 923ee33 to 883a4fb Compare June 12, 2026 08:12
@tdjager tdjager marked this pull request as ready for review June 12, 2026 08:31

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020: Date-Aware Reference Resolution (as_of)

This RFC is carefully grounded — every legal claim is tied to a verbatim statutory quote or pinned ECLI, the three temporal axes are cleanly separated, the lex-mitior boundaries faithfully track the gewijzigd-inzicht requirement (ABRvS 21-05-2025 is a notable recent anchor), and the design correctly rejects engine-level optimization in favour of a law-authored selector. The memoization cache key note (must include as_of to avoid Example C collision) and schema versioning implication are both caught and flagged. No critical or significant issues.


Minor

🟡 Example A: invalid YAML — duplicate input: key in a single code block (lines 134–147)

The ex-nunc and ex-tunc alternatives are shown in one fenced block with input: appearing twice at the top level. That is technically invalid YAML; a YAML parser would silently discard the first block or error, and readers trying to adapt the pattern get a misleading template. RFC-019's examples avoid this. These should be two separate fenced blocks with a brief label each.

# Alternative 1 — ex nunc (default): no as_of, uses calculation_date
input:
  - name: oorspronkelijk_recht
    source: { regulation: vreemdelingenwet_2000, output: verblijfsvergunning_verleend }
# Alternative 2 — ex tunc / eerbiedigende werking
input:
  - name: oorspronkelijk_recht
    source:
      regulation: vreemdelingenwet_2000
      output: verblijfsvergunning_verleend
      as_of: $besluit_datum

🟡 "Still open" is missing the calculation_date reset case

The propagation rule (§2) states "inherited down the subtree, innermost wins" with no mechanism to explicitly reset to calculation_date from within an inherited context. The only escape would be as_of: $calculation_date, but calculation_date is an engine-level value that is not currently accessible as a $-parameter in law YAML. If a referenced law is intended to always resolve its own sub-references at the current date (e.g., a dynamic rate table), there is presently no way to express that. This is likely intentional for temporal consistency, but the decision should be documented — either as an acknowledged constraint or as a third open point in the "Still open" section.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020: Date-Aware Reference Resolution (as_of)

This RFC is well-grounded and clearly structured. The three-axes separation (instrument validity / substantive scope / applicable-date selection) is the right framing, the legal citations are present and pinned to ECLIs, and the lex-mitior worked example is correctly expressed within the existing operation grammar. Two issues in the new text warrant attention before this lands.


🟠 Significant

1. Self-contradictory $calculation_date escape (Still open §, lines 200–205)

The RFC correctly states that calculation_date is not addressable as a $-parameter in law YAML. This is confirmed by the engine: Context stores it as a separate field distinct from the parameters: BTreeMap. But the same paragraph immediately proposes as_of: $calculation_date as "a schema addition":

calculation_date is an engine-level value that is not addressable as a $-parameter in law YAML … if a real corpus case needs it, an explicit escape (e.g. as_of: $calculation_date) would be a schema addition.

A schema addition alone cannot make $calculation_date resolve — the engine would also need to inject it into the parameters map, which has its own implications (reserved name collision; semantics in a nested-subtree context). As written this is misleading: it implies the escape is low-cost when it is not. Either say "no escape is currently possible — this is a deliberate constraint" (honest about the tradeoff already named), or explicitly flag it as requiring an engine change with the attendant design questions.

2. as_of propagation into IoC (open_terms/implements) not specified (§5, lines 109–113)

Section 5 addresses how as_of interacts with hooks and overrides, but the IoC pattern (RFC-003 open_terms/implements) is a third cross-law resolution path and is not mentioned. When law A references law B with as_of: $besluit_datum, and law B has open_terms filled by implementing regulation C, should C be selected at $besluit_datum (consistent with propagation semantics) or at calculation_date (current IoC behaviour)? Both answers are defensible but have different legal implications:

  • Propagating into IoC: a statische verwijzing to an older version of B uses the implementing regulations that were in force on that date — legally correct but non-trivial to implement.
  • Not propagating: the implementing regulation is always selected at calculation_date — simpler, but an undocumented exception to the "inherited down the subtree" rule.

This should be decided and stated explicitly, not left implicit.


🟡 Minor

3. Inconsistent YAML style in worked examples (lines 130, 178)

Example A (default case) and Example C use inline flow style (source: { regulation: wet_x, output: ..., as_of: ... }); the ex-tunc variant of Example A and Example B use block style. All four examples should use the same style; block style matches the existing corpus convention.

4. Missing test case: as_of past the target's own valid_to (Testing §, lines 213–214)

Test case 2 covers the success path: as_of before the target's valid_to resolves an old version even after calculation_date has moved past valid_to. The failure path is not tested: as_of: '2027-01-01' on a law with valid_to: '2026-12-31' should yield a SelectionReason error, not a silent miss. Implementation notes mention reporting selection failures relative to the resolved date, but a concrete test case should anchor this boundary.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020 - Date-Aware Reference Resolution (as_of)

Scope: One new file, docs/src/content/rfcs/rfc-020.md (292 added lines, 0 deleted).

Summary

The RFC is carefully grounded. Every legal claim cites a statute, Aanwijzing, or pinned ECLI. The worked examples use valid schema grammar -- MIN is confirmed as a schema-level arithmeticOperation enum value and is implemented in packages/engine/src/operations.rs. The source.additionalProperties: false constraint and the need for a schema version bump are correctly identified. The core design decision (per-reference as_of, propagated down the subtree, innermost wins) correctly solves the problem RFC-019's single global date cannot.


Findings

Minor -- Test case 2 wording collides with test case 5

(Testing section, second bullet)

statische verwijzing: a fixed-date as_of keeps resolving an earlier version after the target's valid_to

Read in isolation, "after the target's valid_to" suggests the as_of date itself is past valid_to -- which is exactly test case 5 (error path). The intended meaning is that the current calculation_date is past valid_to while the frozen as_of is still within the validity window. Since the distinction between "as_of past valid_to -> error" and "calculation_date past valid_to, as_of within range -> success" is the central behaviour of the feature, the test description should be unambiguous.

Suggested rewording: "a fixed-date as_of resolves correctly when calculation_date is past the target's valid_to but as_of is within the validity window".


Minor -- RFC-013 missing from Depends on

**Depends on:** RFC-019 (Law End Dates), RFC-007 (Cross-Law Execution Model), RFC-008 (Awb Administrative Procedures)

Section 2 explicitly extends RFC-013's trace/receipt format: "the resolved date and selected version go into trace and receipt (RFC-013)". RFC-019, which extends the same receipt in the same way, consistently lists RFC-013 in its own Depends on. For implementer discoverability, RFC-013 should appear here too.


Minor -- Example C parameter provenance unannotated

as_of: $overtreding_datum
...
as_of: $beschikking_datum

Example A annotates $besluit_datum with "RFC-008 stage input; propagates through the referenced subtree". Example C uses two analogous stage inputs without an equivalent comment. Since overtreding_datum does not appear in RFC-008's existing bezwaartermijn example, a reader implementing the lex-mitior case will need to know where these parameters originate.


Not flagged

  • Legal grounding -- all six ECLI anchors and statutory quotes (Awb art. 7:11 lid 1, art. 5:46 lid 4) correctly cited; Aanwijzingen 3.47/5.60/5.61/5.64 attribution accurate.
  • IoC/implements propagation -- selecting implementing regulations at the propagated as_of date is legally correct and internally consistent with the subtree invariant.
  • Memoization/cycle-detection key requirement -- correctly identified.
  • "No reset to calculation_date" constraint -- explicitly flagged in Still Open with engine-change implications; adequate for draft stage.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020 Review

Single file added: docs/src/content/rfcs/rfc-020.md. All findings are scoped to lines added in this diff.


Legal faithfulness

The legal grounding is solid. All cited articles are quoted correctly: Awb art. 5:46 lid 4 ("Artikel 1, tweede lid, van het Wetboek van Strafrecht is van overeenkomstige toepassing") is accurate; Aanwijzing 3.47 on statische/dynamische verwijzing and Aanwijzing 5.61 on onmiddellijke werking as the default are correctly characterised. The Greenpeace standard arrest (ECLI:NL:RVS:2020:2571) as the ex-nunc anchor and ABRvS 21-05-2025 (ECLI:NL:RVS:2025:2223) for the gewijzigd-inzicht boundary are appropriate; noting that Aanwijzingen are a besluit van de Minister-President (not substantive law) is the right calibration. The three-axis table (instrument validity / substantive scope / applicable-date selection) is a correct and useful clarification.


Minor issues

🟡 $beschikking_datum is not a defined RFC-008 stage input

Example C introduces $beschikking_datum and $overtreding_datum as $-parameters, citing them as "RFC-008 stage inputs, like $besluit_datum in Example A." RFC-008's lifecycle definitions use besluit_datum (at the BEHANDELING stage output), not beschikking_datum. A boete procedure would need its own lifecycle with overtreding_datum as a stage input — that lifecycle isn't yet modelled in RFC-008. Either use the established $besluit_datum naming or explicitly note that the boete lifecycle adds new stage inputs (and cross-reference where those will be defined).


🟡 Failure path testing: as_of before valid_from is missing

Test case 5 covers as_of past valid_to (law has ended). The symmetric case — as_of before valid_from (law hadn't entered force yet) — is equally likely in practice (e.g. resolving a rate table with an overtreding_datum predating the law). Add a sixth test case: as_of before the target law's valid_from should yield a SelectionReason error in the same way.


🟡 Unaddressed failure mode: as_of: $param when param is absent

§2 allows as_of: $param, but the "Still open" section and Implementation Notes are silent on the runtime case where the parameter is not supplied. Should the engine fall back to calculation_date (lenient) or treat it as a resolution error (strict)? Given the engine's zero-domain-knowledge policy (RFC-015), a silent fallback seems wrong — a missing required date is a caller error. Worth adding to the open items.


🟡 Typo: "corpus AWB" should be "corpus Awb"

In the Example A footnote: "Awb 7:11 is not yet in the corpus AWB" — all other occurrences in the document use "Awb". Trivial but inconsistent.


Schema claim verified

The source object in schema/v0.5.3/schema.json (line 571) does have "additionalProperties": false, confirming that adding as_of requires a new minor schema version as stated.

RFC cross-references verified

All referenced RFCs (001, 003, 007, 008, 013, 014, 015, 019) exist in docs/src/content/rfcs/. The /reference/glossary link resolves to docs/src/content/docs/reference/glossary.md. RFC-001 §8 ("Temporal Specifications") is the correct target.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020 Review

Single file added: docs/src/content/rfcs/rfc-020.md. All findings are scoped to lines added in this diff.


Legal faithfulness

The legal grounding is solid. All cited articles are quoted correctly: Awb art. 5:46 lid 4 ("Artikel 1, tweede lid, van het Wetboek van Strafrecht is van overeenkomstige toepassing") is accurate; Aanwijzing 3.47 on statische/dynamische verwijzing and Aanwijzing 5.61 on onmiddellijke werking as the default are correctly characterised. The Greenpeace standard arrest (ECLI:NL:RVS:2020:2571) as the ex-nunc anchor and ABRvS 21-05-2025 (ECLI:NL:RVS:2025:2223) for the gewijzigd-inzicht boundary are appropriate. The three-axis table (instrument validity / substantive scope / applicable-date selection) is a correct and useful clarification.


Minor issues

🟡 $beschikking_datum is not a defined RFC-008 stage input

Example C introduces $beschikking_datum and $overtreding_datum as parameters, citing them as "RFC-008 stage inputs, like $besluit_datum in Example A." RFC-008's lifecycle definitions use besluit_datum (at the BEHANDELING stage output), not beschikking_datum. A boete procedure would need its own lifecycle with overtreding_datum as a stage input — that lifecycle is not yet modelled in RFC-008. Either use the established $besluit_datum naming or explicitly note that the boete lifecycle adds new stage inputs and cross-reference where those will be defined.

🟡 Failure path testing: as_of before valid_from is missing

Test case 5 covers as_of past valid_to (law has ended). The symmetric case — as_of before valid_from (law not yet in force) — is equally likely in practice (e.g. resolving a rate table with an overtreding_datum predating the law). Add a sixth test case: as_of before the target law's valid_from should yield a SelectionReason error in the same way.

🟡 Unaddressed failure mode: as_of: $param when param is absent

§2 allows as_of: $param, but the "Still open" section and Implementation Notes are silent on the runtime case where the parameter is not supplied. Should the engine fall back to calculation_date (lenient) or treat it as a resolution error (strict)? Given the zero-domain-knowledge policy (RFC-015), a silent fallback seems wrong — a missing required date is a caller error. Worth adding to the open items.

🟡 Typo: "corpus AWB" should be "corpus Awb"

In the Example A footnote: "Awb 7:11 is not yet in the corpus AWB" — all other occurrences in the document use "Awb".


Verified

  • source in schema/v0.5.3/schema.json has "additionalProperties": false — the schema version bump claim is correct.
  • All referenced RFCs (001, 003, 007, 008, 013, 014, 015, 019) exist. /reference/glossary resolves. RFC-001 §8 ("Temporal Specifications") is the correct target for the three-axes table citation.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020 Review

Single file added: `docs/src/content/rfcs/rfc-020.md`. All findings are scoped to lines added in this diff.


Legal faithfulness

The legal grounding is solid. Awb art. 5:46 lid 4 is quoted correctly; Aanwijzing 3.47 on statische/dynamische verwijzing and Aanwijzing 5.61 on onmiddellijke werking as the default are correctly characterised. The Greenpeace standard arrest (ECLI:NL:RVS:2020:2571) as the ex-nunc anchor and ABRvS 21-05-2025 (ECLI:NL:RVS:2025:2223) for the gewijzigd-inzicht boundary are appropriate. The three-axis table (instrument validity / substantive scope / applicable-date selection) is a correct and useful distinction.


🟡 Minor: `$beschikking_datum` is not a defined RFC-008 stage input

Example C introduces `$beschikking_datum` and `$overtreding_datum`, citing them as "RFC-008 stage inputs, like `$besluit_datum` in Example A." RFC-008's lifecycle definitions use `besluit_datum` at the BEHANDELING stage, not `beschikking_datum`. A boete procedure needs its own lifecycle with `overtreding_datum` as a stage input — that lifecycle isn't modelled in RFC-008 yet. Either use the established `$besluit_datum` or explicitly note that a boete lifecycle adds these new stage inputs (and where they'll be defined).


🟡 Minor: failure path testing missing the `as_of` before `valid_from` case

Test case 5 covers `as_of` past `valid_to` (law ended). The symmetric case — `as_of` before `valid_from` (law not yet in force) — is equally likely in practice (e.g. an `overtreding_datum` that predates the law). A sixth test case would complete the symmetry.


🟡 Minor: unaddressed failure mode when `as_of: $param` and param is absent

§2 allows `as_of: $param` but the "Still open" section and Implementation Notes are silent on the runtime case where the parameter isn't supplied. Should the engine fall back to `calculation_date` or error? Given RFC-015's zero-domain-knowledge policy a silent fallback seems wrong — worth adding to the open items.


🟡 Minor: typo "corpus AWB" should be "corpus Awb"

In the Example A footnote: "Awb 7:11 is not yet in the corpus AWB" — every other occurrence in this document uses "Awb".


Verified

  • `source` in `schema/v0.5.3/schema.json` has `"additionalProperties": false` — the schema-version-bump claim is correct.
  • All referenced RFCs (001, 003, 007, 008, 013, 014, 015, 019) exist in `docs/src/content/rfcs/`. `/reference/glossary` resolves. RFC-001 §8 ("Temporal Specifications") is the correct section for the three-axes table citation.

@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from c9b8bf2 to a27f897 Compare June 12, 2026 09:07
tdjager added a commit that referenced this pull request Jun 12, 2026
* docs(rfc): add RFC-019 temporal validity and date-aware reference resolution

Draft RFC for law end dates (valid_to) and date-aware cross-law reference
resolution. Adds the instrument end date and selection upper bound, an as_of
that lets a reference resolve at a date differing from the global
calculation_date and propagate down its subtree (ex tunc / statische
verwijzing / eerbiedigende werking), and conservative diagnostics for ended
references. Builds on RFC-001/003/007/008/010/012/013/015; every claim is
grounded in actual law (Aanwijzingen, Awb 7:11, Twm art. VIII, CEK23, WOR)
with a worked YAML example per mechanism.

* docs: tighten RFC-019 — remove duplication, fix valid_to consistency

Remove repeated inclusive-bound explanation, the duplicated axis-separation
note, and the duplicate Twm YAML block; trim wordy phrasing. Fix §1 which
still showed valid_to 2027-01-01 (now 2026-12-31, consistent with the
inclusive-bound conversion).

* docs(rfc-019): resolve open questions with legal grounding

Investigate the open questions against law and jurisprudence and fold the
findings back in:
- applicable-date rule is layered (art. 7:11 Awb + jurisprudential ex-nunc
  reading; overgangsrecht default = onmiddellijke werking, Aanwijzing 5.61,
  with exceptions in the specific law per Aanwijzing 5.60) -> jurisprudential
  defaults belong in Engine Policy, not engine code
- multiple dates per beoordeling are codified: art. 5:46 lid 4 Awb lex mitior
  (ABRvS 21-05-2025, 30-08-2023; CBb 07-07-2021) -> confirms as_of must be
  per-reference; add a lex-mitior MIN-over-two-as_of example
- as_of propagation, instrument-vs-substantive scope, and hooks/overrides
  resolved as design decisions

* docs(rfc-019): sharpen lex mitior — mandatory, offender-favourable, punitive-only

Clarify that art. 5:46 lid 4 Awb lex mitior is mandatory (not a free choice
and not favourable-to-administration), means favourable to the offender,
applies only to punitive sanctions (not a general best-of-dates rule), and
that the favourable-selector lives in the law (MIN for a fine, IF if the
conduct is no longer punishable) — the engine never optimizes over dates.

* docs(rfc-019): cite primary source for Aanwijzingen + clarify their status

Add the official consolidated text (BWBR0005730) as the primary source for the
Aanwijzingen voor de regelgeving, and clarify that they are a besluit van de
Minister-President (drafting guidelines binding on the wetgever, not
substantive law) — substantive temporal effects rest on the laws and
jurisprudence. kcbr.nl deep links kept as the official navigation portal.

* feat(engine): law end dates (valid_to) — RFC-019 concept 1

Schema v0.5.3 adds an optional law-level valid_to (instrument end date), set
only when a law genuinely ends with no successor. Engine version selection
gains an inclusive upper bound: past valid_to a version is no longer in force
and the engine does NOT fall through to an older version (RFC-019 §2). A typed
SelectionReason (NotFound / NotYetInForce / EndedOn) backs honest diagnostics
(§3) without asserting 'geen grondslag'. The harvester derives valid_to from
BWB einddatum only for the final consolidation with a finite einddatum
(manifest::resolve_valid_to), wired through the pipeline, and the writer emits
it. Engine version bumped 0.2.0 -> 0.3.0.

Scope: concept 1 of the split. Date-aware resolution (as_of, ex nunc/ex tunc,
statische verwijzing, lex mitior) is RFC-020.

* fix(harvester): add valid_to to LawMetadata doctest example

* docs(rfc-019): 'routinely' -> 'can' end without a successor

Drop the subjective frequency claim; a single instance is enough to motivate
the feature.

* feat(corpus): add CEK23 prijsplafond regeling as executable valid_to example

Harvest Subsidieregeling bekostiging plafond energietarieven kleinverbruikers
2023 (BWBR0047628) and interpret art. 2.2 (plafondtarief) and art. 2.3
(volumeplafond) as machine_readable — grounded, reverse-validated constants the
articles literally vaststellen. valid_to '2026-12-31' from art. 8.1 lid 2
('vervalt met ingang van 1 januari 2027'). Demonstrates RFC-019 end-to-end:
evaluate at 2023-06-01 returns the caps; at 2027-06-01 the law is not in force.

The consumer korting (verbruik x (contracttarief - plafondtarief)) is NOT in
the regeling text and is deliberately not modeled. RFC-019 worked example A
updated to match the real law. Layer corrected to MINISTERIELE_REGELING.

* docs(rfc-019): reframe valid_to around termination; ground end date in art. 8.1

- Reframe semantics: valid_to marks that the law is *terminated* (vervalt /
  ingetrokken), not 'ends without a successor'. A law repealed and replaced by a
  different $id is still terminated and still carries valid_to.
- Two provenance modes: intrinsic (horizonbepaling in the law's own text) vs
  extrinsic (terminated by another instrument -> date derived from that
  instrument, analogous to RFC-007 overrides; future work).
- Boundary note: validity is static version-selection metadata today; deriving
  it (extrinsic, or 'bij KB te bepalen tijdstip') makes in-force status an
  executed property -- a deliberate boundary RFC-019 does not cross.
- CEK23: art. 8.1 now outputs the grounded vervaldatum ('2027-01-01', verbatim
  lid 2); top-level valid_to is the denormalised inclusive last day (D-1).
- Align schema description + engine/harvester doc comments with the framing.

* docs(rfc-019): fix Twm example, ground the valid_to-per-version rejection, add KB example

- The syntax/example snippets used the Twm with a literal valid_to '2022-05-18'
  — but that date is not in the law: art. VIII lets a KB extend the geldingsduur
  (or parts) and the end followed from the rejected 5th-extension goedkeuringswet.
  Replace the §1 syntax snippet with the clean intrinsic CEK23 case, and reframe
  example B as the hard case: dynamic, KB-determined, per-provision end that a
  static law-level valid_to cannot capture (the §1 boundary).
- Reframe the 'valid_to on every version' rejection around faithfulness: a
  superseded version has no end date in the text, so adding one would be invented
  — not 'redundant with newest-wins'.
- Add concrete extrinsic KB examples (Twm verlengings-KBs; Wet open overheid
  art. 10.2f 'vervalt op een bij KB te bepalen tijdstip').

* docs(rfc-019): replace em dashes with hyphens

* feat(engine): wire honest expired-law diagnostics into the cross-law path (RFC-019 §3)

Review findings on #766:

- SelectionReason was only used by its own unit tests; an ended law still
  surfaced as 'Law not found' on the cross-law path - exactly the misleading
  diagnostic §3 prohibits. The three law lookups in service.rs now map the
  typed reason to honest errors: LawNotYetInForce / LawEnded carry the
  reference date and valid_to ('no version of law X in force on D; last in
  force until E'), never a legal verdict.
- EndedOn now carries a NaiveDate instead of a String.
- ResolutionContext rejects malformed calculation dates up front: an
  unparseable date silently became None and bypassed valid_from/valid_to
  version selection entirely.
- The receipt's loaded_regulations records valid_to next to valid_from (§4).
- schema v0.5.3: valid_to pattern is date-only. It had inherited valid_from's
  '#.+' alternative, but the engine reads valid_to as static metadata and
  silently ignores unparseable values - a schema-valid '#ref' would make a
  law never end. Derived end dates are explicitly future work (§1 boundary).
- BDD scenarios promised by the RFC's Testing section: features/einddatum.feature
  with test laws test_einddatum / test_einddatum_afnemer (inclusive upper
  bound, no resolution after the end date, expired cross-reference states
  the data fact).

* chore: point annotation tooling at schema v0.5.3

The PR adds schema/v0.5.3 (including an unchanged annotation-schema.json copy)
but the annotation validators and the editor's annotation writers still
referenced v0.5.2, leaving the new copy unused. Bump the embedded schema
includes and the $schema URLs to v0.5.3.

* docs(rfc-019): correct legal facts against the official sources

Every claim re-verified against wetten.overheid.nl, Staatsblad and the
Eerste Kamer dossiers:

- Twm: measures vervielen per 20 mei 2022 (last day in force 19 mei), not
  'on 19 mei'; the rejected goedkeuringswet is dossier 36.042; art. VIII
  lid 3 allows extension by *at most* three months ('steeds ten hoogste');
  the hypothetical literal valid_to is 2022-05-19.
- Tijdelijke regeling (BWBR0044416): vervallen per the *same* date as the
  Twm (20 mei 2022), not one day later.
- Aanwijzingen: only the old Ar 243 (pre-2018) stated 'van rechtswege
  vervallen'; the current 6.24 prescribes explicit mede-intrekking
  precisely because that doctrine caused unclarity.
- Woo art. 10.2f lid 1 quoted verbatim ('Hoofdstuk 6 vervalt bij koninklijk
  besluit'), replacing a paraphrase presented as a quote.
- CEK23 art. 8.1 lid 2 quoted in full, including the staartzin ('blijft van
  toepassing op subsidies die voor die datum zijn verstrekt') - eerbiedigende
  werking inside the worked example itself, now flagged as the canonical
  real-corpus bridge case for RFC-020's as_of.
- Faithfulness note corrected: the supplier subsidy formula *is* in
  art. 3.1-3.5 (volume-capped verbruik x (contracttarief - plafondtarief));
  what the regeling never computes is a consumer-facing korting.
- source.regulation lives in RFC-001 §9, not §6.
- CEK23 YAML: plafondtarief outputs typed number (euro-per-unit rates with
  five decimals; the corpus amount convention is whole eurocents and
  type_spec has no tariff units yet - schema gap noted in the RFC).
- Testing/§3 text updated to match the implemented behaviour (typed errors,
  features/einddatum.feature; evaluate at 2027-06-01 now fails with the
  data fact instead of returning no outputs).

* docs(rfc-019): add a real Aanwijzing 6.24 example (Stb. 2018, 362)

The verweesde-regelingen paragraph cited Aanwijzing 6.24 without a law that
actually follows it. Stb. 2018, 362 is the textbook case: one besluit drops
the delegation basis (art. 76 Gwwd) and explicitly repeals the Fokkerijbesluit
and Fokkerijregeling resting on it, with a nota van toelichting that cites
aanwijzing 6.24 verbatim. Quotes verified against officielebekendmakingen.nl.

* docs(rfc-019): article repeal is succession; terminates binds at harvest/load time, not execution time

Two clarifications from review discussion:

- Scope note: repealing a single article (Stb. 2018, 362 art. 2 / art. 76
  Gwwd) is an ordinary amendment handled by version succession (new
  consolidation, new version of the same $id, newest-wins) - no
  article-level valid_to exists or is needed.
- Extrinsic mode: the unilateral declaration shape matches
  overrides/implements, but the binding moment cannot - in-force status must
  be known before execution, so a terminates-effect binds at harvest time
  today (BWB einddatum -> literal valid_to) and at load/index time in the
  future mechanism; moving end dates (Twm verlengings-KBs) favour load-time
  derivation over harvest-time literals.

* docs(rfc-019): separate the two halves of the Stb. 2018, 362 example

Art. 2 (article repeal) is ordinary succession, not this RFC's problem;
art. 3 (Fokkerijbesluit + Fokkerijregeling repealed as whole instruments)
is exactly the valid_to case. Make that explicit so the example is not read
as an article-level valid_to.

* docs: add RFC-020 article repeal as version succession; renumber as_of to RFC-022

Review discussion on #766 surfaced that single-article repeal (Stb. 2018,
362 art. 2 / art. 76 Gwwd) deserved its own RFC rather than a scope-note
paragraph. RFC-020 now documents the succession model: a repealed article
is a new version of the same $id (BWB consolidation -> harvester version
file -> newest-wins), no article-level valid_to exists or is needed, and a
post-repeal lookup fails with the OutputNotFound data fact.

Since RFC-020 merges with this PR, the date-aware resolution RFC (as_of,
#767) is renumbered to RFC-022 (021 is claimed by #796); all references in
RFC-019 and the engine doc comment follow.

* docs(rfc-020): precise grounding of the file-per-version convention

The one-file-per-consolidation-date layout was only visible in RFC-003/018
examples, never decided; RFC-020 now states it makes that explicit instead
of mis-citing RFC-001 §5 (which grounds valid_from, not the layout). Also
link the Stb. 2018, 362 art. 2 half in RFC-019 directly to RFC-020.

* docs(rfc-019): fold the article-repeal RFC back in as an Alternatives entry

On reflection, article repeal contains no decision of its own: like any
amendment it is enacted by another instrument and is already fully expressed
by version succession (new consolidation -> new version of the same $id ->
newest-wins). A standalone RFC that decides nothing dilutes the register, so
rfc-020.md is removed and its substance lands where the question is actually
asked: the scope note (succession + the file-per-consolidation-date layout)
and a rejected alternative 'Article-level valid_from/valid_to' with the
Stb. 2018, 362 / art. 76 Gwwd grounding. The as_of follow-up reclaims number
RFC-020 (renumber reverted in #767).

* docs(rfc-019): tighten - problem/solution/examples, bullets over prose

Same verified facts, ~35% shorter. Restructured per review feedback:
problem statement and scope as bullet lists, a Grounding table naming the
real legislation the RFC is built on (CEK23, WOR, Twm/Trm, Stb. 2018, 362,
Woo 10.2f, Aanwijzingen, BWB einddatum), the decision sections as bullets,
worked examples kept with their YAML, and the per-law reference links
deduplicated into the table.

* fix: address claude-review findings on valid_to robustness

- schema v0.5.3: $id and title still self-reported as v0.5.2 in both
  schema.json and annotation-schema.json (copy-paste from the v0.5.2 dir);
  external JSON Schema tooling keys on $id, so both bumped to v0.5.3.
- engine: a format-valid but calendar-invalid valid_to ('2023-02-30')
  passed the schema regex and parse_date(..).ok() then silently skipped the
  expiry check - the law would stay in force forever. load_law now rejects
  an unparseable valid_to with a LoadError (valid_from keeps its lenient
  '#'-reference convention). Unit test added.
- resolver: documented that the None arm of get_law_for_date_reported
  deliberately skips the valid_to window (display/listing semantics) and
  that execution paths always pass Some(date).

* fix: carry RFC-019 §3 data facts through the external error boundary

Second claude-review round:

- ExternalError collapsed LawNotYetInForce/LawEnded into a single
  LawNotInForce(law_id), dropping the reference date and 'last in force
  until' fact for WASM/API consumers. The validity window is public legal
  data, not internal detail - the external variants now mirror the internal
  ones with the same honest messages.
- harvester resolve_valid_to: comment documenting that the lexicographic
  date comparison is sound because BWB dates are ISO 8601.
- The eurocent/tariff-unit gap flagged on CEK23 art. 2.2 is already
  documented in the YAML comments and the RFC (acknowledged by the review
  itself); no change.
Base automatically changed from docs/rfc-019-temporal-validity to main June 12, 2026 09:11
tdjager added 6 commits June 12, 2026 09:13
Reclaims number 020 now that the article-repeal RFC is folded back into
RFC-019 as an Alternatives entry (#766). Review findings processed:

- Example C (lex mitior) rewritten to the existing operation grammar: two
  inputs with their own as_of + MIN over variables. The previous shape put
  source-objects inside operation values, which the schema does not allow -
  only as_of itself is new now.
- Jurisprudence precision: ECLIs added for all six anchors; the CBb lex
  mitior date corrected to 6 juli 2021 (ECLI:NL:CBB:2021:700, not 7 juli -
  secondary-source error); ABRvS 30-08-2023 pinned to ECLI:NL:RVS:2023:3291
  (a same-day lex-certa ruling 2023:3188 made the bare date ambiguous);
  Greenpeace marked as the standard arrest and Berg en Dal as the
  exception-side anchor.
- Fourth lex-mitior boundary added: the change must rest on the
  legislature's gewijzigd inzicht (ABRvS 21-05-2025).
- 'zoals dat luidde op <datum>' attributed to the statutory formula (Wet IB
  2001 art. 10bis.1); Aanwijzing 3.47's own wording quoted separately.
- Example A marked illustrative (vreemdelingenwet_2000 has no
  verblijfsvergunning_verleend output yet; Awb 7:11 not yet in corpus).
- Per-reference resolved date recorded in trace/receipt (RFC-013), as_of in
  memoization and cycle-detection keys, and the additionalProperties:false
  schema-bump note added to Implementation Notes.
Same structure pass as RFC-019: problem and scope as bullet lists, a
Grounding table naming the law and jurisprudence the RFC is built on (Awb
7:11 and 5:46 lid 4, the six ECLI-pinned arresten, Aanwijzingen
3.47/5.60/5.61/5.64, Wet IB 2001 static-reference formula, the modeled
bezwaartermijn chain), decision sections as bullets, worked YAML examples
kept, the redundant 'Findings on the open questions' recap dropped (its
content lives in the decision sections), and per-source links deduplicated
into the table. All verified facts and verbatim quotes retained.
…date escape cost, YAML style, failure-path test
…rom failure test, absent-param semantics, Awb typo
@tdjager tdjager force-pushed the docs/rfc-020-date-aware-resolution branch from a27f897 to 1897eb9 Compare June 12, 2026 09:13
@github-actions github-actions Bot deleted a comment from claude Bot Jun 12, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020: Date-Aware Reference Resolution — review

One 🟡 minor finding. The legal grounding, section cross-references (RFC-019 §3/§4, RFC-007 §3), and worked-example logic are all sound.


Legal faithfulness

Citations verified:

  • Awb art. 7:11 lid 1 and 5:46 lid 4 (BWBR0005537) ✓
  • Art. 1 lid 2 Sr (BWBR0001854) ✓
  • Aanwijzingen 3.47 / 5.60 / 5.61 / 5.64 (BWBR0005730) ✓
  • All six ECLI anchors consistent with the body text ✓

The four lex-mitior boundaries (mandatory, punitive-only, gewijzigd inzicht, selector-in-law) are correct and important for keeping the engine free of domain knowledge.

Schema / YAML examples

🟡 Minor — Example C values uses flow-sequence style inconsistent with corpus convention

      values: [$boete_overtredingsmoment, $boete_besluitmoment]

Existing corpus files (e.g. wet_op_de_zorgtoeslag/2025-01-01.yaml) always use block-sequence style:

      values:
        - $boete_overtredingsmoment
        - $boete_besluitmoment

Both are valid YAML and schema-compliant, but the flow form is inconsistent with every other values array in the corpus and will likely be copy-pasted during implementation. Worth normalising before this ships as a reference example.

Cross-law reference integrity

The source.additionalProperties: false constraint is correctly identified as requiring a schema minor bump. No new source.regulation slugs are introduced (all examples are illustrative with noted corpus gaps).

Testing section

The six listed BDD test cases cover the key paths well (ex nunc/ex tunc, statische verwijzing, lex mitior, propagation, both failure directions). One gap: there is no test case for IoC propagation with as_of — the case stated in §5 where an outer as_of selects implementing regulations at the propagated date rather than calculation_date. Given that this is the most novel part of §5 and the one most likely to be implemented incorrectly, it warrants its own scenario alongside the existing six.

Still Open

The open points are well-scoped. The as_of: $param absent at runtime question should resolve to a strict error (as the RFC leans toward) rather than a silent fallback to calculation_date — the RFC's reasoning here (RFC-015 zero-domain-knowledge, mirroring missing required inputs) is correct.

@github-actions github-actions Bot deleted a comment from claude Bot Jun 12, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC-020: Date-Aware Reference Resolution (as_of) — Review

Scope: This PR adds only docs/src/content/rfcs/rfc-020.md. The applicable review dimensions are legal faithfulness of the proposed design, coherence with existing RFCs, and correctness of the worked examples.

RFC-019 is confirmed merged (file present in repo); the diff is rfc-020 only. The MIN operation used in Example C is valid per the current schema (arithmeticOperation.operation enum includes MIN/MAX). The source.additionalProperties: false constraint is correctly identified as requiring a minor schema bump.


Legal Faithfulness

No issues. Awb 7:11 lid 1 text is quoted accurately. Art. 5:46 lid 4 Awb / art. 1 lid 2 Sr grounding for lex mitior is correct. ECLI format is valid throughout (ECLI:NL:RVS:..., ECLI:NL:CBB:...). Attribution of onmiddellijke werking to drafting convention (not statutory law) is correctly hedged. The four lex-mitior faithfulness boundaries in §4 are well-reasoned.


Issues

🟡 Minor — as_of scope not conditioned on regulation being present

The source object has regulation as optional (schema: "Omit for external data sources that must be resolved outside the YAML"). as_of on a source without regulation has no defined semantics — there is no law to select a version of. The RFC and Implementation Notes do not clarify whether:

  • as_of is a schema-level error when regulation is absent, or
  • it is silently ignored.

Given additionalProperties: false, the schema bump is the right place to enforce this, e.g. via if: {required: ["regulation"]}, then: {properties: {as_of: {...}}}. Worth specifying before implementation.


🟡 Minor — as_of type union left implicit in Implementation Notes

The notes say "literal date or $-parameter" without specifying the JSON Schema encoding. A plain {type: string} would admit malformed dates ("2000-99-99") and misspelled params ("besluit_datum" without $). The existing pattern for this union is oneOf: [variableReference, {type: string, format: date}]variableReference already carries the ^\$[A-Za-z_]... pattern. Naming this in the Implementation Notes avoids implementation ambiguity and ensures corpus validation stays strict.


🟡 Minor — Testing item 6 conflates two different SelectionReason subtypes

Item 5: as_of past valid_to → maps to RFC-019 §3's EndedOn(date).
Item 6: as_of before valid_from → maps to RFC-019 §3's NotYetInForce.

Item 6 currently says "yields the same SelectionReason error" — but these are different subtypes with different messages ("nog niet in werking" vs "laatst geldig t/m <valid_to>"). Should read "yields a SelectionReason error (NotYetInForce)" to stay consistent with RFC-019 §3's typed-reason taxonomy.


🟡 Minor — Cycle detection: regressive-date chains not addressed

The RFC correctly says include the resolved as_of date in cycle-detection keys. This catches same-date self-references. It does not catch a chain where law A (at D) references law A (at D−1 via inner as_of: $earlier_param) which in turn references law A (at D−2), etc. — each call has a unique key. In practice, corpus YAMLs are static and cannot self-generate older as_of values, but the RFC should note that RFC-007's existing depth limit is the backstop for this case, or explicitly state this as a deliberate out-of-scope constraint.


Still-Open items

The four open items are honestly disclosed and appropriately deferred. The lean toward "strict resolution error" for absent as_of: $param (last item) is the right call; marking it as decided rather than likely would strengthen the RFC without requiring implementation work.


No critical or significant issues. The legal grounding is thorough, the design composes cleanly with RFC-019, and the YAML examples stay within the existing operation grammar.

…nd type union, SelectionReason subtypes, regressive-chain backstop
@tdjager tdjager merged commit d3413f4 into main Jun 12, 2026
29 checks passed
@tdjager tdjager deleted the docs/rfc-020-date-aware-resolution branch June 12, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant