RFC-015: Engine Policy

ProposedDate: 2026-04-04 · Authors: Eelco Hotting · Depends on: RFC-007 (Cross-Law Execution), RFC-009 (Multi-Organization Execution), RFC-013 (Execution Provenance)

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, separate from both the corpus where real law lives and 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, separate from the corpus, that defines the meta-rules for law execution.

# 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:

  1. National default: ships with the engine distribution. Defines the Dutch legal system baseline. This is the policy shown above.
  2. 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 code

The 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 can add waterschap_code as a scope dimension. A test harness might use a simplified hierarchy, and a non-Dutch deployment would replace the policy entirely.

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_coercion entry, does it append or replace? Section-level replacement is explicit and predictable.

Implementation Notes

Phase 1: Extract and externalize

  • Create engine-policy/v1/schema.json for policy validation
  • Create engine-policy/default.yaml with current hardcoded values
  • Replace priority.rs:layer_rank() with policy-driven lookup
  • Replace resolver.rs:matches_scope() hardcoded gemeente_code with policy-driven scope dimensions
  • Replace service.rs eurocent rounding with policy-driven type coercion
  • Replace service.rs delegation type validation with policy-driven rules
  • Add policy hash to Execution Receipt

Phase 2: Organization overlay

  • Implement extends mechanism for policy overlay
  • Add policy loading to LawExecutionService configuration
  • 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

RegelRecht

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

Links

GitHub repository
How it works
Stay informed
Documentation

Contact

regelrecht@minbzk.nl

Part of

Bureau Architectuur
Ministry of the Interior and Kingdom Relations