Vier RFC’s beschrijven lagen van dezelfde machine, van buiten naar binnen.
RFC-026, Werkvoorraad. Welke artikelen verrijkt moeten worden en in welke volgorde. Levert taken op.
RFC-027, Enrichment Quality. De rollen, de poorten en de faalvormen die bepalen of één artikel goed verrijkt is. Verbruikt taken.
RFC-028, Steps and Runtimes. Hoe één stap uit die keten draait.
RFC-029, Test Fixtures, deze RFC. De maatlat voor het geheel.
corpus/regulation/ bevat 25 YAML-bestanden: 21 regelingen die zich voordoen als
Nederlands recht en 4 synthetische test_*-wetten. Alle 25 zijn schema-valide.
Daarnaast staan er 5 feature-bestanden onder corpus/regulation/**/scenarios/
met samen 39 scenario’s.
De deterministische controles die vandaag in packages/pipeline/src/enrich_v2/
zijn gebouwd, geven over die 25 bestanden 435 bevindingen:
| check | aantal |
|---|---|
coverage | 374 |
binding | 39 |
enum-provenance | 22 |
schema | 0 |
Commando: cargo run --manifest-path packages/pipeline/Cargo.toml --bin law-check -- --corpus corpus/regulation $(find corpus/regulation -name '*.yaml' ! -name '.*').
De 39 binding-bevindingen zijn grotendeels één patroon: 37 keer source: {},
verspreid over 8 bestanden. Dat is een input zonder herkomst, geen regulation,
geen output, geen reden.
law-source, de bronpoort uit RFC-027, houdt het bestand tegen de
officiële BWB-toestand. Over de interne Wet op de zorgtoeslag
(2025-01-01.yaml, BWBR0018451, toestand 2025-01-01, 9 artikelen in de bron):
Artikel 1a is fabricated, het staat niet in de toestand. De inhoud ervan is
overigens niet verzonnen: “Onze Minister: Onze Minister van Volksgezondheid,
Welzijn en Sport” is artikel 1, eerste lid, onder a. Het is dus verplaatst, en
dat telt voor de reparatie.
Artikel 2 loopt na 226 tekens uiteen. De officiële tekst zegt “voor zover dat
toetsingsinkomen het drempelinkomen te boven gaat” en noemt 4,289% en 13,730%,
te wijzigen bij algemene maatregel van bestuur. Het interne bestand zegt 4,273%
en 13,700% en noemt ministeriële regeling. De drempelclausule waarop de
berekening rust ontbreekt in de tekst, terwijl het machine_readable-blok hem
wel implementeert via MIN plus GREATER_THAN. Het model is dus trouwer aan de
wet dan de tekst waar het onder hangt.
Artikel 5 bevat letterlijk 2. Tot en met 4. [Procedurele bepalingen over aanvraag en betaling]. In de toestand staan daar drie leden die afwijken van
Awir 25, 15 en 16.
Over de Wet inkomstenbelasting 2001 (2025-01-01.yaml):
Nul artikelen verified. De opdracht noemt 514 artikelen; de toestand van
2025-01-01 telt er 498, en het geoogste corpus telt er 2272 op lidniveau. Welk
getal klopt hangt af van de toestandsdatum en van wat je een artikel noemt. Die
granulariteitsdefinitie staat niet vast.
De Algemene wet bestuursrecht in de interne set staat op valid_from: 1994-01-01. Die toestand is niet op te halen bij
repository.officiele-overheidspublicaties.nl; de aanroep eindigt in een
redirect-fout. Het geoogste corpus heeft als oudste Awb-toestand 2002-04-01.
Een fixture op 1994-01-01 valt dus buiten het bereik van elke geautomatiseerde
trouwcontrole.
Het diepere probleem zit in de indeling. Eén map dient nu drie doelen met drie verschillende faalbetekenissen, en daardoor is geen enkele bevinding actiegericht.
Engine-conformance. Spreekt deze engine de taal. De vier test_*-wetten
horen hier, en bdd/conformance/*.feature verwijst er 34 keer naar. Een
falende conformance-test betekent: de engine kan iets niet. Deze fixtures moeten
per constructie deterministisch zijn en mogen nooit meebewegen met wetgeving.
Ze staan vandaag in corpus/regulation/nl/wet/, waar ze worden meegevalideerd
als Nederlands recht, meegeteld in de Tier A-corpusdifferentieel en gescand door
script/cross-law-integriteit.py.
Wetsvalidatie. Klopt deze wet nog. Dat is bucket A, de 5 feature-bestanden.
Een falend scenario betekent: de wet is veranderd of het scenario is verouderd,
en een mens beslist welke van de twee. Dat oordeel is alleen mogelijk als het
wetsbestand aantoonbaar de wet is. Vandaag is dat voor de zorgtoeslag aantoonbaar
niet zo, dus is elke uitkomst van eligibility.feature betekenisloos als
uitspraak over de zorgtoeslag. Het scenario test wel dat de engine doet wat het
YAML-bestand zegt; dat is dus een engine-test.
Enricher-evaluatie. Wordt de vertaler beter. RFC-027 meet over deze 21 bestanden ongeveer 100 bevindingen op 60 gemodelleerde artikelen, met de kanttekening dat het alfa-materiaal is. Die kanttekening klopt en is meteen het probleem: een evaluatieset waarvan je moet zeggen dat hij niet representatief is, meet niets.
De 374 coverage-bevindingen illustreren het. “Artikel 17 heeft geen
machine_readable, 6 leden” is een ware uitspraak over een alfa-testset en een
zinloze over een fixture. Zolang dezelfde map beide is, is de teller niet te
interpreteren.
MinBZK/regelrecht-corpus bevat 3850 regelingmappen (git-tree API op main):
2770 onder wet/, 1054 onder amvb/, 25 onder beleidsregel/, 1 onder
waterschaps_verordening/. Elke map bevat de volledige toestandsreeks, voor de
Awb 176 toestanden vanaf 2002-04-01.
De tekst is in orde. Ik heb de artikelen 1, 2, 3 en 5 van de geoogste
zorgtoeslag (toestand 2026-01-01) naast de toestand gelegd en ze komen overeen,
inclusief de derogatie in 3.1 (“In afwijking van artikel 7, derde lid, van de
Algemene wet inkomensafhankelijke regelingen”) en de drie AWIR-afwijkingen in
5.2 tot en met 5.4. Er zit een preamble in, en verwijzingen zijn
gemarkeerd als markdown-links met een references-blok met bwb_id en
artikel. Dat is materiaal waar je een fixture op kunt bouwen.
Drie dingen kloppen niet, en ze moeten opgelost zijn voordat de fixtures op dit corpus gebaseerd kunnen worden.
Eén. De geoogste bestanden nummeren op lidniveau: 1.1, 1.1.b, 2.3,
5.4. De interne bestanden nummeren op artikelniveau met alle leden in één
text. De bronpoort, gebouwd om trouw te bewijzen, verwerpt een vers geoogst
bestand:
Harvester en bronpoort zijn het oneens over wat een artikel is. Dat is een bug in een van beide en geen ontwerpvraag over fixtures, en hij blokkeert alles wat hierna komt.
Twee. De regulatory_layer klopt niet. regeling_aanpassing_voertuigen
(BWBR0025732, een ministeriële regeling) heeft regulatory_layer: WET. Van de
2770 slugs onder regulation/nl/wet/ beginnen er 715 met regeling_. RFC-003
leest dat veld voor lex-superior-prioriteit en voor de validatie dat
delegation_type bij regulatory_layer past. Een delegatieketen uit het
geoogste corpus is dus niet zonder meer bruikbaar.
Drie. De regeling die de open term standaardpremie invult, staat niet in
het corpus. Ik heb de volledige boom van 3850 slugs doorzocht op
standaardpremie en bestuursrechtelijke_premie en vind niets. De keten die de
huidige fixtures modelleren is dus niet uit het corpus te reconstrueren zonder
eerst te oogsten.
Er zijn 17 schemaversies geregistreerd in packages/engine/src/schema.rs, van
v0.2.0 tot v0.7.0, met schema/latest als symlink naar v0.7.0. De interne
corpus gebruikt er drie door elkaar: 19 bestanden op v0.5.0, 1 op v0.5.3, 1 op
v0.5.6, 4 met een gevouwen $schema-scalar. Het geoogste corpus is nog breder
gespreid; de twee bestanden die ik ophaalde staan op v0.5.2 en v0.3.1.
Er zijn 9 conformance-manifesten (conformance/v0.5.0, v0.5.4 tot en met
v0.6.0, en v0.7.0) voor 17 versies; packages/engine/tests/conformance_coverage.rs
leest ze inmiddels alle via all_manifests(). De JSON-testgevallen die RFC-014
beschrijft bestaan niet.
De sprong naar v0.7.0 is niet additief. Hij voegt placement,
markings en declares toe, verbreedt open_terms, en verwijdert
untranslatables en enables (RFC-031). Een bestand op v0.6.0 valideert dus
niet zonder meer tegen v0.7.0, en de additiviteitstest die hieronder wordt
voorgesteld moet dat kunnen uitdrukken in plaats van erop te vertrouwen. Of de
overige overgangen additief zijn heb ik niet gecontroleerd.
De eenheid, de locatie, de eigenaar en de toelatingseis verschillen per soort. De mapstructuur brengt dat verschil aan, zodat aan het pad te zien is wat een rode uitkomst betekent.
A. Engine-conformance. Eenheid: één construct. Locatie: bdd/laws/ voor de
wetsbestanden, bdd/conformance/*.feature voor de scenario’s,
conformance/<schemaversie>/ voor de taalonafhankelijke JSON-gevallen uit
RFC-014. Eigenaar: het engine-team, meebewegend met de engine. Falen betekent:
de engine kan iets niet, of een operatie is stilletjes van betekenis veranderd.
Toelatingseis: $id begint met test_, geen bwb_id, geen url naar
wetten.overheid.nl, isoleert precies één construct zodat een verwerping
toewijsbaar is, en wordt aangeroepen vanuit minstens één feature of manifest.
Deze bestanden worden schema-gevalideerd en verder met rust gelaten: geen
bronpoort, geen coverage, geen cross-law-integriteitscheck.
B. Wetsvalidatie. Eenheid: een scenario over een vastgepinde toestand van een
echte wet. Locatie: corpus/golden/nl/<laag>/<slug>/<toestandsdatum>.yaml met de
scenario’s ernaast in scenarios/. Eigenaar: een genoemd mens per wet, in
CODEOWNERS. Falen betekent: de wet is veranderd of het scenario is verouderd.
Toelatingseis: law-source geeft Verified voor elk artikel, law-check
geeft nul schema- en nul binding-bevindingen, elke legal_basis wijst naar
een artikel dat in die toestand bestaat, en naast het bestand staat een sidecar
met bwb_id, toestandsdatum, een digest van de opgehaalde XML en een hash per
lid (§2). Eén Drift of één Fabricated en het is geen
gouden fixture. Coverage is geen toelatingseis: een gouden wet mag deels
ongemodelleerd zijn, mits het ongemodelleerde deel een marking of een
open_term heeft in plaats van te zijn weggelaten.
C. Enricher-evaluatie. Eenheid: een claim, niet een bestand. Locatie:
eval/gold/. Eigenaar: wie de enrichment-skill wijzigt. Falen betekent: de skill
is geregresseerd.
RFC-027 beschrijft het recordformaat al (id, bron, claim_type, fout,
correct, reden, toets, herkomst) en de regel die het scheidt van bucket
A: de runner genereert vers, het ingecheckte corpus doet niet mee. Dat is de
enige constructie waarin “de vertaler wordt beter” een meetbare uitspraak is.
Een claim die zich in een uitvoeringsuitkomst manifesteert hoort als Gherkin in
bdd/; de vormclaims horen dat expliciet niet, want daar betekent falen iets
anders.
A meet de engine tegen de taal, C meet de vertaler tegen de wet. Een bestand kan nooit beide zijn.
Ja, het kan op het echte corpus. Het bezwaar dat een echte wet verandert vervalt
zodra je ziet wat een toestand is. regulation/nl/wet/<slug>/<datum>.yaml
is de wet zoals die gold op die datum, en dat is een historisch feit dat BWB niet
herschrijft. Een gouden fixture is dus een paar (bwb_id, toestandsdatum), en
dat paar beweegt alleen als een mens het verplaatst.
Vastgepinde snapshots naast de toestand zijn overbodig, want de toestand is de
snapshot. Er is wel een digest van de opgehaalde XML nodig in de sidecar,
zodat een stille correctie bij BWB zichtbaar wordt. Daarnaast hoort er een
nachtelijke run van law-source over de gouden set te draaien die meldt dat er
een nieuwere toestand bestaat, zonder de build te breken.
Een digest over het hele opgehaalde document meldt een verandering zonder te zeggen waar. Een hash per lid zegt het wel. PR #727 werkt dat uit: naast de documentdigest komt er een genormaliseerde hash per lid in de sidecar, zodat een afwijking “artikel 2.75, lid 1” meldt in plaats van “de Zorgverzekeringswet”. Bij een wet van 742 lid-artikelen scheelt dat een melding tegen een zoekopdracht. De normalisatie is die van de bronpoort: witruimte binnen een alinea wordt één spatie, alineagrenzen blijven staan, en accenten, leestekens en casing blijven significant.
Twee dingen horen bij die keuze. De hash hangt aan het adres binnen het artikel
(3.2.a) en niet aan een positie in de lijst. Hangt hij aan een positie, dan
verschuift een ingevoegd lid elke hash erna en meldt de poort een wijziging op
plekken waar niets veranderd is. En de poort loopt over de verwachte
verzameling en niet over de gevonden. Een lid dat uit het bestand verdwijnt,
een artikel dat leeg achterblijft en een bestand dat niet meer parseert horen
alle drie een bevinding te zijn; een poort die de gevonden verzameling afloopt
geeft in die drie gevallen groen.
Twee grenzen aan die aanpak, allebei gemeten. Toestanden van vóór ongeveer 2002 zijn niet ophaalbaar; de Awb op 1994-01-01 gaf een redirect-fout. En regelgeving zonder BWB-nummer (de APV Amsterdam, de afstemmingsverordening Diemen) is principieel niet te poorten. Die blijven handgeschreven en krijgen dat als status in het bestand.
De keuze is één keten plus drie losse gevallen.
| Wet | Toestand | Omvang | Construct |
|---|---|---|---|
| Wet op de zorgtoeslag (BWBR0018451) | 2026-01-01 | 35 lid-artikelen | berekening, drempel, delegatie, cross-law, procedure |
| Regeling vaststelling standaardpremie (BWBR0037841) | nader te bepalen | klein | implements, de invulzijde van de delegatie |
| Awir (BWBR0018472) | 2026-01-01 | 326 lid-artikelen, art. 3/7/8 gemodelleerd | partnerbegrip, toetsingsinkomen, derogatie |
| Wet IB 2001 (BWBR0011353) | 2026-01-01 | 2272 lid-artikelen, art. 5.2 gemodelleerd | rendementsgrondslag als bronwaarde |
| Zorgverzekeringswet (BWBR0018450) | 2026-01-01 | 742 lid-artikelen, art. 1/2 gemodelleerd | verzekerdenbegrip |
| Algemene wet bestuursrecht (BWBR0005537) | 2026-01-01 | 1745 lid-artikelen, art. 3:46/4:13/6:7 gemodelleerd | hooks, procedures |
| Vreemdelingenwet 2000 (BWBR0011823) | 2026-01-01 | 939 lid-artikelen, art. 69 gemodelleerd | override, lex specialis |
| Burgerlijk Wetboek Boek 5 (BWBR0005288) | 2024-01-01 | 390 lid-artikelen, art. 5:42 gemodelleerd | open norm ingevuld door lagere regelgeving |
| APV Amsterdam, afstemmingsverordening Diemen | n.v.t. | klein | gemeentelijke invulling, handgeschreven |
De tabel hierboven is gekozen op constructen: berekening, drempel, delegatie, cross-law, procedure, open norm. Dat is niet dezelfde as als het rechtsgebied, en beide zijn nodig.
Een testset die alleen inkomensafhankelijke regelingen dekt bewijst minder dan hij lijkt. Toeslagen rekenen met bedragen en percentages over een tijdvak; milieurecht werkt met normwaarden, drempels per inrichting, meet- en rapportageverplichtingen en een vergunningstelsel; strafrecht met kwalificaties en strafmaxima; burgerlijk recht met termijnen en rechtsvermoedens. Een vertaalinstructie die goed presteert op de ene soort kan op de andere de plank misslaan zonder dat de meetlat dat ziet, want de meetlat is op de eerste soort afgeregeld.
De gouden set moet daarom over rechtsgebieden gespreid zijn en niet alleen over constructen. De keuze hierboven dekt sociale zekerheid, belastingen, zorgverzekering, bestuursrecht, vreemdelingenrecht en burgerlijk recht. Milieurecht ontbreekt, en dat is een gat: PR #498 zette machineleesbaar milieurecht neer en is gesloten omdat de bodem eronder verschoof, maar hij laat zien om wat voor materiaal het gaat en welke constructen daar spelen. Bij de uitbreiding van de gouden set hoort minstens één milieurechtelijke regeling, en het is de moeite waard die PR er dan bij te pakken.
Dezelfde redenering geldt voor het decentrale niveau, dat in de tabel alleen via de APV en de afstemmingsverordening voorkomt, en voor Europees recht dat via een implementatiewet doorwerkt. Over soorten die niet in de set zitten doet de meting geen uitspraak.
Waarom de zorgtoeslag als kern. Het is de kleinste volledige berekening in
het Nederlandse recht waarin alle zes constructen binnen negen artikelen
voorkomen: een berekening met een drempel (art. 2, tweede en derde lid), een
vermogenstoets met derogatie op een hogere wet (art. 3, eerste lid), twee
verschillende delegatievormen (art. 2 derde lid AMvB, art. 2 zesde lid
ministeriële regeling, art. 4 vaststelling standaardpremie), verwijzingen naar
drie andere wetten, en een procedurele staart (art. 5). Het artikelenaantal is
sinds 2006 negen gebleven; jaarlijks bewegen alleen de percentages en de
grensbedragen, en dat vangt een toestandspin af. De wet heeft
bovendien al een scenarios/eligibility.feature in het geoogste corpus staan.
Waarom de grote wetten er toch bij horen, ondanks omvang. Een keten die stopt bij een verzonnen bronwaarde test de keten niet. De Awir, de Wet IB 2001 en de Zorgverzekeringswet komen er als volledige toestand in en worden voor drie tot vijf artikelen gemodelleerd. Zo gaan we om met omvang: pin de wet heel, modelleer hem deels, en verantwoord het ongemodelleerde deel. De alfa-set deed het omgekeerde en hield vier artikelen over van 498, waardoor de wet als bron onbruikbaar werd en de rest van het bestand een verkeerde indruk wekt.
De Awb en de Vreemdelingenwet zitten erin omdat hooks en overrides (RFC-007) nergens anders realistisch te testen zijn. BW Boek 5 met de APV zit erin als het enige geval van gemeentelijke invulling van een rijksnorm, en tegelijk als voorbeeld van een fixture die de bronpoort maar half kan dekken: BW 5 wel, de APV niet.
De twee routes zijn niet gelijkwaardig. De uitvoerbaarheid geeft de doorslag.
Een bewaarde engineversie per schemaversie geeft exacte historische semantiek en maakt cross-law-uitvoering onmogelijk zodra twee wetten in één keten op verschillende schemaversies staan. Dat is vandaag al de toestand: de interne zorgtoeslag staat op v0.5.6 en verwijst naar de Awir op v0.5.0 en de Wet IB 2001 op v0.5.0. Eén uitvoering kan niet in twee engines draaien. Het is bovendien niet wat het gedeployde systeem doet: één editor, één WASM-engine, één corpus met gemengde versies.
Dus: één engine die alle uitgebrachte schemaversies leest. Dat is alleen houdbaar met een regel die vandaag impliciet is en expliciet moet worden.
Een schemaversie mag uitsluitend optionele constructen toevoegen. Een wijziging die iets verwijdert of vernauwt is een corpusmigratie, geen schemaversie.
RFC-013 besluit het omgekeerde in vorm: de engine declareert supported-schemas,
een regeling met een niet-ondersteunde versie is een harde fout, en “support all
schema versions forever” staat daar als verworpen alternatief. Dat verschil is
echt en het valt op te lossen, maar niet stilzwijgend.
De grond onder die verwerping is de stapel compatibiliteitscode: “after 20 schema versions, the engine is mostly compatibility code”. Additiviteit haalt die grond weg. Een versie die uitsluitend optionele constructen toevoegt vraagt geen tweede leespad, dus er is één model dat alles leest en geen enkele shim. RFC-013 verwerpt achterwaartse compatibiliteit door opstapeling, en die verwerping blijft hier staan.
Het mechanisme van RFC-013 blijft daarmee overeind, met één wijziging in wat het
uitdrukt. supported-schemas blijft, de harde fout bij een onbekende versie
blijft, en de lijst wordt een aaneengesloten bereik vanaf een ondergrens in
plaats van een selectie. Een versie laten vallen blijft toegestaan als expliciete
en geversioneerde beslissing, alleen is het voortaan eerst een corpusmigratie: de
ondergrens gaat pas omhoog nadat elk document eronder omhoog is gebracht.
Vandaag verwijst de interne zorgtoeslag op v0.5.6 naar de Awir op v0.5.0. Een engine die v0.5.0 laat vallen zonder die migratie breekt de keten, en één uitvoering kan niet in twee engines draaien.
De ondergrens uit RFC-026 is een ander ding met dezelfde vorm. Die zegt dat de graafindex documenten onder schema 0.7.0 niet opneemt, omdat ze de velden missen die de graaf nodig heeft. De engine voert zo’n document nog wel uit; het staat alleen niet in de index.
Die regel is testbaar en wordt niet getest. De concrete invulling: een test die
voor elk opeenvolgend paar versies aantoont dat elk document dat valide is
onder vN-1 valide blijft onder vN na uitsluitend het herschrijven van
$schema. De sprong naar v0.7.0 voldoet daar niet aan (zie hierboven); de andere
overgangen zijn ongecontroleerd.
De gevolgen van die route, één voor één.
Voor de testsuite. Conformance-gevallen zijn per schemaversie, zoals RFC-014
al zegt, en draaien allemaal tegen dezelfde engine. Een geval dat voor v0.5.0 is
geschreven moet op de engine van vandaag nog slagen, en dat is het
achterwaartse-compatibiliteitsbewijs. Concreet: conformance_coverage.rs moet
over alle conformance/v*/manifest.json itereren in plaats van één
include_str!, en er moet een manifest zijn per uitgebrachte versie. Het eerste
is gedaan; nu zijn er negen manifesten voor zeventien versies. Een ontbrekend
manifest laat de suite falen.
Voor de migratie van het corpus. Het geoogste corpus migreert niet. Gemengde
versies zijn de realistische toestand van 22.000 bestanden die over een jaar zijn
geoogst, en de engine leest ze allemaal. just validate blijft daar
versietolerant. De gouden set migreert wel, en staat altijd op de nieuwste
versie, want dat is de versie die gespecificeerd wordt. Dat maakt just validate
tweeledig: tolerant over corpus/legacy en bdd/laws, strikt over
corpus/golden.
Voor het uitbrengen van een nieuwe schemaversie. Vier stappen, in deze
volgorde: bewijs dat de wijziging additief is; schrijf conformance/vNEW/manifest.json
en de gevallen die het nieuwe construct dekken; migreer de gouden fixtures door
$schema te herschrijven en opnieuw te poorten; laat het geoogste corpus staan.
Weggooien maakt van een ontwerpbeslissing een storing van twee weken; laten staan zoals het is houdt in stand dat een lezer er onjuiste conclusies over Nederlands recht uit trekt. We halen de set daarom uit elkaar naar wat elk bestand werkelijk is.
De vier test_*-wetten deugen als fixture maar staan op de verkeerde plaats.
Ze verhuizen naar bdd/laws/. Ze zijn het enige deterministische
materiaal in de repo en blijven ongewijzigd.
De vijf bucket-A-featurebestanden blijven, de wetten eronder niet. Een
scenario wordt herricht op een gouden wet zodra die er is, en krijgt tot dat
moment @wip (de runner slaat die al over). Dat betekent tijdelijk 39 scenario’s
op inactief, en het alternatief is 39 scenario’s die groen zijn zonder betekenis.
De 21 alfa-regelingen verhuizen naar corpus/legacy/ met een README die zegt
wat het is: alfa-materiaal uit de periode zonder schema en zonder skills, geen
referentie, geen nieuw werk hier. Ze blijven schema-valide en blijven bruikbaar
als volumemateriaal voor de loader-tests en de benchmarks.
Per afhankelijkheid, en de lijst is volledig voor zover ik hem kon vaststellen.
| Afhankelijkheid | Wat er moet gebeuren |
|---|---|
just validate en de pre-commit-hook validate-law-yaml | Padset verbreden naar corpus/** en bdd/laws/. Tolerant over legacy, strikt over golden. |
just conformance Tier A (packages/engine/tests/conformance.rs:83) | Alleen het pad wijzigen. Tier A is een schema↔model-differentieel en geeft niet om wetstrouw, dus legacy is legitieme invoer. De assert checked > 0 blijft. |
script/cross-law-integriteit.py (CI, fail-closed) | Alleen over corpus/golden draaien. Over legacy meldt hij per constructie DANGLING en PLAIN-PARAM (37 keer source: {}), en fail-closed zou daar elke toekomstige PR blokkeren. |
Loader-integratietests in src/article.rs (14 vaste paden), src/engine.rs, src/service.rs, src/resolver.rs | Herschrijven tegen bdd/laws/. Ze asserteren nu op de inhoud van alfa-bestanden (vermogensgrenzen €161.329, standaardpremie == 211200, de namen van de open_terms in Participatiewet art. 8) terwijl ze de loader testen. Dit is de grootste post en tegelijk de meest lonende. |
tests/trace_integration.rs met expected_zorgtoeslag_trace.txt en expected_standaardpremie_trace.txt | Tekstsnapshots van een trace over een alfa-wet. Tijdelijk naar een synthetische wet, daarna op de gouden zorgtoeslag. RFC-027 noemt golden-trace-snapshots; keten-checkpoints zijn er de robuustere vorm van. |
tests/golden_tests.rs en tests/fixtures/*.json (54 gevallen) | Corpusonafhankelijk, dus niet urgent. Wel migreren naar conformance/<versie>/: dit is feitelijk al het JSON-formaat dat RFC-014 beschrijft en het leidt nu een parallel bestaan. real_regulations.json hernoemen, de twee gevallen erin gaan over verzonnen test_wet en test_toeslag. |
frontend/e2e/helpers-corpus.js en de drie specs die hem gebruiken | CORPUS_ROOT naar corpus/golden/nl. De twee kopieën in frontend/e2e/fixtures/zorgtoeslag-*.yaml zijn ongesynchroniseerde forks van de alfa-wet en gaan weg. |
frontend/Dockerfile regel 109-114 | De COPY van corpus/regulation naar het image is dode data: de ingecheckte corpus-registry.yaml heeft geen local source. Verwijderen. Hij zorgt er nu alleen voor dat elke corpuscommit het editor-image invalideert. |
TUI (packages/tui/src/backend/corpus_scanner.rs) | Fallback corpus/golden en daarna corpus/legacy. |
Benches law_loading.rs en service_e2e.rs | Geen REGULATION_PATH-override, die krijgen ze, gericht op legacy. Ze willen volume, geen trouw. |
corpus/annotations/ en just validate-annotations | Annotaties verhuizen mee met de wet die ze annoteren. CODEOWNERS bijwerken. |
CI-job “Check corpus schema references” (ci.yml:161) | Padset verbreden. De fallback die regel 2 en 3 afpelt bij gevouwen $schema-scalars is broos en verdient een echte YAML-parse. |
Skills law-download, law-generate, law-interpret | Hardcoderen corpus/regulation/nl/... en noemen de alfa-zorgtoeslag als canoniek voorbeeld. Dat is het mechanisme waardoor het alfa-materiaal zich voortplant: elke nieuw gegenereerde wet wordt op een fout voorbeeld verankerd. Herrichten op de gouden zorgtoeslag. Goedkoop en met het meeste effect. |
Een fixture zegt of een uitkomst klopt. Voor de vraag of de enricher beter wordt is dat niet genoeg, zoals ronde 3 liet zien.
Die ronde draaide twee varianten over dezelfde bodem met één verschil. De variant zonder contextbrief kreeg twee bevindingen, de variant met brief vierentwintig. Op het oog was de eerste beter. Hij had alleen geen enkele cross-law binding gelegd waar de tweede er acht had, en een model dat niets probeert kan nergens fout in zitten. Elke poort die vraagt of iets klopt geeft terughoudendheid en leegte dezelfde score.
Daarom telt law-check sinds die ronde ook wat een vertaling ondernam:
artikelen met logica tegenover artikelen met alleen een markering tegenover
artikelen zonder iets, het aantal bindingen en naar hoeveel van de aangehaalde
wetten, de markeringen per soort, en hoeveel outputs door een ander artikel
gelezen worden.
Die laatste teller vroeg een correctie, en die legt de faalvorm bloot. In de eerste versie telde een declaratie als haar eigen lezer, en dan staat de uitkomst op 60 van 61 en zegt hij niets. Alleen consumptie van buiten het eigen artikel telt, en dan staat hij op 17 van 61. Dat verschil is de losse restrictie die de juridische review aanwees: de leeftijdstoets en de vermogenstoets worden geproduceerd en door niemand gelezen, waardoor het model toekent aan een zestienjarige en aan een miljonair terwijl elk artikel afzonderlijk klopt.
Een teller die ook een poort is, wordt een doel. Ronde 4 laat zien wat dat
kost: de poort cross_law_references accepteerde een substring in de
markeringen als bewijs dat een verwijzing behandeld was, en 43 van de 101 gaten
in dat corpus zijn kruisverwijzingen naar een wet die al geoogst is, in de vorm
van een gat opgeschreven omdat die vorm de poort passeerde. Een teller hoort
daarom te tellen wat er staat en de vraag of het klopt aan een andere controle
te laten (RFC-031).
Een teller is geen kwaliteitsoordeel. Meer bindingen is niet beter dan minder; het is een andere vertaling van dezelfde tekst. De vergelijking tussen twee ronden heeft beide nodig, en een ronde die alleen op bevindingen wordt afgerekend beloont de vertaling die minder aandurft.
Fase 0, ongeveer een dag, geen inhoudelijke keuze. De map splitsen:
corpus/regulation naar corpus/legacy, de vier test_* naar bdd/laws/, een
lege corpus/golden/ aanmaken, en de ongeveer vijftien padconstanten
herrichten. Semantisch verandert er niets en elke latere fase wordt daarmee een
lokale wijziging. De BDD-World moet vanaf hier twee wortels preloaden.
Fase 1, blokkerend, twee tot drie dagen. Het granulariteitscontract tussen
harvester en bronpoort beslechten. Of de poort leert lidnummering lezen, of de
harvester levert artikelniveau met de leden erbinnen. Zonder dit kan geen enkel
geoogst bestand een gouden fixture worden. In dezelfde beweging de afleiding van
regulatory_layer in de harvester repareren; BWB draagt de soort in de
metadata, dus dit is werk van uren, geen ontwerpvraag.
Fase 2, ongeveer een week. De eerste gouden keten: zorgtoeslag 2026-01-01,
Awir, Wet IB 2001, Zorgverzekeringswet, plus de standaardpremieregeling die
eerst geoogst moet worden. Elk bestand door de poort, daarna eligibility.feature
erop porten. Opleverpunt: één keten waarin law-source voor elk artikel
Verified geeft en just bdd groen is.
Fase 3, ongeveer een week. De overige vier ketens, één per PR: Participatiewet
met Diemen, Woo, BW 5 met de APV, Awb met de Vreemdelingenwet. @wip tot af.
Fase 4, parallel, engine-team, ongeveer een week. De loader-integratietests
herschrijven tegen bdd/laws/, de trace-snapshots herbaseren,
tests/fixtures/*.json naar conformance/<versie>/ migreren,
conformance_coverage.rs over alle manifesten laten itereren, en de
compatibiliteitstest voor schemaversies schrijven, die per overgang vastlegt
welke velden erbij komen en welke verdwijnen. Additiviteit is geen aanname meer
sinds v0.7.0 er drie verwijdert.
Fase 5, onafhankelijk. De gouden set voor de enricher (eval/gold/) met de
regressierunner uit RFC-027. Enige koppeling met het voorgaande: hij mag
corpus/legacy niet lezen.
De dure post is niet het schrijven van fixtures. Het zit erin dat veertien vaste paden, twee trace-snapshots en drie Playwright-specs asserteren op de inhoud van alfa-bestanden. Dat is de rekening voor het gebruiken van één testcorpus als demodata, runtimedata en specificatie tegelijk, en die rekening komt één keer.
main
(git-tree, contents, status.yaml). De branch development waar
corpus-registry.yaml naar wijst, en enrich/claude en enrich/opencode, heb
ik alleen steekproefsgewijs bekeken.law-source op drie interne bestanden gedraaid. De Awb-aanroep faalde
op een redirect, dus daarvoor heb ik geen oordeel.just bdd, just conformance en just test heb ik niet gedraaid. De uitspraken
over welke test breekt bij een verplaatsing komen uit de code, niet uit
observatie.“Het echte geoogste corpus is wél in orde.” Voor de tekst klopt dat en ik heb
het geverifieerd. Voor de metadata niet: 715 ministeriële regelingen staan als
regulatory_layer: WET onder regulation/nl/wet/, en RFC-003 leest dat veld voor
prioriteit en delegatievalidatie. En het geoogste bestand faalt de eigen
bronpoort van de repo met fabricated=31, omdat harvester en poort het oneens
zijn over wat een artikel is. “Baseren op het echte corpus” is daarmee een
substantiëlere klus dan een kopieerslag, en fase 1 hierboven bestaat alleen
daarom.
“De Wet IB 2001 heeft er vier tegen 514 in de wet.” De toestand van 2025-01-01 telt 498 artikelen; het geoogste corpus telt er 2272 op lidniveau. Het getal hangt af van de toestandsdatum en van de granulariteitsdefinitie, en die definitie staat niet vast. Dat is zelf de bevinding.
“Artikel 1a bestaat niet in de wet.” Dat klopt, de poort noemt het
fabricated. De inhoud is echter verplaatst: het is artikel
1, eerste lid, onder a. Dat geldt breder. De alfa-bestanden zijn overwegend
verschoven en samengeperst en zelden uit het niets opgeschreven, en daarom levert
herverankeren op toestanden een groot deel van het werk terug in plaats van dat
het opnieuw moet.
De aanname dat de fixtures fout zijn. De meting wijst naar iets anders. Van de
435 bevindingen zijn er 374 coverage, en die zeggen “dit artikel heeft geen
machine_readable”. Dat is waar over een alfa-testset en betekenisloos over een
fixture. Het probleem zit erin dat één map
drie doelen dient met drie faalbetekenissen, waardoor geen enkele bevinding
actiegericht is. De bestanden repareren zonder de doelen te scheiden reproduceert
de situatie binnen een jaar.
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