Gunnar

mikronova/modgunn

.nornir/verdict-guide.md

# verdict (Engine A) — guide

> **Class:** `guide` · audience: a techie embedding the gate in a Rust app. The *what* is in
> [`verdict-design.md`](verdict-design.md); the *why* in [`verdict-reasoning.md`](verdict-reasoning.md).

## f0 — score one artifact in four moves

```rust
use modgunn::core::{ArtifactRef, Decision};
use modgunn::scan::{Advisory, AdvisoryDb, Artifact, OsvScanner, Policy, Provenance, ScanEngine};

// 1 · the advisory db (from feeds / the warehouse — here inline)
let db = AdvisoryDb::new("2026-07-04-a", vec![
    Advisory::basic("RUSTSEC-2024-0001", "cargo", "some-crate", vec!["1.2.3".into()],
                    modgunn::core::Severity::High, "use-after-free"),
]);

// 2 · the policy (license allow-list, provenance stance, exploit gating)
let policy = Policy::default();

// 3 · the artifact under test
let artifact = Artifact::new(
    ArtifactRef { ecosystem: "cargo".into(), name: "some-crate".into(),
                  version: "1.2.3".into(), blob_sha256: "abc…".into() },
    Some("MIT".into()),
    Provenance::signed_by("release-bot"),
);

// 4 · the verdict
let verdict = OsvScanner::new(policy, db).scan(&artifact);
assert_ne!(verdict.decision, Decision::Block);   // Pass / Warn / Block
```

The scanner folds vuln + license + provenance findings and reduces to the strictest
`Decision`. `verdict.cache_key()` is `(blob_sha256, policy_version, advisory_db_rev)` — cache a
clean result on it (holger does this on the gate/promotion path).

## f1 — tuning each check

- **exploit-intel gating.** Set `policy.kev_blocks = true` to escalate KEV-listed advisories to
  `Critical`; set `policy.epss_block_threshold = Some(0.5)` to escalate any advisory with
  EPSS ≥ 0.5 to `High`. Populate `Advisory.kev` / `Advisory.epss` via `feeds::enrich` first
  (see [`feeds-guide.md`](feeds-guide.md)).
- **VEX suppression.** Push advisory ids you have assessed `not_affected` into
  `policy.vex_not_affected` — they are dropped from the verdict.
- **license.** `LicensePolicy::allowlist(["MIT","Apache-2.0"])` warns on anything else; set
  `deny` to block outright; set `on_unknown` for artifacts with no declared license.
- **provenance.** `ProvenancePolicy::Require` blocks unsigned artifacts; `Warn` advises;
  `Ignore` passes.

## f1 — building the AdvisoryDb from feeds

In production the db is not inline — it comes from parsed feeds or the warehouse:

```rust
let advisories = modgunn::osv::parse_and_merge(osv_json_docs);   // range-native, deduped
let db = AdvisoryDb::new(db_rev, advisories);
```

or, under the `warehouse` feature, `Warehouse::load_advisory_db(Some("cargo"))` returns the
latest imported generation. See [`warehouse-guide.md`](warehouse-guide.md).

## Gotchas

- An **empty** `Advisory.affected_versions` with no `ranges` matches *all* versions of the
  package — that is deliberate (a whole-package advisory), not a bug.
- `decide` returns `Pass` for zero findings; the `Ord` on `Decision`/`Severity` is what makes
  the fold a `max()` — don't reorder those enums.
- Engine A never touches the network or the warehouse; feed it an `AdvisoryDb` you built.