v3 Batch 3 contracts: IEffectiveNameGuard authoring-time uniqueness seam

Wave-A shared contract. WP2 implements + registers the guard; WP1 consumes it
in UnsTreeService reference/VirtualTag/ScriptedAlarm mutations. Ordinal, mirrors
the deploy-time DraftValidator UnsEffectiveNameCollision rule.

Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
Joseph Doherty
2026-07-16 05:53:52 -04:00
parent a88dc86173
commit 6dc5af7aa2
@@ -0,0 +1,61 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>
/// Which authoring surface a proposed effective name comes from. Used to word the collision
/// error and to exclude the correct same-kind self-row on an edit (rename / override change).
/// </summary>
public enum EffectiveNameSourceKind
{
/// <summary>A <see cref="Configuration.Entities.UnsTagReference"/> (effective name =
/// <c>DisplayNameOverride</c> else the backing raw tag's <c>Name</c>).</summary>
Reference,
/// <summary>An equipment-bound <see cref="Configuration.Entities.VirtualTag"/> (effective name = <c>Name</c>).</summary>
VirtualTag,
/// <summary>An equipment-bound <see cref="Configuration.Entities.ScriptedAlarm"/> (effective name = <c>Name</c>).</summary>
ScriptedAlarm,
}
/// <summary>
/// v3 Batch 3 authoring-time guard for the UNS effective-name uniqueness rule. Within an
/// equipment the EFFECTIVE leaf name must be unique across its <c>UnsTagReferences</c>
/// (<c>DisplayNameOverride</c> else the backing raw tag's <c>Name</c>), its <c>VirtualTags</c>
/// (<c>Name</c>), and its <c>ScriptedAlarms</c> (<c>Name</c>) — the three share the equipment's
/// UNS NodeId space (<c>{EquipmentId}/{EffectiveName}</c>). This is the authoring mirror of the
/// deploy-time <c>DraftValidator</c> <c>UnsEffectiveNameCollision</c> rule; comparison is
/// <see cref="StringComparer.Ordinal"/> to match NodeId semantics and the validator.
/// </summary>
/// <remarks>
/// Injectable (registered <c>Scoped</c>, its own pooled context per call — the same pattern
/// <c>UnsTreeService</c> uses). Consumed by <c>UnsTreeService</c>'s reference / virtual-tag /
/// scripted-alarm mutations, which call <see cref="CheckAsync"/> before persisting and surface a
/// non-null result as the mutation's readable failure. The deploy gate remains the backstop for
/// rename-induced collisions the authoring check never saw.
/// </remarks>
public interface IEffectiveNameGuard
{
/// <summary>
/// Tests whether <paramref name="proposedEffectiveName"/> would collide with an existing
/// effective name within <paramref name="equipmentId"/>.
/// </summary>
/// <param name="equipmentId">The owning equipment's logical id.</param>
/// <param name="proposedEffectiveName">The effective name to be authored (override else raw name for a reference; Name for a VT/alarm).</param>
/// <param name="kind">Which surface the proposed name is for (words the error; identifies the self-row kind to exclude).</param>
/// <param name="excludeSourceId">
/// On an edit, the logical id of the row being changed (its <c>UnsTagReferenceId</c> /
/// <c>VirtualTagId</c> / <c>ScriptedAlarmId</c>) so it is excluded from the collision set;
/// <see langword="null"/> on a create.
/// </param>
/// <param name="ct">Cancellation token.</param>
/// <returns>
/// <see langword="null"/> when the name is free; otherwise a readable error naming the
/// colliding existing source and the equipment.
/// </returns>
Task<string?> CheckAsync(
string equipmentId,
string proposedEffectiveName,
EffectiveNameSourceKind kind,
string? excludeSourceId,
CancellationToken ct = default);
}