namespace ZB.MOM.WW.OtOpcUa.Cluster;
public sealed class AkkaClusterOptions
{
public const string SectionName = "Cluster";
/// Gets or sets the Akka system name.
public string SystemName { get; set; } = "otopcua";
/// Gets or sets the hostname to bind to (default 0.0.0.0).
public string Hostname { get; set; } = "0.0.0.0";
/// Gets or sets the port to listen on (default 4053).
public int Port { get; set; } = 4053;
///
/// Hostname advertised in cluster gossip. Must be reachable by other nodes.
/// In docker-compose this is the container DNS name; in bare metal it's the
/// host's stable LAN address.
///
public string PublicHostname { get; set; } = "127.0.0.1";
///
/// Gets or sets the seed nodes for cluster bootstrapping.
///
///
///
/// ORDER IS LOAD-BEARING (decision 2026-07-22): a node that is one of its own seeds
/// must list ITSELF first. Akka runs FirstSeedNodeProcess — the only bootstrap
/// path that can form a NEW cluster when no peer answers InitJoin — exclusively
/// when seed-nodes[0] is this node's own address; any other node runs
/// JoinSeedNodeProcess, which retries InitJoin forever and can never form a
/// cluster. So listing both peers does NOT mean either can cold-start alone: a node that
/// lists its partner first never comes Up while that partner is down. Self-first ordering
/// closes that gap inside Akka's own handshake — unlike the retired
/// Cluster:SelfFormAfter watchdog, which sat outside it and could not tell "no
/// seed answered" from "a seed answered and the join is in flight".
///
///
/// The rule is conditional: it binds only when this node's own address appears in
/// this list. A node seeded exclusively by someone else — today's docker-dev site nodes,
/// which list only central-1 — is legitimately not a seed and is exempt. Enforced
/// at boot by ; identity is
/// + (what Akka puts in
/// SelfAddress), never the bind address.
///
///
public string[] SeedNodes { get; set; } = Array.Empty();
///
/// Cluster roles for this node. When empty the role list comes from
/// OTOPCUA_ROLES via . Allowed values:
/// admin, driver, dev.
///
public string[] Roles { get; set; } = Array.Empty();
///
/// How the cluster decides to down a node it can no longer reach. One of auto-down
/// (default) or keep-oldest; any other value fails the host at startup.
///
///
///
/// auto-down — availability. The leader among the reachable members
/// downs the unreachable peer after
/// . A hard crash of either
/// node — including the oldest — fails over to the survivor with no operator action.
/// The trade is that a genuine network partition (both nodes alive, link cut) leaves
/// both sides running active until an operator restarts one.
///
///
/// keep-oldest — partition-safety. The SBR resolver sacrifices the younger
/// side of a split, so a partition can never run dual-active. The cost is severe for a
/// two-node pair: it cannot survive a crash of the oldest node at all. Akka.NET's
/// KeepOldest.OldestDecision only lets down-if-alone rescue a side holding
/// >= 2 members, so the 1-vs-1 survivor downs itself and shuts down — the
/// redundancy pair turns a single-node crash into a total outage. Choose this only for
/// clusters of three or more nodes, or where dual-active is genuinely worse than an
/// outage.
///
///
public string SplitBrainResolverStrategy { get; set; } = "auto-down";
///
/// The simultaneous-cold-start split-brain guard (Cluster:BootstrapGuard). Default OFF — a
/// dark switch, so existing deployments and tests keep Akka's config-driven self-first auto-join
/// unchanged. See for the decision logic and
/// ClusterBootstrapCoordinator for the runtime.
///
public ClusterBootstrapGuardOptions BootstrapGuard { get; set; } = new();
}
///
/// Configuration for the simultaneous-cold-start split-brain guard. When
/// , the node does NOT auto-join from its config seeds; a coordinator picks the
/// join order (founder self-first / joiner peer-first) after a reachability probe. See
/// .
///
public sealed class ClusterBootstrapGuardOptions
{
///
/// Gets or sets whether the guard is active. Default — Akka auto-joins from
/// the config seed list exactly as before. Only meaningful on a node that is one of its own two
/// pair seeds; inert everywhere else.
///
public bool Enabled { get; set; }
///
/// Gets or sets how long the higher-address node probes its partner's Akka endpoint before
/// concluding the partner is dead and forming alone. Must comfortably exceed the partner's
/// worst-case process-start-to-Akka-bind time so a slow-but-alive partner is never mistaken for a
/// dead one (which would re-open the split). Default 25 s.
///
public int PartnerProbeSeconds { get; set; } = 25;
///
/// Gets or sets the interval between partner reachability probes, in milliseconds. Default 500 ms.
///
public int PartnerProbeIntervalMs { get; set; } = 500;
///
/// Gets or sets the per-probe TCP connect timeout, in milliseconds. Default 1000 ms.
///
public int ProbeConnectTimeoutMs { get; set; } = 1000;
}