docs(health): GrpcDependencyHealthCheck has no consumers - keep it, correct SPEC/README (scadaproj#28)

SPEC 2.4 named OtOpcUa and MxGateway as users; neither is (verified against every
app's default branch; MxGateway worker IPC is named pipes, OtOpcUa deliberately
does not gate readiness on gateway channels). Keep the check (removal is a breaking
API change; small, tested building block), mark it as no-current-consumers in SPEC
2.4/6/7, both READMEs, the shared-contract doc and GAPS.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 14:19:09 -05:00
parent 726fbccf54
commit 91fb2b48dd
5 changed files with 26 additions and 20 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ The library normalizes the three-tier health endpoint convention (`/health/ready
| Package | Description | Key Dependencies |
|---|---|---|
| `ZB.MOM.WW.Health` | Core tiers, `MapZbHealth` extension, canonical JSON writer (`ZbHealthWriter`), `IActiveNodeGate` seam, `GrpcDependencyHealthCheck` reachability probe, and tier-tag constants (`ZbHealthTags`). No Akka or EF dependency. | `Microsoft.AspNetCore.App` (framework ref), `Grpc.Net.Client` |
| `ZB.MOM.WW.Health` | Core tiers, `MapZbHealth` extension, canonical JSON writer (`ZbHealthWriter`), `IActiveNodeGate` seam, `GrpcDependencyHealthCheck` reachability probe (available; no app currently uses it), and 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` three-way / `OtOpcUaCompat` two-way), `ActiveNodeHealthCheck` with an optional role filter, and `AkkaActiveNodeGate` that backs `IActiveNodeGate` from the cluster member state. | `ZB.MOM.WW.Health`, `Akka.Cluster` |
| `ZB.MOM.WW.Health.EntityFrameworkCore` | `DatabaseHealthCheck<TContext>` with `CanConnectAsync` by default and an optional `ProbeQuery` delegate for custom connectivity validation. | `ZB.MOM.WW.Health`, `Microsoft.EntityFrameworkCore` |
@@ -20,8 +20,8 @@ The library normalizes the three-tier health endpoint convention (`/health/ready
| Consumer | `ZB.MOM.WW.Health` (core) | `ZB.MOM.WW.Health.Akka` | `ZB.MOM.WW.Health.EntityFrameworkCore` |
|---|:---:|:---:|:---:|
| **OtOpcUa** | yes (+ `GrpcDependencyHealthCheck` for the MxAccessGateway channel) | yes | yes |
| **MxAccessGateway** | yes (+ `GrpcDependencyHealthCheck` for the x86 worker IPC) | — | — |
| **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.
+4 -3
View File
@@ -84,10 +84,11 @@ Checks that can only return **Degraded** (so never cause a 503): OtOpcUa `akka`
failures and reset only on a factory success, which needs an RPC, which a 503-ed pod never receives;
caller cancellation also counted. Fixed on main `5de3dd9`: caller cancellation no longer counts and each keepalive tick probes one session while the count is non-zero; still present on deployed builds until historiangw#78 redeploys. Detail:
[current-state/historiangateway](current-state/historiangateway/CURRENT-STATE.md).
- **SPEC text is stale on §2.4.** It names OtOpcUa and MxGateway as `GrpcDependencyHealthCheck` users;
- **SPEC text was stale on §2.4 - corrected (scadaproj#28).** It named OtOpcUa and MxGateway as `GrpcDependencyHealthCheck` users;
**no app uses it** (HistorianGateway, once cited as a user, now probes the Historian through its
connection pool). The check stays in the library but has zero consumers; the SPEC/README wording should
be corrected when the spec is next revised.
connection pool). Decision: **keep** the check (a breaking public-API removal for no gain; small, unit-tested,
a ready building block if a gRPC dependency ever needs a readiness probe) but document it as having no current
consumers. SPEC §2.4/§6/§7, both READMEs and the shared-contract doc now say so.
## Deferred items — status from code (2026-11-09)
+4 -4
View File
@@ -40,8 +40,8 @@ below describe the original 2026-06 design rationale.
writer wiring needed).
- `IActiveNodeGate` seam — generalized from ScadaBridge's `ActiveNodeGate`; wired into `MapZbHealth`
for automatic active-tier response.
- `GrpcDependencyHealthCheck` — reachability probe for a downstream gRPC dependency (covers
OtOpcUa → MxAccessGateway channel and MxAccessGateway → worker IPC).
- `GrpcDependencyHealthCheck` — reachability probe for a downstream gRPC dependency. Shipped but
**currently has no consumers** (neither OtOpcUa nor MxAccessGateway adopted it; scadaproj#28).
- `AkkaClusterHealthCheck` (in `ZB.MOM.WW.Health.Akka`) with a configurable status policy.
Default = ScadaBridge's three-way policy; `OtOpcUaCompat` preset preserves OtOpcUa's two-way
self-Up-among-members scan.
@@ -62,7 +62,7 @@ below describe the original 2026-06 design rationale.
- MxAccessGateway's `GatewayHealthReply` metadata (`DefaultBackend`, `WorkerProtocolVersion`) —
keep as a bespoke `/info` endpoint.
- The x86 worker process — out of process and out of scope; the gateway-side
`GrpcDependencyHealthCheck` observes it indirectly.
no probe observes it directly (worker IPC is named pipes, not gRPC).
## Package structure
@@ -70,7 +70,7 @@ below describe the original 2026-06 design rationale.
| Package | Contents | Consumers |
|---|---|---|
| `ZB.MOM.WW.Health` | Core tiers, `MapZbHealth`, canonical writer, `IActiveNodeGate`, `GrpcDependencyHealthCheck` | All three |
| `ZB.MOM.WW.Health` | Core tiers, `MapZbHealth`, canonical writer, `IActiveNodeGate`, `GrpcDependencyHealthCheck` (no consumers) | 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 |
@@ -31,8 +31,8 @@ into each app — there is no central health service. Consumers reference only w
| `…Health.EntityFrameworkCore` | EF Core | — | ✅ | ✅ |
**Why MxGateway takes only core:** it is not Akka-based and does not use EF Core. The
`GrpcDependencyHealthCheck` in the core package covers its only probe need (worker channel
reachability), so it avoids the Akka and EF transitive trees entirely.
Core is all it needs, so it avoids the Akka and EF transitive trees entirely. (An earlier draft said
`GrpcDependencyHealthCheck` would cover a worker-channel probe; the worker IPC is named pipes, so no app uses it - scadaproj#28.)
## `ZB.MOM.WW.Health`
@@ -277,7 +277,7 @@ public sealed class DatabaseHealthCheckOptions<TContext>
| Consumer | Packages | Notes |
|---|---|---|
| **MxGateway** | `ZB.MOM.WW.Health` (core only) | `GrpcDependencyHealthCheck` on the worker channel; all three tiers via `MapZbHealth()`; `IActiveNodeGate` not needed (not Akka-based) |
| **MxGateway** | `ZB.MOM.WW.Health` (core only) | no gRPC-dependency probe (worker IPC is named pipes); all three tiers via `MapZbHealth()`; `IActiveNodeGate` not needed (not Akka-based) |
| **OtOpcUa** | All three | `AkkaClusterHealthCheck` + `OtOpcUaCompat` preset → `Default` on convergence; `ActiveNodeHealthCheck(role: "admin")`; `DatabaseHealthCheck<T>` with `ProbeQuery` delegate |
| **ScadaBridge** | All three | `AkkaClusterHealthCheck` + `Default` policy; `ActiveNodeHealthCheck` (role-less); `DatabaseHealthCheck<T>` default probe; `AkkaActiveNodeGate` replaces inline `ActiveNodeGate` |
+12 -7
View File
@@ -15,7 +15,7 @@ OtOpcUa and ScadaBridge; the `ClusterActiveNode` oldest-`Up`-member rule and the
`OldestNodeActiveHealthCheck` — the two private replacements each app wrote after the original
leader-based check proved wrong on a live rig; a generic
`DatabaseHealthCheck<TContext>` that covers both apps' EF Core probe patterns; a
`GrpcDependencyHealthCheck` for downstream gRPC reachability.
`GrpcDependencyHealthCheck` for downstream gRPC reachability (shipped, currently no consumers - see §2.4).
**Explicitly NOT normalized** (domain-specific — keep per project): which probes each app
registers and how it wires them to tags; orchestrator / Traefik routing rules and routing priorities;
@@ -123,12 +123,17 @@ payload alone.
Checks that a downstream gRPC channel is reachable by invoking a caller-supplied probe
delegate (`Func<GrpcChannel, CancellationToken, Task<bool>>`). The default probe calls
`GrpcChannel.ConnectAsync`. Used by:
`GrpcChannel.ConnectAsync`.
- OtOpcUa — checks the MxAccessGateway gRPC channel.
- MxGateway — checks the x86 worker gRPC channel.
**Current consumers: none** (verified 2026-11-09 against every app's default branch). Earlier drafts
named OtOpcUa (MxAccessGateway channel) and MxGateway (x86 worker channel) as users, but neither adopted it:
MxGateway's worker IPC is named pipes, not gRPC, and workers exist only per session; OtOpcUa creates its
gateway channels per driver from DB config and deliberately keeps serving last-known-good when a source is
down, so it does not gate readiness on them (see `GAPS.md`, "Downstream dependency probes"). The check is
retained in the library as an available, unit-tested building block for a future gRPC dependency; it is not
part of any app's convergence target.
Registered to the `ready` tag.
If used, register it on the `ready` tag.
## 3. Response-writer contract
@@ -219,14 +224,14 @@ predicates and response writer.
|---|---|---|---|
| **OtOpcUa** | All three tiers present (`/health/ready`, `/health/active`, `/healthz`); `DatabaseHealthCheck`, `AkkaClusterHealthCheck`, `AdminRoleLeaderHealthCheck` inline. | Inline probes diverge from the shared policy model; no `IActiveNodeGate`. | Replace inline `AkkaClusterHealthCheck` with shared + `OtOpcUaCompat` preset; replace `AdminRoleLeaderHealthCheck` with shared `ActiveNodeHealthCheck(role: "admin")`; replace inline `DatabaseHealthCheck` with shared generic; call `app.MapZbHealth()`. |
| **ScadaBridge** | `/health/ready` + `/health/active` present; no `/healthz`; `DatabaseHealthCheck`, `AkkaClusterHealthCheck`, `ActiveNodeHealthCheck`, `ActiveNodeGate` inline. | Missing `/healthz` live tier; inline implementations. | Add `/healthz` via `MapZbHealth()`; replace inline probes with shared equivalents (Default policy); replace inline `ActiveNodeGate` with `AkkaActiveNodeGate`. |
| **MxGateway** | Only `/health/live` (custom `GatewayHealthReply`); `AddHealthChecks()` called but zero probes registered. | Missing `ready` and `active` tiers; no probes; not using standard health middleware. | Replace custom endpoint with `app.MapZbHealth()`; register `GrpcDependencyHealthCheck` for the x86 worker channel on the `ready` tag. |
| **MxGateway** | Only `/health/live` (custom `GatewayHealthReply`); `AddHealthChecks()` called but zero probes registered. | Missing `ready` and `active` tiers; no probes; not using standard health middleware. | Replace custom endpoint with `app.MapZbHealth()`; no gRPC-dependency probe (the worker IPC is named pipes - see §2.4 / GAPS). |
## 7. Acceptance (what "converged" means)
A project is converged when: (a) it calls `app.MapZbHealth()` and exposes all three canonical
endpoints; (b) its Akka probes (if applicable) use the `AkkaClusterHealthCheck` + `ActiveNodeHealthCheck`
from `ZB.MOM.WW.Health.Akka` with the Default policy; (c) its DB probe uses `DatabaseHealthCheck<TContext>`
from `ZB.MOM.WW.Health.EntityFrameworkCore`; (d) its gRPC-dependency probe (if applicable) uses
from `ZB.MOM.WW.Health.EntityFrameworkCore`; (d) its gRPC-dependency probe (if it has one; none currently do) uses
`GrpcDependencyHealthCheck`; (e) its `IActiveNodeGate` implementation is `AkkaActiveNodeGate`
(or a project-specific implementation of the shared interface); (f) all health endpoints return the
canonical JSON shape defined in §3.