feat(admin): runtime feature flags (catalog-in-code, admin toggle, FE+backend)

Catalog declared in code (Domain/Features/FeatureFlags.cs, build-validated), on/off state
persisted in SQLite (FeatureFlagStore + migration). GET /flags (drives FE gating) + admin
PUT /admin/flags/{key} (new flags:manage capability + FlagsAdmin gate). Enforced end-to-end:
the `inschrijving-open` flag hides the Inschrijven nav item + dashboard action (FE) AND makes
POST /applications for a registratie 403 when off (backend). FE FeatureFlagStore mirrors
AccessStore (enabled() deny-by-default); admin toggle page at /beheer/functies in ADMIN_LINKS.
+4 backend tests, /me cap-list updated, client regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 22:29:48 +02:00
co-authored by Claude Opus 4.8
parent ed264be714
commit 67802c68b4
28 changed files with 1154 additions and 52 deletions
@@ -0,0 +1,22 @@
namespace BigRegister.Domain.Features;
/// A known feature flag — DECLARED in code (this catalog, build-validated), TOGGLED at runtime
/// (on/off state persisted in SQLite by FeatureFlagStore). Same split as stamdata (catalog is
/// config-as-code) × org-templates (runtime state in the DB): what flags exist is code; whether
/// they're on is operational config an admin flips.
public sealed record FeatureFlagDef(string Key, string Description, bool DefaultEnabled);
public static class FeatureFlags
{
/// Whether self-service registration (inschrijving) is open. When off, the FE hides the
/// "Inschrijven" action and POST /applications for a `registratie` is refused (server-enforced).
public const string InschrijvingOpen = "inschrijving-open";
public static readonly IReadOnlyList<FeatureFlagDef> Catalog = new[]
{
new FeatureFlagDef(
InschrijvingOpen,
"Zelf-inschrijving in het BIG-register is opengesteld.",
DefaultEnabled: true),
};
}