Audited the registration and mapping code of each app at its origin default branch: OtOpcUa c904472a, MxAccessGateway de86f8b8, ScadaBridge c7d868c5, HistorianGateway 4d6a9ad (all on Health 0.3.0, all MapZbHealth with default paths: /healthz runs nothing, /health/ready and /health/active by tag). GAPS.md now carries the verified per-endpoint coverage matrix and a SPEC-by-app table; each current-state doc lists exactly what each endpoint runs, and HistorianGateway gets its first current-state doc. Recorded: mxaccessgw#206 (worker-executable + galaxy-sql readiness checks, on /health/ready) is merged; historiangw#87 (session-pool check latches 503 until restart) is still open at 4d6a9ad. Deferred P2 downstream probes are now per-app; ScadaBridge IActiveNodeGate unification stays deferred (shared rule already used, only the interface is local). Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
4.7 KiB
Health — current state: OtOpcUa
Verified 2026-11-09 at lmxopcua@c904472a (origin/master) for scadaproj#11. Read-only git grep /
git show against the origin default branch; all paths relative to the repo root (~/Desktop/OtOpcUa).
The pre-adoption state (verified 2026-06-01: three bespoke checks incl. AdminRoleLeaderHealthCheck,
default writer) is gone; see git history of this file.
- Packages:
ZB.MOM.WW.Health,.Akka,.EntityFrameworkCoreall 0.3.0 (Directory.Packages.props:211,219,220; referenced bysrc/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj:37-39;.Akkaalso byControlPlaneandRuntime, which useClusterActiveNodefor the redundancy election). - Registration:
builder.Services.AddOtOpcUaHealth(hasAdmin, hasDriver)(src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs:581) →src/Server/ZB.MOM.WW.OtOpcUa.Host/Health/HealthEndpoints.cs:40-130. The check set depends on the node's roles. - Mapping:
app.MapOtOpcUaHealth()(Program.cs:767) →app.MapZbHealth()with defaults (HealthEndpoints.cs:135).
Per-endpoint contents (by node role)
| Endpoint | admin-only node | fused admin+driver | driver-only node |
|---|---|---|---|
/healthz |
none | none | none |
/health/ready |
configdb, akka |
opcua-endpoint, configdb, akka |
opcua-endpoint, akka |
/health/active |
configdb, akka, cluster-primary, localdb-replication |
configdb, akka, cluster-primary, localdb-replication |
opcua-endpoint, akka, cluster-primary, localdb-replication |
opcua-endpoint is deliberately not on the fused node's active tier: Traefik routes the AdminUI by
/health/active, and a failed SDK start must not take the operator console out of rotation; OPC UA
failover is handled by the Runtime (ServiceLevel 0 + OpcUaEndpointStepDownActor, lmxopcua#762)
(HealthEndpoints.cs:44-65).
Checks
| Name | Class | Tags | Probes | Failure |
|---|---|---|---|---|
opcua-endpoint (driver nodes) |
OpcUaEndpointHealthCheck (host, Health/OpcUaEndpointHealthCheck.cs) |
ready (+ active on driver-only) | IOpcUaEndpointState.Status: Started healthy |
Unhealthy on NotStarted / Stopped / failed |
configdb (admin nodes) |
shared DatabaseHealthCheck<OtOpcUaConfigDbContext> |
ready, active | ProbeQuery = Deployments.AsNoTracking().Take(1) (implies schema applied) |
Unhealthy |
akka |
shared AkkaClusterHealthCheck, policy OtOpcUaCompat |
ready, active | self Up among reachable members |
Degraded only (never 503) |
cluster-primary |
shared ActiveNodeHealthCheck, RolePreference = [admin, driver] |
active | oldest Up member of the first preferred role held |
Unhealthy on standby and on a node holding neither role; Degraded while the ActorSystem/cluster is not yet available |
localdb-replication |
LocalDbReplicationHealthCheck (host) |
active | ISyncStatus connected + oplog backlog < 100 000 |
Degraded only; Healthy when LocalDb or replication is absent/off |
Driver-only nodes register no configdb check: they hold no ConfigDb (FetchAndCache from central), and the
type-activated check would throw resolving the factory.
Active-node gating
- Probe:
cluster-primaryabove — the sharedClusterActiveNoderule. Per-cluster meshes (Phase 6) make "oldestUpmember" already per-Cluster, so exactly one node perClusteranswers 200.RedundancyStateActor's Primary election routes through the same primitive, so the tier and the OPC UA ServiceLevel 250/240 split cannot disagree. - Request gate: none. OtOpcUa registers no
IActiveNodeGateand uses noRequireActiveNode(); the AdminUI is pinned to the active node by Traefik routing on/health/active. - Extra probes on the active tier:
configdb(Unhealthy) — a ConfigDb outage makes the admin pair answer 503 on both nodes, so Traefik has no AdminUI backend until it recovers.akkaandlocaldb-replicationcan only Degrade, so they never change routing.
Other endpoints
MapOtOpcUaMetrics(); mesh gRPC services (MapOtOpcUaMeshGrpcServices). No bespoke health endpoint.
vs SPEC
| SPEC | Status | Note |
|---|---|---|
§1 three tiers via MapZbHealth, canonical tags |
✅ | |
§1 nothing on live |
✅ | |
§2.1 DatabaseHealthCheck<T> |
✅ | ProbeQuery variant |
| §2.2 / §7(b) Akka probe with Default policy | 🟡 | still OtOpcUaCompat (Degraded-only); see GAPS A1 |
§2.3 / §7(b) ActiveNodeHealthCheck |
✅ | role-preference mode |
| §2.4 downstream (mxaccessgw) gRPC probe | ⛔ not used | gateway channels are per-driver, created from DB config; see GAPS P2 |
| §3 canonical JSON writer | ✅ | |
§4 / §7(e) IActiveNodeGate |
n/a | no gated endpoints; Traefik routes by the probe |