Rewrites the active-node sections of the spec and shared contract, which described the leader-based design that both consumers rejected. - SPEC §2.3 replaced: the rule, the ordered RolePreference, why a node holding none of the preferred roles is Unhealthy rather than Healthy, and the data payload. §4 (IActiveNodeGate) and the tier table follow. - SPEC §6 marked historical: it still tells adopters to use ActiveNodeHealthCheck(role: "admin") and AkkaActiveNodeGate, which is the advice that produced the defect. Kept for provenance, annotated so nobody follows it again. - GAPS: gaps L1/L2 recorded as closed the wrong way, reopened, then closed at 0.3.0 — including the part worth remembering, that both apps adopted the shared check as specified, hit it live, and reverted to private copies, so the surface reached zero consumers by being avoided rather than unused. - Index rows updated with the consumer-visible changes and both app branches.
Health (readiness / liveness / active-node)
Second normalized component under the operability cluster. Goal: path to shared code — converge
the three sister projects onto a common three-tier health endpoint convention and a set of shared
probe implementations, proposed as the ZB.MOM.WW.Health library set (3 packages), while each
project keeps its own probe registration and orchestrator wiring.
- The one target:
spec/SPEC.md - The proposed shared library:
shared-contract/ZB.MOM.WW.Health.md - Divergences + backlog:
GAPS.md - Current state, per project:
current-state/
Why health is a strong normalization candidate
Both OtOpcUa and ScadaBridge trace their health-check structure to the same "ScadaLink three-tier
pattern" (HealthEndpoints.cs:13 says so explicitly) but have already diverged in probe logic,
status semantics, response writer, and endpoint registration style. MxAccessGateway has no shared
ancestry here — it has a single hardcoded /health/live endpoint with no real probes at all.
The common core (three tiers, database probe, Akka cluster probe, active-node probe) is
re-implemented twice and absent once. Shared probe implementations with configurable policies
close the gap without forcing identical behavior onto projects with legitimately different cluster
semantics.
Status by project
| Project | Endpoints today | Probes today | Response writer | /healthz |
IActiveNodeGate |
Adoption status |
|---|---|---|---|---|---|---|
| OtOpcUa | /health/ready, /health/active, /healthz |
Database (query), AkkaCluster (2-way), AdminRoleLeader (role-filtered) | Default (plain-text/JSON) | ✅ present | — | Not started |
| MxAccessGateway | /health/live only (raw MapGet; hardcoded "Healthy") |
None (AddHealthChecks() called but unused) |
Bespoke GatewayHealthReply JSON |
⛔ absent | — | Not started |
| ScadaBridge | /health/ready, /health/active |
Database (CanConnectAsync), AkkaCluster (3-way), ActiveNode (role-less) |
HealthChecks.UI.Client JSON |
⛔ absent | ActiveNodeGate (backs Inbound API 503 gate) |
Not started |
See each project's current-state/<project>/CURRENT-STATE.md for the
code-verified detail and its adoption plan.
Normalized vs. left per-project
Normalized (the shared target):
- Three-tier endpoint convention:
/health/ready(tagready),/health/active(tagactive),/healthz(bare liveness). Mapped byapp.MapZbHealth()fromZB.MOM.WW.Health. - Canonical JSON response writer (lifted from
HealthChecks.UI.Clientstyle; no per-project writer wiring needed). IActiveNodeGateseam — generalized from ScadaBridge'sActiveNodeGate; wired intoMapZbHealthfor automatic active-tier response.GrpcDependencyHealthCheck— reachability probe for a downstream gRPC dependency (covers OtOpcUa → MxAccessGateway channel and MxAccessGateway → worker IPC).AkkaClusterHealthCheck(inZB.MOM.WW.Health.Akka) with a configurable status policy. Default = ScadaBridge's three-way policy;OtOpcUaCompatpreset preserves OtOpcUa's two-way self-Up-among-members scan.ActiveNodeHealthCheck(inZB.MOM.WW.Health.Akka) with an optional role filter. Role-less = ScadaBridge's behavior (Up + cluster leader); role-filtered = OtOpcUa'sAdminRoleLeaderbehavior.DatabaseHealthCheck<TContext>(inZB.MOM.WW.Health.EntityFrameworkCore) with defaultCanConnectAsyncand an optionalProbeQuerydelegate.AllowAnonymouson all three tiers by default (consistent across all three projects today).
Left per-project (not forced together):
- Which probes each app registers, their names, and which tags they carry.
- Orchestrator / Traefik wiring (sidecars, route rules, upstreams).
- ScadaBridge's
HealthMonitoring/distributed aggregation pipeline (SiteHealthCollector,CentralHealthAggregator,HealthReportSender, etc.) — domain-specific, no shared-library equivalent. - MxAccessGateway's
GatewayHealthReplymetadata (DefaultBackend,WorkerProtocolVersion) — keep as a bespoke/infoendpoint. - The x86 worker process — out of process and out of scope; the gateway-side
GrpcDependencyHealthCheckobserves it indirectly.
Package structure
ZB.MOM.WW.Health ships as three dependency-split packages:
| Package | Contents | Consumers |
|---|---|---|
ZB.MOM.WW.Health |
Core tiers, MapZbHealth, canonical writer, IActiveNodeGate, GrpcDependencyHealthCheck |
All three |
ZB.MOM.WW.Health.Akka |
AkkaClusterHealthCheck + status presets, ActiveNodeHealthCheck + role filter |
OtOpcUa, ScadaBridge |
ZB.MOM.WW.Health.EntityFrameworkCore |
DatabaseHealthCheck<TContext> + optional probe delegate |
OtOpcUa, ScadaBridge |
MxAccessGateway consumes the core package only (no Akka, no EF). OtOpcUa and ScadaBridge consume all three.
Component status
Status: Draft — library built at 0.1.0. Spec and shared-contract written; current-state docs
verified; GAPS backlog populated. Library implemented and packed at
../../ZB.MOM.WW.Health/ (3 packages, 58 tests;
ZB.MOM.WW.Health, ZB.MOM.WW.Health.Akka, ZB.MOM.WW.Health.EntityFrameworkCore).
Adoption by the three apps is the next follow-on tracked in GAPS.md.