Files
Joseph Doherty 3dd7aa40ff docs: publish LocalDb 0.1.0 to the feed + reconcile component status against verified state
Publishes the three ZB.MOM.WW.LocalDb packages to the Gitea feed (restore-verified
from a scratch consumer) and adds the build/push.sh the other shared libs already have.

The status prose across CLAUDE.md, README.md and components/*/GAPS.md had drifted from
reality, so it was re-derived from the feed listing and the actual PackageReferences +
registration calls on each consumer's default branch rather than from prior claims.
Five claims were false: Health "not yet adopted" (all four apps wire MapZbHealth),
GalaxyRepository's mxaccessgw adoption "a follow-on" (its Server wires
AddZbGalaxyRepository), Configuration "not yet pushed", Secrets G-8 "not yet
committed", and Theme pinned at 0.2.0 (all four are on 0.3.1). Every doc also said
"three apps" while HistorianGateway is a fourth consumer of seven libs, and all
eight libraries' test counts were stale (re-ran each suite; all green).

Surfaces one previously unrecorded gap: Secrets source is at 0.1.3 with KEK rotation
committed, but the feed tops out at 0.1.2, so no app can consume rotation until it
is published.

Health and observability divergence tables are labelled historical, not re-verified —
the libraries are proven wired, but per-app probe coverage vs spec was not re-walked.

Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
2026-07-18 03:06:49 -04:00
..

Configuration validation (config binding + startup validation)

Normalized component for startup configuration validation across the three sister projects. Goal: path to shared code — converge the apps onto one IValidateOptions failure-accumulation base, a shared set of rule primitives, a single bind+validate+ValidateOnStart DI helper, and a pre-host raw-config aggregator, extracted as the ZB.MOM.WW.Configuration library (single package), while each app keeps its own options classes and domain rules.

Why config validation is a normalization candidate

All three apps fail-fast on bad configuration at startup — and all three hand-roll the same plumbing to do it:

  • OtOpcUa has no startup options validation at allLdap/OpcUa are bound with bare .Bind() and trusted; a bad value fails opaquely on first use. (Its DraftValidator is runtime config-content validation, a different concern, out of scope.)
  • MxAccessGateway has one large GatewayOptionsValidator (~360 LOC, nine sub-validators) with a private List<string> accumulator and AddIfBlank/AddIfNotPositive/AddIfInvalidPath helpers, wired through a bespoke AddGatewayConfiguration extension.
  • ScadaBridge is the heaviest: four per-module *OptionsValidator (Cluster / Security / HealthMonitoring / AuditLog), each open-coding the same accumulation, plus a raw-config pre-Akka StartupValidator.

The common core — accumulate-all-failures IValidateOptions, reusable rule primitives, AddValidatedOptions, and a ConfigPreflight that generalizes StartupValidator — is genuinely shareable; the options classes and domain rules stay per-project. The unifying detail: ConfigPreflight.ThrowIfInvalid() reproduces ScadaBridge's StartupValidator thrown message byte-for-byte, so the heaviest migration is behaviour-preserving.

Status by project

Project Options validators today Pre-host preflight Failure accumulation Adoption status
OtOpcUa none (bare .Bind(); DraftValidator is out-of-scope runtime validation) none n/a Not started (additive, optional)
MxAccessGateway 🟡 GatewayOptionsValidator (hand-rolled IValidateOptions) none 🟡 manual List<string> Not started (follow-on)
ScadaBridge 🟡 four *OptionsValidator (hand-rolled) StartupValidator (raw config, pre-Akka) 🟡 manual List<string> ×4 Not started (follow-on; heaviest)

See each project's current-state/<project>/CURRENT-STATE.md for the code-verified detail and its adoption plan.

Normalized vs. left per-project

Normalized (the shared target):

  • OptionsValidatorBase<TOptions> — abstract IValidateOptions<TOptions>; override protected void Validate(ValidationBuilder, TOptions); the base aggregates all failures and returns Success only when clean.
  • ValidationBuilder rule primitives — Required, Port, HostPort, PositiveTimeSpan, OneOf, MinCount, plus RequireThat/Add for custom and cross-field rules; consistent "<field> <reason>" wording via the internal Checks seam.
  • AddValidatedOptions<TOptions, TValidator>(IConfiguration, sectionPath) — bind + register validator + ValidateOnStart in one DI call; returns OptionsBuilder<TOptions>.
  • ConfigPreflight — fluent pre-host raw-IConfiguration aggregator (For/Require/RequireValue/ RequirePort/When/ThrowIfInvalid); generalizes StartupValidator, with a byte-compatible thrown message.
  • The error-handling contract: accumulate ALL failures; two surfacing paths (OptionsValidationException at host start via ValidateOnStart, vs ConfigPreflight.ThrowIfInvalid()'s InvalidOperationException); "<field> <reason>" messages.

Left per-project (not forced together):

  • Each app's options classes (GatewayOptions, ClusterOptions, SecurityOptions, HealthMonitoringOptions, AuditLogOptions, NodeOptions, …) and all of their domain rules — worker .exe paths, split-brain strategy, Akka heartbeat/threshold ordering, audit retention bounds, gRPC-port-vs-remoting-port topology, etc.
  • OtOpcUa's draft/generation-content validation (DB-side sp_ValidateDraft; the C# DraftValidator / DraftSnapshot is dormant, no src/ caller) — config-content validation, out of scope entirely.

Package structure

ZB.MOM.WW.Configuration ships as a single package, one DLL — no third-party packages, no ASP.NET Core framework reference:

Package Contents Consumers
ZB.MOM.WW.Configuration OptionsValidatorBase<TOptions>, ValidationBuilder, ServiceCollectionExtensions.AddValidatedOptions, ConfigPreflight, internal Checks All three (ScadaBridge heaviest)

Dependency closure: Microsoft.Extensions.{Options, Options.ConfigurationExtensions, Configuration.Abstractions, DependencyInjection.Abstractions}.

Component status

Status: Draft. Library BUILT @ 0.1.0; NOT YET ADOPTED by the three apps. Adoption is the backlog (tracked in GAPS.md). Unlike the observability pass, this release carries no in-pass sister-repo adoption — it is library-only.

The shared library lives at ~/Desktop/scadaproj/ZB.MOM.WW.Configuration/ (.NET 10; single package; 27 tests; dotnet pack → 1 nupkg @ 0.1.0). Build/test/pack from ZB.MOM.WW.Configuration/:

dotnet test ZB.MOM.WW.Configuration.slnx
dotnet pack ZB.MOM.WW.Configuration.slnx -c Release -o ./artifacts