feat(deploy): activate full DraftValidator gate (reject on any validation error)

This commit is contained in:
Joseph Doherty
2026-06-07 11:19:23 -04:00
parent fc52fbce49
commit 5aba418074
3 changed files with 174 additions and 18 deletions
@@ -79,18 +79,19 @@ public sealed class AdminOperationsActor : ReceiveActor
return;
}
// Surgical pre-seal gate: reject only on a Tag↔VirtualTag NodeId collision. The other
// DraftValidator rules still run (one pass) but must NOT block here — they are dormant
// and the current non-canonical company overlay would otherwise fail them. Filter to the
// single collision code so a real OPC UA address-space clash can never be deployed.
// Full pre-seal gate: run the complete DraftValidator (one pass, all rules) and reject
// on ANY validation error. The config is now canonical — the company overlay loader emits
// canonical EquipmentIds and the seed is clean — so every rule (UNS segments, EquipmentId
// derivation, cross-cluster/namespace binding, driver-namespace compat, signal collisions,
// …) gates the deploy. A green build in this repo does not prove the config is valid; this
// is the last guard before a bad address space (or a non-derived EquipmentId) ships.
var draft = await DraftSnapshotFactory.FromConfigDbAsync(db);
var collisions = DraftValidator.Validate(draft)
.Where(e => e.Code == "EquipmentSignalNameCollision")
.ToList();
if (collisions.Count > 0)
var errors = DraftValidator.Validate(draft);
if (errors.Count > 0)
{
var summary = string.Join("; ", collisions.Select(e => e.Message));
_log.Warning("StartDeployment rejected (signal collision): {Summary}", summary);
var summary = string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}"));
_log.Warning("StartDeployment rejected ({Count} validation error(s)): {Summary}",
errors.Count, summary);
replyTo.Tell(new StartDeploymentResult(
StartDeploymentOutcome.Rejected,
DeploymentId: null,