Resolves the 35 findings from the 2026-06-01 baseline (commit 26ba1c7),
test-first for every behavioral change. +51 tests (331 -> 382 passing, 0 failed).
- Telemetry-001 (HIGH): RedactionEnricher now honours property removal, so a
redactor that drops a key actually scrubs the secret from the event.
- Auth: LDAP validator ValidateOnStart; API-key verify no longer fails on a
best-effort MarkUsed write or a corrupt scopes column (fail-closed); LDAP cert
validation hook; KeyPrefix persistence aligned; README algorithm corrected.
- Health: Akka checks return Degraded (not throw) when the cluster isn't up yet;
GrpcDependencyHealthCheck catch-all; null 'description' rendered; composite
endpoint builder; XML docs shipped.
- Audit: CompositeAuditWriter no longer re-throws OperationCanceledException;
TruncatingAuditRedactor over-redact scrubs Target + safe negative max; options
record; XML docs shipped.
- Configuration: TryAddEnumerable idempotent registration; consistent port
quoting; strict invariant port parsing; XML docs + README packaged.
- Theme: mobile toggle is now CSS-only (no Bootstrap JS); token/CSS hygiene;
XML docs on the public parameter surface.
Shared-contract/spec docs updated where the code was the source of truth
(observability service.instance.id, MapZbMetrics, redactor reach). All changes
additive/back-compatible at v0.1.0. code-reviews bookkeeping follows separately.
ZB.MOM.WW.Configuration
Startup configuration-validation library for the ZB.MOM.WW SCADA family (OtOpcUa, MxAccessGateway, ScadaBridge). This is a library, not a service — the package is linked directly into the consuming application at build time. It extracts the IValidateOptions plumbing the three apps share — failure accumulation, rule primitives, bind+validate DI wiring, and pre-host preflight — so that domain-specific validation rules stay per-project and the boilerplate does not drift.
What's in the box
| Type | Description |
|---|---|
OptionsValidatorBase<TOptions> |
Abstract IValidateOptions<TOptions>. Override protected void Validate(ValidationBuilder v, TOptions o) to declare failures; the base aggregates all failures and returns a single ValidateOptionsResult. |
ValidationBuilder |
Failure accumulator. Primitives: Required, Port, HostPort, PositiveTimeSpan, OneOf, MinCount, RequireThat(bool, msg), Add(msg). Properties: Failures (read), IsValid. |
ServiceCollectionExtensions |
AddValidatedOptions<TOptions, TValidator>(IConfiguration config, string sectionPath) — binds the section, registers the validator, and calls ValidateOnStart() in a single extension method. Returns OptionsBuilder<TOptions>. |
ConfigPreflight |
Pre-host raw-IConfiguration checker. Fluent API: For(config), .Require(key, predicate, reason), .RequireValue(key), .RequirePort(key), .When(cond, block), .ThrowIfInvalid(). |
Usage
1. Validator subclass
public sealed class ClusterOptionsValidator : OptionsValidatorBase<ClusterOptions>
{
protected override void Validate(ValidationBuilder v, ClusterOptions o)
{
v.MinCount(o.SeedNodes, 2, "Cluster:SeedNodes");
v.OneOf(o.Strategy, new[] { "keep-oldest" }, "Cluster:Strategy");
v.PositiveTimeSpan(o.StableAfter, "Cluster:StableAfter");
}
}
2. DI wiring
builder.Services.AddValidatedOptions<ClusterOptions, ClusterOptionsValidator>(
builder.Configuration, "ScadaBridge:Cluster");
This binds ScadaBridge:Cluster, registers ClusterOptionsValidator, and enables ValidateOnStart — the app refuses to start if the section fails validation.
3. Pre-host preflight
ConfigPreflight.For(configuration)
.Require("Node:Role", v => v is "Central" or "Site", "must be 'Central' or 'Site'")
.RequirePort("Node:RemotingPort")
.When(role == "Site", p => p.RequireValue("Node:SiteId"))
.ThrowIfInvalid();
Use ConfigPreflight before WebApplication.CreateBuilder for critical keys (node role, remoting port, site ID) that must be present and valid before the DI container is even constructed.
Building and testing
# from ZB.MOM.WW.Configuration/
dotnet test ZB.MOM.WW.Configuration.slnx
All tests run with no external dependencies:
| Assembly | Tests |
|---|---|
ZB.MOM.WW.Configuration.Tests |
27 |
| Total | 27 |
Packing
dotnet pack ZB.MOM.WW.Configuration.slnx -c Release -o ./artifacts
Produces one .nupkg file in artifacts/:
ZB.MOM.WW.Configuration.0.1.0.nupkg
GeneratePackageOnBuild is off — pack explicitly as above. Version is set in Directory.Build.props.
Dependencies
The package has a minimal closure — only Microsoft.Extensions.* abstractions:
Microsoft.Extensions.OptionsMicrosoft.Extensions.Options.ConfigurationExtensionsMicrosoft.Extensions.Configuration.AbstractionsMicrosoft.Extensions.DependencyInjection.Abstractions
No third-party packages; no ASP.NET Core framework reference.
Status
Built at 0.1.0. Not yet adopted by the three apps. Adoption is tracked in the component backlog:
~/Desktop/scadaproj/components/configuration/GAPS.md
Design documentation lives alongside that backlog:
~/Desktop/scadaproj/components/configuration/spec/SPEC.md— normalized validation target~/Desktop/scadaproj/components/configuration/shared-contract/ZB.MOM.WW.Configuration.md— proposed API~/Desktop/scadaproj/components/configuration/current-state/— per-project current state (code-verified)