docs: add RFC-020 date-aware reference resolution (as_of)#767
Conversation
1faea2c to
0d1a2a4
Compare
…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.
7ecd905 to
e3ef970
Compare
… 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).
e3ef970 to
47599a3
Compare
47599a3 to
923ee33
Compare
…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.
… 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).
c87c6c2 to
f00727d
Compare
923ee33 to
883a4fb
Compare
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_dateis 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.
There was a problem hiding this comment.
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_ofkeeps resolving an earlier version after the target'svalid_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_datumExample 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_ofdate 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
sourceinschema/v0.5.3/schema.jsonhas"additionalProperties": false— the schema version bump claim is correct.- All referenced RFCs (001, 003, 007, 008, 013, 014, 015, 019) exist.
/reference/glossaryresolves. RFC-001 §8 ("Temporal Specifications") is the correct target for the three-axes table citation.
There was a problem hiding this comment.
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.
c9b8bf2 to
a27f897
Compare
* 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.
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.
… calculation_date reset gap
…date escape cost, YAML style, failure-path test
…ncy, Example C provenance comments
…rom failure test, absent-param semantics, Awb typo
a27f897 to
1897eb9
Compare
There was a problem hiding this comment.
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_besluitmomentBoth 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.
There was a problem hiding this comment.
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_ofis a schema-level error whenregulationis 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
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.What it proposes
as_ofon a cross-law reference / open-term resolution. Default = dynamic (followscalculation_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).as_ofmust 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).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_ofitself is new.Review round processed
MINover variables (the earlier shape putsource-objects inside operation values, which the schema does not allow).vreemdelingenwet_2000has noverblijfsvergunning_verleendoutput yet; Awb 7:11 not yet in the corpus).as_ofin memoization/cycle-detection keys;sourceisadditionalProperties: falseso 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.