using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Core.Stability;
///
/// Demand-aware driver-wedge detector per docs/v2/plan.md decision #147.
/// Flips a driver to only when BOTH of the following hold:
/// (a) there is pending work outstanding, AND (b) no progress has been observed for longer
/// than . Idle drivers, write-only burst drivers, and subscription-only
/// drivers whose signals don't arrive regularly all stay Healthy.
///
///
/// Pending work signal is supplied by the caller via :
/// non-zero Polly bulkhead depth, ≥1 active MonitoredItem, or ≥1 queued historian read
/// each qualifies. The detector itself is state-light: all it remembers is the last
/// LastProgressUtc it saw and the last wedge verdict. No history buffer.
///
/// Default threshold per plan: 5 × PublishingInterval, with a minimum of 60 s.
/// Concrete values are driver-agnostic and configured per-instance by the caller.
///
public sealed class WedgeDetector
{
/// Wedge-detection threshold; pass < 60 s and the detector clamps to 60 s.
public TimeSpan Threshold { get; }
/// Whether the driver reported itself at construction.
public WedgeDetector(TimeSpan threshold)
{
Threshold = threshold < TimeSpan.FromSeconds(60) ? TimeSpan.FromSeconds(60) : threshold;
}
///
/// Classify the current state against the demand signal. Does not retain state across
/// calls — each call is self-contained; the caller owns the LastProgressUtc clock.
///
public WedgeVerdict Classify(DriverState state, DemandSignal demand, DateTime utcNow)
{
if (state != DriverState.Healthy)
return WedgeVerdict.NotApplicable;
if (!demand.HasPendingWork)
return WedgeVerdict.Idle;
var sinceProgress = utcNow - demand.LastProgressUtc;
return sinceProgress > Threshold ? WedgeVerdict.Faulted : WedgeVerdict.Healthy;
}
}
///
/// Caller-supplied demand snapshot. All three counters are OR'd — any non-zero means work
/// is outstanding, which is the trigger for checking the clock.
///
/// Polly bulkhead depth (in-flight capability calls).
/// Number of live OPC UA MonitoredItems bound to this driver.
/// Pending historian-read requests the driver owes the server.
/// Last time the driver reported a successful unit of work (read, subscribe-ack, publish).
public readonly record struct DemandSignal(
int BulkheadDepth,
int ActiveMonitoredItems,
int QueuedHistoryReads,
DateTime LastProgressUtc)
{
/// True when any of the three counters is > 0.
public bool HasPendingWork => BulkheadDepth > 0 || ActiveMonitoredItems > 0 || QueuedHistoryReads > 0;
}
/// Outcome of a single call.
public enum WedgeVerdict
{
/// Driver wasn't Healthy to begin with — wedge detection doesn't apply.
NotApplicable,
/// Driver claims Healthy + no pending work → stays Healthy.
Idle,
/// Driver claims Healthy + has pending work + has made progress within the threshold → stays Healthy.
Healthy,
/// Driver claims Healthy + has pending work + has NOT made progress within the threshold → wedged.
Faulted,
}