Gunnar

mikronova/modgunn

.nornir/provenance-guide.md

# provenance — guide

> **Class:** `guide` · audience: someone checking build provenance / SLSA / Scorecard. The
> *what* is in [`provenance-design.md`](provenance-design.md).

## f0 — require an attestation

```rust
use modgunn::provenance::{Attestation, ProvenanceRequirement};

let req = ProvenanceRequirement {
    require_min_slsa: 3,
    trusted_signers: vec!["release-bot@example.com".into()],
    expected_source_repo: Some("https://codeberg.org/nordisk/modgunn".into()),
};

let att = Attestation::signed_by("release-bot@example.com", 3);
let (provenance, findings) = req.evaluate(Some(&att));   // findings empty ⇒ satisfied
```

Feed `provenance` into a `scan::Artifact` so Engine A's `provenance_finding` folds it into the
verdict (see [`verdict-guide.md`](verdict-guide.md)). `req.evaluate(None)` scores a missing
attestation against `ProvenancePolicy`.

## f2 — detect a provenance change

```rust
let deltas = modgunn::provenance::attestation_delta(Some(&old_att), Some(&new_att));
// non-empty ⇒ signer / source-repo / presence changed (A25) — review before promoting
```

## f3 — Scorecard signals

```rust
use modgunn::provenance::{parse_scorecard, ScorecardSignals};

// Ingest a raw OpenSSF Scorecard report (`scorecard --format json`) offline:
let sc = parse_scorecard(json)?;        // → ScorecardSignals { subject, overall, … }
// …or hand-build the signals directly:
let sc = ScorecardSignals { subject: "some-crate".into(), overall: 4.2, maintained: true,
                            branch_protection: false, signed_releases: false,
                            dangerous_workflow: true };
for f in sc.findings() { /* low score / unmaintained / dangerous workflow / no branch protection */ }
```

`parse_scorecard` reads Scorecard's 0..=10 check scale (higher = better; `-1` = inconclusive):
a check passes at `score >= 5`, and **Dangerous-Workflow is inverted** (a present score in
`0..10` means a dangerous CI pattern was found → a High finding). Persist with
`Warehouse::append_scorecard(&[sc])`; read the latest-per-subject with `load_scorecard()`.

Or drive the whole flow from the CLI:

```text
# parse + print the signals and their findings (offline, no catalog):
modgunn scorecard --path report.json
# …and persist into the shared warehouse (append→load path):
modgunn scorecard --path report.json --subject acme/widget --warehouse <root>
# read the persisted signals + findings back out:
modgunn warehouse provenance-log --warehouse <root>
```

## Gotchas

- `evaluate` returns *both* a `Provenance` and findings — use the `Provenance` for the artifact
  and surface the findings; don't discard either.
- Scorecard signals are advisory features (A14/A24), not hard blocks — they inform, they don't
  by themselves force a `Block`.