fix(host): OPC UA endpoint probe is Ready-only on a fused admin+driver node

Coordinator ruling (I6): until ServiceLevel consumes IOpcUaEndpointState (F-C1), the probe must not sit on
the Active tier of a fused node, because Traefik routes the AdminUI on /health/active. Driver-only nodes keep
it on Ready + Active; admin-only nodes do not register it. Pinned per role.

Refs #733
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 13:38:03 -04:00
parent c0a0e7aa81
commit 88268ea3a6
3 changed files with 36 additions and 16 deletions
@@ -50,31 +50,38 @@ public sealed class ActiveTierRegistrationTests
}
/// <summary>
/// R03:2.1 — the OPC UA endpoint probe is on BOTH tiers of a driver node (it cannot serve, and must not
/// be routed to) and absent from an admin-only node, which starts no endpoint to be degraded by.
/// R03:2.1 + fix round 1 / I6 (coordinator ruling) — the OPC UA endpoint probe's tiers by role:
/// <list type="bullet">
/// <item>driver-only: Ready + Active (it cannot serve and must not be routed to);</item>
/// <item>fused admin+driver: Ready ONLY, until the ServiceLevel calculator consumes the fact
/// (F-C1) — Traefik routes the AdminUI on <c>/health/active</c>, so a failed SDK on the active
/// central node must not take the AdminUI out of rotation;</item>
/// <item>admin-only: absent (it starts no endpoint to be degraded by).</item>
/// </list>
/// </summary>
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Opcua_endpoint_probe_is_registered_on_ready_and_active_for_driver_nodes_only(bool hasDriver)
[InlineData(false, true, true, true)]
[InlineData(true, true, true, false)]
[InlineData(true, false, false, false)]
public void Opcua_endpoint_probe_tiers_follow_the_node_roles(bool hasAdmin, bool hasDriver, bool ready, bool active)
{
var registrations = new ServiceCollection()
.AddOtOpcUaHealth(hasAdmin: true, hasDriver: hasDriver)
.AddOtOpcUaHealth(hasAdmin: hasAdmin, hasDriver: hasDriver)
.BuildServiceProvider()
.GetRequiredService<IOptions<HealthCheckServiceOptions>>()
.Value.Registrations
.Where(r => r.Name == OpcUaEndpointHealthCheck.Name)
.ToList();
if (!hasDriver)
if (!ready && !active)
{
registrations.ShouldBeEmpty();
return;
}
var registration = registrations.ShouldHaveSingleItem();
registration.Tags.ShouldContain(ZbHealthTags.Ready);
registration.Tags.ShouldContain(ZbHealthTags.Active);
registration.Tags.Contains(ZbHealthTags.Ready).ShouldBe(ready);
registration.Tags.Contains(ZbHealthTags.Active).ShouldBe(active);
}
[Fact]