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;
}
}