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 | | 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.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` | | `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` | | 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 | | **OtOpcUa** | yes | yes | yes |
| **MxAccessGateway** | yes (+ `GrpcDependencyHealthCheck` for the x86 worker IPC) | — | — | | **MxAccessGateway** | yes | — | — |
| **ScadaBridge** | yes | yes | 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. 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; 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: 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). [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 **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 connection pool). Decision: **keep** the check (a breaking public-API removal for no gain; small, unit-tested,
be corrected when the spec is next revised. 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) ## 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). writer wiring needed).
- `IActiveNodeGate` seam — generalized from ScadaBridge's `ActiveNodeGate`; wired into `MapZbHealth` - `IActiveNodeGate` seam — generalized from ScadaBridge's `ActiveNodeGate`; wired into `MapZbHealth`
for automatic active-tier response. for automatic active-tier response.
- `GrpcDependencyHealthCheck` — reachability probe for a downstream gRPC dependency (covers - `GrpcDependencyHealthCheck` — reachability probe for a downstream gRPC dependency. Shipped but
OtOpcUa → MxAccessGateway channel and MxAccessGateway → worker IPC). **currently has no consumers** (neither OtOpcUa nor MxAccessGateway adopted it; scadaproj#28).
- `AkkaClusterHealthCheck` (in `ZB.MOM.WW.Health.Akka`) with a configurable status policy. - `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 Default = ScadaBridge's three-way policy; `OtOpcUaCompat` preset preserves OtOpcUa's two-way
self-Up-among-members scan. self-Up-among-members scan.
@@ -62,7 +62,7 @@ below describe the original 2026-06 design rationale.
- MxAccessGateway's `GatewayHealthReply` metadata (`DefaultBackend`, `WorkerProtocolVersion`) — - MxAccessGateway's `GatewayHealthReply` metadata (`DefaultBackend`, `WorkerProtocolVersion`) —
keep as a bespoke `/info` endpoint. keep as a bespoke `/info` endpoint.
- The x86 worker process — out of process and out of scope; the gateway-side - 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 ## Package structure
@@ -70,7 +70,7 @@ below describe the original 2026-06 design rationale.
| Package | Contents | Consumers | | 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.Akka` | `AkkaClusterHealthCheck` + status presets, `ActiveNodeHealthCheck` + role filter | OtOpcUa, ScadaBridge |
| `ZB.MOM.WW.Health.EntityFrameworkCore` | `DatabaseHealthCheck<TContext>` + optional probe delegate | 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 | — | ✅ | ✅ | | `…Health.EntityFrameworkCore` | EF Core | — | ✅ | ✅ |
**Why MxGateway takes only core:** it is not Akka-based and does not use EF Core. The **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 Core is all it needs, so it avoids the Akka and EF transitive trees entirely. (An earlier draft said
reachability), so it avoids the Akka and EF transitive trees entirely. `GrpcDependencyHealthCheck` would cover a worker-channel probe; the worker IPC is named pipes, so no app uses it - scadaproj#28.)
## `ZB.MOM.WW.Health` ## `ZB.MOM.WW.Health`
@@ -277,7 +277,7 @@ public sealed class DatabaseHealthCheckOptions<TContext>
| Consumer | Packages | Notes | | 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 | | **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` | | **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 `OldestNodeActiveHealthCheck` — the two private replacements each app wrote after the original
leader-based check proved wrong on a live rig; a generic leader-based check proved wrong on a live rig; a generic
`DatabaseHealthCheck<TContext>` that covers both apps' EF Core probe patterns; a `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 **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; 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 Checks that a downstream gRPC channel is reachable by invoking a caller-supplied probe
delegate (`Func<GrpcChannel, CancellationToken, Task<bool>>`). The default probe calls delegate (`Func<GrpcChannel, CancellationToken, Task<bool>>`). The default probe calls
`GrpcChannel.ConnectAsync`. Used by: `GrpcChannel.ConnectAsync`.
- OtOpcUa — checks the MxAccessGateway gRPC channel. **Current consumers: none** (verified 2026-11-09 against every app's default branch). Earlier drafts
- MxGateway — checks the x86 worker gRPC channel. 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 ## 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()`. | | **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`. | | **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) ## 7. Acceptance (what "converged" means)
A project is converged when: (a) it calls `app.MapZbHealth()` and exposes all three canonical 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` 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.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` `GrpcDependencyHealthCheck`; (e) its `IActiveNodeGate` implementation is `AkkaActiveNodeGate`
(or a project-specific implementation of the shared interface); (f) all health endpoints return the (or a project-specific implementation of the shared interface); (f) all health endpoints return the
canonical JSON shape defined in §3. canonical JSON shape defined in §3.