A reduction (RFC-022 §4.1) is rulework of its own kind: regulatory_layer: REDUCTIE. It lives with the cell. It reads the cell’s decisions and returns one answer, which fills an open term of the article that asks it (implements). The cell answers that term, the engine records who answered, and the cell explains its own answer on request. This holds wherever the article runs, also when another article calls it anew.
The example is “slechts eenmaal … per schooljaar” (Leerplichtwet 1969 art. 13a lid 2).
| Part | What it does |
|---|---|
| Art. 13a | Declares the open term verlof_al_verleend_in_schooljaar, which the head decides per case (decided_per_case_by), and refuses leave when it is true. |
| School policy | Says when the school year of a date begins and ends. It reads no decisions. |
| Reduction | Fills that open term (implements). It reads the decisions under art. 13a, asks the policy for the school year, and says whether the child already got leave in it. |
| Cell | Answers the engine when an article reads the term: it runs the reduction on its own chronicle and returns the value with its provenance. |
The decision records the answer in fields. It is not among the decision’s findings: the reduction filled the term, not the head.
source: {decisions}source: {decisions: leerplichtwet_1969#13a} means “the decisions taken under this article”.
source.REDUCTIE may use it. A REDUCTIE may not have hooks, overrides, execution.produces or open_terms. Since schema v0.8.5 it may have implements. The loader refuses implements on a REDUCTIE whose $schema names an older version, as the schema of that version does. The loader accepts implements on a version without $schema.A reduction may call other rulework as usual. The school’s reduction gets the school year from the policy through source.regulation.
A decision counts when all of these hold:
verlof_verleend is true;$besluit.bsn_jongere equals the parameter $bsn_jongere (rows are read through $besluit, RFC-049);A school year runs from 1 August to 31 July. The Wet op het primair onderwijs (art. 1) uses that period, but it is not in the corpus, so the policy has no legal_basis. Leave that spans two school years counts for the first. That is our own choice.
What happens inside a cell is the cell’s responsibility. The position paper puts it as “Reductie vindt altijd plaats ín de cel waar de betreffende chronolexogrammen zijn vastgelegd, op verzoek van een businessproces”, and the cell returns the lexostatus to the process that asked. The project set the same line from the law’s side on 9 October 2026. The law defines the interface of a cell, and the cell decides how it reduces. A reduction in the law format is one way to do that.
So the engine does not run the reduction. A caller may give an execution an answerer (OpenTermAnswerer, passed in a Call next to the findings). When an article reads an open term, at any depth, the engine asks the answerer first. The cell is that answerer during a decision. It answers only a term that one of its reductions in force on the day names in implements. The engine knows nothing of cells or chronicles.
Art. 13a leaves the term to the head (decided_per_case_by: het hoofd (artikel 13a, tweede lid)), without a default. The answer comes before the order of precedence of RFC-047: it wins over an implementation and over a finding of the head. Without any of them the execution fails. A missing answer therefore never grants a second leave.
The cell passes the reduction the parameters it declares, by name, from the article that asks the term (bsn_jongere, eerste_verlofdag). The engine hands those over in its question.
The engine records the answer as one step. ArticleResult.open_terms reports it as filled_by: {answer: ...}, with the cell (answerer), the reduction (law_id, valid_from of the version) and how far it read the chronicle (records_read, last_record). The trace has one node for the term with the same provenance, and no FOREACH. The engine also never runs a reduction as an implementation by itself: a REDUCTIE stays out of the implements index, so its implements is the cell’s declaration and nothing more.
A cell is opened with its directory: Cell::open and Cell::in_memory take it as a required argument. They load the reductions from reducties/<id>/<valid_from>.yaml in that directory, so a cell cannot be opened without them. The loader refuses a reduction that fills no open term, since nothing would ask its answer. It also refuses a version whose $id is not its directory’s name or whose file is not named after its valid_from, and any entry under reducties/ that is not such a version. A hidden entry, such as .gitkeep, is ignored. The loader would otherwise skip that file silently, and the term would become the head’s. When the cell decides:
reading::load loads every version of the reductions into the service, so the cell can run them there, and unloads them after the decision; it refuses a law already loaded under a reduction’s id;reading::refuse_undeclared_terms refuses a reduction in force on the day that fills a term its article does not declare that day, since the engine would never ask it;Reader answers the engine; for each input with source.decisions it passes the cell’s own decisions as rows, under the input’s name, so no data source is involved.The cell directory decides which terms are the cell’s. A term that a reduction in reducties/ implements, in any version, is the cell’s to fill. Without its directory the cell could not tell such a term from a finding of the head, which is why opening a cell requires it.
The cell refuses a decision or a replay on a day on which no reduction in force fills such a term. This holds from the first valid_from of a version that implements the term. Before that day the term was the head’s, so a decision taken then still replays. The error names the term and the reducties directory. The cell checks this condition with reading::unfilled for every decision, whether that decision reaches the term or not.
A cell without reductions is opened with a directory that has no reducties, or an empty one. In that cell a term is the head’s finding. A path that is not a directory is an error, and so is a reducties that is not a directory.
A lexostatus is the answer a cell gives from its own facts (RFC-022 §4.1). Cell::answers lists the lexostatussen the cell can answer: per version of each reduction in its directory, the open term it fills (law, article, open_term), the reduction’s $id and the version’s valid_from. This list is a RegelRecht addition. In the Chronolexografie position paper a cell reduces its chronicle on request into a lexostatus. The paper does not have a cell declare which lexostatussen it can answer; that idea comes from RFC-022 §4.1.
The answerer serves the whole decision, so the engine reaches the cell at any depth. When Awb 7:11 calls art. 13a anew (RFC-050), the engine asks the cell in that call. The cell reads its chronicle as it stood before the decision and leaves the contested decision out of the rows.
Outside a cell nothing implements the term. A scenario then gives the head’s finding under the id of the term, at the top or under findings of a decision value.
Cell::explain takes a decision again with tracing, on its own day, with the cell answering from the chronicle as it stood before that decision. Its trace shows the answer as one node. It fails when the outcome differs from the one recorded. A different lexostatus fails the replay only where it changes that outcome.
Cell::explain_answer(service, decision, term) is the cell’s own account. It replays the decision and runs the reduction for that term with tracing, with the parameters and the chronicle the decision gave it. That trace shows the reduction’s FOREACH: which decisions it read and which the filter skipped. It is separate from the decision’s trace, and it is not stored.
Cell::explain_answer), also for a nested call.REDUCTIE under corpus/regulation.regulatory_layer: REDUCTIE and source.decisions. Schema v0.8.5: implements on a REDUCTIE; load_check::reject_misplaced_reduction refuses it under an older $schema.Source::decisions, resolved through a data source or a parameter of the same name; load_check::reject_misplaced_reduction. The answerer (answer.rs: OpenTermAnswerer, Question, Answer, AnswerProvenance, Call) is asked first in resolve_open_term; FilledBy::Answer and PathNode.answer record it. The resolver leaves a REDUCTIE out of its implements index.corpus/cells/obs_de_klimop/.packages/cel: Reduction, reading::Reader, reading::load, reading::unfilled, Cell::open, Cell::in_memory, Cell::answers, Cell::explain, Cell::explain_answer.Note: RFC-051 adds source.executograms for the executograms of a stream of the cell, and has the cell answer inside its own reductions. The school’s reduction reductie_verlof_valt_buiten_schoolvakanties (RFC-051) fills verlof_valt_buiten_schoolvakanties of art. 13a.
Note: RFC-050 first had the cell add bestreden_besluit_herroepen to each row: whether a decision on an objection revoked the decision. Awb 7:11 now holds the procedure in rulework. A row therefore carries the decision’s id instead, and nothing the cell adds. The reduction reads the decisions under Awb 7:11 as a second decisions input and leaves out a grant that one of them revoked.
Changed on 2026-10-16: the first version had art. 13a ask the answer as a parameter, and the cell ran the reduction and passed the answer by name (Bound::answer). In a nested call the engine could not find it, so the cell refreshed the fields of the decision value it passed (RFC-050), and the trace of art. 13a did not show why the reduction answered as it did. Now the reduction fills an open term with implements, and this RFC no longer forbids that key on a REDUCTIE.
Changed on 2026-10-17: the version of 16 October had the engine run the reduction as an implementation, so the decision’s trace held the reduction’s FOREACH. The cell bound its decisions as a data source. Now the cell answers the term through an answerer the caller passes, the engine records who answered and from what, and the cell explains its answer apart (Cell::explain_answer). implements on a REDUCTIE stays, as the cell’s declaration of the term it answers.
An exploration into transparent, executable legislation, and one of the three projects in the starting selection of the Nederlandse Digitale Dienst.
GitHub repository
How it works
Stay informed
Roadmap (Dutch)
Documentation
Research
Nederlandse Digitale Dienst
Ministry of Economic Affairs and Climate Policy