fix(host): Program.cs derives roles from the one authority, not OTOPCUA_ROLES alone (archreview 01/S-12 = 03/C5)

This commit is contained in:
Joseph Doherty
2026-08-20 09:46:27 -04:00
parent 7dfd6dcc9f
commit 792e8f422a
2 changed files with 16 additions and 9 deletions
@@ -19,7 +19,7 @@
{
"id": "T3",
"subject": "Program.cs derives hasAdmin/hasDriver/roleSuffix/banner from the authority, not OTOPCUA_ROLES alone (01/S-12 = 03/C5)",
"status": "pending",
"status": "completed",
"blockedBy": [
"T2"
]
+15 -8
View File
@@ -57,13 +57,19 @@ using ZB.MOM.WW.Secrets.DependencyInjection;
using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet;
using ZB.MOM.WW.Secrets.Sqlite;
// Roles drive the entire conditional wiring below — see ZB.MOM.WW.OtOpcUa.Cluster.RoleParser.
var roles = RoleParser.Parse(Environment.GetEnvironmentVariable("OTOPCUA_ROLES"));
var hasAdmin = roles.Contains("admin");
var hasDriver = roles.Contains("driver");
var builder = WebApplication.CreateBuilder(args);
// The SINGLE role authority: Cluster:Roles first, OTOPCUA_ROLES as the fallback (archreview
// 01/S-12 = 03/C5, R3-04). Resolved off builder.Configuration BEFORE the per-role overlay is
// added, because the overlay's FILENAME depends on the answer — which is why a role overlay may
// not itself set Cluster:Roles (AkkaClusterOptionsValidator fails start if one ever does).
// EffectiveRoles drives the entire conditional wiring below — see ZB.MOM.WW.OtOpcUa.Cluster.
var effectiveRoles = EffectiveRoles.Resolve(builder.Configuration);
var roles = effectiveRoles.Gossiped;
var hasAdmin = effectiveRoles.HasAdmin;
var hasDriver = effectiveRoles.HasDriver;
builder.Services.AddSingleton(effectiveRoles);
// Razor class library static assets (_content/<libname>/...) only auto-enable in
// the Development environment. Opt in explicitly so the AdminUI's CSS/JS works
// regardless of ASPNETCORE_ENVIRONMENT.
@@ -77,7 +83,7 @@ builder.Host.UseWindowsService(options => options.ServiceName = "OtOpcUaHost");
// Per-role appsettings overlay: appsettings.{role}.json (single role) or appsettings.admin-driver.json
// (both). Optional — base appsettings.json carries enough to boot if these don't exist.
var roleSuffix = roles.Length == 0 ? null : string.Join('-', roles.OrderBy(r => r, StringComparer.Ordinal));
var roleSuffix = roles.Count == 0 ? null : string.Join('-', roles.OrderBy(r => r, StringComparer.Ordinal));
if (roleSuffix is not null)
{
builder.Configuration.AddJsonFile($"appsettings.{roleSuffix}.json", optional: true, reloadOnChange: true);
@@ -786,8 +792,9 @@ if (hasDriver && telemetryListenPort > 0)
app.MapOtOpcUaHealth();
app.MapOtOpcUaMetrics();
Log.Information("OtOpcUa.Host starting with roles=[{Roles}] (admin={HasAdmin}, driver={HasDriver})",
string.Join(",", roles), hasAdmin, hasDriver);
Log.Information(
"OtOpcUa.Host starting with roles=[{Roles}] (admin={HasAdmin}, driver={HasDriver}) source={Source}",
string.Join(",", roles), hasAdmin, hasDriver, effectiveRoles.Source);
// #561 — startup growth self-check: the banner above went through the live pipeline; verify each
// configured file sink physically grew. A sink whose writes silently fail (the wonder-app-vd03