"""The downstream policy type -- generic resilience settings, no service specifics. This is the library's concrete `Policy` (it satisfies `guards.Policy` structurally). It carries the generic resilience invariants every consumer wants, so it lives apart from this service's catalog (`policies.py`) and lifts into the shared library alongside `breaker.py`/`guards.py`. """ from dataclasses import dataclass # Reads at or beyond this many seconds are "long" -- retrying them wastes a worker's time budget, # so retries are forbidden (the breaker + timeout are the protection there). LONG_READ_SECONDS = 30 @dataclass(frozen=True) class DownstreamPolicy: """Timeout, retry and circuit-breaker settings for one downstream resource.""" connect_timeout: float read_timeout: float fail_max: int # CONSECUTIVE failures (CLOSED) that trip; not a rolling window (deliberate) reset_timeout: float success_threshold: int = ( 2 # consecutive half-open probe successes to re-close (anti-flap) ) retries: int = 0 # only applied to idempotent calls; blocked on long reads below def __post_init__(self) -> None: """Validate the policy invariants at construction (import) time.""" if self.reset_timeout < self.read_timeout: raise ValueError('reset_timeout must be >= read_timeout') if self.retries and self.read_timeout >= LONG_READ_SECONDS: raise ValueError('retries not allowed on long read_timeout resources')