RFC-043: Fetch Only What the Outcome Reads

AcceptedImplemented
Depends on

Context

RFC-007 evaluates an article in stages: resolve the inputs, resolve the open terms, execute the actions. The engine does exactly that. Every declared input is resolved, and every cross-law reference behind it executed, before the first action runs. Then every action of the article runs, whichever output the caller asked for.

Two things follow. AND, OR and IF stop at the first operand that decides (RFC-036), but by the time they do, all the data is already in, so a condition that settles the outcome early saves no retrieval at all. And an output nobody asked for is still computed, with everything it reads. Issue #1626 measured one case: asking the demo zorgtoeslag only whether the conditions are met consults 17 sources through 40 cross-law references, among them income, assets and partner income, and the life expectancy the CBS publishes.

RFC-007 set data minimization aside as “an authoring-time concern”. Under the current pipeline an author cannot act on it: however the conditions are ordered, the engine fetches the same data.

The predecessor of this engine, poc-machine-law, did not work this way. It looked up an input when an operation read it, computed only the actions a requested output depended on, and stopped when its requirements failed. That behavior was lost in the move to the current engine, and nothing recorded it as a design decision, so nothing flagged its loss. This RFC records it.

Decision

An input is resolved when an operation first reads it, and the result is kept for the rest of the execution. The same holds for open terms and for references to another article’s output. An input that is never read is never resolved: it is not fetched, it does not appear in the trace, and it cannot raise an error.

An article executes only the actions the requested outputs depend on. The dependency closure is computed from the $name references between the article’s own outputs. The hook and override outputs that RFC-007 always preserves stay preserved.

The order of operands decides what is fetched, never what comes out. AND and OR already continue past an unknown operand to look for one that decides (strong Kleene, the RFC-036 table), so reordering conditions gives the same result. The one order dependence that exists stays as RFC-036 defines it: an absent operand read before its guard is an authoring error, not an outcome.

A voids is checked before the output it voids is computed, not afterwards over the finished results. Which void applies is still decided by RFC-041.

This amends the lifecycle in RFC-007: stages 2 and 3 no longer run ahead of stage 4 but inside it, at the point of use. Hooks keep their hook points.

Consequences

The value of a requested output does not change. An operation reads the same operands whether they were fetched beforehand or at the moment of reading, and AND, OR and IF already ignore what they do not read. What changes is everything around that value. An error in an input or an action the requested output does not depend on (a null for a non-nullable input, a law that is not loaded, an absent operand in another action) no longer fails the execution. And outputs outside the dependency closure are no longer in the result: asking whether the conditions are met no longer returns an amount alongside it, unknown or not. Scenarios that assert such an error or such an output will change. The implementing change names each one and updates it deliberately; none is updated to make a test pass.

In the trace, a resolution moves from the head of the article to the operation that read it. The trace schema stays as it is, but node_id is positional (RFC-039), so identifiers change, and tooling that expects cross-law references at the top of an article (the demo’s lineage view) has to descend into them.

A refusal rests on one sufficient ground rather than on every failing condition. That matches how a decision is reviewed: one refusal ground that carries the decision is enough (ABRvS 1 February 2017, ECLI:NL:RVS:2017:88). After an objection, the decision is executed again on the facts as they then stand, and whatever that execution reads is fetched then.

The engine does not decide what is a knock-out. A law that computes an amount without making it depend on its conditions still gets that amount computed when it is asked for. Stopping early is the law’s structure (a condition in front of the calculation, or a voids as in RFC-041), never a gate the engine adds. Where the law settles entitlement per month, as the Wet op de zorgtoeslag does (art. 2(5)), a condition decides per month too, and so does what it saves.

Why

Consulting data is processing. The GDPR defines processing to include retrieval and consultation (art. 4(2)), and the Court of Justice reads that broadly (C‑579/21, Pankki S, para. 46). Art. 25(2) requires that “by default, only personal data which are necessary for each specific purpose of the processing are processed”. The EDPB adds that a datum being necessary for a purpose “does not mean that all types of, and frequencies of, processing operations may be carried out on the data” (Guidelines 4/2019, para. 51), and that efficiency does not establish necessity. Partner income is necessary for the zorgtoeslag; fetching it for someone who is not a verzekerde is not. The Dutch data protection authority made this argument against Toeslagen itself: a second nationality was unnecessary “aangezien de Nederlandse nationaliteit al aanspraak geeft” (fine decision of 25 November 2021, para. 42).

The law prescribes no order. Issue #1078 looked for one in the Wet op de zorgtoeslag, the Awir, the parliamentary history and the case law, and found none. This RFC does not claim one. It fetches what the evaluation reads, in whatever order the law is written.

The trace should show what was consulted. Case law on automated decisions asks the authority to make the data it used and the choices it made visible (ABRvS 17 May 2017, ECLI:NL:RVS:2017:1259, r.o. 14.4), and a data subject is entitled to learn which of their data was used and how (C‑203/22, para. 61). A trace that lists every input the article declares answers a different question.

Alternatives considered

Plan the fetches ahead of time. What an execution needs depends on values it has not seen yet, such as the date of birth. A static plan can only list what might be needed, which is the current behavior.

Bring back requirements as a gate. RFC-001 removed it, and a gate the engine enforces is a construct in the engine rather than in the law. The law already has the means to stop early.

Let the engine choose the least invasive order. Ranking the invasiveness of data sources is a normative choice (the paper, §7.1). Who makes it, and in what instrument, is open. Out of scope here.

A mode that evaluates every condition to list all reasons. Useful for advice to a citizen, but a separate purpose that needs its own ground for the data it reads. Out of scope here.

Limits

Fetching less is not disclosing less. Because the income is fetched only when the age test passes, the request to the income source itself tells that source the age test passed. This RFC minimizes which sources are consulted, not what a consultation reveals, and not the precision of the value returned.

References

RegelRecht

An exploration into transparent, executable legislation, and one of the three projects in the starting selection of the Nederlandse Digitale Dienst.

Links

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

Contact

regelrecht@minbzk.nl

Part of

Nederlandse Digitale Dienst
Ministry of Economic Affairs and Climate Policy