package health import ( "context" "errors" "time" "proxy-pool/internal/config" "proxy-pool/internal/domain/activitypool" healthDomain "proxy-pool/internal/domain/health" ) var ( ErrInvalidPolicyResolver = errors.New("invalid health policy resolver") ErrUnconfiguredUpstream = errors.New("health observation references an unconfigured upstream") ) // ConfigurationSource returns a detached, validated configuration snapshot. // config.Store implements this interface without exposing its publication // mechanism to the health package. type ConfigurationSource interface { Current() *config.Config } // NewConfiguredFailureThresholdResolver resolves the policy against the // proxy's authoritative upstream at apply time. It keeps Checker input from // choosing its own failure threshold and follows Controller config reloads. func NewConfiguredFailureThresholdResolver( configuration ConfigurationSource, proxies activitypool.ProxyUpstreamReader, now func() time.Time, ) (FailureThresholdResolver, error) { if nilInterface(configuration) || nilInterface(proxies) || now == nil { return nil, ErrInvalidPolicyResolver } return func(ctx context.Context, observation healthDomain.Observation) (int, error) { if ctx == nil { return 0, ErrInvalidPolicyResolver } if err := ctx.Err(); err != nil { return 0, err } current := configuration.Current() if current == nil { return 0, ErrInvalidPolicyResolver } currentTime := now() if currentTime.IsZero() { return 0, ErrInvalidPolicyResolver } upstreamID, err := proxies.UpstreamForProxy(ctx, observation.ProxyID, currentTime.UTC()) if err != nil { return 0, err } upstream, exists := current.Upstreams[upstreamID] if !exists || !upstream.Enabled { return 0, ErrUnconfiguredUpstream } check := config.EffectiveCheck(current.Defaults.Check, upstream.Check) if check.MaxConsecutiveFailures <= 0 { return 0, ErrInvalidThreshold } return check.MaxConsecutiveFailures, nil }, nil }