本文へ移動
cccskills
無料GitHub で公開

law-generate

Generates machine_readable execution logic for Dutch law YAML files: writes the machine_readable sections, validates against the schema, and (when a shell is available) runs the BDD suites until the model is correct. Use this skill proactively when: editing or creating machine_readable sections in law YAML files, working with corpus regulation files, or when user mentions 'generate', 'machine_readable', or wants to make a law executable. Activate automatically when user discusses law YAML files that need executable logic.

インストール方法を見る

含まれるファイル(7)

  • SKILL.md40.0 KB
  • examples.md25.4 KB
  • reference.md24.9 KB
  • scripts/batch_validate.sh3.9 KB
  • scripts/extract_operations.py7.5 KB
  • scripts/migrate_v04_to_v05.py9.3 KB
  • testing.md3.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Law Generate

Writes machine_readable sections for Dutch law YAML files against schema/latest/schema.json. The schema is the single source of truth and just validate is the arbiter. When in doubt, read the schema.

Text before intent

The specification describes what the legal text says, not what the legislature meant. You translate the words in the text field of the entry you are working on. Nothing else.

The mistake comes from reading the article well. You will see which outcome is evidently intended, and model the shortest route to that outcome. That route is almost always simpler than the text, and it is almost always wrong: it drops a condition the text states, or supplies one the text does not, and the model then produces the right answer for the wrong reason until a case arrives where the intent and the text part ways.

So no shortcuts and no repair of a provision that reads badly. When faithfulness to the text yields something other than what was evidently intended, that is the correct result, and one this corpus is meant to make visible. Record it and move on. Where the text genuinely cannot be followed, say so with a marking or an open term, both described below, and never by quietly modelling the intent instead.

Setup

  1. Read the target law YAML file.
  2. Read reference.md (schema shapes) and examples.md (worked patterns) in this skill directory.
  3. Count the entries. Over 20, work in batches of roughly 15. This does not apply when the prompt names the entries: then that list is the batch.

Do not read another corpus law as a template. The existing corpus predates several of the rules below and copying its habits reproduces its defects; examples.md has the patterns that are current.

The entries you were given

When the prompt names entry numbers, that list is your entire scope. Leave every entry outside it untouched, including one that plainly needs work.

The length of the list says which kind of run this is, and the two ask different things of you.

Several entries is a window of a walk through the law. A run before this one took the entries above and a run after it takes the ones below. Covering the window is the assignment: every entry in the list is answered, with a model, a marking, an open term or a declaration, and none is passed over. The worker measures progress inside the window and rejects a run that shows none.

One entry is targeted work, and there is no walk. No later run picks up the rest, and the entry was named because something specific is wanted from it: an article a waiting root needs, an article whose open term a newly harvested regulation now fills, or an entry that came back wrong. Coverage of the law is not the assignment here. Judge what the entry needs from the entry, report on it alone, and do not list what other entries still lack: that reads as work someone forgot rather than work nobody asked for.

Scope

Each machine_readable interprets ONLY the text of its own entry.

Do not pull anything in. No conditions from other articles, no hardcoded values that another provision sets, no "obvious" requirement the text does not mention, no merging of two leden the text keeps apart. If article 2 grants an entitlement and the age requirement sits in article 11 of another law, article 2 gets a cross-law input, not an inline leeftijd >= 18.

And do not leave a condition unconnected. This half is easier to forget, and it is the half this corpus gets wrong. If your entry produces an output that expresses a restriction (an age test, an asset test, an insurance requirement), some entry has to read it. Where the restriction belongs to the entitlement in this entry, this model reads it. Where it belongs elsewhere, the entry that grants the entitlement reads it across a source binding, and that is not a scope violation: the condition it then applies is "geen aanspraak op grond van artikel 3", which its own text does refer to.

An output that nothing consumes restricts nothing, however cautious it looks. Measured on the zorgtoeslag in round 3: the age test of article 1 and the asset test of article 3 were both produced and neither was ever read. The model granted the allowance to a sixteen-year-old and to a millionaire, and every individual article looked correct.

Binding to an entry outside your scope is allowed and expected, in a window run and in a targeted one alike. Editing your own entry to add a binding is not editing another entry.

The law may be inefficient, redundant or circular. Model it as written.

Aanhef and onderdelen

An aanhef with its onderdelen is one norm spread over several entries. The corpus splits below article level. A lid with an enumeration is stored as one entry for the aanhef (3.2) and one per onderdeel (3.2.a to 3.2.f), each with its own text and its own place for a machine_readable. Neither entry states a rule on its own.

The model for such a lid goes on the aanhef entry; the onderdeel entries stay without a machine_readable. The aanhef states the operative words ("wordt mede verstaan onder partner degene die ... en:"), so it is the only entry whose text names what is being decided. Each onderdeel then appears in that model as its own named parameter or intermediate output, with the onderdeel letter in its description and its own legal_basis, so a reader can still see which onderdeel produced which branch. The condition the aanhef states applies to all of them and is modelled once, as an AND around the OR of the onderdelen.

Modelling an onderdeel by itself drops that condition silently. In round 3 is_partner_op_grond_van_erkenning came out as a bare OR of the two erkenningsvormen, without the requirement that both are registered at the same woonadres, which makes the rule wider than the law.

An onderdeel that is a self-standing definition ("Onze Minister: Onze Minister van Volksgezondheid, Welzijn en Sport") is the exception: it states a complete norm and gets its own model.

The aanhef is read for its own words too, not only as the place the onderdelen land. It carries what holds for all of them at once: the reach of what is being defined, and the caveats that qualify every onderdeel under it. Those go first when the onderdelen are worked through one by one, because each onderdeel is answerable on its own and the sentence above them belongs to no single one. Read the aanhef twice: once for what it opens, and once for what it says.

Four options at a hard spot

At every place where an entry resists translation, exactly one of these applies. Work down the list in order and take the first that fits. Round 4 structurally chose the cheapest option, which is the last one in this list.

The place is the unit, not the entry. An entry has as many of these answers as it has hard spots, and the four never compete for one slot. Definitions that all bind to another law (option 1) under a chapeau that states a reach the format has no field for (option 3) give you the bindings and the marking, and neither makes the other unnecessary. One answer per entry is how a construct nobody can express ends up unrecorded behind a model that looks finished.

1. The value comes from another law or another article: bind to it. "de schadeverzekering, bedoeld in artikel 1, onder d, van de Zorgverzekeringswet" is not a gap. It is an input with source.regulation and source.output. That the target law is not in the corpus yet changes nothing about the entry: whether a regulation has been harvested is a state of the corpus, it is fixed by harvesting, and it does not belong in the law file. Write the binding and leave it standing. In round 4, 43 of the 101 norm gaps were cross-law references written up as gaps.

2. The law leaves the content open and another regulation or implementing policy fills it: open_terms. "Bij ministeriële regeling wordt de standaardpremie vastgesteld" is a complete legal instruction that lacks a number. So is "voor zover dat redelijk is", "zo spoedig mogelijk" and "onverwijld": the law states a norm and leaves its content to be filled. Where the article names who fills it, delegated_to and delegation_type say so; where it names nobody, decided_per_case_by names the authority that is competent in the individual case and fills the term through implementing policy with a motivation. The concept does not change with the question whether the law appoints a filler, and every open term names one of the three (see below).

3. The format cannot express the construct: markings. The words are clear and the language has no shape for them. See below.

4. None of the above: model it. Most articles land here, including many that look procedural at first read.

Two mistakes from round 4 come straight from skipping this list. "Zo spoedig mogelijk" and "onverwijld" were marked as untranslatable while they are open norms (option 2). The citeertitel was marked as untranslatable while declares exists for exactly that (option 4, see Declarations below).

Silence is never one of the options. An entry you pass over without a word is indistinguishable from an entry nobody read, and a check now reports it as such. An entry can also be silent about one construct while it answers the rest, and that shape costs a reader more: the model beside it vouches for the omission. Look hard before concluding an entry states nothing: going through the Awir and the zorgtoeslag entry by entry turned up almost none, and every candidate turned out to be a kind of provision nobody had looked for. A definition by reference is a cross-law binding. A naming provision ("de normpremie: de aan de hand van het drempelinkomen berekende premie") belongs to an output another entry computes. Even the citation title fixes what every execution trace calls this law.

A provision that limits what the administration may do is never passed over. Limitation periods, minimum and maximum amounts, rounding floors, hardship clauses, revision windows, exclusions, transitional law: these bound the citizen's exposure, and leaving them out shifts the balance one way while every rule you did translate shifts it the other. Measured on the Awir in round 3: the five-year limitation periods that protect the administration were translated, and the € 24 rounding floor, the € 121 threshold, the revision limitation period and the hardship clause were all passed over without a word.

Markings

A marking flags one construct in an article that is otherwise worked out. It says the format cannot express that construct, it names it, and it leaves standing everything that does fit.

machine_readable:
  markings:
    - about: de week waarin de aanvraag is ingediend
      reason: >-
        DATE_PART leest jaar, maand en dag uit een datum en kent geen
        weeknummer; de week is hier een zelfstandige waarde waarop de
        bepaling rust.
      resolution: operation       # operation | model
      resolved_by: Een weeknummer als waarde van `in` bij DATE_PART
      target: []
      legal_text_excerpt: de week waarin de aanvraag is ingediend
      accepted: false
  execution:
    # everything that CAN be expressed, which is nearly always most of it

resolution: operation means the operation does not exist and has to be built. The value names the thing to be added, not the place it runs: building one touches the schema, the model, the evaluation in the engine, the BDD grammar and the frontend. resolution: model means the operation set is not the problem: the format has no shape for the construct at all (quantification over persons, a rule about a set rather than a value, a legal fiction). Nothing else belongs in a marking. Group markings by resolved_by and you can read the backlog of missing operations off the corpus, which is what the field is for.

legal_text_excerpt is required and quotes this entry's own text. A marking that cannot quote the words it is about is about something else.

A provision that repeats per period

A provision saying an outcome is established anew for every period, continuing through time, is resolution: model. The format has no shape for a result that is re-established per period, and no operation gives it one. Do not approximate it with a loop and do not file it as operation.

Article 2, fifth paragraph of the Wet op de zorgtoeslag is the case: "De aanspraak op een zorgtoeslag wordt voor iedere kalendermaand afzonderlijk bepaald." An enricher asked for "periodieke evaluatie (iteratie over kalendermaanden) in de engine", which is the wrong answer. The entitlement is decided again each month, running through time, and that needs state outside the execution recording which months are already decided. One execution covers one month.

resolved_by names what is missing: an execution per period with state held outside it. The marking is the result of reading the provision, not a shortfall in the model: it turns a silent gap into a known one.

The three prose fields

about, reason and resolved_by do different work, and each of the three is required because none of them can be recovered from the other two.

  • about is what. The construct, in the words the article uses.
  • reason is why. Why that construct does not fit, stated in terms of what the format does have. This is the diagnosis.
  • resolved_by is what would close it. The change, concrete enough to become work.

The order is not free. The change follows from the reading of the provision; the reading does not follow from the change. A marking from round 4 shows it:

about: de toepassing van deze wet voor zover een oude regeling haar van toepassing verklaart

reason: "Voor zover" beperkt de toepassing per bepaling van deze wet, en niet de wet als geheel: een oude regeling kan deze wet voor het ene onderwerp wel en voor het andere niet van toepassing verklaren. Het model kent alleen toepasselijkheid van een hele wet.

resolved_by: Een vorm waarin toepasselijkheid per bepaling van een wet kan worden uitgedrukt, in plaats van als één booleaanse uitkomst voor de hele wet.

Read the third line on its own and you have a wish. Nothing in it says what "voor zover" does in this provision, so nothing in it tells you whether the wish is earned. That is the half a reviewer needs, because without a diagnosis a gap somebody worked through reads exactly like a gap nobody opened.

What separates a good reason from a bad one: a good one names what the format does have and says where it falls short. "Het model kent alleen toepasselijkheid van een hele wet" names the shape that comes closest and the distance to it. "Dit past niet in het model" names nothing: it is the claim the marking already makes by existing, written out a second time. Two more failures of the same kind, and a check reports all three:

  • A reason that restates about. A restatement answers the first question a second time, and this field asks the second one.
  • A reason that is resolved_by said again. Then the wish stands there twice and the reading is gone.

The test for a marking

The test is not whether you can execute the article. It is: which word can I not fill in, and what is left when I leave that word as an open value?

An entry that stays empty behind a marking is a defect. You lose every rule that was derivable, and the marking then measures the gap far larger than it is. This was the biggest failure form of round 4, and the file shows both shapes two entries apart, in corpus/regulation/nl/wet/wet_op_de_zorgtoeslag/2026-01-01.yaml:

  • Entry 1.1, the aanhef of article 1, got one marking about the reach of a begrippenlijst and nothing else. No outputs, no actions, nothing a reader can do anything with. The marking was the right half. The empty article beside it was the defect, so the repair fills the article and keeps the marking. It never drops the marking and fills the article. Round 5 did the second, on this same article.
  • Entry 1.1.c, the definition of "verzekerde", is fully worked out: parameters, four cross-law inputs, an output and the actions that compute it. It has one marking, for "vanaf de eerste dag van de kalendermaand volgende op de maand waarin hij achttien jaar wordt", because at the time no operation read the month out of a date. The model asks for the first day of the assessed month as a parameter, compares the eighteenth birthday against it, and still produces is_verzekerde.

Write markings like the second one.

That second marking is also the example of a marking that has since expired. START_OF and DATE_PART write the figure it names in two lines, so an entry that carries it today is stale. A marking states what the format cannot do at the moment of writing, and the operation list in reference.md is what that claim is checked against. Read it before you mark.

An article that works and still owes a marking

The empty article behind a marking is one failure. Dropping the marking and filling the article is the other, and it is the more expensive of the two, because the file then looks finished. Nothing is left blank and no check reports a gap; the construct the format cannot hold has stopped being mentioned.

Round 5 produced it on the article round 4 got wrong. Article 1 of the Wet op de zorgtoeslag came back fully modelled: three begripsbepalingen as definitions, an executable is_verzekerde with four cross-law bindings, drempelinkomen computed from the minimumloon, an action and a legal_basis for every onderdeel. Its chapeau appears nowhere in the file:

In deze wet en de daarop berustende bepalingen wordt, tenzij anders is geregeld, verstaan onder: a. Onze Minister: … b. zorgverzekering: …

Those words say something about the definitions under them that no onderdeel says about itself. "En de daarop berustende bepalingen" gives them a reach past this document. They govern the wording of every regulation that rests on this law, and that regulation does not have to refer to them for it to hold. What the corpus can express is the opposite direction: a document reads another through a source binding it writes itself. Reading is not governing, there is no field for the reach of a definition, and no operation supplies one. That is a marking with resolution: model.

markings:
  - about: het bereik van de begripsbepalingen over de daarop berustende bepalingen
    reason: >-
      De begrippen van dit artikel beheersen ook het woordgebruik van elke
      regeling die op deze wet berust, zonder dat die regeling ernaar verwijst.
      Het model kent alleen de omgekeerde richting: een document leest een ander
      document via een source-binding die het zelf schrijft. Een bereik dat geldt
      zonder dat de andere kant het aanroept, heeft geen veld en geen bewerking.
    resolution: model
    resolved_by: >-
      Een vorm waarin de begripsbepalingen van een wet in bereik komen bij het
      uitvoeren van een regeling die op die wet berust, afgeleid uit de
      legal_basis die de regeling zelf declareert.
    target: []
    legal_text_excerpt: In deze wet en de daarop berustende bepalingen wordt, tenzij anders is geregeld, verstaan onder
    accepted: false

"Tenzij anders is geregeld" is not a second marking. A displacement is declared by the regulation that departs and not by the article departed from, so this clause asks nothing of article 1: it permits displacement, and overrides is that mechanism already. It belongs in the legal_basis.explanation of the definitions it qualifies, in this article's own words. Two clauses in one sentence, two different answers, and reading them as one is how the marking channel fills with work that the format can already do.

The chapeau of a begrippenlijst is the standard place for this, and a check reports an article whose chapeau reaches past the document while the model beside it says nothing about that reach.

The target list

target is an assertion, so it has to be true. It lists the values in this entry that cannot be produced because of this marking: outputs, inputs or parameters by name.

  • An empty list is a claim, not an omission. It says the entry stays executable and only its explanation is incomplete. That is the normal case.
  • A name in the list is a value this entry's own model declares: an input, a parameter, an output, a definition or an open term. An invented word is not a target, and a check reports a name the entry declares nowhere. Naming what the entry would have produced is the work.
  • A name in the list means that value is computed nowhere in this entry. If an action produces it anyway, the marking contradicts the model. The same holds for reading it: an action that calculates with a targeted name rests on a value the marking says is not there, and the check reports both shapes.

Today the predecessor field blocks is empty in 39 of 39 markings, and of the 72 values that stood marked as blocked not one is actually left out. Both halves of that are wrong and both are cheap to get right. Name the values the entry genuinely does not produce, and make sure the actions really leave them out.

Do not approximate around a marking. No ten-case IF tree standing in for a table, no arithmetic trick standing in for rounding, no pre-computed aggregate hardcoded as a literal.

Open terms

The law declares the term and references it as a $variable; the regulation that fills it registers as filling it. Under delegation that regulation is a ministeriële regeling or AMvB. A municipal ordinance in co-government (medebewind, e.g. Participatiewet art. 8) is not delegated: the municipality executes the act on its own authority. It is still bound by the act and yields to it (Gemeentewet art. 121-122). The mechanism is the same; do not call the ordinance delegated.

# wet_op_de_zorgtoeslag, article 4
machine_readable:
  open_terms:
    - id: standaardpremie
      type: amount              # string | number | boolean | amount | date
      required: true
      delegated_to: Onze Minister          # omit when the article names nobody
      delegation_type: MINISTERIELE_REGELING   # omit likewise
      legal_basis: artikel 4 Wet op de zorgtoeslag
  execution:
    output:
      - name: standaardpremie
        type: amount
        type_spec:
          unit: eurocent
    actions:
      - output: standaardpremie
        value: $standaardpremie   # the engine resolves this through implements
# regeling_standaardpremie, article 1
machine_readable:
  implements:
    - law: wet_op_de_zorgtoeslag
      article: '4'
      open_term: standaardpremie
      gelet_op: Gelet op artikel 4 van de Wet op de zorgtoeslag
  execution:
    actions:
      - output: standaardpremie
        value: 211200

The output name in the implementing regulation matches the open term id. Priority between competing implementations follows lex superior and lex posterior.

Who fills it

Every open term names someone. A check reports a term with none of delegated_to, expected_source and decided_per_case_by filled in: a norm nobody is competent to fill is not left open, it is left out.

  • delegated_to plus delegation_type where the article authorises a layer ("bij ministeriële regeling wordt ... vastgesteld"). The type is a closed list and it is a competence claim: a regulation at another layer does not fill this term.
  • expected_source where the text names the filling regulation itself ("de regeling, bedoeld in artikel 1 van de Regeling zorgverzekering"), by title or by BWB id. Which regulation is meant is a property of the law. Whether that regulation is in the corpus today is not, and it is recorded nowhere.
  • decided_per_case_by where the article appoints nobody and the norm is filled in the individual case ("voor zover dat redelijk is", "in bijzondere gevallen"). Name the authority and the article that makes it competent. Its answer needs a motivation under Awb 3:46 and is part of the besluit, which is what separates a discretionary power from a value that is merely missing.

Do not record whether the filling regulation is currently in the corpus. That is a state of the corpus, it is untrue by next week, and nobody cleans it up. The resolve step and the work queue track it (RFC-026).

Declarations

Some provisions compute nothing and are not open either. They establish something the rest of the corpus depends on.

declares:
  - property: name        # name | officiele_titel | valid_from | valid_to |
                          # regulatory_layer | legal_basis
    value: Algemene wet inkomensafhankelijke regelingen

Awir article 51 ("Deze wet wordt aangehaald als: ...") is the clearest case: every execution trace that names this law is quoting that article. Article 50 ("treedt in werking op 1 september 2005 en geldt voor berekeningsjaren die aanvangen op of na 1 januari 2006") fixes valid_from and, through applies_from, a floor on the berekeningsjaar that no calculation may go below.

The document header already contains these values, copied there by the harvester. Recording the article that decides them turns a copy into a derivation, and a check holds the two against each other: when they disagree, the article is right and the header is stale.

Displacement, "in afwijking van"

Two things go wrong here often enough to state.

The article named is not the article displaced. "In afwijking van artikel 7, derde lid, van de Awir, bestaat geen aanspraak op een zorgtoeslag" names the rule being departed from, while the subject of the clause says what is displaced: the entitlement that another article of this law establishes. Put the override on the article that produces the thing, not on the article the sentence cites. An override addressed at the wrong article never fires, and the engine then returns both the displaced value and the displacing one without complaint. The article field must match the producing entry's number exactly as this file writes it: in round 3 four of five overrides pointed at "2", "3" and "7" while the producers are stored as 2.1, 3.1 and 7.3, so nothing fired.

"Bestaat geen aanspraak" is not an entitlement of zero. With an entitlement of zero there is a decision, legal remedies and a ground for recovery; with no entitlement there is none of that. Article 2 of the zorgtoeslag says "aanspraak ter grootte van dat verschil": the amount is a property of the entitlement, so where the entitlement does not arise there is no amount to compute either.

overrides:
  - article: '2.1'                  # where the entitlement is produced
    output: aanspraak_op_zorgtoeslag
    voids: true                     # it does not arise, rather than becoming zero
    legal_text_excerpt: bestaat geen aanspraak op een zorgtoeslag

voids is what the engine acts on. legal_text_excerpt is why, in this article's own words, quoted verbatim. Do not classify the ground into a category of your own; copy the statute.

A derogation exists only where words say so. "In afwijking van", "onverminderd", "blijft buiten toepassing" and "met dien verstande" are such words. A second sentence that adds a ground is not one, and neither is a reading that makes the article tidier. Quote the words you relied on in the legal_basis.explanation of the action they shaped. If you cannot quote them, you invented the derogation.

Do not let the producing article read the exclusion instead. Article 2 says nothing about wealth, so pulling that condition in is a scope violation, however helpful it looks.

Numbers and units

Every number in a model appears literally in the text of the entry the model belongs to. Not "derivable from", not "equal to": present.

  • "vier weken" is weeks: 4, never days: 28.
  • "ten minste 10 percent lager" is a comparison against 10 percent, never a multiplication by 0.9.
  • "naar tijdsgelang herrekend" states no denominator. If the text names no measure, the measure is an open term, not a division by 12 you supply.
  • A fallback that borrows a number from another lid is a number from another lid. If this lid states no fallback, it has none.

The one sanctioned conversion is money. Every monetary value in every file is type: amount with type_spec: { unit: eurocent }, and the literal from the text is multiplied by 100. This is a corpus-wide convention and it outranks the rule above, because unit is a label and never a conversion (RFC-023): a law written in euro and a law written in eurocent will bind to each other and the engine will not notice. In round 3 the Awir chose euro and the zorgtoeslag chose eurocent, each internally consistent, and the same person came out at € 827,63 or € 1.550,46 depending on which file you believed. A file that uses unit: euro anywhere is broken even if it is consistent with itself.

Rounding is not part of this. The engine has ROUND, CEIL and FLOOR, so model the rounding the law states and round nothing where it states none.

Actions and operations

Every action has an output and a value. The value is a literal, a $variable, or an operation.

Naming an output is not computing it. An entry that declares outputs and has no action, definition, open term or implements that produces any of them is reported by a check, because from the outside it promises a value the engine never delivers.

actions:
  - output: heeft_recht
    value:
      operation: AND
      conditions:
        - operation: GREATER_THAN_OR_EQUAL
          subject: $leeftijd
          value: 18
        - operation: EQUALS
          subject: $is_verzekerd
          value: true
    legal_basis:
      law: Wet op de zorgtoeslag
      article: '2'
      paragraph: '1'
      explanation: Dutch explanation of how this action follows from the text
CategoryOperationsOperand shape
ArithmeticADD, SUBTRACT, MULTIPLY, DIVIDE, MIN, MAXvalues: array
ComparisonEQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUALsubject: (must be a $variable) + value:
LogicalAND, ORconditions: array
NegationNOTvalue:
MembershipINsubject: + values: or value:
ConditionalIFcases: (each when/then) + default:
CollectionLISTitems: array
RoundingROUND, CEIL, FLOORvalue: + precision: (required)
DateAGE (date_of_birth, reference_date), DATE_ADD (date + years/months/weeks/days), DATE (year, month, day), DATE_DIFF (from, to, in), DAY_OF_WEEK (date), DATE_PART (date, in), START_OF (date, in)named fields

ROUND is half-up (rekenkundig, the Hoge Raad default), CEIL rounds up, FLOOR rounds down. precision counts decimals in the value's own unit, so a eurocent amount rounded to whole euros is precision: -2.

Operations nest: any operand may itself be an operation. reference.md has the full shapes and examples.md the worked cases.

Legal text to operation

Legal textOperation
"heeft de leeftijd van X jaar bereikt"AGE + GREATER_THAN_OR_EQUAL
"ten minste X"GREATER_THAN_OR_EQUAL
"niet meer dan X"LESS_THAN_OR_EQUAL
"indien ... en ... " / "indien ... of ..."AND / OR with conditions
"tenzij" / "niet"NOT wrapping the positive condition
"vermenigvuldigd met" / "verminderd met" / "vermeerderd met"MULTIPLY / SUBTRACT / ADD
"afgerond op hele euro's"ROUND with precision: -2 on a eurocent value
"binnen X weken na"DATE_ADD with weeks
"het aantal maanden tussen"DATE_DIFF with in: months
"het kalenderjaar waarin" / "het berekeningsjaar"DATE_PART with in: year
"de eerste dag van de kalendermaand volgende op"DATE_ADD with months: 1 over START_OF with in: month, in that order
"na de eerste dag van de maand"GREATER_THAN on DATE_PART with in: day, value 1
"voor zover"a bound on an amount: MAX/MIN around the qualifying part. A boolean here loses the partial case, and always in the citizen's disfavour
"dan wel" (two measures side by side)OR over two comparisons, each with its own parameter. One parameter whose description covers both is not a model of the disjunction
"in afwijking van artikel X"overrides declaration
"bij ministeriële regeling"open_terms + implements

Bindings

An input that names a concept another provision owns MUST carry a real source: block. A description is documentation; it makes the engine resolve nothing.

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

Omit regulation for a reference to another article of the same law.

A source: belongs under input:, never under parameters:. This is the most common way a binding looks real and silently does nothing. Parameter has no source field, so a source: under parameters: is dropped at parse time and the value degrades to a plain caller-supplied parameter. It still passes any BDD scenario that injects the value directly, which hides the defect. The bound value goes under input: with its source:; the leaf parameters that feed source.parameters stay under parameters:.

Never fall back to a plain parameter because "the engine cannot resolve multiple source bindings per article". It can, and they resolve.

If the local name differs from the target's output name, put the target's name in source.output, the local name in name, and the reason in description.

Where the target law does not produce the output you need, add that output to the target law first, on the article that should own it, then bind to it. Do not leave the reference in a description only.

Every law the entry's text cites is read by the model or fills an open term. The harvester records the citation in the entry's references block, and a check holds that block against what the model does: a cited law that the model neither binds to nor names on an open term is a reference nobody acted on. Bind to the article the text names, or put the regulation in expected_source on the term it fills.

Hooks and produces

machine_readable:
  hooks:
    - hook_point: pre_actions        # pre_actions | post_actions
      applies_to:
        legal_character: BESCHIKKING # required
        stage: BESLUIT               # AANVRAAG | BEHANDELING | BESLUIT |
                                     # BEKENDMAKING | BEZWAAR
  execution:
    produces:
      legal_character: BESCHIKKING   # BESCHIKKING | TOETS | WAARDEBEPALING |
                                     # BESLUIT_VAN_ALGEMENE_STREKKING | INFORMATIEF
      decision_type: TOEKENNING

Hooks let an article (typically from the AWB) fire on every matching lifecycle event, which is how cross-cutting duties such as the motivation requirement are modelled. Procedures with their stages are declared at the top level of the file, not inside an article; reference.md has the shape.

Field types

ContextValid types
parametersstring, number, boolean, date
input and outputstring, number, boolean, amount, object, array, date

$referencedate is not a built-in. Declare it as a parameter with type: date if you use it.

Quoting in the YAML

You write with Edit, straight into the file, and a file that stops parsing is broken from that keystroke onward. Nothing after it can be read: not by the schema check, not by the measurement afterwards, not by you. In round 5 one unquoted sentence in article 5 cost the whole law, and the actual defect of that run was never found because there was no longer a file to look at.

A value that contains a colon followed by a space must be quoted. Everything before that colon is read as a key, and the file ends there. This is the sentence that broke:

explanation: Eerste lid: de Dienst Toeslagen is belast met de uitvoering van deze wet.

Quoted, or as a block scalar, it is fine:

explanation: "Eerste lid: de Dienst Toeslagen is belast met de uitvoering van deze wet."
explanation: >-
  Eerste lid: de Dienst Toeslagen is belast met de uitvoering van deze wet.

Dutch legal prose walks into this constantly, and the openings are recognisable: "Eerste lid: ", "Let op: ", "Onze Minister: ", "wordt aangehaald als: ". The corpus already quotes the ones it has: text: 'Deze regeling wordt aangehaald als: Subsidieregeling …', description: 'Leeftijd in jaren (bron: BRP)'.

The trap sits unnoticed because most colons in legal text are harmless. Zie artikel 5:2 Awb and AWB 4:1 parse without a murmur: the rule is about the space, not about the colon. So the habit you build on article numbers does not carry over, and the one sentence that puts a space after the colon is the one that breaks the file. Quote on the punctuation you see, not on how dangerous the sentence feels.

Quote what would otherwise read as a number or a date. number and valid_from are strings in the schema, and unquoted they change type silently: number: 3.1 becomes the float 3.1, valid_from: 2025-01-01 becomes a date. Every article number and every date in this corpus is quoted (number: '3.1', valid_from: '2025-01-01'); keep it that way. A value that genuinely is a number, value: 198700, stays unquoted.

The same caution covers a value that opens with punctuation YAML reserves (*, &, !, %, @, `, [, {), which is read as syntax rather than as text. Quote it and the question does not come up.

The safe default for any prose field is a >- block scalar. It holds a colon, a quote and an apostrophe without escaping, and it is what the rest of the file already does.

Validate and test

When a shell is available, follow testing.md in this skill directory for the validate-and-test loop. In the enrichment pipeline there is no shell and the worker validates instead.

Write the related-legislation envelope

After the machine_readable sections are final, write a sibling envelope so the pipeline can harvest the legislation this law depends on. It goes next to the law YAML as .enrichment-result.yaml.

law_id: wet_op_de_zorgtoeslag
related_legislation:
  - name: Regeling vaststelling standaardpremie en bestuursrechtelijke premie
    relation: delegated_regeling      # source_regulation | legal_basis | delegated_regeling
    bwb_id: BWBR0037841               # optional, best effort
    slug: regeling_standaardpremie    # optional, best effort
    open_term: standaardpremie        # optional, delegations only
  - name: Algemene wet inkomensafhankelijke regelingen
    relation: source_regulation

One entry for every source.regulation you bound, every legal_basis you anchored on, and every open term you declared. name is required; the rest is best effort.

This must not go in the law YAML. The law file stays strictly schema-conformant. There is no related_legislation: key anywhere inside it.

Report

Report on the entries you were given and on nothing else. {TOTAL} is the size of that scope, not the size of the law: a targeted run of one entry reports one.

Interpreted {LAW_NAME}

  Entries processed: {TOTAL}
  Made executable: {EXECUTABLE_COUNT}
  Validation: {PASSED/FAILED}
  BDD scenarios: {PASS}/{TOTAL}

  Markings: {N} in {N} entries
  - {entry}: {about} ({resolution}), target: {names or "none"}

  Open terms: {N} in {N} entries
  - {entry}: {id}, filled by {delegated_to or "any competent authority"}

  Remaining issues:
  - {unresolved failures}

  TODOs:
  - {laws that still have to be harvested}
  - Review markings and set accepted: true for the ones a human has confirmed

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Check if a RIG preview deployment is actually running and healthy. Verifies CI triggered, images exist, pods started, and services respond. Use when a PR deployment seems broken or after pushing code.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

Performs critical code reviews using REVIEW.md guidelines. Evaluates code quality, architecture, testing, and domain-specific concerns (legal faithfulness, cross-law references, engine safety). Use after completing significant code changes or before merging.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

Processes all open Dependabot PRs sequentially — rebases if needed, analyzes for breaking changes and security issues, then merges or requests changes. Use when you want to handle pending Dependabot dependency updates.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

Writing rules for the regelrecht docs site (docs/). Use when writing or editing prose under docs/ — RFCs, guides, component docs, concept pages, and the NL/EN landing copy. Keeps docs prose free of AI-generated tells and consistent with the site's government-technical register. Not for code, commit messages, or PR descriptions.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

Provides context about the regelrecht Figma design system and helps with Figma-related tasks like component lookups and design comparisons. Use this skill proactively when: implementing UI components, looking up Figma node IDs, comparing code with Figma designs, downloading Figma assets, or working with design tokens. Activate automatically when user mentions Figma, design system, component styling, or asks about RR-Components.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

Downloads Dutch official legal publications including national laws, local regulations, and implementation policies from government sources (wetten.overheid.nl, lokaleregelgeving.overheid.nl). Use this skill proactively when: user mentions downloading, fetching, or importing a Dutch law by name or BWB/CVDR identifier, when a source.regulation reference points to a law not in the corpus, or when user provides a wetten.overheid.nl URL. Activate automatically when user mentions a specific Dutch law name, BWB identifier (BWBR...), or CVDR identifier.

日本語の概要は準備中です。原文の説明を表示しています。

NederlandseDigitaleDienst/regelrecht252026年10月13日 更新

NederlandseDigitaleDienst のスキルをすべて見る

このスキルの問題を報告する