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
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.
- The one target:
spec/SPEC.md - The shared library (paper API):
shared-contract/ZB.MOM.WW.Configuration.md - Divergences + adoption backlog:
GAPS.md - Current state, per project:
current-state/
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 all —
Ldap/OpcUaare bound with bare.Bind()and trusted; a bad value fails opaquely on first use. (ItsDraftValidatoris runtime config-content validation, a different concern, out of scope.) - MxAccessGateway has one large
GatewayOptionsValidator(~360 LOC, nine sub-validators) with a privateList<string>accumulator andAddIfBlank/AddIfNotPositive/AddIfInvalidPathhelpers, wired through a bespokeAddGatewayConfigurationextension. - ScadaBridge is the heaviest: four per-module
*OptionsValidator(Cluster / Security / HealthMonitoring / AuditLog), each open-coding the same accumulation, plus a raw-config pre-AkkaStartupValidator.
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>— abstractIValidateOptions<TOptions>; overrideprotected void Validate(ValidationBuilder, TOptions); the base aggregates all failures and returnsSuccessonly when clean.ValidationBuilderrule primitives —Required,Port,HostPort,PositiveTimeSpan,OneOf,MinCount, plusRequireThat/Addfor custom and cross-field rules; consistent"<field> <reason>"wording via the internalChecksseam.AddValidatedOptions<TOptions, TValidator>(IConfiguration, sectionPath)— bind + register validator +ValidateOnStartin one DI call; returnsOptionsBuilder<TOptions>.ConfigPreflight— fluent pre-host raw-IConfigurationaggregator (For/Require/RequireValue/RequirePort/When/ThrowIfInvalid); generalizesStartupValidator, with a byte-compatible thrown message.- The error-handling contract: accumulate ALL failures; two surfacing paths
(
OptionsValidationExceptionat host start viaValidateOnStart, vsConfigPreflight.ThrowIfInvalid()'sInvalidOperationException);"<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.exepaths, 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/DraftSnapshotis dormant, nosrc/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