From fc784b413713b1643a21d573e55c00bcc05bd8ee Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 7 Aug 2026 07:33:47 -0400 Subject: [PATCH] fix(secrets): registration and hub mapping share the UsesGrpcHub predicate (review Important) AddScadaBridgeSecrets now branches on UsesGrpcHub instead of re-deriving the same condition, so the doc's single-predicate claim is enforced rather than aspirational; the role split is a switch with a default-throw so a future third role must choose its hub half explicitly. Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1 --- .../SecretsRegistration.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs index a3ee18c7..c96f7f90 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs @@ -187,32 +187,41 @@ public static class SecretsRegistration ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(config); - var enabled = config.GetValue(ReplicationEnabledKey); - var mode = ResolveReplicationMode(config); - - if (enabled && mode == SecretsReplicationMode.Grpc) + // UsesGrpcHub is the single predicate shared with MapScadaBridgeSecretsHub — the branch + // condition must never be re-derived here, or registration and endpoint mapping could + // disagree about whether the hub is on. + if (UsesGrpcHub(config)) { // Neither gRPC extension registers the local store — pull-only means local writes // never leave the node, so there is nothing to decorate and the store stays exactly as // configured. Register it first; both halves resolve it. services.AddZbSecrets(config, SecretsSectionPath); - if (role == SecretsNodeRole.Central) + switch (role) { - // Throws when Secrets:GrpcHub:BearerToken is unset. Deliberately not caught: a hub - // that starts and refuses every follower looks like a network fault from the site - // end, and would be discovered as a stale secret rather than as a boot failure. - services.AddZbSecretsGrpcHub(config, GrpcHubSectionPath); - } - else - { - // Throws when Secrets:GrpcHub:Endpoint or :BearerToken is unset. Same reasoning. - services.AddZbSecretsGrpcHubClient(config, GrpcHubSectionPath); + case SecretsNodeRole.Central: + // Throws when Secrets:GrpcHub:BearerToken is unset. Deliberately not caught: a + // hub that starts and refuses every follower looks like a network fault from + // the site end, and would be discovered as a stale secret rather than as a + // boot failure. + services.AddZbSecretsGrpcHub(config, GrpcHubSectionPath); + break; + case SecretsNodeRole.Site: + // Throws when Secrets:GrpcHub:Endpoint or :BearerToken is unset. Same reasoning. + services.AddZbSecretsGrpcHubClient(config, GrpcHubSectionPath); + break; + default: + throw new ArgumentOutOfRangeException(nameof(role), role, + "Unknown SecretsNodeRole — a new role must decide explicitly which half " + + "of the gRPC hub it composes rather than fall into either branch."); } return services; } + // Reached with the mode already validated (UsesGrpcHub resolves it on every path), so a + // false answer here means SqlServer mode or replication off — never an unvetted mode value. + var enabled = config.GetValue(ReplicationEnabledKey); var connectionString = config[HubConnectionStringKey]; if (enabled && !string.IsNullOrWhiteSpace(connectionString))