Files
ehoandClaude Opus 4.8 1c3c195d32
CI / frontend (push) Successful in 2m59s
CI / backend (push) Successful in 1m27s
CI / semgrep (push) Successful in 58s
CI / e2e (push) Successful in 2m30s
CI / api-client-drift (push) Canceled after 1m14s
CI / storybook-a11y (push) Canceled after 29m8s
feat(backend): expand stamdata + OpenZaak-ready cases seam (WP-49)
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>
2026-07-24 15:01:06 +02:00

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; beroepberoepen.code) and specialismen (beroepberoepen.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 has geldigVan+geldigTot). Holds Validate() (referential integrity) and RowsOn(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 /stamdata and GET /stamdata/{table}?peildatum=reads only, both behind the StamdataAdmin gate.

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.tsinfrastructure/stamdata.adapter.ts (list()/load() with a parseStamdataTable trust boundary; no write method).
  • application/stamdata.store.ts — root singleton; derives dirty/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)

  1. Add a typed record + its professions.json-style embedded JSON in Stamdata/.
  2. Register it with one line in StamdataCatalog.cs.
  3. If it references another table, add a StamdataRef to 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:edit capability + admin gating.