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

4.8 KiB
Raw Blame History

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.