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);