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:
@@ -43,7 +43,7 @@ public static class Authz
|
||||
public static IReadOnlyList<string> RoleCapabilities(Principal principal) => principal.Role switch
|
||||
{
|
||||
PrincipalRole.Approver => new[] { "brief:approve", "brief:reject", "brief:send" },
|
||||
PrincipalRole.Admin => new[] { "orgtemplate:edit", "stamdata:edit", "cases:manage" },
|
||||
PrincipalRole.Admin => new[] { "orgtemplate:edit", "stamdata:edit", "cases:manage", "flags:manage" },
|
||||
_ => Array.Empty<string>(),
|
||||
};
|
||||
|
||||
@@ -74,6 +74,9 @@ public static class Authz
|
||||
/// list + admin delete.
|
||||
public static bool CanManageCases(Principal principal) => principal.Role == PrincipalRole.Admin;
|
||||
|
||||
/// Feature-flag management (WP-47): admin-only, resource-independent — role IS the decision.
|
||||
public static bool CanManageFeatureFlags(Principal principal) => principal.Role == PrincipalRole.Admin;
|
||||
|
||||
/// Field-level PII (PRD-0002 §5c, phase P2): the case screen's BIG-nummer ships
|
||||
/// masked by default; only the behandelaar (Drafter) composing the case — the actor
|
||||
/// whose behandel-scherm shows the field — may reveal it. Role-based in the POC; a
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user