fix(cluster): all four role-scoped validators read the authority — 'Driver' no longer skips every driver rule (archreview 01/C-9)

This commit is contained in:
Joseph Doherty
2026-08-20 09:51:23 -04:00
parent 792e8f422a
commit bcec88ef70
10 changed files with 172 additions and 88 deletions
@@ -15,18 +15,17 @@ namespace ZB.MOM.WW.OtOpcUa.Cluster;
/// </remarks>
public sealed class ConfigSourceOptionsValidator : IValidateOptions<ConfigSourceOptions>
{
private readonly IConfiguration _configuration;
private readonly EffectiveRoles _effectiveRoles;
/// <summary>
/// DI-constructed by <c>AddValidatedOptions</c> (a plain <c>AddSingleton</c>), so a
/// constructor dependency is safe here. <see cref="IConfiguration"/> — not
/// <c>IClusterRoleInfo</c> — is the source of this node's roles: <c>IClusterRoleInfo</c>'s
/// implementation needs the <c>ActorSystem</c>, which does not exist yet at
/// <c>ValidateOnStart</c> time.
/// constructor dependency is safe here. This validator's only use of configuration is this
/// node's roles, so it takes <see cref="EffectiveRoles"/> — the single role authority
/// (archreview R3-04) — directly rather than <see cref="IConfiguration"/>.
/// </summary>
public ConfigSourceOptionsValidator(IConfiguration configuration)
public ConfigSourceOptionsValidator(EffectiveRoles effectiveRoles)
{
_configuration = configuration;
_effectiveRoles = effectiveRoles;
}
/// <inheritdoc />
@@ -93,15 +92,12 @@ public sealed class ConfigSourceOptionsValidator : IValidateOptions<ConfigSource
// Per-cluster mesh Phase 4: a driver-only node has no central ConfigDb connection to fall back
// on, so Direct is only ever valid on a fused admin+driver node (admin holds the SQL
// connection). Roles come from configuration, not IClusterRoleInfo — see the ctor remark — and
// through RoleParser.ResolveEffectiveRoles so the OTOPCUA_ROLES fallback applies (Gitea #532).
// Reading Cluster:Roles alone exempted the exact node this rule exists for: an env-only
// driver-only node has no ConfigDb (Program.cs gates it on the same env var) yet saw an empty
// role list here and sailed through on Direct, to read its configuration from nothing.
var roles = RoleParser.ResolveEffectiveRoles(
_configuration.GetSection("Cluster:Roles").Get<string[]>());
var isDriver = Array.IndexOf(roles, RoleParser.Driver) >= 0;
var isAdmin = Array.IndexOf(roles, RoleParser.Admin) >= 0;
// connection). EffectiveRoles is the single role authority (archreview R3-04): its precedence
// already applies the OTOPCUA_ROLES fallback (Gitea #532) and its normalization already folds
// case, so neither an env-only driver-only node nor one spelled 'Driver' can sail through here
// on Direct to read its configuration from nothing.
var isDriver = _effectiveRoles.HasDriver;
var isAdmin = _effectiveRoles.HasAdmin;
if (isDriver && !isAdmin && isDirect)
{