using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.Server.Redundancy; /// /// Snapshot of the cluster topology the holds. Read /// once at startup + refreshed on publish-generation notification. Immutable — every /// refresh produces a new instance so observers can compare identity-equality to detect /// topology change. /// /// /// Per Phase 6.3 Stream A.1. Invariants enforced by the loader (see /// ): at most one Primary per cluster for /// WarmActive/Hot redundancy modes; every node has a unique ApplicationUri (OPC UA /// Part 4 requirement — clients pin trust here); at most 2 nodes total per cluster /// (decision #83). /// public sealed record RedundancyTopology( string ClusterId, string SelfNodeId, RedundancyRole SelfRole, RedundancyMode Mode, IReadOnlyList Peers, string SelfApplicationUri) { /// Peer count — 0 for a standalone (single-node) cluster, 1 for v2 two-node clusters. public int PeerCount => Peers.Count; /// /// ServerUriArray shape per OPC UA Part 4 §6.6.2.2 — self first, peers in stable /// deterministic order (lexicographic by NodeId), self's ApplicationUri always at index 0. /// public IReadOnlyList ServerUriArray() => new[] { SelfApplicationUri } .Concat(Peers.OrderBy(p => p.NodeId, StringComparer.OrdinalIgnoreCase).Select(p => p.ApplicationUri)) .ToList(); } /// One peer in the cluster (every node other than self). /// Peer's stable logical NodeId (e.g. "LINE3-OPCUA-B"). /// Peer's declared redundancy role from the shared config DB. /// Peer's hostname / IP — drives the health-probe target. /// Peer's OPC UA endpoint port. /// Peer's dashboard / health-endpoint port. /// Peer's declared ApplicationUri (carried in ). public sealed record RedundancyPeer( string NodeId, RedundancyRole Role, string Host, int OpcUaPort, int DashboardPort, string ApplicationUri); /// Thrown when the loader detects a topology-invariant violation at startup or refresh. public sealed class InvalidTopologyException(string message) : Exception(message);