Files
atomic-design-poc/docs/reference/architecture/0004-stamdata-as-code.md
T
ehoandClaude Opus 4.8 c459fa0a60
CI / frontend (push) Failing after 1m21s
CI / storybook-a11y (push) Failing after 5m27s
CI / backend (push) Successful in 1m35s
CI / codeql (csharp) (push) Failing after 1m50s
CI / codeql (javascript-typescript) (push) Failing after 1m30s
CI / api-client-drift (push) Successful in 2m1s
CI / e2e (push) Failing after 3h14m48s
feat(stamdata): extract policy-question text into Stamdata
Move the geldigheidsvragen wording out of DiplomaRules into
Stamdata.PolicyQuestions (business-editable text, config-as-code); DiplomaRules
keeps only the rule of which questions apply. Extend StamdataValidationTests
(no blank id/wording, distinct ids in the manual set) and update ADR-0004.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:46:38 +02:00

83 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0004 — Stamdata as code (config-as-code, not a production database)
Status: Accepted · Date: 2026-07-20
## Context
The business needs to control certain inputs that change over time — the clearest example
being **which professions link to which diplomas** (`geneeskunde → Arts`, …), but also
tunable thresholds, policy-question text, document-category definitions, and some letter
copy. Two hard constraints:
1. **No managing this through a production database.** A live admin surface writing DB rows
means a bad value ships silently and is discovered in production.
2. **Issues must be caught at compile time.** A change should be typed, reviewed, and
versioned before it can affect anyone.
The codebase already leans this way but had never named it as a pattern, and one key table
was neither isolated nor validated:
- All reference data and thresholds are **compiled-in C# constants**, served through
screen-shaped BFF-lite endpoints; the frontend renders decisions and holds no reference
data (ADR-0001).
- User-facing UI copy is already **`$localize`** (`src/locale/*.xlf`) — git-tracked, and a
second locale is a translation file, not a code change. That is already the compile-time
model for text.
- The profession↔diploma map lived as a *private* `Dictionary` inside `DiplomaRules`, mixed
in with the rules that consume it, with **no cross-reference check**: a diploma whose
program wasn't in the map silently rendered `"Onbekend"`.
## Decision
Treat business-tunable reference data as **stamdata-as-code**: typed, checked-in
configuration, changed through the normal git → PR → build → deploy pipeline. Never a
production database, never runtime-editable.
1. **One home, typed.** Business-editable reference data lives in the
`BigRegister.Stamdata` namespace (`backend/src/BigRegister.Api/Stamdata/`), one file per
concern, as plain typed C# data (records / dictionaries). Separate the **data** (what the
business tunes) from the **rules** (dev-owned logic that consumes it): the profession
*table* is `Stamdata.Professions`; the *rule* "an English diploma needs a B2 question"
stays in `DiplomaRules`.
2. **Served unchanged.** The existing BFF-lite endpoints keep serving this data
(`/duo/diplomas`, `/intake/policy`, `/uploads/categories`, …). No frontend change — the
FE still renders decisions.
3. **Two gates.** The **C# compiler** catches shape and type mistakes. A build-time
**`StamdataValidationTests`** catches the referential integrity the compiler can't —
every seeded diploma program resolves to a real profession, no blank keys/values,
thresholds in range. CI runs it, so a bad edit fails the build and never merges.
4. **Business control = config-as-code (GitOps).** The business owns the content of these
files; a change is a reviewed edit, not a live DB write. A future low-code editor could
commit a PR on their behalf without changing this model (the compile-time gate stays).
### Where each kind of business-controllable thing lives
| Kind | Home | Gate |
| --- | --- | --- |
| Reference tables + tunable numbers (professions↔diplomas, thresholds, policy questions, document categories) | `Stamdata/` typed C# | compiler + `StamdataValidationTests` |
| User-facing UI copy | `$localize` → `src/locale/*.xlf` | build (`i18nMissingTranslation: error`) |
| Letter / brief passage content | config-as-code in the backend (seed content), **not** the DB | compiler + endpoint tests |
### The deliberate exception: org-templates
Per-organization letterhead (return address, footer, signature, margins) **is**
runtime-editable in SQLite, via the org-template admin editor (WP-23/26). That is
intentional and does not contradict this ADR: it is *operational configuration* owned by an
admin persona, versioned with publish/rollback inside the app, and specific to one
sub-organization's identity — not the shared business rules a wrong value would break for
everyone. Stamdata (the rules and reference tables the whole register runs on) stays code.
## Consequences
- **+** Every change is typed, reviewed, versioned, and rollback-able through git; zero
production-DB risk; a dangling reference fails the build with a clear message instead of
reaching users.
- **−** A change needs the PR pipeline — not instant, and a non-developer may need dev
assistance to edit C# (mitigated later by a low-code editor that emits a PR, or by a
data-file format if hand-editing ergonomics ever outweigh maximal compile-time safety).
- **Shipped with this ADR:** the profession↔diploma map (`Stamdata/Professions.cs`) and the
policy-question wording (`Stamdata/PolicyQuestions.cs`) extracted from `DiplomaRules`,
which now consumes both (behaviour unchanged), guarded by `StamdataValidationTests`.
Document-category definitions follow the same pattern as the obvious next step; not moved
yet.