docs(health): oldest-Up-member is the active-node rule (0.3.0)

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.
This commit is contained in:
Joseph Doherty
2026-07-24 13:24:30 -04:00
parent a61e041e58
commit 6a4fa220de
5 changed files with 139 additions and 49 deletions
+11 -6
View File
@@ -4,7 +4,7 @@ Health-check libraries for the **ZB.MOM.WW SCADA family** (OtOpcUa, MxAccessGate
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.2.0 and published to the Gitea NuGet feed. Adopted by all four apps** — adoption is tracked in `~/Desktop/scadaproj/components/health/GAPS.md`.
**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`.
---
@@ -13,7 +13,7 @@ The library normalizes the three-tier health endpoint convention (`/health/ready
| 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`), `ActiveNodeHealthCheck` with an optional role filter, and `AkkaActiveNodeGate` that backs `IActiveNodeGate` from cluster member state. | `ZB.MOM.WW.Health`, `Akka.Cluster` |
| `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` |
---
@@ -46,14 +46,19 @@ dotnet test ZB.MOM.WW.Health.slnx
dotnet pack ZB.MOM.WW.Health.slnx -c Release -o ./artifacts
```
All three test assemblies run offline (the cluster-data tests form a single-node in-process Akka cluster on a loopback port):
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` | 39 |
| `ZB.MOM.WW.Health.Akka.Tests` | 45 |
| `ZB.MOM.WW.Health.EntityFrameworkCore.Tests` | 6 |
| **Total** | **70** |
| **Total** | **76** |
`GeneratePackageOnBuild` is off — pack explicitly with the command above.
@@ -61,7 +66,7 @@ All three test assemblies run offline (the cluster-data tests form a single-node
## Status
Built at **0.2.0** and published to the Gitea NuGet feed. **Adopted** by all four apps — adoption is tracked in the component backlog:
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