Files
scadaproj/components/configuration/current-state/otopcua/CURRENT-STATE.md
T
Joseph Doherty fbf0f23e76 docs(config): correct OtOpcUa draft-validation description
The C# DraftValidator/DraftSnapshot has NO live caller in OtOpcUa src/ (verified
repo-wide) — it is dormant complement code. The enforced pre-publish draft
validation runs DB-side in the sp_ValidateDraft stored procedure (Status='Draft'
-> sp_PublishGeneration lifecycle). Reframe across current-state/SPEC/GAPS/README/
CLAUDE.md from 'runtime draft validation' + a false publish-pipeline caller to
'dormant managed validator; enforcement is DB-side'. Out-of-scope conclusion
for ZB.MOM.WW.Configuration is unchanged.
2026-06-01 10:13:29 -04:00

7.1 KiB
Raw Blame History

Configuration validation — current state: OtOpcUa

Repo: ~/Desktop/OtOpcUa. Stack: .NET 10, OPC UA, gRPC; solution ZB.MOM.WW.OtOpcUa.slnx. All paths relative to repo root. Verified 2026-06-01.

Headline: OtOpcUa has no startup options validation at all. A repo-wide search for IValidateOptions and ValidateOnStart returns zero hits in src/. Options are bound with bare .Bind(...) and never validated. The only validation-shaped type in the configuration namespace is the C# DraftValidator — but it is dormant (no live caller in src/) and, by design, concerns config-generation content, not IConfiguration/options. The enforced pre-publish draft validation actually runs DB-side in the sp_ValidateDraft stored procedure. Either way, draft/generation validation is out of scope for the shared options-validation library.

This makes OtOpcUa the lightest consumer: there is nothing to replace, only an optional opportunity to add the missing startup validation using the shared base.

1. Options binding — no validation

src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs:

  • :99builder.Services.AddOptions<LdapOptions>().Bind(builder.Configuration.GetSection("Ldap")); Bound, not validated — no ValidateOnStart(), no registered IValidateOptions<LdapOptions>. A blank Ldap:Server / Ldap:SearchBase would surface only later, as a low-level LDAP error on the first login (the exact failure mode ScadaBridge's SecurityOptionsValidator exists to prevent).

src/Server/ZB.MOM.WW.OtOpcUa.Host/OpcUa/OtOpcUaServerHostedService.cs:

  • :63_configuration.GetSection("OpcUa").Bind(options); — the OpcUa section is bound imperatively inside the hosted service, again with no validation pass.

There is no *OptionsValidator type and no AddValidatedOptions-style helper anywhere in src/. The repo simply trusts its config sections.

2. DraftValidator / DraftSnapshot — dormant managed draft validator (OUT OF SCOPE)

src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs:

  • :14public static class DraftValidator — a managed pre-publish validator (its own doc-comment, :713, frames it as the managed-code complement to the T-SQL sp_ValidateDraft). In the current tree that complement is not wired in — see the no-caller note below.
  • :24public static IReadOnlyList<ValidationError> Validate(DraftSnapshot draft)would run seven rule groups (:2834): UNS segment regex (:42), path length ≤ 200 (:64), EquipmentUuid immutability (:89), same-cluster namespace binding (:104), reservation pre-flight (:125), EquipmentId derivation (:153), driver/namespace compatibility (:165).
  • :206public static IReadOnlyList<ValidationError> ValidateClusterTopology(...) — a second managed guard for cluster topology vs RedundancyMode.
  • It returns every failing rule in one pass — same "surface all errors" philosophy this component normalizes — but over database draft rows (DraftSnapshot), not IConfiguration.

src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftSnapshot.cs:

  • :9public sealed class DraftSnapshot — the input bag: namespaces, driver instances, equipment, UNS areas/lines, tags, poll groups, plus prior-generation rows for cross-generation invariants. These are domain entities (ZB.MOM.WW.OtOpcUa.Configuration.Entities), not options.

DraftValidator is referenced only by its tests (tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/DraftValidatorTests.cs) — a repo-wide search finds no live caller in src/ (nothing constructs a DraftSnapshot or calls DraftValidator.Validate/ValidateClusterTopology), and it is never registered in DI or options. The enforced pre-publish validation lives DB-side in the sp_ValidateDraft stored procedure (src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260417215224_StoredProcedures.cs:157+, called as part of the Status='Draft' → sp_PublishGeneration generation lifecycle); the managed DraftValidator is currently dormant complement code. When it does run it produces ValidationError (.../Validation/ValidationError.cs), a domain record, not ValidateOptionsResult.

Why it stays per-project: it (and its live DB counterpart sp_ValidateDraft) validates an operator's configuration content (the equipment hierarchy they are about to publish), with rules that are entirely OtOpcUa domain knowledge (UNS regex, EquipmentId derivation, Galaxy driver/namespace rules). It is not the cross-cutting "validate the host's config section at startup" concern the shared library normalizes. Nothing about it changes on adoption.

3. Summary

Surface What exists Shared-lib relevance
Startup options validation NoneLdapOptions/OpcUa bound with bare .Bind() Gap — could adopt OptionsValidatorBase + AddValidatedOptions
IValidateOptions / ValidateOnStart Zero usages in src/ nothing to migrate
Pre-host raw-config preflight None could adopt ConfigPreflight if pre-host keys emerge
Draft/generation validation DB sp_ValidateDraft (live, in the publish lifecycle) + C# DraftValidator (dormant, no src/ caller) out of scope — stays per-project

Adoption plan → ZB.MOM.WW.Configuration

OtOpcUa is the lightest consumer — adoption is additive, not a replacement, and is entirely optional (no existing validation is wrong, there just isn't any).

Add startup validation for the bound sections (optional, recommended):

  • For Ldap: add an LdapStartupOptionsValidator : OptionsValidatorBase<LdapOptions> that calls v.Required(o.Server, "Ldap:Server") and v.Required(o.SearchBase, "Ldap:SearchBase") (mirroring ScadaBridge's SecurityOptionsValidator intent), then replace Program.cs:99's AddOptions<LdapOptions>().Bind(...) with AddValidatedOptions<LdapOptions, LdapStartupOptionsValidator>(builder.Configuration, "Ldap").
  • For OpcUa: if any field has a fail-fast invariant (e.g. a required endpoint or a port), add an OptionsValidatorBase<OpcUaOptions> and move the :63 imperative .Bind into AddValidatedOptions at composition time. Skip if the section has no hard invariants.

Keep bespoke (unchanged):

  • DraftValidator and DraftSnapshotout of scope. Draft/generation content validation (enforced DB-side by sp_ValidateDraft, with the managed DraftValidator as dormant complement code), domain rules, ValidationError output — all stay exactly as they are. Do not fold them into OptionsValidatorBase; they are not options validation. (Whether the unused C# DraftValidator should be revived or removed is an OtOpcUa housekeeping question, unrelated to this component.)

Status: OtOpcUa has no validator to migrate today, so its adoption is purely the new guarding work above. It is a follow-on (tracked in ../GAPS.md), low priority — the lowest-stakes of the three because there is no drift to correct, only an absence to optionally fill once the package is referenced.