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
This commit is contained in:
Joseph Doherty
2026-08-07 07:33:47 -04:00
parent c8e90daafb
commit fc784b4137
@@ -187,32 +187,41 @@ public static class SecretsRegistration
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(config);
var enabled = config.GetValue<bool>(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.
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);
}
else
{
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<bool>(ReplicationEnabledKey);
var connectionString = config[HubConnectionStringKey];
if (enabled && !string.IsNullOrWhiteSpace(connectionString))