RFC-015: Engine Policy
Status: Proposed Date: 2026-04-04 Authors: Eelco Hotting
Context
The engine's founding principle is zero built-in domain knowledge (RFC-007): all legal and domain knowledge comes from law YAML files and parameters. The engine provides pure operations (arithmetic, comparison, date math) and a resolution framework. It does not know about Easter, King's Day, public holidays, tax rates, or government structure.
The engine does need certain knowledge to function that no specific law declares: the regulatory layer hierarchy (constitutional doctrine), territorial scoping dimensions (gemeente_code, provincie_code), type coercion rules (eurocent rounding), and delegation validation rules. This is the meta-knowledge about how to process law, distinct from the law itself. It belongs in configuration, not in the corpus alongside real law, and not compiled into the engine binary.
Decision
1. Engine Policy as structured configuration
The engine reads its domain knowledge from an Engine Policy file at startup: a YAML configuration file that defines the meta-rules for law execution.
The Engine Policy is engine configuration expressed in structured YAML, separate from the corpus.
# engine-policy.yaml
$schema: https://raw.githubusercontent.com/MinBZK/regelrecht/refs/tags/engine-policy-v1/schema/engine-policy/v1/schema.json
id: regelrecht-default
name: RegelRecht National Default Policy
version: '1.0'
# Regulatory layer hierarchy for lex superior resolution.
# Lower rank = higher authority. Used when multiple regulations
# implement the same open term.
regulatory_layers:
- layer: VERDRAG
rank: 0
- layer: EU_VERORDENING
rank: 1
- layer: EU_RICHTLIJN
rank: 2
- layer: GRONDWET
rank: 3
- layer: WET
rank: 4
- layer: KONINKLIJK_BESLUIT
rank: 5
- layer: AMVB
rank: 6
- layer: MINISTERIELE_REGELING
rank: 7
- layer: PROVINCIALE_VERORDENING
rank: 8
- layer: GEMEENTELIJKE_VERORDENING
rank: 9
- layer: BELEIDSREGEL
rank: 10
- layer: UITVOERINGSBELEID
rank: 11
# Scope dimensions for territorial filtering.
# Each dimension names a field on ArticleBasedLaw that the engine
# uses to filter regulations during resolution.
scope_dimensions:
- field: gemeente_code
description: Dutch municipality code (e.g., GM0363)
- field: provincie_code
description: Dutch province code
- field: waterschap_code
description: Water board code
# Type coercion rules applied at the output boundary.
# When an output's type_spec.unit matches, the engine applies
# the specified coercion before returning the value.
type_coercions:
- unit: eurocent
coercion: round_to_integer
description: Currency in eurocents must be whole numbers
# Delegation rules: which regulatory layers can delegate to which.
# If absent, any layer can implement any open term (no validation).
delegation_rules:
- from: WET
allowed_targets: [AMVB, MINISTERIELE_REGELING, KONINKLIJK_BESLUIT]
- from: AMVB
allowed_targets: [MINISTERIELE_REGELING]2. Layered override (national → organization)
The Engine Policy follows the same layered pattern as the laws it executes:
- National default: ships with the engine distribution. Defines the Dutch legal system baseline. This is the policy shown above.
- Organization override: each organization can provide its own policy file that overrides specific sections of the national default.
Override semantics are section-level replacement, not deep merge. If an organization provides regulatory_layers, it replaces the entire hierarchy. If it provides type_coercions, it replaces all coercion rules. Sections not present in the override inherit from the national default.
# org-policy.yaml - example: a municipality that needs an extra scope dimension
$schema: ...
id: gemeente-amsterdam
name: Gemeente Amsterdam Engine Policy
extends: regelrecht-default
scope_dimensions:
- field: gemeente_code
description: Dutch municipality code
- field: stadsdeel_code
description: Amsterdam borough codeThe extends field references the base policy. The engine loads the base first, then applies overrides.
3. Policy in the Execution Receipt
The active Engine Policy (after overlay resolution) is recorded in the Execution Receipt (RFC-013). A reviewer can see which hierarchy, coercion rules, and scope dimensions were active for any given decision.
{
"engine_config": {
"policy": {
"id": "gemeente-amsterdam",
"extends": "regelrecht-default",
"hash": "sha256:abc123..."
}
}
}The policy hash is a content hash of the resolved (post-overlay) policy. Combined with the engine version and regulation hashes already in the receipt, this completes the reproducibility chain.
4. What stays in the engine
Pure operations stay in the engine. The line is:
- In the engine: arithmetic, comparison, logical, date, and aggregate operations. Pure math, no legal meaning.
- In the policy: anything that requires knowledge of the Dutch legal system, government structure, or financial conventions.
Date arithmetic (RFC-007) stays in the engine. The clamping rule (Jan 31 + 1 month = Feb 28) is standard calendar math confirmed by the Hoge Raad (HR 1 September 2017, ECLI:NL:HR:2017:2225). Age calculation stays. Day-of-week numbering stays (ISO 8601). These are the same in any jurisdiction.
Why
Benefits
The engine binary contains no Dutch legal knowledge. Everything comes from law YAML or policy YAML.
The policy is in the Execution Receipt, so an auditor can see which hierarchy and scoping rules were active for any decision. Policy changes are tracked in version control like any other configuration.
Organizations can customize without forking. A water board adds waterschap_code as a scope dimension. A test harness uses a simplified hierarchy. A future non-Dutch deployment replaces the entire policy.
Tradeoffs
The engine must validate the policy at startup. Malformed policies (circular delegation rules, unknown coercion types) must fail fast.
Adding new policy sections requires schema versioning, same as the regulation schema.
Section-level replacement is coarse. An organization that wants to add one scope dimension must repeat all existing dimensions. Fine-grained merge would create ambiguity about the effective policy, so we accept the repetition.
Policy lookups (layer rank, scope matching) must be fast. The current hardcoded match statements are O(1). Policy-driven lookups should use pre-built HashMaps at startup, not YAML traversal at runtime.
Alternatives Considered
Alternative 1: Keep domain knowledge in Rust, document it thoroughly
- Accept the violation, add extensive comments explaining each piece.
- Rejected: documentation doesn't make the knowledge auditable or adaptable. Organizations still need to fork.
Alternative 2: Express domain knowledge as law YAML in the corpus
- Create a synthetic "regelrecht engine law" in the corpus.
- Rejected: the corpus contains real law. Engine operating rules are meta-rules about how to process law. Mixing them creates confusion about what is legally authoritative.
Alternative 3: Rust configuration file (TOML/JSON) without overlay
- Single configuration file, no organization-level override.
- Rejected: the multi-organization model (RFC-009) requires per-org customization. A single policy file forces all organizations to agree on every rule.
Alternative 4: Deep merge for overrides
- Organization policies merge at the field level rather than section level.
- Rejected: deep merge creates ambiguity. If an org adds a
type_coercionentry, does it append or replace? Section-level replacement is explicit and predictable.
Implementation Notes
Phase 1: Extract and externalize
- Create
engine-policy/v1/schema.jsonfor policy validation - Create
engine-policy/default.yamlwith current hardcoded values - Replace
priority.rs:layer_rank()with policy-driven lookup - Replace
resolver.rs:matches_scope()hardcodedgemeente_codewith policy-driven scope dimensions - Replace
service.rseurocent rounding with policy-driven type coercion - Replace
service.rsdelegation type validation with policy-driven rules - Add policy hash to Execution Receipt
Phase 2: Organization overlay
- Implement
extendsmechanism for policy overlay - Add policy loading to
LawExecutionServiceconfiguration - Add CLI flag:
--policy <path>(defaults to built-in national policy)
Phase 3: Policy in CI
- Validate policy files in CI
- Conformance tests verify that the default policy produces identical results to the current hardcoded behavior
References
- RFC-003: Inversion of Control — IoC pattern that the policy overlay mechanism mirrors
- RFC-007: Cross-Law Execution — established the zero-domain-knowledge principle
- RFC-008: Bestuursrecht/AWB — reinforced the principle for Easter/public holidays
- RFC-009: Multi-Organisation Execution — per-organization customization requirement
- RFC-013: Execution Provenance — Execution Receipt that must capture the active policy
- #472: Extract hardcoded domain knowledge — detailed inventory of all hardcoded domain knowledge
- How It Works — states the zero-domain-knowledge principle
- Glossary of Dutch Legal Terms