# WP-29 — Stamdata beheer editor (low-code, PR-emitting) Status: done (0e77faf) Phase: follow-on — ADR-0004 realization (not part of the 2026-07-02 showcase audit) ## Why ADR-0004 (stamdata-as-code) named a **future low-code editor that commits a PR** as its mitigation for "a non-developer may need dev assistance to edit C#", and floated a **data-file format** as the alternative to typed-C# constants when hand-editing ergonomics outweigh maximal compile-time safety. This WP realizes both: an admin-only maintenance editor that reads the stamdata catalog, edits rows in a grid, and produces the edited JSON data-file the admin drops into the repo — the existing CI build + `StamdataValidationTests` stay the authority. No production database, no runtime write path (ADR-0004 unchanged). ## Read first - ADR-0004 (`docs/reference/architecture/0004-stamdata-as-code.md`) — the model this obeys. - ADR-0001 (BFF-lite + decision DTOs) — the endpoints are screen-shaped, admin-gated reads. - `src/app/brief/**` (WP-23/26) — the org-template admin editor is the closest prior art (root store + machine + capability guard + admin role). ## Decisions (pre-made, don't relitigate) - **Read-only endpoints only.** `GET /stamdata` (catalog) and `GET /stamdata/{table}?peildatum=` (schema + rows). No POST/PUT/DELETE — the edit lands as a reviewed PR, not a write. The `stamdata:edit` capability / `CanEditStamdata` gate the _reads_ (naming is the enforce-twin of a future edit capability; deliberate). - **Generic, schema-driven.** One endpoint pair + one grid editor serve every table. Columns are reflected from the typed record (`StamdataTable.Of`); the FE renders inputs by column type (`date`/`number`/`enum`/`text`). A new table is one line in `StamdataCatalog` — no new endpoint, UI, or test. (Catalog of one today; this is the shape ADR-0004 prescribed.) - **Data-file format for `professions`.** `professions.json` (embedded resource) replaces the hardcoded C# dictionary, deserialized into `ProfessionMapping`. This trades compile-time _value_ checking (gate #1) for editor ergonomics — the value gate becomes `StamdataValidationTests` (gate #2), exactly the trade-off ADR-0004's consequences listed. - **Valid-time.** `geldigVan`/`geldigTot` (half-open `[van, tot)`); a table is temporal iff it has both columns. `peildatum` previews "which rows applied on date X". `Professions.ByProgram` preserves pre-valid-time behaviour by filtering to rows active today. - **Apply path = download → PR.** The editor's `download()` serializes the draft to `{table}.json`; the admin commits it. `mutation-command` is intentionally not used. - Admin-only, resource-independent authz — same shape as org-template management (role IS the decision), denials audited (no PII). ## Files - Backend: `backend/src/BigRegister.Api/Stamdata/{StamdataCatalog,StamdataTable,StamdataFile,ProfessionMapping}.cs` (new), `Professions.cs` (now loads the data-file), `professions.json` (new), `BigRegister.Api.csproj` (embed `Stamdata\*.json`); `Program.cs` (two GET endpoints + `StamdataAdmin` gate), `Contracts/Dtos.cs` (3 DTOs), `Domain/Authorization/Authz.cs` (`stamdata:edit` + `CanEditStamdata`); tests `StamdataEndpointTests.cs` (new), `StamdataValidationTests.cs` (generic `Every_catalog_table_is_valid`). - Frontend: `src/app/beheer/**` (contracts / domain + specs / infrastructure + spec / application / ui + organism story); `app.routes.ts` (guarded lazy route), `shared/domain/capability.ts` + `shared/infrastructure/me.adapter.ts` (`stamdata:edit`), `eslint.config.mjs` (`beheer` boundary rules), `tsconfig.json` (`@beheer/*` alias); regenerated `backend/swagger.json` + `src/app/shared/infrastructure/api-client.ts`. ## Acceptance criteria - [x] `GET /stamdata` and `GET /stamdata/{table}` return admin-only (403 + audit otherwise). - [x] `professions` served from `professions.json`; `ProfessionMapping` typed; behaviour of `Professions.ByProgram` unchanged for all-current rows. - [x] Generic build gate: `Every_catalog_table_is_valid` covers every catalog table (keys non-blank, no overlapping validity, well-formed windows). - [x] `beheer/stamdata` route capability-guarded; page shows denial for non-admin; grid renders from reflected schema; edits update dirty/change-count; `download()` yields a valid `{table}.json`; `peildatum` before 2000-01-01 → zero rows. - [x] Full gate GREEN both sides; `npm run gen:api` leaves no drift; new stamdata story passes axe. ## Verification `cd backend && dotnet test && dotnet format --verify-no-changes`; `npm run lint && npm run check:tokens && npm test && npm run build && npm run build-storybook && npm run test-storybook:ci`; `npm run gen:api && git diff --exit-code -- backend/swagger.json src/app/shared/infrastructure/api-client.ts`. Live: `/beheer/stamdata?role=admin` renders + edits + downloads; without `?role=admin` denies. ## Out of scope - Runtime persistence of edits (would contradict ADR-0004) — the download-to-PR path is the design. - Migrating `PolicyQuestions` / document-categories into the catalog (each is a later one-liner). - A write-back "commit a PR on the admin's behalf" integration (the `download()` seam is where it slots in). ## Risks - Data-file weakens compile-time value safety for `professions` — mitigated by `StamdataValidationTests` running in CI (a bad value fails the build, never prod). - Generic reflection assumes the record's first property is the key and camelCase JSON names — documented conventions in `StamdataTable`; covered by the endpoint schema test.