diff --git a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/RequiredRoleMatrixTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/RequiredRoleMatrixTests.cs
new file mode 100644
index 00000000..ba2305c4
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/RequiredRoleMatrixTests.cs
@@ -0,0 +1,208 @@
+using System.Runtime.CompilerServices;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
+using ZB.MOM.WW.ScadaBridge.Security;
+
+namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;
+
+///
+/// Frozen authorization matrix (arch-review UA2). Every registered management command
+/// MUST have an entry in giving its required-roles set (any-of
+/// semantics) or null when the command is available to any authenticated user.
+/// A newly-added command with no entry fails ,
+/// forcing a deliberate authorization decision instead of silently defaulting to the
+/// read-only (any-authenticated) gate — the class of gap that produced C2/C4.
+///
+/// The table reflects ACTUAL current behavior of
+/// ManagementActor.GetRequiredRoles; it is authored by hand from the switch and
+/// cross-checked against docs/requirements/Component-ManagementService.md
+/// §Authorization. Do not regenerate it mechanically from the source under test — that
+/// would defeat the freeze.
+///
+public class RequiredRoleMatrixTests
+{
+ private static readonly string[] AreaManagers = [Roles.Designer, Roles.Deployer];
+ private static readonly string[] SecuredWriteReaders = [Roles.Operator, Roles.Verifier, Roles.Administrator];
+
+ private static readonly Dictionary Expected = new(StringComparer.OrdinalIgnoreCase)
+ {
+ // ---- Administrator-only -----------------------------------------------------
+ ["CreateSite"] = [Roles.Administrator],
+ ["UpdateSite"] = [Roles.Administrator],
+ ["DeleteSite"] = [Roles.Administrator],
+ ["ListRoleMappings"] = [Roles.Administrator],
+ ["CreateRoleMapping"] = [Roles.Administrator],
+ ["UpdateRoleMapping"] = [Roles.Administrator],
+ ["DeleteRoleMapping"] = [Roles.Administrator],
+ ["ListApiKeys"] = [Roles.Administrator],
+ ["CreateApiKey"] = [Roles.Administrator],
+ ["DeleteApiKey"] = [Roles.Administrator],
+ ["UpdateApiKey"] = [Roles.Administrator],
+ ["SetApiKeyMethods"] = [Roles.Administrator],
+ ["ListScopeRules"] = [Roles.Administrator],
+ ["AddScopeRule"] = [Roles.Administrator],
+ ["DeleteScopeRule"] = [Roles.Administrator],
+ ["QueryAuditLog"] = [Roles.Administrator],
+ ["UpdateSmtpConfig"] = [Roles.Administrator],
+ ["UpdateSmsConfig"] = [Roles.Administrator],
+ ["TrustServerCert"] = [Roles.Administrator],
+ ["RemoveServerCert"] = [Roles.Administrator],
+ ["ListServerCerts"] = [Roles.Administrator],
+ // Transport inbound bundle handling mutates cross-cutting config → Admin.
+ ["PreviewBundle"] = [Roles.Administrator],
+ ["ImportBundle"] = [Roles.Administrator],
+
+ // ---- Area management: any-of [Designer, Deployer] ---------------------------
+ ["CreateArea"] = AreaManagers,
+ ["UpdateArea"] = AreaManagers,
+ ["DeleteArea"] = AreaManagers,
+
+ // ---- Designer-only ----------------------------------------------------------
+ ["CreateTemplate"] = [Roles.Designer],
+ ["UpdateTemplate"] = [Roles.Designer],
+ ["DeleteTemplate"] = [Roles.Designer],
+ ["ValidateTemplate"] = [Roles.Designer],
+ ["CreateExternalSystem"] = [Roles.Designer],
+ ["UpdateExternalSystem"] = [Roles.Designer],
+ ["DeleteExternalSystem"] = [Roles.Designer],
+ ["CreateExternalSystemMethod"] = [Roles.Designer],
+ ["UpdateExternalSystemMethod"] = [Roles.Designer],
+ ["DeleteExternalSystemMethod"] = [Roles.Designer],
+ ["CreateNotificationList"] = [Roles.Designer],
+ ["UpdateNotificationList"] = [Roles.Designer],
+ ["DeleteNotificationList"] = [Roles.Designer],
+ ["CreateDataConnection"] = [Roles.Designer],
+ ["UpdateDataConnection"] = [Roles.Designer],
+ ["DeleteDataConnection"] = [Roles.Designer],
+ ["MoveDataConnection"] = [Roles.Designer],
+ ["AddTemplateAttribute"] = [Roles.Designer],
+ ["UpdateTemplateAttribute"] = [Roles.Designer],
+ ["DeleteTemplateAttribute"] = [Roles.Designer],
+ ["AddTemplateAlarm"] = [Roles.Designer],
+ ["UpdateTemplateAlarm"] = [Roles.Designer],
+ ["DeleteTemplateAlarm"] = [Roles.Designer],
+ ["AddTemplateNativeAlarmSource"] = [Roles.Designer],
+ ["UpdateTemplateNativeAlarmSource"] = [Roles.Designer],
+ ["DeleteTemplateNativeAlarmSource"] = [Roles.Designer],
+ ["AddTemplateScript"] = [Roles.Designer],
+ ["UpdateTemplateScript"] = [Roles.Designer],
+ ["DeleteTemplateScript"] = [Roles.Designer],
+ ["AddTemplateComposition"] = [Roles.Designer],
+ ["DeleteTemplateComposition"] = [Roles.Designer],
+ ["ResyncInheritedMembers"] = [Roles.Designer],
+ ["CreateSharedScript"] = [Roles.Designer],
+ ["UpdateSharedScript"] = [Roles.Designer],
+ ["DeleteSharedScript"] = [Roles.Designer],
+ ["CreateSharedSchema"] = [Roles.Designer],
+ ["UpdateSharedSchema"] = [Roles.Designer],
+ ["DeleteSharedSchema"] = [Roles.Designer],
+ ["CreateDatabaseConnectionDef"] = [Roles.Designer],
+ ["UpdateDatabaseConnectionDef"] = [Roles.Designer],
+ ["DeleteDatabaseConnectionDef"] = [Roles.Designer],
+ ["CreateApiMethod"] = [Roles.Designer],
+ ["UpdateApiMethod"] = [Roles.Designer],
+ ["DeleteApiMethod"] = [Roles.Designer],
+ ["CreateTemplateFolder"] = [Roles.Designer],
+ ["RenameTemplateFolder"] = [Roles.Designer],
+ ["MoveTemplateFolder"] = [Roles.Designer],
+ ["ReorderTemplateFolder"] = [Roles.Designer],
+ ["DeleteTemplateFolder"] = [Roles.Designer],
+ ["MoveTemplateToFolder"] = [Roles.Designer],
+ ["BrowseNode"] = [Roles.Designer],
+ ["SearchAddressSpace"] = [Roles.Designer],
+ ["VerifyEndpoint"] = [Roles.Designer],
+ ["ExportBundle"] = [Roles.Designer],
+
+ // ---- Deployer-only ----------------------------------------------------------
+ ["CreateInstance"] = [Roles.Deployer],
+ ["MgmtDeployInstance"] = [Roles.Deployer],
+ ["MgmtEnableInstance"] = [Roles.Deployer],
+ ["MgmtDisableInstance"] = [Roles.Deployer],
+ ["MgmtDeleteInstance"] = [Roles.Deployer],
+ ["SetConnectionBindings"] = [Roles.Deployer],
+ ["SetInstanceOverrides"] = [Roles.Deployer],
+ ["SetInstanceArea"] = [Roles.Deployer],
+ ["SetInstanceAlarmOverride"] = [Roles.Deployer],
+ ["DeleteInstanceAlarmOverride"] = [Roles.Deployer],
+ ["SetInstanceNativeAlarmSourceOverride"] = [Roles.Deployer],
+ ["DeleteInstanceNativeAlarmSourceOverride"] = [Roles.Deployer],
+ ["GetDeploymentDiff"] = [Roles.Deployer],
+ ["MgmtDeployArtifacts"] = [Roles.Deployer],
+ ["QueryDeployments"] = [Roles.Deployer],
+ ["RetryParkedMessage"] = [Roles.Deployer],
+ ["DiscardParkedMessage"] = [Roles.Deployer],
+ ["DebugSnapshot"] = [Roles.Deployer],
+
+ // ---- Two-person secured write ----------------------------------------------
+ ["SubmitSecuredWrite"] = [Roles.Operator],
+ ["ApproveSecuredWrite"] = [Roles.Verifier],
+ ["RejectSecuredWrite"] = [Roles.Verifier],
+ ["ListSecuredWrites"] = SecuredWriteReaders,
+
+ // ---- Read-only: any authenticated user (null gate) -------------------------
+ ["ListTemplates"] = null,
+ ["GetTemplate"] = null,
+ ["GetResolvedTemplateMembers"] = null,
+ ["ListTemplateFolders"] = null,
+ ["ListTemplateNativeAlarmSources"] = null,
+ ["ListInstances"] = null,
+ ["GetInstance"] = null,
+ ["ListInstanceAlarmOverrides"] = null,
+ ["ListInstanceNativeAlarmSourceOverrides"] = null,
+ ["ListSites"] = null,
+ ["GetSite"] = null,
+ ["ListAreas"] = null,
+ ["ListDataConnections"] = null,
+ ["GetDataConnection"] = null,
+ ["ListExternalSystems"] = null,
+ ["GetExternalSystem"] = null,
+ ["ListExternalSystemMethods"] = null,
+ ["GetExternalSystemMethod"] = null,
+ ["ListNotificationLists"] = null,
+ ["GetNotificationList"] = null,
+ ["ListSmtpConfigs"] = null,
+ ["ListSmsConfigs"] = null,
+ ["ListSharedScripts"] = null,
+ ["GetSharedScript"] = null,
+ ["ListSharedSchemas"] = null,
+ ["GetSharedSchema"] = null,
+ ["ListDatabaseConnections"] = null,
+ ["GetDatabaseConnection"] = null,
+ ["ListApiMethods"] = null,
+ ["GetApiMethod"] = null,
+ ["GetHealthSummary"] = null,
+ ["GetSiteHealth"] = null,
+ ["QueryEventLogs"] = null,
+ ["QueryParkedMessages"] = null,
+ ["ReadTagValues"] = null,
+ // ResolveRoles is intentionally not dispatched (retired two-step flow); it
+ // falls through to the default null gate. Present here so the frozen table
+ // still covers the type reflected in the assembly.
+ ["ResolveRoles"] = null,
+ };
+
+ public static IEnumerable