ecluse
Safe HaskellNone
LanguageGHC2021

Ecluse.Rules

Description

The policy rules engine.

A rule set is evaluated against a single PackageDetails snapshot to produce a Decision. The model is deny by default; precedence decides: each rule carries an integer precedence (PrecededRule), and the highest-precedence rule that does not abstain wins. At equal precedence a deny beats an allow, and the built-in deny defaults sit strictly above the allow defaults (defaultPrecedence), so "any deny overrides any allow" holds out of the box. If every rule abstains the package is denied by default. Because precedence — not list order — decides, and every remaining equal-precedence tie is broken by rule identity (ruleName) rather than list position, the credited rule is fully order-independent; only the order in which abstain reasons are gathered for the audit trail follows the input list.

The initial rule set is pure (no IO). Effectful rules (CVE lookups, etc.) are a later tier layered on top of this one; see docs/architecture.md. The rule data types live in Ecluse.Rules.Types.

Synopsis

Documentation

ruleName :: Rule -> Text Source #

A stable, human-facing name for a rule (for logs and denial messages).

evalRule :: EvalContext -> Rule -> PackageDetails -> RuleOutcome Source #

Evaluate a single rule against a single package version. Total — a malformed rule or package yields an outcome, never an exception, so hostile metadata cannot crash the gate.

evalRules :: EvalContext -> [PrecededRule] -> PackageDetails -> Decision Source #

Evaluate a package version against a rule set.

Precedence decides. Every rule that does not abstain is a candidate, and the highest-precedence candidate wins; at equal precedence a Deny beats an Allow, and any remaining equal-precedence tie (e.g. two allows) is broken by rule identity — the lexicographically-smallest ruleName is credited — not by list position. The winner yields Approved or Denied. If no rule takes a position — every rule abstains, including the empty rule set — the package is DeniedByDefault, with every abstain reason collected in list order for the audit trail and denial message. The decision, and the rule credited for it, are independent of the input rule order; only the gathered abstain-reason order follows the list.

evalRulesWithPrecedence :: EvalContext -> [PrecededRule] -> PackageDetails -> (Maybe Int, Decision) Source #

Evaluate a rule set, returning the winning rule's precedence alongside the Decision. The precedence is Nothing for a deny-by-default (no rule took a position), and Just p for the position-taking rule that won at precedence p.

This is the pure tier as the effectful tier consults it: the winning precedence is what the effectful tier compares its own rules against to decide which — if any — could still change the outcome, so a rule ranked below the pure winner is skipped (see Ecluse.Rules.Effectful). evalRules is this with the precedence dropped.

renderDecision :: PackageDetails -> Decision -> Text Source #

A human-readable summary of a decision, suitable for logs and the denial response body.

renderDuration :: NominalDiffTime -> Text Source #

Render a duration as an approximate, human-friendly string for use in decision messages. Always non-negative.

>>> renderDuration 604800
"7 days"
>>> renderDuration 90
"1 minute"