diff --git a/backend/src/BigRegister.Api/Domain/Diplomas/DiplomaRules.cs b/backend/src/BigRegister.Api/Domain/Diplomas/DiplomaRules.cs
index 445e2fd..cc2a2bc 100644
--- a/backend/src/BigRegister.Api/Domain/Diplomas/DiplomaRules.cs
+++ b/backend/src/BigRegister.Api/Domain/Diplomas/DiplomaRules.cs
@@ -1,29 +1,23 @@
+using BigRegister.Stamdata;
+
namespace BigRegister.Domain.Diplomas;
///
-/// SERVER-OWNED business rules for diplomas. This is the single place a policy
-/// changes: which profession a study program maps to, and which policy questions
-/// (geldigheidsvragen) apply to a diploma. The frontend renders these; it never
-/// derives them.
+/// SERVER-OWNED business rules for diplomas: which profession a study program maps to,
+/// and which policy questions (geldigheidsvragen) apply to a diploma. The frontend
+/// renders these; it never derives them.
+///
+/// The profession↔program *data* is business-editable stamdata in
+/// (config-as-code, ADR-0004); the *rules* below consume it.
///
public static class DiplomaRules
{
- // RULE: study program → BIG profession.
- private static readonly Dictionary ProfessionByProgram = new(StringComparer.OrdinalIgnoreCase)
- {
- ["geneeskunde"] = "Arts",
- ["verpleegkunde"] = "Verpleegkundige",
- ["fysiotherapie"] = "Fysiotherapeut",
- ["farmacie"] = "Apotheker",
- ["tandheelkunde"] = "Tandarts",
- };
-
+ // RULE: study program → BIG profession (data lives in Stamdata.Professions).
public static string ProfessionFor(Diploma d) =>
- ProfessionByProgram.TryGetValue(d.Opleiding, out var beroep) ? beroep : "Onbekend";
+ Professions.ByProgram.TryGetValue(d.Opleiding, out var beroep) ? beroep : "Onbekend";
/// Professions a user may declare for a manual (unlisted) diploma.
- public static IReadOnlyList ManualProfessions() =>
- ProfessionByProgram.Values.Distinct().ToList();
+ public static IReadOnlyList ManualProfessions() => Professions.All();
// --- Policy questions (geldigheidsvragen) ---
diff --git a/backend/src/BigRegister.Api/Stamdata/Professions.cs b/backend/src/BigRegister.Api/Stamdata/Professions.cs
new file mode 100644
index 0000000..1bbd686
--- /dev/null
+++ b/backend/src/BigRegister.Api/Stamdata/Professions.cs
@@ -0,0 +1,32 @@
+namespace BigRegister.Stamdata;
+
+///
+/// BUSINESS-EDITABLE STAMDATA (config-as-code). Which BIG profession (beroep) each
+/// study program (opleiding) maps to. This is the one table the business tunes when
+/// a program starts or stops leading to a registered profession.
+///
+/// Change it by editing this file and opening a PR — NOT via a production database.
+/// The C# compiler catches shape/type mistakes; StamdataValidationTests catches
+/// the referential integrity it can't (e.g. a seeded diploma whose program has no
+/// profession here). So a bad edit fails the build, never prod. See ADR-0004
+/// (docs/reference/architecture/0004-stamdata-as-code.md).
+///
+/// This is DATA, not logic: the rules that consume it (which questions a diploma needs,
+/// how a manual diploma is treated) stay in DiplomaRules.
+///
+public static class Professions
+{
+ public static readonly IReadOnlyDictionary ByProgram =
+ new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ ["geneeskunde"] = "Arts",
+ ["verpleegkunde"] = "Verpleegkundige",
+ ["fysiotherapie"] = "Fysiotherapeut",
+ ["farmacie"] = "Apotheker",
+ ["tandheelkunde"] = "Tandarts",
+ };
+
+ /// Distinct professions, in declaration order — the list a user may declare
+ /// for a manual (unlisted) diploma.
+ public static IReadOnlyList All() => ByProgram.Values.Distinct().ToList();
+}
diff --git a/backend/tests/BigRegister.Tests/StamdataValidationTests.cs b/backend/tests/BigRegister.Tests/StamdataValidationTests.cs
new file mode 100644
index 0000000..aa927f7
--- /dev/null
+++ b/backend/tests/BigRegister.Tests/StamdataValidationTests.cs
@@ -0,0 +1,41 @@
+using BigRegister.Api.Data;
+using BigRegister.Domain.Diplomas;
+using BigRegister.Stamdata;
+
+namespace BigRegister.Tests;
+
+///
+/// The compile-time gate for business-editable stamdata (ADR-0004). The C# compiler
+/// already catches shape/type mistakes; these tests catch the referential integrity it
+/// can't, so a bad config edit fails the build instead of reaching production.
+///
+public class StamdataValidationTests
+{
+ [Fact]
+ public void Every_seeded_diploma_program_maps_to_a_known_profession()
+ {
+ // The dangling-reference guard: a seed program with no entry in Professions would
+ // silently render "Onbekend" to the user. Fail the build instead.
+ foreach (var d in SeedData.Diplomas)
+ Assert.True(DiplomaRules.ProfessionFor(d) != "Onbekend",
+ $"Diploma program '{d.Opleiding}' has no profession in Stamdata.Professions.");
+ }
+
+ [Fact]
+ public void Profession_map_has_no_blank_programs_or_professions()
+ {
+ Assert.All(Professions.ByProgram, kv =>
+ {
+ Assert.False(string.IsNullOrWhiteSpace(kv.Key), "A profession-map program key is blank.");
+ Assert.False(string.IsNullOrWhiteSpace(kv.Value), $"Program '{kv.Key}' maps to a blank profession.");
+ });
+ }
+
+ [Fact]
+ public void Manual_professions_are_non_empty_and_distinct()
+ {
+ var professions = DiplomaRules.ManualProfessions();
+ Assert.NotEmpty(professions);
+ Assert.Equal(professions.Count, professions.Distinct().Count());
+ }
+}