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.
This commit is contained in:
Joseph Doherty
2026-10-15 06:14:58 -04:00
parent ebcbd81fca
commit 826312befe
430 changed files with 4434 additions and 3923 deletions
@@ -36,7 +36,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
public sealed record InitializeSucceeded(int Generation);
public sealed record InitializeFailed(string Reason, int Generation);
public sealed record DisconnectObserved(string Reason);
/// <summary>#477 — sent to the parent (<see cref="DriverHostActor"/>) on every connectivity transition:
/// <summary>Sent to the parent (<see cref="DriverHostActor"/>) on every connectivity transition:
/// <c>Connected=true</c> on entering the Connected state, <c>false</c> on entering Reconnecting. The host
/// annotates this driver's native alarm conditions' source-data Quality from it (comms lost → Bad,
/// restored → Good) — independently of alarm transitions, since a comms-lost driver emits no alarm
@@ -44,8 +44,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
public sealed record ConnectivityChanged(string DriverInstanceId, bool Connected);
/// <summary>
/// Sent to the parent from <see cref="PostRestart"/> so the host re-provisions THIS incarnation
/// (archreview 03/S6, 03/S16).
/// Sent to the parent from <see cref="PostRestart"/> so the host re-provisions THIS incarnation.
/// </summary>
/// <remarks>
/// A supervision restart recreates the actor object in place: the desired subscription sets are gone
@@ -113,7 +112,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// (<c>DriverHostActor.FanCommsLossToDependencyMux</c>), which was the last sender to fabricate this
/// message, now fabricates a batch too. The dependency mux still accepts it for tests and direct senders.
/// <para><paramref name="StatusCode"/> is the driver's full OPC UA status code including its sub-code
/// (#519) — not a severity bucket. Classify it with <see cref="OpcUaStatus.IsGood"/> and friends rather
/// — not a severity bucket. Classify it with <see cref="OpcUaStatus.IsGood"/> and friends rather
/// than comparing it to <see cref="OpcUaStatus.Good"/>.</para></summary>
public sealed record AttributeValuePublished(string DriverInstanceId, string FullReference, object? Value, uint StatusCode, DateTime TimestampUtc);
/// <summary>One value within an <see cref="AttributeValueBatchPublished"/>.
@@ -123,7 +122,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
public readonly record struct AttributeValueItem(string FullReference, object? Value, uint StatusCode, DateTime TimestampUtc);
/// <summary>Batched form of <see cref="AttributeValuePublished"/> — every changed attribute of ONE
/// driver production burst in one message. The parent fans the whole batch to the mux and the OPC UA
/// publish path in one hop each. Status codes ride verbatim (#519).</summary>
/// publish path in one hop each. Status codes ride verbatim.</summary>
public sealed record AttributeValueBatchPublished(string DriverInstanceId, IReadOnlyList<AttributeValueItem> Items);
private sealed record DataChangeForward(string FullReference, DataValueSnapshot Snapshot);
/// <summary>Self-sent when a batch-capable driver (<see cref="IBatchSubscribable"/>) raises
@@ -153,9 +152,9 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// behaviour for the same reason as <see cref="RediscoveryRaised"/> — a probe tick can land while the
/// driver is between connects.</summary>
private sealed record HostStatusRaised;
/// <summary>Self-sent when the wrapped driver raises <see cref="IDriverConnectionLossSource.ConnectionLost"/> (#676)
/// <summary>Self-sent when the wrapped driver raises <see cref="IDriverConnectionLossSource.ConnectionLost"/>
/// — it KNOWS its connection is gone for good. Marshals the event off the driver's thread. Acted on only in
/// Connected (it becomes a disconnect at once, instead of after the #527 watchdog's silence window); every other
/// Connected (it becomes a disconnect at once, instead of after the watchdog's silence window); every other
/// behaviour is already reconnecting or connecting and ignores it.</summary>
private sealed record ConnectionLossRaised(DriverConnectionLossEventArgs Args);
@@ -170,7 +169,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <summary>
/// How long a Connected driver may go without a single successful read — while also reporting
/// itself non-Healthy — before the actor declares comms lost (Gitea #527). Evaluated on the
/// itself non-Healthy — before the actor declares comms lost. Evaluated on the
/// existing <see cref="HealthPollInterval"/> tick, so the effective detection granularity is
/// 30 s and the worst-case detection latency is this value plus one tick.
/// </summary>
@@ -178,8 +177,8 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// 90 s = three health-poll ticks. Long enough that a device answering slowly under load is not
/// torn down, short enough that an operator watching a dead PLC sees it inside two minutes.
/// Hardcoded rather than exposed in appsettings, matching <see cref="HealthPollInterval"/> and
/// <see cref="DefaultReconnectInterval"/>. Unlike the periodic drift-detector removed by Gitea
/// #523, this generates <b>no</b> outbound traffic on a healthy system: the watchdog is a local
/// <see cref="DefaultReconnectInterval"/>. Unlike the periodic drift-detector removed earlier,
/// this generates <b>no</b> outbound traffic on a healthy system: the watchdog is a local
/// read of <see cref="IDriver.GetHealth"/>, and it only reaches the network once a driver has
/// already stopped reading successfully.
/// </remarks>
@@ -202,7 +201,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
private readonly TimeSpan _healthPollInterval;
/// <summary>Comms-loss watchdog threshold (#527); <see cref="TimeSpan.Zero"/> or negative disables it.</summary>
/// <summary>Comms-loss watchdog threshold; <see cref="TimeSpan.Zero"/> or negative disables it.</summary>
private readonly TimeSpan _commsLossTimeout;
/// <summary>When the actor last entered <c>Connected</c>. The comms-loss watchdog's baseline for a
@@ -274,14 +273,14 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// newly-added alarm for the life of the connection. Empty whenever no handle is held.</para></summary>
private IReadOnlyList<string> _subscribedAlarmRefs = Array.Empty<string>();
/// <summary>#662 — consecutive <see cref="HandleSubscribeAlarmsAsync"/> failures; reset on success and on
/// <summary>Consecutive <see cref="HandleSubscribeAlarmsAsync"/> failures; reset on success and on
/// <see cref="DetachAlarmSource"/> (a new connection starts a new streak).</summary>
private int _alarmSubscribeFailures;
/// <summary>#662 — health-poll ticks the alarm reconcile still skips before its next retry (backoff).</summary>
/// <summary>Health-poll ticks the alarm reconcile still skips before its next retry (backoff).</summary>
private int _alarmRetrySkipTicks;
/// <summary>#662 — backoff cap: at the production 30 s tick a failing alarm subscribe is retried at least
/// <summary>Backoff cap: at the production 30 s tick a failing alarm subscribe is retried at least
/// every 4 minutes.</summary>
private const int MaxAlarmRetrySkipTicks = 7;
@@ -305,7 +304,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <param name="healthPollInterval">Optional period of the health-poll heartbeat, which also drives the
/// subscription reconcile (<see cref="ReconcileSubscription"/>); defaults to
/// <see cref="HealthPollInterval"/>. Exists so a test can drive the reconcile without waiting 30 s.</param>
/// <param name="commsLossTimeout">Optional comms-loss watchdog threshold (#527); defaults to
/// <param name="commsLossTimeout">Optional comms-loss watchdog threshold; defaults to
/// <see cref="DefaultCommsLossTimeout"/>. <see cref="TimeSpan.Zero"/> disables the watchdog — used by
/// tests that need a Connected driver to stay Connected while reporting non-Healthy.</param>
/// <returns>A <see cref="Akka.Actor.Props"/> instance configured to create the wrapped <see cref="DriverInstanceActor"/>.</returns>
@@ -343,7 +342,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
{
var isWindowsOnly = driverType is "Galaxy";
if (!OperatingSystem.IsWindows() && isWindowsOnly) return true;
// R3-04 (theme 4 guard): GossipedRolesHave, not a bare "dev" literal Contains — the same
// GossipedRolesHave, not a bare "dev" literal Contains — the same
// case-insensitivity the rest of the role authority gives every other consumer.
if (RoleParser.GossipedRolesHave(roles, RoleParser.Dev) && isWindowsOnly) return true;
return false;
@@ -361,7 +360,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// defaults to <see cref="NullDriverCapabilityInvoker"/> (pass-through) when null.</param>
/// <param name="healthPollInterval">Period of the health-poll heartbeat, which also drives the
/// subscription reconcile; defaults to <see cref="HealthPollInterval"/> when null.</param>
/// <param name="commsLossTimeout">Comms-loss watchdog threshold (#527); defaults to
/// <param name="commsLossTimeout">Comms-loss watchdog threshold; defaults to
/// <see cref="DefaultCommsLossTimeout"/> when null, and is disabled when zero or negative.</param>
public DriverInstanceActor(
IDriver driver,
@@ -411,14 +410,11 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
Timers.StartPeriodicTimer("health-poll", HealthPollTick.Instance, _healthPollInterval);
}
// Ask the host to re-provision this incarnation - see ReprovisionRequested for why a supervision
// restart otherwise leaves a dead child. base.PostRestart runs first because it runs PreStart, which
// re-attaches the rediscovery / host-status sources and restarts the health timer; the reprovision
// is the part PreStart cannot do, since only the host knows this driver's config and desired set.
/// <inheritdoc />
/// <remarks>
/// Ask the host to re-provision this incarnation — see <see cref="ReprovisionRequested"/> for why a
/// supervision restart otherwise leaves a dead child. <c>base.PostRestart</c> runs first because it
/// runs <see cref="PreStart"/>, which re-attaches the rediscovery / host-status sources and restarts
/// the health timer; the reprovision is the part <see cref="PreStart"/> cannot do, since only the
/// host knows this driver's config and desired set.
/// </remarks>
protected override void PostRestart(Exception reason)
{
base.PostRestart(reason);
@@ -500,17 +496,17 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
private void Connected()
{
// #477 — announce connectivity to the host so it can clear any comms-lost Quality annotation on this
// Announce connectivity to the host so it can clear any comms-lost Quality annotation on this
// driver's native alarm conditions (Bad → Good). Fire-and-forget; the host defaults conditions to a
// non-Good "waiting for initial data" quality at materialise, and this is what confirms them Good.
Context.Parent.Tell(new ConnectivityChanged(_driverInstanceId, Connected: true));
// #527 — baseline for the comms-loss watchdog. Re-stamped on every Connected entry so a driver that
// Baseline for the comms-loss watchdog. Re-stamped on every Connected entry so a driver that
// reconnects but never manages a read is measured from THIS connect, not from a stale earlier one.
_connectedSinceUtc = DateTime.UtcNow;
Receive<DisconnectObserved>(msg => HandleDisconnectObserved(msg.Reason));
// #676 — the driver KNOWS its connection is gone (e.g. OpcUaClient: the upstream server halted or forgot the
// The driver KNOWS its connection is gone (e.g. OpcUaClient: the upstream server halted or forgot the
// session). Same handling as the watchdog's disconnect, minus the ~90 s of silence it would need to prove it.
// Handled inline rather than re-told as DisconnectObserved (Reconnecting also swallows a stray DisconnectObserved).
Receive<ConnectionLossRaised>(msg => HandleDisconnectObserved($"driver reported connection lost: {msg.Args.Reason}"));
@@ -571,7 +567,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>
/// Gitea #527 — the comms-loss watchdog. Declares a Connected driver disconnected once it has gone
/// The comms-loss watchdog. Declares a Connected driver disconnected once it has gone
/// <see cref="_commsLossTimeout"/> without a single successful read <b>while also</b> reporting
/// itself non-Healthy, by self-sending <see cref="DisconnectObserved"/>.
/// </summary>
@@ -582,9 +578,9 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// Reconnect, so a driver that connected successfully and then lost its device stayed Connected
/// forever: it published Bad for every tag, never re-initialised, and did not recover when the device
/// came back — live-confirmed against a stopped Modbus fixture, which needed a node restart. Both
/// comms-loss quality paths (#477 native conditions, #481 scripted-alarm inputs) hang off
/// comms-loss quality paths (native conditions, scripted-alarm inputs) hang off
/// <see cref="ConnectivityChanged"/>, so both were wired to a signal that never fired.
/// Since Gitea #670 this is also <c>OpcUaClientDriver</c>'s only recovery path for a lost upstream
/// This is also <c>OpcUaClientDriver</c>'s only recovery path for a lost upstream
/// session (its in-driver SDK reconnect handler was deleted).
/// </para>
/// <para>
@@ -634,7 +630,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
// Measure silence from the LATER of the last successful read and THIS connect — the grace is per
// connection. `??` was wrong: drivers PRESERVE LastSuccessfulRead across Degraded/Faulted writes (that
// preservation is the #527 design), so after a trip + reconnect to a device that accepts the connection
// preservation is the watchdog design), so after a trip + reconnect to a device that accepts the connection
// but still serves no data, the stamp is already older than the threshold and the FIRST tick of the new
// connection re-trips — churn at health-tick cadence instead of the designed grace. `??` only ever helped
// a driver that had never read at all.
@@ -653,7 +649,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>
/// Periodic desired-vs-actual subscription reconcile (#488), riding the existing <c>health-poll</c>
/// Periodic desired-vs-actual subscription reconcile, riding the existing <c>health-poll</c>
/// tick. While Connected, a driver that has a desired subscription set but no live handle is
/// re-subscribed.
/// </summary>
@@ -701,7 +697,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>
/// #662 — the native-alarm half of <see cref="ReconcileSubscription"/>. The data reconcile above never
/// The native-alarm half of <see cref="ReconcileSubscription"/>. The data reconcile above never
/// looked at alarms, so an alarm subscribe that failed on (re)connect — e.g. a session not yet ready
/// after an outage — left the feed gated for the life of the connection, with the driver Healthy and
/// its data streaming. Re-asserts both halves the feed needs: the <c>OnAlarmEvent</c> handler
@@ -718,7 +714,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
if (_driver is not IAlarmSource || _desiredAlarmRefs.Count == 0) return;
if (_alarmEventHandler is null)
{
// Unreachable while every Connected entry attaches — so if this ever logs, something regressed #662.
// Unreachable while every Connected entry attaches — so if this ever logs, something regressed.
_log.Warning("DriverInstance {Id}: connected with desired alarm refs but no native-alarm handler — re-attaching (periodic reconcile)",
_driverInstanceId);
AttachAlarmSource();
@@ -739,11 +735,11 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
private void Reconnecting()
{
// #477 — announce comms loss to the host so it annotates this driver's native alarm conditions Bad
// Announce comms loss to the host so it annotates this driver's native alarm conditions Bad
// (a comms-lost driver emits no alarm events, so this is the ONLY signal that the source is unreachable).
Context.Parent.Tell(new ConnectivityChanged(_driverInstanceId, Connected: false));
// #527 — tear the dead transport down before re-initialising, or every retry leaks one.
// Tear the dead transport down before re-initialising, or every retry leaks one.
Receive<RetryConnect>(_ => InitializeAsync(_currentConfigJson ?? "{}", teardownFirst: true));
// Fast-fail writes while reconnecting (same reason as Connecting — avoids the 8s host Ask
// timeout on an inbound write to a transiently-down driver). Synchronous Receive.
@@ -799,7 +795,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <param name="teardownFirst">
/// When true, <see cref="IDriver.ShutdownAsync"/> is awaited before <see cref="IDriver.InitializeAsync"/>
/// — the sequence every driver's own <c>ReinitializeAsync</c> uses. Set on the <c>Reconnecting</c>
/// retry path (#527), because several drivers <em>overwrite</em> their transport field in
/// retry path, because several drivers <em>overwrite</em> their transport field in
/// <c>InitializeAsync</c> without disposing the previous one: <c>ModbusDriver</c> assigns
/// <c>_transport = _transportFactory(...)</c> and <c>MqttDriver</c> assigns <c>_connection</c>, both
/// over an <see cref="IAsyncDisposable"/>. Re-initialising without tearing down therefore leaks one
@@ -836,7 +832,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
"initialising anyway", driverInstanceId);
}
}
// R2-10 T10: the connect path was the ONE capability call the invoker never saw, so a
// The connect path was the ONE capability call the invoker never saw, so a
// driver that could not connect contributed zero breaker input and the resilience panel
// stayed quiet for a dead endpoint. Keyed on the driver-instance id (init spans the
// whole driver, like a bulk subscribe). The Initialize policy never retries — this
@@ -1045,7 +1041,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// Called on PostStop and on Connected → Reconnecting transitions so a stale handler doesn't push
/// data-change / alarm events to an actor that has lost its driver connection.
/// <para>Not for the data Unsubscribe / re-subscribe path — that uses <see cref="DetachDataSubscription"/>
/// (#662).</para></summary>
/// instead.</para></summary>
private void DetachSubscription()
{
DetachDataSubscription();
@@ -1054,7 +1050,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <summary>Tear down the data-change (per-tag AND batch) handlers + null the data handle, leaving the
/// native-alarm handler and handle alone.
/// <para>#662: the data Unsubscribe / re-subscribe path used to run the full <see cref="DetachSubscription"/>,
/// <para>The data Unsubscribe / re-subscribe path used to run the full <see cref="DetachSubscription"/>,
/// so every deploy that re-subscribed a Connected driver's DATA also unhooked <c>OnAlarmEvent</c> and
/// forgot the alarm handle — the alarm subscription still logged "established" but its events reached
/// nobody until a reconnect or respawn. The two subscriptions are independent at the driver; only a lost
@@ -1111,7 +1107,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>
/// Subscribe the driver's <see cref="IDriverConnectionLossSource.ConnectionLost"/> (#676), if it is one,
/// Subscribe the driver's <see cref="IDriverConnectionLossSource.ConnectionLost"/>, if it is one,
/// marshalling each raise to the actor thread — actor state is never touched on the driver's thread. Attached
/// once in <c>PreStart</c> and detached in <c>PostStop</c>, like <see cref="AttachRediscoverySource"/>: the
/// <see cref="IDriver"/> instance can outlive this actor, so a per-connect attach would leak a handler (holding
@@ -1134,7 +1130,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>
/// A connection loss reported while NOT Connected (#676): a (re)initialise is already pending or running, so
/// A connection loss reported while NOT Connected: a (re)initialise is already pending or running, so
/// there is nothing to tear down — and acting on it could tear down the connection that attempt is building.
/// </summary>
private void IgnoreConnectionLoss(ConnectionLossRaised msg) =>
@@ -1142,8 +1138,8 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
_driverInstanceId, msg.Args.Reason);
/// <summary>
/// Connected → Reconnecting on an observed disconnect — from the #527 watchdog or a driver's definitive
/// <see cref="IDriverConnectionLossSource.ConnectionLost"/> (#676).
/// Connected → Reconnecting on an observed disconnect — from the watchdog or a driver's definitive
/// <see cref="IDriverConnectionLossSource.ConnectionLost"/>.
/// </summary>
private void HandleDisconnectObserved(string reason)
{
@@ -1218,7 +1214,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
}
/// <summary>Bounded driver diagnostics, or null when the driver is not an <see cref="IDriverDiagnostics"/>
/// (Gitea #640). Pulled fresh and called UNGUARDED for the same reasons as
/// implementation. Pulled fresh and called UNGUARDED for the same reasons as
/// <see cref="CurrentSkippedTags"/>; a throwing implementation loses only the diagnostics.</summary>
private IReadOnlyDictionary<string, string>? CurrentDiagnostics()
{
@@ -1295,7 +1291,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <summary>Calls the driver's <see cref="IAlarmSource.SubscribeAlarmsAsync"/> (bounded) to register the
/// alarm subscription that un-gates its native feed, caching the returned handle. Re-checks the guard
/// (the desired set may have cleared, or another SubscribeAlarms may have already established a handle,
/// while this was queued). Failures are logged and retried on the next Connected entry or health-poll reconcile tick (#662) — the feed
/// while this was queued). Failures are logged and retried on the next Connected entry or health-poll reconcile tick — the feed
/// stays gated until then.</summary>
private async Task HandleSubscribeAlarmsAsync(SubscribeAlarms _)
{
@@ -1381,7 +1377,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <summary>Forward a per-tag-only driver's data change to the parent for OPC UA publication, as a
/// single-item batch — so the parent sees ONE message shape whether or not the driver batches.
/// <para>The driver's status code rides through UNCHANGED (#519). This used to project it onto a
/// <para>The driver's status code rides through UNCHANGED. This used to project it onto a
/// 3-state Good/Uncertain/Bad enum here, which the node manager then re-expanded to a bare
/// <c>0x80000000</c> — so no driver's sub-code ever reached a client, and the status constants each
/// driver maintains were diagnostic in logs only.</para></summary>
@@ -1395,7 +1391,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// <summary>Forward one batch-capable driver production burst to the parent as a single message.
/// <para>Every item's status code and timestamp ride through exactly as
/// <see cref="OnDataChangeForward"/> carries them for a per-tag driver (#519; timestamps come from the
/// <see cref="OnDataChangeForward"/> carries them for a per-tag driver (timestamps come from the
/// driver's snapshot, never from this actor's clock).</para></summary>
private void OnDataChangeBatchForward(DataChangeBatchForward msg)
{
@@ -1444,7 +1440,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// snapshot via <c>DriverStatusHub.JoinDriver</c> which reads the store directly.
/// </summary>
/// <returns>The health observed on this call, or null when <see cref="IDriver.GetHealth"/> threw.
/// Returned so the caller can reuse the same observation — the #527 comms-loss watchdog rides this
/// Returned so the caller can reuse the same observation — the comms-loss watchdog rides this
/// call rather than polling the driver a second time and risking a different answer.</returns>
private DriverHealth? PublishHealthSnapshot()
{
@@ -1476,7 +1472,7 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
// dedup would swallow. Flattened to a STRING for the same reason as the host digest — the
// report holds an IReadOnlyList, which a tuple compares by reference.
//
// The diagnostics digest (Gitea #640) is the fourth instance: a diagnostic fact (S7's PDU size
// The diagnostics digest is the fourth instance: a diagnostic fact (S7's PDU size
// changing across a reconnect) moves none of the other components, so without it the publish
// that carries it is deduped away. Flattened to a STRING - the bag is a dictionary, which a
// tuple compares by reference.