From cb720bb8c3d8e56c9ce88fd384a3ea1362e412f7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 15 Jul 2026 18:52:19 -0400 Subject: [PATCH] v3 batch1 contracts: RawPaths identity helper + reshaped greenfield entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared-type foundation for Batch 1 fan-out (coordinator contracts commit): - New Commons/Types/RawPaths.cs — the v3 identity authority (Build/Combine/ Split/Leaf/TryParent/ValidateSegment, ordinal comparer). - New entities RawFolder, TagGroup, UnsTagReference. - Reshape DriverInstance (drop NamespaceId, add nullable RawFolderId), Tag (require DeviceId, add nullable TagGroupId, drop DriverInstanceId/ EquipmentId/FolderPath, keep WriteIdempotent), Equipment (drop DriverInstanceId/DeviceId), Device (doc: now universal), ServerCluster (nav Namespaces -> RawFolders). - Delete Namespace, EquipmentImportBatch(+Row), NamespaceKind enum. - DriverTypeMetadata: drop AllowedNamespaceKinds + NamespaceKindCompatibility. Solution is intentionally red below the foundation projects until Wave C integrates. Commons + Core.Abstractions compile green. --- .../Types/RawPaths.cs | 135 ++++++++++++++++++ .../Entities/Device.cs | 8 +- .../Entities/DriverInstance.cs | 9 +- .../Entities/Equipment.cs | 11 +- .../Entities/EquipmentImportBatch.cs | 124 ---------------- .../Entities/Namespace.cs | 37 ----- .../Entities/RawFolder.cs | 30 ++++ .../Entities/ServerCluster.cs | 4 +- .../Entities/Tag.cs | 44 +++--- .../Entities/TagGroup.cs | 30 ++++ .../Entities/UnsTagReference.cs | 35 +++++ .../Enums/NamespaceKind.cs | 19 --- .../DriverTypeRegistry.cs | 18 +-- 13 files changed, 265 insertions(+), 239 deletions(-) create mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs delete mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/EquipmentImportBatch.cs delete mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Namespace.cs create mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/RawFolder.cs create mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/TagGroup.cs create mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsTagReference.cs delete mode 100644 src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NamespaceKind.cs diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs new file mode 100644 index 00000000..1b38957f --- /dev/null +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Types/RawPaths.cs @@ -0,0 +1,135 @@ +namespace ZB.MOM.WW.OtOpcUa.Commons.Types; + +/// +/// The v3 identity authority. A RawPath is the cluster-scoped, slash-separated raw device +/// path — <Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Tag> — and is +/// the single string used at every identity seam: ns=Raw NodeIds, the driver wire-reference +/// (EquipmentTagRefResolver key), the value fan-out / write-routing maps +/// (DriverHostActor), the historian default tagname + mux interest key, the native-alarm +/// ConditionId, and ctx.GetTag script paths. +/// +/// RawPaths are ALWAYS built through this helper — never string-concatenated ad hoc — so the +/// separator and the segment charset are enforced in exactly one place. Comparison is +/// case-sensitive ordinal everywhere (see ). +/// +/// +public static class RawPaths +{ + /// The single path separator character. Forbidden inside any segment. + public const char Separator = '/'; + + /// The path separator as a string (join/split convenience). + public const string SeparatorString = "/"; + + /// The one comparer for RawPaths and their segments: case-sensitive ordinal. + public static StringComparer Comparer => StringComparer.Ordinal; + + /// + /// Validate a single path segment. A segment must be non-empty, contain no + /// , and carry no leading/trailing whitespace (the separator and + /// surrounding whitespace would corrupt the NodeId / round-trip). + /// + /// The candidate segment (a folder / driver / device / group / tag name). + /// A human-readable error when invalid, or when the segment is valid. + public static string? ValidateSegment(string? segment) + { + if (string.IsNullOrEmpty(segment)) + return "Name must not be empty."; + if (segment.IndexOf(Separator) >= 0) + return $"Name must not contain '{Separator}'."; + if (char.IsWhiteSpace(segment[0]) || char.IsWhiteSpace(segment[^1])) + return "Name must not have leading or trailing whitespace."; + return null; + } + + /// True when passes . + /// The candidate segment. + /// when the segment is a legal RawPath segment. + public static bool IsValidSegment(string? segment) => ValidateSegment(segment) is null; + + /// + /// Build a RawPath from ordered segments (root-first). Every segment is validated via + /// ; an invalid or empty segment list throws. + /// + /// The ordered, non-empty segments (root → leaf). + /// The slash-joined RawPath. + /// A segment is invalid, or the sequence is empty. + public static string Build(IEnumerable segments) + { + ArgumentNullException.ThrowIfNull(segments); + var list = segments as IReadOnlyList ?? segments.ToList(); + if (list.Count == 0) + throw new ArgumentException("A RawPath must have at least one segment.", nameof(segments)); + + for (var i = 0; i < list.Count; i++) + { + var error = ValidateSegment(list[i]); + if (error is not null) + throw new ArgumentException($"Invalid RawPath segment at index {i} ('{list[i]}'): {error}", nameof(segments)); + } + + return string.Join(Separator, list); + } + + /// Build a RawPath from ordered segments (root-first). See . + /// The ordered, non-empty segments (root → leaf). + /// The slash-joined RawPath. + public static string Build(params string[] segments) => Build((IEnumerable)segments); + + /// + /// Append a child segment to an existing (already-built) parent RawPath. The child is + /// validated; a blank parent yields the (validated) child alone. + /// + /// The parent RawPath (assumed already valid), or blank for a root segment. + /// The child segment to append. + /// The combined RawPath. + /// The child segment is invalid. + public static string Combine(string? parentPath, string childSegment) + { + var error = ValidateSegment(childSegment); + if (error is not null) + throw new ArgumentException($"Invalid RawPath segment ('{childSegment}'): {error}", nameof(childSegment)); + + return string.IsNullOrEmpty(parentPath) ? childSegment : parentPath + Separator + childSegment; + } + + /// Split a RawPath into its ordered segments (root → leaf). + /// The RawPath to split. + /// The ordered segments; a single-element array for a one-segment path. + public static IReadOnlyList Split(string rawPath) + { + ArgumentNullException.ThrowIfNull(rawPath); + return rawPath.Split(Separator); + } + + /// The leaf (last) segment of a RawPath. + /// The RawPath. + /// The final segment. + public static string Leaf(string rawPath) + { + ArgumentNullException.ThrowIfNull(rawPath); + var idx = rawPath.LastIndexOf(Separator); + return idx < 0 ? rawPath : rawPath[(idx + 1)..]; + } + + /// + /// Get the parent RawPath (everything up to, not including, the leaf). Returns + /// for a single-segment path (no parent). + /// + /// The RawPath. + /// The parent RawPath when this returns . + /// when a parent exists. + public static bool TryParent(string rawPath, out string parent) + { + ArgumentNullException.ThrowIfNull(rawPath); + var idx = rawPath.LastIndexOf(Separator); + if (idx < 0) + { + parent = string.Empty; + return false; + } + + parent = rawPath[..idx]; + return true; + } +} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs index 0fca1d16..58b52274 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Device.cs @@ -1,6 +1,12 @@ namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; -/// Per-device row for multi-device drivers (Modbus, AB CIP). Optional for single-device drivers. +/// +/// v3: a device under a . Now UNIVERSAL — every driver has ≥1 device +/// (single-endpoint drivers get an auto-created default device). Endpoint/connection settings live +/// in (the Kepware channel/device split: DriverConfig keeps +/// protocol/channel-level settings; DeviceConfig carries host/endpoint + per-device +/// settings). The device name is a RawPath segment. +/// public sealed class Device { /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstance.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstance.cs index 6bc962a2..a63cf0e8 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstance.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstance.cs @@ -13,12 +13,13 @@ public sealed class DriverInstance public required string ClusterId { get; set; } /// - /// Logical FK to . Same-cluster binding enforced by - /// sp_ValidateDraft: Namespace.ClusterId must equal DriverInstance.ClusterId. + /// v3: logical FK to a containing ; + /// = the driver sits at the cluster root of the Raw tree. Contributes the leading segments of + /// every child tag's RawPath. /// - public required string NamespaceId { get; set; } + public string? RawFolderId { get; set; } - /// Gets or sets the friendly name of this driver instance. + /// Gets or sets the friendly name of this driver instance. A RawPath segment. public required string Name { get; set; } /// Galaxy | Modbus | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Equipment.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Equipment.cs index e6eef1c8..ce537148 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Equipment.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Equipment.cs @@ -17,14 +17,9 @@ public sealed class Equipment /// UUIDv4, IMMUTABLE across all generations of the same EquipmentId. Downstream-consumer join key. public Guid EquipmentUuid { get; set; } - /// - /// Optional logical FK to the driver providing data for this equipment. - /// null = VirtualTag-only / driver-less equipment (no field driver). - /// - public string? DriverInstanceId { get; set; } - - /// Optional logical FK to a multi-device driver's device. - public string? DeviceId { get; set; } + // v3: equipment no longer binds a driver or device. It references existing raw tags via + // UnsTagReference and hosts VirtualTags + ScriptedAlarms. The old DriverInstanceId / DeviceId + // binding columns are retired. /// Logical FK to . public required string UnsLineId { get; set; } diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/EquipmentImportBatch.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/EquipmentImportBatch.cs deleted file mode 100644 index 35c58cf2..00000000 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/EquipmentImportBatch.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; - -/// -/// Staged equipment-import batch per Phase 6.4 Stream B.2. Rows land in the child -/// table under a batch header; operator reviews + either -/// drops (via DropImportBatch) or finalises (via FinaliseImportBatch) in one -/// bounded transaction. The live Equipment table never sees partial state. -/// -/// -/// User-scoped visibility: the preview modal only shows batches where -/// equals the current operator. Prevents accidental -/// cross-operator finalise during concurrent imports. An admin finalise / drop surface -/// can override this — tracked alongside the UI follow-up. -/// -/// stamps the moment the batch promoted from staging -/// into Equipment. Null = still in staging; non-null = archived / finalised. -/// -public sealed class EquipmentImportBatch -{ - /// Gets or sets the unique identifier for this batch. - public Guid Id { get; set; } - - /// Gets or sets the cluster identifier. - public required string ClusterId { get; set; } - - /// Gets or sets the user name who created this batch. - public required string CreatedBy { get; set; } - - /// Gets or sets the UTC timestamp when this batch was created. - public DateTime CreatedAtUtc { get; set; } - - /// Gets or sets the total number of rows staged in this batch. - public int RowsStaged { get; set; } - - /// Gets or sets the number of rows accepted in this batch. - public int RowsAccepted { get; set; } - - /// Gets or sets the number of rows rejected in this batch. - public int RowsRejected { get; set; } - - /// Gets or sets the UTC timestamp when this batch was finalised, or null if still in staging. - public DateTime? FinalisedAtUtc { get; set; } - - /// Gets or sets the collection of staged rows in this batch. - public ICollection Rows { get; set; } = []; -} - -/// -/// One staged row under an . Mirrors the required -/// + optional columns from the CSV importer's output + an -/// flag + a string the preview modal -/// renders. -/// -public sealed class EquipmentImportRow -{ - /// Gets or sets the unique identifier for this row. - public Guid Id { get; set; } - - /// Gets or sets the parent batch identifier. - public Guid BatchId { get; set; } - - /// Gets or sets the line number in the source file. - public int LineNumberInFile { get; set; } - - /// Gets or sets a value indicating whether this row was accepted. - public bool IsAccepted { get; set; } - - /// Gets or sets the reason this row was rejected, if applicable. - public string? RejectReason { get; set; } - - /// Gets or sets the Z tag identifier. - public required string ZTag { get; set; } - - /// Gets or sets the machine code. - public required string MachineCode { get; set; } - - /// Gets or sets the SAP identifier. - public required string SAPID { get; set; } - - /// Gets or sets the equipment identifier. - public required string EquipmentId { get; set; } - - /// Gets or sets the equipment UUID. - public required string EquipmentUuid { get; set; } - - /// Gets or sets the equipment name. - public required string Name { get; set; } - - /// Gets or sets the UNS area name. - public required string UnsAreaName { get; set; } - - /// Gets or sets the UNS line name. - public required string UnsLineName { get; set; } - - /// Gets or sets the manufacturer name. - public string? Manufacturer { get; set; } - - /// Gets or sets the equipment model. - public string? Model { get; set; } - - /// Gets or sets the serial number. - public string? SerialNumber { get; set; } - - /// Gets or sets the hardware revision. - public string? HardwareRevision { get; set; } - - /// Gets or sets the software revision. - public string? SoftwareRevision { get; set; } - - /// Gets or sets the year of construction. - public string? YearOfConstruction { get; set; } - - /// Gets or sets the asset location. - public string? AssetLocation { get; set; } - - /// Gets or sets the manufacturer URI. - public string? ManufacturerUri { get; set; } - - /// Gets or sets the device manual URI. - public string? DeviceManualUri { get; set; } - - /// Gets or sets the parent batch. - public EquipmentImportBatch? Batch { get; set; } -} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Namespace.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Namespace.cs deleted file mode 100644 index f7324fb2..00000000 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Namespace.cs +++ /dev/null @@ -1,37 +0,0 @@ -using ZB.MOM.WW.OtOpcUa.Configuration.Enums; - -namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; - -/// -/// OPC UA namespace served by a cluster. Generation-versioned — -/// namespaces are content (affect what consumers see at the endpoint), not topology. -/// -public sealed class Namespace -{ - /// Gets or sets the row identifier for this namespace. - public Guid NamespaceRowId { get; set; } - - /// Stable logical ID, e.g. "LINE3-OPCUA-equipment". Globally unique in v2. - public required string NamespaceId { get; set; } - - /// Gets or sets the cluster identifier. - public required string ClusterId { get; set; } - - /// Gets or sets the namespace kind. - public required NamespaceKind Kind { get; set; } - - /// E.g. "urn:zb:warsaw-west:equipment". Unique fleet-wide per generation. - public required string NamespaceUri { get; set; } - - /// Gets or sets a value indicating whether the namespace is enabled. - public bool Enabled { get; set; } = true; - - /// Gets or sets optional notes about the namespace. - public string? Notes { get; set; } - - /// Optimistic concurrency token for last-write-wins detection in the v2 live-edit model. - public byte[] RowVersion { get; set; } = Array.Empty(); - - /// Gets or sets the associated server cluster. - public ServerCluster? Cluster { get; set; } -} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/RawFolder.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/RawFolder.cs new file mode 100644 index 00000000..c3947912 --- /dev/null +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/RawFolder.cs @@ -0,0 +1,30 @@ +namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; + +/// +/// v3 Raw-tree driver-organizing folder. Nestable (Kepware-style) directly under a cluster or +/// under another . Folders group rows; they +/// contribute the leading segments of a driver's RawPath. +/// +public sealed class RawFolder +{ + /// Gets or sets the database row identifier (PK). + public Guid RawFolderRowId { get; set; } + + /// Stable logical ID, unique fleet-wide. + public required string RawFolderId { get; set; } + + /// Logical FK to — the folder is cluster-rooted. + public required string ClusterId { get; set; } + + /// Logical FK to a parent ; = root under the cluster. + public string? ParentRawFolderId { get; set; } + + /// Sibling-unique folder name. A RawPath segment: no /, no lead/trail whitespace. + public required string Name { get; set; } + + /// Sibling display ordering. + public int SortOrder { get; set; } + + /// Optimistic concurrency token for last-write-wins detection in the v2 live-edit model. + public byte[] RowVersion { get; set; } = Array.Empty(); +} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ServerCluster.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ServerCluster.cs index 192964f4..ffcc94b2 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ServerCluster.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ServerCluster.cs @@ -47,6 +47,6 @@ public sealed class ServerCluster // Navigation /// Gets or sets the collection of cluster nodes. public ICollection Nodes { get; set; } = []; - /// Gets or sets the collection of namespaces in the cluster. - public ICollection Namespaces { get; set; } = []; + /// Gets or sets the collection of root-level Raw-tree folders in the cluster. + public ICollection RawFolders { get; set; } = []; } diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs index 32fefab5..0bc1e25f 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/Tag.cs @@ -3,46 +3,34 @@ using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; /// -/// One canonical tag (signal) in a cluster's generation. set ⟺ the -/// tag participates in the Equipment tree. A GalaxyMxGateway-bound equipment tag is an -/// ordinary equipment tag (GalaxyMxGateway is a standard Equipment-kind driver) that carries its -/// Galaxy attribute reference in TagConfig.FullName — there is no separate alias concept. +/// v3 raw-only tag (signal). A tag is authored ONCE, under a in the Raw +/// tree; its identity is its RawPath +/// (<Folder/…>/<Driver>/<Device>/<TagGroup/…>/<Name>). UNS +/// surfaces it by reference (), never by re-authoring. The tag's +/// driver-specific address lives inside as an ordinary field (what the +/// driver dials), not as system identity. /// public sealed class Tag { - /// - /// Gets or sets the unique database row identifier for the tag. - /// + /// Gets or sets the unique database row identifier for the tag (PK). public Guid TagRowId { get; set; } - /// - /// Gets or sets the tag identifier. - /// + /// Gets or sets the stable logical tag identifier (unique fleet-wide). public required string TagId { get; set; } - /// - /// Gets or sets the driver instance identifier for this tag. - /// - public required string DriverInstanceId { get; set; } + /// v3: required logical FK to . Every tag lives under a device. + public required string DeviceId { get; set; } /// - /// Gets or sets the device identifier. + /// Optional logical FK to a containing ; + /// = the tag sits directly under its device. Contributes the middle + /// segments of the tag's RawPath. /// - public string? DeviceId { get; set; } + public string? TagGroupId { get; set; } - /// - /// Set when the tag belongs to an Equipment; NULL for FolderPath-scoped (namespace-root) tags. - /// - public string? EquipmentId { get; set; } - - /// - /// Gets or sets the tag name. - /// + /// Gets or sets the tag name. A RawPath segment: no /, no lead/trail whitespace. public required string Name { get; set; } - /// Only used when is NULL (FolderPath-scoped namespace tag). - public string? FolderPath { get; set; } - /// OPC UA built-in type name (Boolean / Int32 / Float / etc.). public required string DataType { get; set; } @@ -51,7 +39,7 @@ public sealed class Tag /// public required TagAccessLevel AccessLevel { get; set; } - /// Opt-in for write retry eligibility. + /// Opt-in for write retry eligibility (R2 resilience contract — retained in v3). public bool WriteIdempotent { get; set; } /// diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/TagGroup.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/TagGroup.cs new file mode 100644 index 00000000..cd0a32b4 --- /dev/null +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/TagGroup.cs @@ -0,0 +1,30 @@ +namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; + +/// +/// v3 Raw-tree tag-organizing group. Nestable (Kepware-style) directly under a +/// or under another . Groups contribute the middle +/// segments of a tag's RawPath (between the device and the tag name). +/// +public sealed class TagGroup +{ + /// Gets or sets the database row identifier (PK). + public Guid TagGroupRowId { get; set; } + + /// Stable logical ID, unique fleet-wide. + public required string TagGroupId { get; set; } + + /// Logical FK to — the group lives under a device. + public required string DeviceId { get; set; } + + /// Logical FK to a parent ; = root under the device. + public string? ParentTagGroupId { get; set; } + + /// Sibling-unique group name. A RawPath segment: no /, no lead/trail whitespace. + public required string Name { get; set; } + + /// Sibling display ordering. + public int SortOrder { get; set; } + + /// Optimistic concurrency token for last-write-wins detection in the v2 live-edit model. + public byte[] RowVersion { get; set; } = Array.Empty(); +} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsTagReference.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsTagReference.cs new file mode 100644 index 00000000..1b8e4fab --- /dev/null +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/UnsTagReference.cs @@ -0,0 +1,35 @@ +namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities; + +/// +/// v3 UNS projection row: a reference from an to an existing raw +/// . UNS authoring is reference-only — equipment no longer authors +/// TagConfig or binds a driver; it points at raw tags (optionally under a display-name +/// override). The backing raw tag is the single value source; the UNS node fans out from it. +/// +public sealed class UnsTagReference +{ + /// Gets or sets the database row identifier (PK). + public Guid UnsTagReferenceRowId { get; set; } + + /// Stable logical ID, unique fleet-wide. + public required string UnsTagReferenceId { get; set; } + + /// Logical FK to — the referencing equipment. + public required string EquipmentId { get; set; } + + /// Logical FK to — the backing raw tag (same cluster as the equipment). + public required string TagId { get; set; } + + /// + /// Optional UNS display-name override; = use the raw tag's Name. + /// The effective name must be unique within the equipment across references, VirtualTags, and + /// ScriptedAlarms (enforced at authoring and at the deploy gate). + /// + public string? DisplayNameOverride { get; set; } + + /// Sibling display ordering within the equipment's Tags list. + public int SortOrder { get; set; } + + /// Optimistic concurrency token for last-write-wins detection in the v2 live-edit model. + public byte[] RowVersion { get; set; } = Array.Empty(); +} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NamespaceKind.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NamespaceKind.cs deleted file mode 100644 index 41969599..00000000 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NamespaceKind.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace ZB.MOM.WW.OtOpcUa.Configuration.Enums; - -/// OPC UA namespace kind. One of each kind per cluster per generation. -public enum NamespaceKind -{ - /// - /// Equipment namespace — raw signals from native-protocol drivers (Modbus, AB CIP, AB Legacy, - /// S7, TwinCAT, FOCAS, and OpcUaClient when gatewaying raw equipment). UNS 5-level hierarchy - /// applies. - /// - Equipment, - - /// - /// Reserved for future replay driver per handoff §"Digital Twin Touchpoints" — not populated - /// in v2.0 but enum value reserved so the schema does not need to change when the replay - /// driver lands. - /// - Simulated, -} diff --git a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs index 9d622322..ac3e43fc 100644 --- a/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs +++ b/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs @@ -84,7 +84,6 @@ public sealed class DriverTypeRegistry /// Per-driver-type metadata used by the Core, validator, and Admin UI. /// Driver type name (matches DriverInstance.DriverType column values). -/// Which namespace kinds this driver type may be bound to. /// JSON Schema (Draft 2020-12) the driver's DriverConfig column must validate against. /// JSON Schema for DeviceConfig (multi-device drivers); null if the driver has no device layer. /// JSON Schema for TagConfig; required for every driver since every driver has tags. @@ -95,24 +94,11 @@ public sealed class DriverTypeRegistry /// hybrid-formula constants, and whether process-level MemoryRecycle / scheduled- /// recycle protections apply (Tier C only). Every registered driver type must declare one. /// +// v3: AllowedNamespaceKinds retired with the Namespace entity — the two OPC UA namespaces (Raw/UNS) +// are implicit, so a driver type no longer declares a namespace-kind compatibility bitmask. public sealed record DriverTypeMetadata( string TypeName, - NamespaceKindCompatibility AllowedNamespaceKinds, string DriverConfigJsonSchema, string? DeviceConfigJsonSchema, string TagConfigJsonSchema, DriverTier Tier); - -/// Bitmask of namespace kinds a driver type may populate. -[Flags] -public enum NamespaceKindCompatibility -{ - /// Driver does not populate any namespace (invalid; should never appear in registry). - None = 0, - - /// Driver may populate Equipment-kind namespaces (UNS path, Equipment rows). - Equipment = 1, - - /// Driver may populate the future Simulated namespace (replay driver — not in v2.0). - Simulated = 4, -}