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.
This commit is contained in:
Joseph Doherty
2026-06-01 10:13:29 -04:00
parent e47ecacb0d
commit fbf0f23e76
5 changed files with 48 additions and 35 deletions
@@ -5,9 +5,11 @@ 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" in the configuration namespace is
`DraftValidator` — but that is **runtime draft/snapshot validation of operator config drafts**,
not `IConfiguration`/options validation, and it is **out of scope** for the shared library.
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.
@@ -28,13 +30,14 @@ opportunity to *add* the missing startup validation using the shared base.
There is no `*OptionsValidator` type and no `AddValidatedOptions`-style helper anywhere in `src/`.
The repo simply trusts its config sections.
## 2. `DraftValidator` / `DraftSnapshot` — runtime draft validation (OUT OF SCOPE)
## 2. `DraftValidator` / `DraftSnapshot` — dormant managed draft validator (OUT OF SCOPE)
`src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs`:
- `:14``public 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`).
- `:24``public static IReadOnlyList<ValidationError> Validate(DraftSnapshot draft)` — runs seven
rule groups (`:2834`): UNS segment regex (`:42`), path length ≤ 200 (`:64`), EquipmentUuid
In the current tree that complement is **not wired in** — see the no-caller note below.
- `:24``public 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`).
- `:206``public static IReadOnlyList<ValidationError> ValidateClusterTopology(...)` — a second
@@ -47,17 +50,21 @@ The repo simply trusts its config sections.
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`) and the publish
pipeline — never from any DI / options registration. It produces `ValidationError`
(`src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/ValidationError.cs`), a domain record, not
`ValidateOptionsResult`.
`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 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.
**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
@@ -66,7 +73,7 @@ about it changes on adoption.
| Startup options validation | **None**`LdapOptions`/`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 |
| Runtime draft validation | `DraftValidator` + `DraftSnapshot` (one-pass, all errors) | **out of scope** — stays per-project |
| Draft/generation validation | DB `sp_ValidateDraft` (live, in the publish lifecycle) + C# `DraftValidator` (dormant, no `src/` caller) | **out of scope** — stays per-project |
---
@@ -88,9 +95,11 @@ optional (no existing validation is wrong, there just isn't any).
**Keep bespoke (unchanged):**
- `DraftValidator` and `DraftSnapshot`**out of scope**. Runtime draft/snapshot validation,
domain rules, `ValidationError` output, publish-pipeline call site — all stay exactly as they
are. Do **not** fold them into `OptionsValidatorBase`; they are not options validation.
- `DraftValidator` and `DraftSnapshot`**out 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`](../GAPS.md)), low priority —