namespace ZB.MOM.WW.OtOpcUa.Server.Redundancy; /// /// Tracks the Recovering-band dwell for a node after a Faulted → Healthy transition. /// Per decision #154 and Phase 6.3 Stream B.4 a node that has just returned to health stays /// in the Recovering band (180 Primary / 30 Backup) until BOTH: (a) the configured /// has elapsed, AND (b) at least one successful publish-witness /// read has been observed. /// /// /// Purely in-memory, no I/O. The coordinator feeds events into , /// , and ; /// becomes true only after both conditions converge. /// public sealed class RecoveryStateManager { private readonly TimeSpan _dwellTime; private readonly TimeProvider _timeProvider; /// Last time the node transitioned Faulted → Healthy. Null until first recovery. private DateTime? _recoveredUtc; /// True once a publish-witness read has succeeded after the last recovery. private bool _witnessed; public TimeSpan DwellTime => _dwellTime; public RecoveryStateManager(TimeSpan? dwellTime = null, TimeProvider? timeProvider = null) { _dwellTime = dwellTime ?? TimeSpan.FromSeconds(60); _timeProvider = timeProvider ?? TimeProvider.System; } /// Report that the node has entered the Faulted state. public void MarkFaulted() { _recoveredUtc = null; _witnessed = false; } /// Report that the node has transitioned Faulted → Healthy; dwell clock starts now. public void MarkRecovered() { _recoveredUtc = _timeProvider.GetUtcNow().UtcDateTime; _witnessed = false; } /// Report a successful publish-witness read. public void RecordPublishWitness() => _witnessed = true; /// /// True when the dwell is considered met: either the node never faulted in the first /// place, or both (dwell time elapsed + publish witness recorded) since the last /// recovery. False means the coordinator should report Recovering-band ServiceLevel. /// public bool IsDwellMet() { if (_recoveredUtc is null) return true; // never faulted → dwell N/A if (!_witnessed) return false; var elapsed = _timeProvider.GetUtcNow().UtcDateTime - _recoveredUtc.Value; return elapsed >= _dwellTime; } }