Stamdata: add beroepen, opleidingen (temporal), and specialismen tables to the schema-driven catalog (zero UI code). opleidingen.beroep and specialismen.beroep both reference beroepen.code — the first stamdata->stamdata references, enforced by two new StamdataRef entries in the CI gate. OpenZaak/ZGW (WP-49, slice 1 — read-only zaken): introduce IZaakSource as the cases read seam. Default LocalZaakSource reads the local SQLite store (offline); an OpenZaakZaakSource (Zgw/ client: HS256 per-call JWT, ZGW->existing-DTO mapper, paginating HTTP source) is selected behind Zgw:Enabled (default false). The FE never changes — same ApplicationSummaryDto, no api-client drift. Unit-tested with fixtures + a stub HttpMessageHandler; no live OpenZaak needed. Docs: ADR-0005, reference/openzaak-integration.md, WP-49..52 roadmap, stamdata.md update, README index rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.6 KiB
Stamdata (config-as-code reference data) — how it's built & extended
Business-tunable reference data — the tables the business controls (profession↔diploma map, policy-question text, thresholds) — is typed, checked-in config changed via git → PR → build, not a runtime-editable database. For the why, see ADR-0004 — Stamdata as code; this page is how the code is laid out and how to add a table without coupling. Built in WP-29, hardened in WP-48.
Tables today: professions (opleiding-program → beroep), beroepen (the BIG professions master
list), opleidingen (temporal; beroep → beroepen.code) and specialismen (beroep →
beroepen.code). The last two are stamdata → stamdata references — one table keyed on by two
others — enforced by the CI gate below.
The one rule that shapes everything: no runtime write path
The catalog is the source of truth and lives in code. The admin editor downloads an
edited {table}.json for a human to commit — it never PUTs. The authority that a change is
valid is the CI gate, not the server. This is what keeps a bad edit out of prod instead
of out of the database.
Layered pipeline
Backend (backend/src/BigRegister.Api/Stamdata/):
StamdataCatalog.cs— the registry. Adding a table is one line:StamdataTable.Of<ProfessionMapping>("professions", …).StamdataTable.cs— generic table model: columns are reflected from the typed record (first property = key; temporal iff it hasgeldigVan+geldigTot). HoldsValidate()(referential integrity) andRowsOn(date).StamdataFile.cs— reads the JSON as an embedded resource (identical read from API and test assembly). No write method exists.- Endpoints in
Program.cs:GET /stamdataandGET /stamdata/{table}?peildatum=— reads only, both behind theStamdataAdmingate.
Frontend (src/app/beheer/, the beheer context):
domain/stamdata.ts(pure model +activeOn/rowErrors/toJson),domain/stamdata-editor.machine.ts(Elm-style union — no save Msg by design).contracts/stamdata.dto.ts→infrastructure/stamdata.adapter.ts(list()/load()with aparseStamdataTabletrust boundary; no write method).application/stamdata.store.ts— root singleton; derivesdirty/counts/errors;download()serializes the edited table to a blob to commit.ui/stamdata.page.ts(thin container) +ui/stamdata-table-editor/(the generic grid).
How to add a table (zero UI code)
- Add a typed record + its
professions.json-style embedded JSON inStamdata/. - Register it with one line in
StamdataCatalog.cs. - If it references another table, add a
StamdataRefto the CI gate (below).
That's it — the editor grid renders from the reflected column schema, so no per-table component. This is the payoff of the schema-driven design.
The CI gate is the authority
backend/tests/BigRegister.Tests/StamdataValidationTests.cs. Every_catalog_table_is_valid
covers every registered table generically; the StamdataRef list catches dangling
references — both seed → stamdata (Diploma.Opleiding → professions.program) and
stamdata → stamdata (Opleiding.beroep → beroepen.code, Specialisme.beroep → beroepen.code). A bad edit, an orphaning delete, or a premature expire fails the PR
build — never prod. Adding a cross-table FK is one StamdataRef entry: the referencing
keys + a resolver against the target table's (valid-today) keys.
Coupling
Low, and deliberately so. The page is a thin container binding store signals to the grid
organism and mapping the organism's outputs back to store commands
((cellEdited)="store.editCell(…)"); the organism owns no state (pure input/output).
Because the editor is schema-driven, a new table adds no UI coupling. The only
editor-side niceties (WP-48) are fast-feedback nudges — onExpire() reuses the CellEdited
output to set geldigTot; the CI gate stays authoritative.
See also
- ADR-0004 — Stamdata as code — the decision + the org-template exception.
- WP-29 (editor), WP-48 (deletion protection).
backend/src/BigRegister.Api/Stamdata/StamdataCatalog.cs— register a table here.backend/tests/BigRegister.Tests/StamdataValidationTests.cs— the authoritative gate.src/app/beheer/ui/stamdata-table-editor/— the generic, schema-driven grid.- Roles & access — the
stamdata:editcapability + admin gating.