# ZB.MOM.WW.Health

Health-check libraries for the **ZB.MOM.WW SCADA family** (OtOpcUa, MxAccessGateway, ScadaBridge). These are **libraries, not a service** — each package is linked directly into the consuming application at build time. There is no central health process or network hop; probes run in-process alongside the application.

The library normalizes the three-tier health endpoint convention (`/health/ready`, `/health/active`, `/healthz`) and provides reusable probe implementations so the three sister projects share a common surface without duplicating probe logic.

**Built at 0.3.0 and published to the Gitea NuGet feed. Adopted by all four apps** — adoption is tracked in `~/Desktop/scadaproj/components/health/GAPS.md`.

---

## Packages

| Package | Responsibilities | Key Dependencies |
|---|---|---|
| `ZB.MOM.WW.Health` | Core tier convention, `MapZbHealth` extension, canonical JSON writer (`ZbHealthWriter`), `IActiveNodeGate` seam, `GrpcDependencyHealthCheck` reachability probe, tier-tag constants (`ZbHealthTags`). No Akka or EF dependency. | `Microsoft.AspNetCore.App` (framework ref), `Grpc.Net.Client` |
| `ZB.MOM.WW.Health.Akka` | `AkkaClusterHealthCheck` with a configurable `AkkaClusterStatusPolicy` (presets: `Default` / `OtOpcUaCompat`); `ClusterActiveNode` — the family-wide **oldest-`Up`-member** rule, optionally role-scoped; `ActiveNodeHealthCheck` over it (role-preference list, `activeRole`/`activeNode` in the result `data`); and `AkkaActiveNodeGate`, which backs `IActiveNodeGate` from the same rule. | `ZB.MOM.WW.Health`, `Akka.Cluster` |
| `ZB.MOM.WW.Health.EntityFrameworkCore` | `DatabaseHealthCheck<TContext>` — resolves `IDbContextFactory<TContext>` when registered, else a scoped `TContext`; pool-safe. Default probe: `CanConnectAsync`. Optional `ProbeQuery` delegate for query-based validation. | `ZB.MOM.WW.Health`, `Microsoft.EntityFrameworkCore` |

---

## Consumer matrix

| Consumer | `ZB.MOM.WW.Health` (core) | `ZB.MOM.WW.Health.Akka` | `ZB.MOM.WW.Health.EntityFrameworkCore` |
|---|:---:|:---:|:---:|
| **OtOpcUa** | yes | yes | yes |
| **MxAccessGateway** | yes | — | — |
| **ScadaBridge** | yes | yes | yes |

MxAccessGateway consumes the core package only — it has no Akka cluster and no EF DbContext. OtOpcUa and ScadaBridge consume all three packages.

---

## Build, test, and pack commands

```bash
# From ZB.MOM.WW.Health/

# Build
dotnet build ZB.MOM.WW.Health.slnx
dotnet build ZB.MOM.WW.Health.slnx -c Release

# Test (no external dependencies — no running Akka cluster, no database)
dotnet test ZB.MOM.WW.Health.slnx

# Pack (three .nupkg files land in artifacts/)
dotnet pack ZB.MOM.WW.Health.slnx -c Release -o ./artifacts
```

All three test assemblies run offline. The Akka suite forms real in-process clusters on loopback: a
single-node one for the cluster-data payload, and a **two-node one built so the oldest member is not
the lowest-addressed one** — the only condition under which age- and leader-ordering disagree, and so
the only fixture that can catch a leader-based active-node regression. It binds fixed ports
19540/19541 (distinct from OtOpcUa's equivalent fixture) and carries a sanity check that the
divergence actually occurred, so the assertions cannot pass for the wrong reason:

| Assembly | Tests |
|---|---|
| `ZB.MOM.WW.Health.Tests` | 25 |
| `ZB.MOM.WW.Health.Akka.Tests` | 45 |
| `ZB.MOM.WW.Health.EntityFrameworkCore.Tests` | 6 |
| **Total** | **76** |

`GeneratePackageOnBuild` is off — pack explicitly with the command above.

---

## Status

Built at **0.3.0** and published to the Gitea NuGet feed. **Adopted** by all four apps — adoption is tracked in the component backlog:

- `~/Desktop/scadaproj/components/health/GAPS.md` — adoption order, effort, and risk

Design documentation:

- `~/Desktop/scadaproj/components/health/spec/SPEC.md` — normalized three-tier target
- `~/Desktop/scadaproj/components/health/shared-contract/ZB.MOM.WW.Health.md` — proposed API (aligned to shipped code)
- `~/Desktop/scadaproj/components/health/current-state/` — per-project current state (code-verified)
