using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Core.Resilience; /// /// Per-tier × per-capability resilience policy configuration for a driver instance. /// Bound from DriverInstance.ResilienceConfig JSON (nullable column; null = tier defaults). /// Per docs/v2/plan.md decisions #143 and #144. /// public sealed record DriverResilienceOptions { /// Tier the owning driver type is registered as; drives the default map. public required DriverTier Tier { get; init; } /// /// Per-capability policy overrides. Capabilities absent from this map fall back to /// for the configured . /// public IReadOnlyDictionary CapabilityPolicies { get; init; } = new Dictionary(); /// Bulkhead (max concurrent in-flight calls) for every capability. Default 32. public int BulkheadMaxConcurrent { get; init; } = 32; /// /// Bulkhead queue depth. Zero = no queueing; overflow fails fast with /// BulkheadRejectedException. Default 64. /// public int BulkheadMaxQueue { get; init; } = 64; /// /// Look up the effective policy for a capability, falling back to tier defaults when no /// override is configured. Never returns null. /// public CapabilityPolicy Resolve(DriverCapability capability) { if (CapabilityPolicies.TryGetValue(capability, out var policy)) return policy; var defaults = GetTierDefaults(Tier); return defaults[capability]; } /// /// Per-tier per-capability default policy table, per decisions #143-144 and the Phase 6.1 /// Stream A.2 specification. Retries skipped on and /// regardless of tier. /// public static IReadOnlyDictionary GetTierDefaults(DriverTier tier) => tier switch { DriverTier.A => new Dictionary { [DriverCapability.Read] = new(TimeoutSeconds: 2, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 0, BreakerFailureThreshold: 5), [DriverCapability.Discover] = new(TimeoutSeconds: 30, RetryCount: 2, BreakerFailureThreshold: 3), [DriverCapability.Subscribe] = new(TimeoutSeconds: 5, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.Probe] = new(TimeoutSeconds: 2, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.AlarmSubscribe] = new(TimeoutSeconds: 5, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.AlarmAcknowledge] = new(TimeoutSeconds: 5, RetryCount: 0, BreakerFailureThreshold: 5), [DriverCapability.HistoryRead] = new(TimeoutSeconds: 30, RetryCount: 2, BreakerFailureThreshold: 5), }, DriverTier.B => new Dictionary { [DriverCapability.Read] = new(TimeoutSeconds: 4, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.Write] = new(TimeoutSeconds: 4, RetryCount: 0, BreakerFailureThreshold: 5), [DriverCapability.Discover] = new(TimeoutSeconds: 60, RetryCount: 2, BreakerFailureThreshold: 3), [DriverCapability.Subscribe] = new(TimeoutSeconds: 8, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.Probe] = new(TimeoutSeconds: 4, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.AlarmSubscribe] = new(TimeoutSeconds: 8, RetryCount: 3, BreakerFailureThreshold: 5), [DriverCapability.AlarmAcknowledge] = new(TimeoutSeconds: 8, RetryCount: 0, BreakerFailureThreshold: 5), [DriverCapability.HistoryRead] = new(TimeoutSeconds: 60, RetryCount: 2, BreakerFailureThreshold: 5), }, DriverTier.C => new Dictionary { [DriverCapability.Read] = new(TimeoutSeconds: 10, RetryCount: 1, BreakerFailureThreshold: 0), [DriverCapability.Write] = new(TimeoutSeconds: 10, RetryCount: 0, BreakerFailureThreshold: 0), [DriverCapability.Discover] = new(TimeoutSeconds: 120, RetryCount: 1, BreakerFailureThreshold: 0), [DriverCapability.Subscribe] = new(TimeoutSeconds: 15, RetryCount: 1, BreakerFailureThreshold: 0), [DriverCapability.Probe] = new(TimeoutSeconds: 10, RetryCount: 1, BreakerFailureThreshold: 0), [DriverCapability.AlarmSubscribe] = new(TimeoutSeconds: 15, RetryCount: 1, BreakerFailureThreshold: 0), [DriverCapability.AlarmAcknowledge] = new(TimeoutSeconds: 15, RetryCount: 0, BreakerFailureThreshold: 0), [DriverCapability.HistoryRead] = new(TimeoutSeconds: 120, RetryCount: 1, BreakerFailureThreshold: 0), }, _ => throw new ArgumentOutOfRangeException(nameof(tier), tier, $"No default policy table defined for tier {tier}."), }; } /// Policy for one capability on one driver instance. /// Per-call timeout (wraps the inner Polly execution). /// Number of retry attempts after the first failure; zero = no retry. /// /// Consecutive-failure count that opens the circuit breaker; zero = no breaker /// (Tier C uses the supervisor's process-level breaker instead, per decision #68). /// public sealed record CapabilityPolicy(int TimeoutSeconds, int RetryCount, int BreakerFailureThreshold);