Dutch legislation follows a hierarchical delegation pattern: a formal law (wet) delegates authority to lower regulatory layers. For example, the Wet op de zorgtoeslag, article 4, delegates the determination of the standard premium (standaardpremie) to the minister via a ministerial regulation.
The engine previously supported this via a top-down resolve action: the higher law explicitly searches for matching lower regulations using legal_basis indexes and select_on criteria. This inverts the real legal relationship. In practice, a ministerial regulation opens with “In consideration of article 4 of the Wet op de zorgtoeslag” (“Gelet op artikel 4 van de Wet op de zorgtoeslag”). It registers itself as filling in a delegated term.
The top-down approach has limitations:
Implement Inversion of Control (IoC) via two new constructs in the schema:
open_terms (on article-level machine_readable)Declares abstract values that can or must be filled by implementing regulations:
implements (on article-level machine_readable)Declares that an article fills an open term from a higher-level law:
implements declarations at law load timeopen_terms, the engine looks up implementationsvalid_from <= calculation_date). Candidates with no valid version for the requested date are excluded. This ensures that e.g. the 2025 standard premium (standaardpremie) is used for a 2025 calculation, even if a 2026 version is also loadedgemeente_code). A scoped regulation only matches when the execution parameters contain the same value. Unscoped (national) regulations always matchvalid_from wins). When candidates have the same layer and date, this is ambiguous: the engine returns an error rather than silently picking one. This is a law authoring error that needs fixingdefault: execute the default actions blockrequired: true + no default: errorrequired: false + no default: skip (traced)ResolutionContext.visited), a CircularReference error is raised. The engine treats circular dependencies as a law authoring problem and does not work around them. The cycle detection key uses \0 (null byte) as separator to prevent key collisions when law IDs or article numbers contain #regulatory_layer matches the open term’s delegation_type. If a municipal ordinance (gemeentelijke verordening) tries to implement a term delegated to a minister, the engine rejects it with a clear erroropen_terms and implements arrays are validated against MAX_ARRAY_SIZE at law load time, preventing resource exhaustionTemporal filtering is needed for correctness when multiple versions of an implementing regulation are loaded (e.g., the 2024 and 2025 standard premium (standaardpremie)). The mechanism works at two levels:
Same $id, multiple versions. The resolver stores multiple versions of the same law (keyed by $id), sorted newest-first. get_law_for_date filters these by valid_from <= calculation_date and returns the most recent valid version. So for a 2025-01-15 calculation with both regeling_standaardpremie versions loaded, the 2025 version is selected. A 2026 version with valid_from: 2026-01-01 would be excluded because 2026-01-01 <= 2025-01-15 is false.
Different $ids implementing the same term. If two separate law IDs both declare implements for the same open term, the implements_index contains entries for both. At resolution time, find_implementations calls get_law_for_date for each candidate independently, so a future-dated law is excluded before it ever reaches priority resolution.
Invariant: valid_from must be present on implementing regulations. The temporal filter uses is_none_or, meaning a regulation without valid_from passes the date check unconditionally, it matches every calculation date. This is by design for laws where the effective date is unknown, but for implementing regulations it undermines temporal correctness. Law authors must ensure that every regulation with an implements declaration has an explicit valid_from date. The schema does not enforce this (since valid_from is optional for other use cases), so this is a convention that must be maintained through review.
Why not filter in the index? The implements_index is built at load time and is date-independent. It records all implementing relationships across all loaded versions. Temporal filtering happens at query time in find_implementations, which is the correct place because the calculation date is only known at execution time.
source.outputWhen multiple articles in the same law need an open term value, only one article should declare the open_terms and serve as the single point of delegation. Other articles reference it via source.output (see RFC-001, Section 9: Input Source Consolidation) without source.regulation:
This ensures the flow is: article 2 → article 4 → IoC → regulation (regeling), rather than article 2 bypassing article 4 and reaching into the regulation (regeling) directly.
Open terms can have an optional default block containing actions. This makes the article executable standalone while allowing refinement by lower regulations. The implementing regulation replaces the default entirely and must handle all cases.
This pattern is more common at lower regulatory layers (a policy rule with a reasonable default that can be overridden by implementation policy) but the mechanism works on all layers.
Defaults also serve a legal correctness role. For example, Participatiewet article 8’s open terms have default blocks with verlaging_percentage: 0 and duur_maanden: 0. Legal basis: art. 18 lid 2 says “verlaagt … overeenkomstig de verordening”, no ordinance (verordening) means no reduction (verlaging). A missing ordinance now results in full social assistance (bijstand) rather than a DelegationError.
source.delegation + select_on + legal_basis_for mechanism with a single, cleaner patternsource.delegationThe old delegation mechanism (source.delegation + select_on) forced the higher law to encode how to find its implementations:
This is backward. The Participatiewet doesn’t know which municipalities (gemeenten) have ordinances (verordeningen); it just delegates. The municipal ordinance (gemeentelijke verordening) knows which law (wet) it implements. IoC corrects this by letting the implementing regulation declare the relationship:
The engine resolves which municipality’s (gemeente) ordinance (verordening) applies; the law itself does not encode this. The engine already knows the execution scope (e.g., gemeente_code: GM0384) from its parameters. The find_implementations method uses a matches_scope helper that checks all scope fields on the candidate law against execution parameters. Currently supports gemeente_code; designed for easy extension to provincie_code etc. This eliminates select_on, legal_basis_for, and source.delegation entirely: all delegation flows through open_terms + implements.
When resolving open terms, the engine does not forward all execution parameters to implementing articles. It uses filter_parameters_for_article to only pass parameters declared in the implementing article’s execution.parameters section (principle of least privilege).
Alternative 1: Extend enables field
enables field was added to the schema in v0.3.1 but never implemented in the engineopen_terms is a cleaner separationAlternative 2: implements as top-level metadata
implements at the law level, alongside legal_basisimplements belongs at the article levelAlternative 3: Default as separate construct
fallback or default_implementation conceptdefault directly on the open term, keeping the declaration and its fallback togetherpackages/engine/src/priority.rs for lex superior/lex posterior resolutionimplements_index in RuleResolver keyed by (law_id, article, open_term_id)evaluate_article_with_service() before pre_resolve_actions()PathNodeType::OpenTermResolution, ResolveType::OpenTerm| Pattern | Use when |
|---|---|
IoC (open_terms + implements) | Any delegation: a higher law delegates a value to a lower regulation (with or without scope) |
Same-law reference (source.output) | Internal: one article needs a value produced by another article in the same law (see RFC-001 §9) |
External reference (source.regulation) | Direct reference: one law needs a specific value from another law (see RFC-001 §9) |
The old source.delegation + select_on + legal_basis_for pattern is superseded by IoC and will be phased out.
source.delegation to open_termssource.delegation, select_on, and legal_basis_for from the schemaThis RFC replaces the original RFC-003 (Delegation Pattern), which described a top-down delegation model using source.delegation + select_on + legal_basis_for. The IoC model described here inverts that relationship.
schema/v0.4.0/schema.jsoncorpus/regulation/nl/wet/wet_op_de_zorgtoeslag/2025-01-01.yaml and corpus/regulation/nl/ministeriele_regeling/regeling_standaardpremie/2025-01-01.yamlcorpus/regulation/nl/gemeentelijke_verordening/amsterdam/apv_erfgrens/2024-01-01.yaml and corpus/regulation/nl/gemeentelijke_verordening/diemen/afstemmingsverordening_participatiewet/2015-01-01.yamlAn exploration by Bureau Architectuur of the Dutch Ministry of the Interior into the possibilities of transparent, executable legislation.
Bureau Architectuur
Ministry of the Interior and Kingdom Relations