Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/modgunn

.nornir/verdict-design.md

verdict (Engine A) — design

Class: design · Function axis: f0 the scan fold · f1 the checks · f2 the policy · f3 the verdict type. Strict: only the shape we run. The why is in verdict-reasoning.md; the system view in design.md.

f0 — the fold

Engine A is the lean, always-on gate (default = ["scan"]). It turns an Artifact (scan.rs:58) into a core::Verdict by folding three independent checks and reducing their findings to the strictest Decision. OsvScanner::scan (impl ScanEngine) runs, in order:

  1. known-vuln match — for each advisory AdvisoryDb::query returns for the artifact (scan.rs:213), vuln_finding produces zero/one Finding;
  2. license — license_finding → zero/one Finding;
  3. provenance — provenance_finding → zero/one Finding;

then decide(&findings) (scan.rs:366) picks the decision. Because Decision derives Ord in declaration order Pass < Warn < Block (core.rs:15), the fold is a max() over severity → decision (decision_for, scan.rs:356) — a clean artifact is Pass (the identity). The result carries its cache key components (policy_version, advisory_db_rev) so a consumer invalidates when either moves.

Reuse: verdict.uses = [feeds, provenance, core]. It matches against feeds' range-native Advisorys and folds provenance's findings; it owns no I/O.

f1 — the three checks

vuln_finding (scan.rs, exploit-intel gating)

One matched Advisory → zero/one Finding, applying, in order:

AdvisoryDb::query (scan.rs:213) filters the db to advisories whose matches accepts the artifact ref — range-native via feeds/versions (see feeds-design.md).

license_finding (scan.rs)

Against LicensePolicy (scan.rs:279): a denied license → High (MODGUNN-LICENSE-DENIED); a license absent from a non-empty allow-list → Medium (MODGUNN-LICENSE-UNREVIEWED); no declared license → severity from on_unknown (MODGUNN-LICENSE-UNKNOWN); otherwise no finding.

provenance_finding (scan.rs)

A signed artifact yields nothing. An unsigned one maps ProvenancePolicy (scan.rs:307) Ignore→Pass / Warn→Warn / Require→Block through severity_for into a MODGUNN-PROVENANCE-MISSING finding. The Provenance value itself is produced by the provenance function's evaluate.

f2 — the policy

Policy (scan.rs:320) is the whole tunable surface: version (a cache-key component), license: LicensePolicy, provenance: ProvenancePolicy, kev_blocks: bool, epss_block_threshold: Option<f64>, vex_not_affected: Vec<String>. Policy::default (scan.rs:339) is the shipped baseline. LicensePolicy::allowlist (scan.rs:292) builds an allow-list policy.

f3 — the verdict type (core.rs)

Verdict (core.rs:59) = { artifact, decision, findings, policy_version, advisory_db_rev }. cache_key (core.rs:70) is (blob_sha256, policy_version, advisory_db_rev) — content-addressed so a clean result is computed once and reused. max_severity is the strongest finding severity. ArtifactRef carries (ecosystem, name, version, blob_sha256); Finding is { severity, id, summary }. This is the exact type Engine B also emits, so consumers read one shape.

Canonical labels (single source). Decision::as_str / Display (core.rs) is the one home of the Pass/Warn/Block strings, and Severity::as_str / Display the one home of the severity labels. Consumers — holger's gate & UI, nornir-viz's Security pane — render THESE rather than hand-rolling a match or leaning on {:?} Debug output, so the label set never drifts across the constellation. modgunn's own CLI dogfoods them (main.rs finding/scorecard rows).

Surface

entry signature file:symbol
construct OsvScanner::new(policy, advisories) scan.rs:394
scan ScanEngine::scan(&self, &Artifact) -> Verdict scan.rs:386
fold decide(&[Finding]) -> Decision scan.rs:366
cache key Verdict::cache_key() core.rs:70

Unit tests cover the fold, gating, and cache key (scan.rs::tests), plus the canonical Decision/Severity label ↔ Display agreement (core.rs::tests).