| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Core.Rules.Effectful
Description
The resilience harness for effectful rules: the per-attempt timeout, bounded
retry with backoff, and per-source circuit breaker wrapped around a rule evaluation
that does IO. Ecluse.Core.Rules attaches a Resilience to each effectful rule at
prepare and runs it through runResilient; the pure built-ins
never enter this module.
A resilient evaluation runs under its breaker's admission gate, a per-attempt
timeout, and bounded retry with backoff. Any RuleVerdict the rule returns -- a
deterministic CannotVet included -- resets the breaker and is passed on Decided,
taken at face value and never retried; only a fault the harness observes (a
timeout, an exception, or the breaker already open) advances the breaker and
resolves to , the alignment from the
rule's Unavailable transience alignment reasonResilience (fail-closed FailDeny or fail-open FailNoDecision). Total:
runResilient never throws; a rule failure becomes a result.
The breaker timing reads the injected resilience clock (resClock), read fresh at
each breaker decision, so it is deterministic under test and independent of the
request snapshot the age rules hold constant. Reading it again after the retry run
means a tripped breaker's cooldown starts when the failure commits, not when the
run began.
Synopsis
- data Resilience = Resilience {}
- data EffectfulConfig = EffectfulConfig {}
- defaultEffectfulConfig :: EffectfulConfig
- newBreaker :: IO (TVar Breaker)
- newtype FaultReporter = FaultReporter (Text -> Text -> IO ())
- reportFault :: FaultReporter -> Text -> Text -> IO ()
- runResilient :: Resilience -> Text -> (PackageDetails -> IO RuleVerdict) -> PackageDetails -> IO RuleEvaluation
- backoffPolicy :: [Int] -> RetryPolicyM IO
The resilience policy
data Resilience Source #
The resilience policy wrapped around an effectful rule's IO: the timeout/retry/
breaker knobs, the per-source circuit-breaker state, its observer, and the
failure alignment an exhausted evaluation resolves to (fail-closed FailDeny or
fail-open FailNoDecision). The alignment rides on the prepared rule, folding away the
separate failure-policy the two-tier design once carried.
Constructors
| Resilience | |
Fields
| |
data EffectfulConfig Source #
The resilience knobs around an effectful rule's IO: a per-attempt timeout,
how many retries to make on failure with the backoff before each, and the breaker
threshold and cooldown. The breaker's timing reads the injected resilience clock
(resClock) fresh at failure commit, not the request snapshot ctxNow.
Constructors
| EffectfulConfig | |
Fields
| |
defaultEffectfulConfig :: EffectfulConfig Source #
Sensible defaults for the resilience knobs: a 2-second per-attempt timeout, two retries at 100ms then 250ms, and a breaker tripping after 5 consecutive failures and cooling for 30 seconds. The caller supplies the rule's IO; the knobs are policy with these defaults.
newBreaker :: IO (TVar Breaker) Source #
A fresh, healthy breaker (no failures recorded) in a new TVar.
Effectful-fault observation
newtype FaultReporter Source #
The observer an exhausted effectful evaluation reports its fault detail to: the
deciding rule's name and the rendered fault (an exception's displayException, or a
timeout). A telemetry-agnostic callback in the shape of BreakerReporter,
so the pure rules engine names no logger; the composition root closes a katip line over
it. Fires once per exhausted evaluation, never on a verdict or a still-cooling breaker.
Constructors
| FaultReporter (Text -> Text -> IO ()) |
reportFault :: FaultReporter -> Text -> Text -> IO () Source #
Report one exhausted evaluation's fault: the rule name and the rendered detail.
Running an evaluation through it
runResilient :: Resilience -> Text -> (PackageDetails -> IO RuleVerdict) -> PackageDetails -> IO RuleEvaluation Source #
Run one effectful rule evaluation through its Resilience policy: the breaker
admission gate, then the per-attempt timeout under bounded retry, then the breaker
settlement. The rule's name tags the audit reason; the evaluator is the rule's raw
per-version IO with the evaluation context already applied. See the module header
for the verdict-vs-fault contract; runEffectfulRule is the
engine-level entry that dispatches a prepared rule here.
backoffPolicy :: [Int] -> RetryPolicyM IO Source #
An ecBackoff schedule compiled to a Control.Retry policy: the retry at
iteration n waits the n-th delay (microseconds) before it, and the policy stops
(yields Nothing) once the schedule is exhausted -- so the list's length is the retry
budget. [] admits no retry (a single attempt); [a, b] admits up to two. Inspect
the resulting delays without sleeping with simulatePolicy.