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.
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.
| Field | Required | Description |
|---|---|---|
$id | Yes | Machine identifier (snake_case) |
regulatory_layer | Yes | WET, AMVB, MINISTERIELE_REGELING, GEMEENTELIJKE_VERORDENING, etc. |
publication_date | Yes | Official publication date (ISO 8601) |
valid_from | No | Effective date |
bwb_id | Conditional | Required for national laws (format: BWBR + 7 digits) |
gemeente_code | Conditional | Required for municipal ordinances (format: GM + 4 digits) |
Each article mirrors a real article in Dutch law:
Constants used in calculations:
The logic of a law is written with operations:
| Category | Operations | Syntax |
|---|---|---|
| Arithmetic | ADD, SUBTRACT, MULTIPLY, DIVIDE | values: [...] |
| Aggregate | MIN, MAX | values: [...] |
| Rounding | ROUND, CEIL, FLOOR | value: + precision: (a single operand, rounded to N decimals; see below) |
| Comparison | EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL | subject:, value: (operands may be numbers or ISO dates, see RFC-021) |
| Logical | AND, OR, NOT | AND/OR: conditions: [...]; NOT: value: (wraps a single operation) |
| Collection | IN, LIST, FOREACH | IN: subject: + value: or values: [...]; LIST: items: [...]; FOREACH: collection: + body:, optional as:, filter: and combine: (see RFC-016) |
| Conditional | IF (alias SWITCH) | cases: [{when:, then:}], default: |
| Date | AGE, DATE_ADD, DATE, DAY_OF_WEEK, DATE_DIFF, DATE_PART, START_OF | AGE: 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.
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 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):
| Operation | Direction | Dutch |
|---|---|---|
ROUND | nearest, half-up (ties away from zero) | rekenkundig afronden |
CEIL | up (toward +∞) | naar boven afronden |
FLOOR | down (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.
See RFC-024: Precision and Rounding for the full design and legal grounding (Hoge Raad half-up default; no implicit intermediate rounding).
$variableName - reference inputs, outputs, definitions, or parameters$referencedate.year - dot notation for property access#output_name - internal reference (same law)Laws reference outputs from other laws via source:
See RFC-007: Cross-Law Execution for details.
Higher laws declare open_terms that lower regulations implement:
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:
This enables the Awb procedure hooks (RFC-008).
Fields can have detailed type information:
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):
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.
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:
| Layer | Laws | Examples |
|---|---|---|
| WET | 29 (of which 13 synthetic) | Participatiewet, Zorgtoeslag, Zorgverzekeringswet, Awb, BW Boek 5 |
| MINISTERIELE_REGELING | 3 | Regeling standaardpremie (2 versions), Subsidieregeling energietarieven |
| GEMEENTELIJKE_VERORDENING | 2 | Amsterdam 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.
An exploration by Bureau Architectuur of the Dutch Ministry of Economic Affairs and Climate Policy into the possibilities of transparent, executable legislation.
GitHub repository
How it works
Stay informed
Roadmap (Dutch)
Documentation
Research
Bureau Architectuur
Ministry of Economic Affairs and Climate Policy