Law Format

The article-based YAML format for machine-readable law, conforming to the official JSON schema.

Laws in RegelRecht are stored as YAML files conforming to the law schema. Each file represents one law at a specific point in time.

File Organization

corpus/regulation/nl/ ├── wet/ # Formal laws (wetten) │ ├── wet_op_de_zorgtoeslag/ │ │ └── 2025-01-01.yaml │ ├── participatiewet/ │ │ └── 2022-03-15.yaml │ └── burgerlijk_wetboek_boek_5/ │ └── 2024-01-01.yaml ├── ministeriele_regeling/ # Ministerial regulations │ └── regeling_standaardpremie/ │ ├── 2024-01-01.yaml │ └── 2025-01-01.yaml └── gemeentelijke_verordening/ # Municipal ordinances ├── amsterdam/ │ └── apv_erfgrens/ │ └── 2024-01-01.yaml └── diemen/ └── afstemmingsverordening_participatiewet/ └── 2015-01-01.yaml

Structure of a Law File

Header Metadata

The $schema line pins the exact schema version the file was written against, by immutable git tag. The current version and the full URL to copy are on the Schema Reference.

$schema: https://raw.githubusercontent.com/.../refs/tags/schema-vX.Y.Z/schema/vX.Y.Z/schema.json $id: wet_op_de_zorgtoeslag regulatory_layer: WET publication_date: '2025-01-01' valid_from: '2025-01-01' bwb_id: BWBR0018451 url: https://wetten.overheid.nl/BWBR0018451/2025-01-01 name: Wet op de zorgtoeslag
FieldRequiredDescription
$idYesMachine identifier (snake_case)
regulatory_layerYesWET, AMVB, MINISTERIELE_REGELING, GEMEENTELIJKE_VERORDENING, etc.
publication_dateYesOfficial publication date (ISO 8601)
valid_fromNoEffective date
bwb_idConditionalRequired for national laws (format: BWBR + 7 digits)
gemeente_codeConditionalRequired for municipal ordinances (format: GM + 4 digits)

Articles

Each article mirrors a real article in Dutch law:

articles: - number: '2' text: | 1. De verzekerde heeft aanspraak op een zorgtoeslag... 2. De hoogte van de zorgtoeslag is het verschil... url: https://wetten.overheid.nl/.../artikel/2 machine_readable: definitions: DREMPELINKOMEN: value: 2500000 # €25,000 in eurocent execution: parameters: - name: bsn type: string required: true input: - name: toetsingsinkomen type: amount source: regulation: algemene_wet_inkomensafhankelijke_regelingen output: toetsingsinkomen parameters: bsn: $bsn output: - name: hoogte_zorgtoeslag type: amount type_spec: unit: eurocent actions: - output: hoogte_zorgtoeslag value: operation: MAX values: - 0 - operation: SUBTRACT values: - $standaardpremie - $normpremie

Key concepts

Definitions

Constants used in calculations:

definitions: norm_alleenstaande: value: 109171 # €1,091.71 in eurocent PERCENTAGE_LID_5: value: 100

Operations

The logic of a law is written with operations:

CategoryOperationsSyntax
ArithmeticADD, SUBTRACT, MULTIPLY, DIVIDEvalues: [...]
AggregateMIN, MAXvalues: [...]
RoundingROUND, CEIL, FLOORvalue: + precision: (a single operand, rounded to N decimals; see below)
ComparisonEQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUALsubject:, value: (operands may be numbers or ISO dates, see RFC-021)
LogicalAND, OR, NOTAND/OR: conditions: [...]; NOT: value: (wraps a single operation)
CollectionIN, LIST, FOREACHIN: subject: + value: or values: [...]; LIST: items: [...]; FOREACH: collection: + body:, optional as:, filter: and combine: (see RFC-016)
ConditionalIF (alias SWITCH)cases: [{when:, then:}], default:
DateAGE, DATE_ADD, DATE, DAY_OF_WEEK, DATE_DIFF, DATE_PART, START_OFAGE: date_of_birth:, reference_date:; DATE_ADD: date: + years:/months:/days:; DATE: year:, month:, day:; DAY_OF_WEEK: date:; DATE_DIFF: from:, to:, in: (days/months/years); DATE_PART: date:, in: (year/month/day); START_OF: date:, in: (year/month)

These 28 operations make up the schema. The engine also accepts the compat aliases NOT_EQUALS, IS_NULL, NOT_NULL, and NOT_IN for backward compatibility, but they are outside the schema, so prefer wrapping the positive operation in NOT. See RFC-004: Uniform Operation Syntax for the full specification.

The set is short on purpose. An operation earns its place when a real law needs it, not when an engine could plausibly offer it: ROUND because a law rounds to whole euros, DATE_DIFF because a deadline is measured in days, FOREACH because a norm counts medebewoners. What an engine can do is close to unbounded, and every operation added on that basis is a promise the schema, the editor, the conformance suite and every other engine have to keep. A law that cannot be expressed is the signal to extend the language; the absence of an operation someone imagined a use for is not.

Absent and unknown values

A register does not hold a value for everyone, and the engine keeps two cases apart (RFC-036). An absent value, null, is a fact: the register says there is none (no partner, no rent). A field may hold it only when its declaration says so (nullable: true, schema v0.5.8); a law tests it with EQUALS … null and may branch on it, but calculating, ordering or deciding on it is an error, because a legal text never treats “geen” as an amount or a verdict without saying so. just validate checks this before the law runs (RFC-037): a null test on a non-nullable field, a null reaching a sum without an absence test, or an IF without default on a non-nullable output is refused. An unknown value is a fact nobody has yet: a source: {} input no data source could supply, or an optional parameter the caller did not pass. A law cannot write an unknown; it propagates through every operation (a definite false still decides an AND, a definite true an OR) and reaches the output carrying the names of the missing facts, so the portal can ask for them. In a data table an empty cell is unknown and the word null is absence; the assertions read is absent and is unknown. Note that this is the reverse of what null means in SQL, where it stands for the unknown: the RFC explains why the literal was kept for absence.

Rounding and precision

Rounding is an explicit, law-modeled instruction: the engine never rounds a value implicitly (not even money). A law that must round says so with one of three unary operations, each taking a single value: operand and a precision: (the number of decimal places to round to, in the value’s own unit):

OperationDirectionDutch
ROUNDnearest, half-up (ties away from zero)rekenkundig afronden
CEILup (toward +∞)naar boven afronden
FLOORdown (toward −∞)naar beneden afronden / afkappen

precision: 0 rounds to whole units, precision: 2 to two decimals, and a negative precision rounds to tens/hundreds (e.g. -2 rounds a eurocent value to whole euros). Intermediate values keep full precision; rounding only happens where a rounding operation appears.

# Round the result of a SUBTRACT to whole eurocent, half-up - output: tegemoetkoming value: operation: ROUND precision: 0 value: operation: SUBTRACT values: - $normbedrag - $eigen_bijdrage

See RFC-024: Precision and Rounding for the full design and legal grounding (Hoge Raad half-up default; no implicit intermediate rounding).

Variable References

  • $variableName - reference inputs, outputs, definitions, or parameters
  • $referencedate.year - dot notation for property access
  • #output_name - internal reference (same law)

Cross-Law References

Laws reference outputs from other laws via source:

input: - name: toetsingsinkomen type: amount source: regulation: algemene_wet_inkomensafhankelijke_regelingen output: toetsingsinkomen parameters: bsn: $bsn

See RFC-007: Cross-Law Execution for details.

Open Terms and Delegation (IoC)

Higher laws declare open_terms that lower regulations implement:

# In the wet (higher law) open_terms: - id: standaardpremie type: amount required: true delegated_to: minister delegation_type: MINISTERIELE_REGELING legal_basis: artikel 4 Wet op de zorgtoeslag default: actions: - output: standaardpremie value: 211200 # In the ministerial regulation (lower law) implements: - law: wet_op_de_zorgtoeslag article: '4' open_term: standaardpremie gelet_op: Gelet op artikel 4 van de Wet op de zorgtoeslag

The engine resolves implementations at runtime using lex superior / lex posterior rules.

See RFC-003: Inversion of Control for the full pattern.

Articles can declare what kind of legal product they produce:

execution: produces: legal_character: BESCHIKKING # Administrative decision decision_type: TOEKENNING # Grant

This enables the Awb procedure hooks (RFC-008).

Type Specifications

Fields can have detailed type information:

output: - name: hoogte_zorgtoeslag type: amount type_spec: unit: eurocent precision: 0

Units (quantity-kind)

A value’s quantity-kind is declared with type_spec.unit, on inputs, outputs, parameters and definitions constants (RFC-023). The available units are euro, eurocent, ratio (a 0–1 fraction), percentage (0–100), and the durations years, weeks, months, days.

A unit is a label, never a computational constraint: tagging a value never changes it. In particular, a percentage is not silently divided by 100; any … / 100 is an explicit operation written where the value is applied. ratio and percentage are distinct labels for the same dimension; the corpus keeps both so a law that says “1,896” (a ratio) and one that says “30 procent” are both transcribed faithfully.

Constants may use the optionally-structured form to declare a unit (the bare naam: 123 form stays valid):

definitions: drempelinkomen_alleenstaande: # money, in whole cents value: 3971900 type: amount type_spec: unit: eurocent percentage_drempelinkomen_alleenstaande: # a 0–1 ratio, explicitly not a percent value: 0.01896 type: number type_spec: unit: ratio

The engine uses these labels to reject nonsensical combinations (adding a eurocent to a days, or euro to eurocent, is a unit mismatch error), while a dimensionless ratio/percentage multiplied by an amount keeps the amount’s unit. Units are opt-in per law: an un-annotated value has unit unknown and is never checked, so existing laws are unaffected until someone annotates them. just validate reports unit mismatches as failures and flags amount outputs that lack a unit (only for laws that already declare units elsewhere). See RFC-023: Quantities in Law YAML.

Corpus Contents

The corpus is still small and growing. It spans three regulatory layers, with national law the bulk of it and a handful of ministerial regulations and municipal by-laws that exercise delegation and the local layer. A snapshot taken on 2026-09-20:

LayerLawsExamples
WET29 (of which 13 synthetic)Participatiewet, Zorgtoeslag, Zorgverzekeringswet, Awb, BW Boek 5
MINISTERIELE_REGELING3Regeling standaardpremie (2 versions), Subsidieregeling energietarieven
GEMEENTELIJKE_VERORDENING2Amsterdam APV erfgrens, Diemen afstemmingsverordening

The 13 synthetic laws are the test_* directories under corpus/regulation/nl/wet/. They are not Dutch law: each one exercises a corner of the language (null semantics, scoped sources, collections, date operations) for the engine-conformance BDD bucket, which needs a law that isolates one feature rather than a real statute that mixes many. That leaves 16 real laws.

These counts move whenever a law is harvested, so read them as an order of magnitude. Count them yourself with grep -rh '^regulatory_layer:' corpus/regulation/ | sort | uniq -c, and see corpus/regulation/ for the authoritative set.

Next steps

RegelRecht

An exploration by Bureau Architectuur of the Dutch Ministry of Economic Affairs and Climate Policy into the possibilities of transparent, executable legislation.

Links

GitHub repository
How it works
Stay informed
Roadmap (Dutch)
Documentation
Research

Contact

regelrecht@minbzk.nl

Part of

Bureau Architectuur
Ministry of Economic Affairs and Climate Policy