From 76295695eefdea935fa1fca68c266e6ac7af2281 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 1 Jun 2026 07:17:18 -0400 Subject: [PATCH] docs(health): align shared-contract to shipped API + per-lib CLAUDE.md + cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Contract: DatabaseHealthCheck ctor now shows IServiceProvider (resolves IDbContextFactory when registered, else a scoped TContext; pool-safe) - Contract: RequireActiveNode gains retryAfterSeconds = 5 default parameter - Packages: remove dangling AspNetCore.HealthChecks.UI.Client PackageVersion (no csproj referenced it) - Tests: fix CS8625 in RoleLessCases — use object?[] so null role rows compile warning-free under Nullable=enable - Add ZB.MOM.WW.Health/CLAUDE.md (packages, responsibilities, consumer matrix, build/test/pack commands, status + pointer to components/health/) --- ZB.MOM.WW.Health/CLAUDE.md | 72 +++++++++++++++++++ ZB.MOM.WW.Health/Directory.Packages.props | 1 - .../ActiveNodeDecisionTests.cs | 10 +-- .../shared-contract/ZB.MOM.WW.Health.md | 6 +- 4 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 ZB.MOM.WW.Health/CLAUDE.md diff --git a/ZB.MOM.WW.Health/CLAUDE.md b/ZB.MOM.WW.Health/CLAUDE.md new file mode 100644 index 0000000..10b2633 --- /dev/null +++ b/ZB.MOM.WW.Health/CLAUDE.md @@ -0,0 +1,72 @@ +# 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.1.0. NOT yet adopted by the three 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`), `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.EntityFrameworkCore` | `DatabaseHealthCheck` — resolves `IDbContextFactory` 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: + +| Assembly | Tests | +|---|---| +| `ZB.MOM.WW.Health.Tests` | 20 | +| `ZB.MOM.WW.Health.Akka.Tests` | 32 | +| `ZB.MOM.WW.Health.EntityFrameworkCore.Tests` | 6 | +| **Total** | **58** | + +`GeneratePackageOnBuild` is off — pack explicitly with the command above. + +--- + +## Status + +Built at **0.1.0** and published to the Gitea NuGet feed. **Not yet adopted** by the three 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) diff --git a/ZB.MOM.WW.Health/Directory.Packages.props b/ZB.MOM.WW.Health/Directory.Packages.props index b49a06a..34e9b88 100644 --- a/ZB.MOM.WW.Health/Directory.Packages.props +++ b/ZB.MOM.WW.Health/Directory.Packages.props @@ -11,7 +11,6 @@ - diff --git a/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Akka.Tests/ActiveNodeDecisionTests.cs b/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Akka.Tests/ActiveNodeDecisionTests.cs index 7659ba0..32b6063 100644 --- a/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Akka.Tests/ActiveNodeDecisionTests.cs +++ b/ZB.MOM.WW.Health/tests/ZB.MOM.WW.Health.Akka.Tests/ActiveNodeDecisionTests.cs @@ -14,12 +14,12 @@ namespace ZB.MOM.WW.Health.Akka.Tests; public sealed class ActiveNodeDecisionTests { // Role-less: requiredRole == null. hasRole is irrelevant. Healthy iff (selfUp && isLeader), else Unhealthy. - public static IEnumerable RoleLessCases() => new[] + public static IEnumerable RoleLessCases() => new[] { - new object[] { true, true, false, (string?)null, HealthStatus.Healthy }, - new object[] { true, false, false, (string?)null, HealthStatus.Unhealthy }, - new object[] { false, true, false, (string?)null, HealthStatus.Unhealthy }, - new object[] { false, false, false, (string?)null, HealthStatus.Unhealthy }, + new object?[] { true, true, false, (string?)null, HealthStatus.Healthy }, + new object?[] { true, false, false, (string?)null, HealthStatus.Unhealthy }, + new object?[] { false, true, false, (string?)null, HealthStatus.Unhealthy }, + new object?[] { false, false, false, (string?)null, HealthStatus.Unhealthy }, }; [Theory] diff --git a/components/health/shared-contract/ZB.MOM.WW.Health.md b/components/health/shared-contract/ZB.MOM.WW.Health.md index e786e78..7686d95 100644 --- a/components/health/shared-contract/ZB.MOM.WW.Health.md +++ b/components/health/shared-contract/ZB.MOM.WW.Health.md @@ -91,7 +91,8 @@ public interface IActiveNodeGate public static class ActiveNodeGateExtensions { public static IEndpointConventionBuilder RequireActiveNode( - this IEndpointConventionBuilder builder); + this IEndpointConventionBuilder builder, + int retryAfterSeconds = 5); } /// Checks that a downstream gRPC channel is reachable. @@ -221,8 +222,9 @@ namespace ZB.MOM.WW.Health.EntityFrameworkCore; public sealed class DatabaseHealthCheck : IHealthCheck where TContext : DbContext { + // Resolves IDbContextFactory when registered, else a scoped TContext; pool-safe. public DatabaseHealthCheck( - IDbContextFactory factory, + IServiceProvider serviceProvider, DatabaseHealthCheckOptions? options = null); public Task CheckHealthAsync(