namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
///
/// A driver-facing snapshot of "should this node be the one asserting an exclusive identity on a
/// shared external system right now?".
///
///
///
/// Why drivers need this at all. The driver layer has no notion of redundancy, by
/// design — a driver talks to a device and the Primary gate is applied above it, in
/// DriverHostActor, on the way in. That works because every gated operation is
/// inbound: a write, an alarm ack, an alerts publish. A Sparkplug primary-host claim is
/// not inbound. It is a standing assertion the driver makes to the broker, on its own
/// connection, for as long as it holds one — there is no message for the host actor to
/// intercept. So the decision has to reach the driver instead (Gitea #511).
///
///
/// The decision, not the role. Same discipline as the alarm-drain view in
/// Runtime: this publishes the verdict a single policy produced, so the policy stays the
/// one place that decides and a driver can never re-derive a subtly different rule from a raw
/// role. The verdict fed here is the data-plane one (an unknown role on a cluster that
/// has a driver peer denies), because two nodes claiming one Sparkplug host id is the harm the
/// gate exists to prevent — not a duplicate row, but a Secondary's Last Will publishing
/// OFFLINE over a live Primary's ONLINE and stopping every edge node in the
/// group.
///
///
/// Polled, not evented, and that is deliberate. A process-wide singleton raising an
/// event onto per-instance drivers outlives every driver it hands a reference to — the exact
/// missing--= leak shape the rediscovery signal had to be careful about. Consumers
/// sample this instead, so a driver that is torn down simply stops asking. The cost is up to
/// one poll interval of latency on a failover, which is inside the edge-visible
/// OFFLINE window a failover already implies.
///
///
public interface IDriverPrimaryRoleSource
{
///
/// Whether this node currently holds the Primary data-plane role — and may therefore claim an
/// exclusive external identity such as a Sparkplug host id.
///
bool ShouldActAsPrimary { get; }
///
/// Records a fresh decision. Called by DriverHostActor on every redundancy snapshot.
///
///
/// The verdict PrimaryGatePolicy.ShouldServiceAsPrimary produced.
///
void PublishPrimaryRole(bool shouldActAsPrimary);
}
///
/// The a driver uses when nothing publishes one — a
/// standalone host, a unit test, or any deployment with no redundancy manager wired.
///
///
///
/// It answers , and the alternative is worse. Defaulting closed
/// would be the reflexive "fail-safe" choice, but there is no safety to gain: with nothing
/// publishing a role there is by definition no peer to collide with, and a closed default
/// would make an exclusive-identity claim silently never happen on the single-node deployment
/// that is the common case — an inert feature with no error, which is precisely the failure
/// mode Gitea #511 was filed about.
///
///
/// The risk this shifts onto the wiring is real and is guarded there instead: a redundant
/// deployment whose composition root forgets to supply the live source would fall back to this
/// and let both nodes claim. DriverFactoryBootstrapPrimaryRoleSourceTests pins that the
/// Host resolves a real one.
///
///
public sealed class UnmanagedDriverPrimaryRoleSource : IDriverPrimaryRoleSource
{
/// The shared instance — it holds no state.
public static readonly UnmanagedDriverPrimaryRoleSource Instance = new();
private UnmanagedDriverPrimaryRoleSource()
{
}
///
public bool ShouldActAsPrimary => true;
///
/// No-op: there is no redundancy manager to publish anything here.
public void PublishPrimaryRole(bool shouldActAsPrimary)
{
}
}