Files
lmxopcua/src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IDriverPrimaryRoleSource.cs
T
Joseph Doherty 826312befe docs: fill missing XML docs and strip tracking IDs from src comments
Clears CommentChecker findings in src/ (3,280 -> 209, the remainder being false
positives like PLC-5/IEEE-754/M0.0) by adding missing summaries/params/returns,
normalising inheritdoc usage, and removing Gitea/review tracking IDs while keeping
the rationale they annotated. Comment-only: comment-stripped sources are identical
to the parent commit.
2026-10-15 06:14:58 -04:00

90 lines
4.5 KiB
C#

namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
/// <summary>
/// A driver-facing snapshot of "should this node be the one asserting an exclusive identity on a
/// shared external system right now?".
/// </summary>
/// <remarks>
/// <para>
/// <b>Why drivers need this at all.</b> 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
/// <c>DriverHostActor</c>, on the way in. That works because every gated operation is
/// <i>inbound</i>: 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.
/// </para>
/// <para>
/// <b>The decision, not the role.</b> Same discipline as the alarm-drain view in
/// <c>Runtime</c>: 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 <i>data-plane</i> 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
/// <c>OFFLINE</c> over a live Primary's <c>ONLINE</c> and stopping every edge node in the
/// group.
/// </para>
/// <para>
/// <b>Polled, not evented, and that is deliberate.</b> A process-wide singleton raising an
/// event onto per-instance drivers outlives every driver it hands a reference to — the exact
/// missing-<c>-=</c> 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
/// <c>OFFLINE</c> window a failover already implies.
/// </para>
/// </remarks>
public interface IDriverPrimaryRoleSource
{
/// <summary>
/// Whether this node currently holds the Primary data-plane role — and may therefore claim an
/// exclusive external identity such as a Sparkplug host id.
/// </summary>
bool ShouldActAsPrimary { get; }
/// <summary>
/// Records a fresh decision. Called by <c>DriverHostActor</c> on every redundancy snapshot.
/// </summary>
/// <param name="shouldActAsPrimary">
/// The verdict <c>PrimaryGatePolicy.ShouldServiceAsPrimary</c> produced.
/// </param>
void PublishPrimaryRole(bool shouldActAsPrimary);
}
/// <summary>
/// The <see cref="IDriverPrimaryRoleSource"/> a driver uses when nothing publishes one — a
/// standalone host, a unit test, or any deployment with no redundancy manager wired.
/// </summary>
/// <remarks>
/// <para>
/// <b>It answers <see langword="true"/>, and the alternative is worse.</b> 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 this contract exists to prevent.
/// </para>
/// <para>
/// 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. <c>DriverFactoryBootstrapPrimaryRoleSourceTests</c> pins that the
/// Host resolves a real one.
/// </para>
/// </remarks>
public sealed class UnmanagedDriverPrimaryRoleSource : IDriverPrimaryRoleSource
{
/// <summary>The shared instance — it holds no state.</summary>
public static readonly UnmanagedDriverPrimaryRoleSource Instance = new();
private UnmanagedDriverPrimaryRoleSource()
{
}
/// <inheritdoc/>
public bool ShouldActAsPrimary => true;
/// <inheritdoc />
public void PublishPrimaryRole(bool shouldActAsPrimary)
{
}
}