fix(runtime): comms-loss grace is per connection — max(LastSuccessfulRead, connectedSince) (archreview 03/S17)

This commit is contained in:
Joseph Doherty
2026-08-20 09:32:24 -04:00
parent 5fd65d190e
commit a801e2aefb
4 changed files with 141 additions and 3 deletions
@@ -561,6 +561,19 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
/// state on sustained failure: the nine poll drivers via Degraded, MQTT via its live connection
/// state, Galaxy via its reconnect supervisor.
/// </para>
/// <para>
/// <b>The grace is per CONNECTION (03/S17).</b> The silence baseline is
/// <c>max(LastSuccessfulRead, _connectedSinceUtc)</c>, not <c>LastSuccessfulRead ?? _connectedSinceUtc</c>.
/// Because <c>LastSuccessfulRead</c> is <em>preserved</em> across Degraded/Faulted writes (see above),
/// a device that accepts a connection but serves no data reconnects carrying a stamp that is already
/// older than the threshold, so a <c>??</c> baseline re-trips on the FIRST health tick of every new
/// connection — a reconnect cycle at health-tick cadence rather than the designed threshold. Taking
/// the later of the two gives each connection its own full window, which is exactly what re-stamping
/// <c>_connectedSinceUtc</c> on every Connected entry was for. Note this <b>paces</b> the cycle, it
/// does not end it: a permanently half-broken device still reconnects once per threshold, and a
/// driver that stops reading <i>within</i> a stable connection still trips on schedule (its read
/// stamp is the later term, exactly as before).
/// </para>
/// </remarks>
/// <param name="health">The health observed on this tick, or null when <see cref="IDriver.GetHealth"/>
/// threw — a failed observation is not evidence of comms loss, so it never advances the watchdog.</param>
@@ -570,8 +583,15 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
if (health is not { } observed) return;
if (observed.State == DriverState.Healthy) return;
// A driver that has never reported a successful read is measured from the current Connected entry.
var lastEvidence = observed.LastSuccessfulRead ?? _connectedSinceUtc;
// 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
// 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.
var lastEvidence = observed.LastSuccessfulRead is { } read && read > _connectedSinceUtc
? read
: _connectedSinceUtc;
var silence = DateTime.UtcNow - lastEvidence;
if (silence < _commsLossTimeout) return;