Gunnar

mikronova/modgunn

.nornir/provenance-design.md

# provenance — design

> **Class:** `design` · **Function axis:** `f0` the check · `f1` attestation eval · `f2`
> attestation delta · `f3` Scorecard signals · `f4` Sigstore/Rekor transparency log. Strict: only the shape we run. The system view
> is in [`design.md`](design.md).

## f0 — the check

`provenance` (module `provenance.rs`, feature `scan`) supplies the integrity half of Engine A's
verdict: it evaluates whether an artifact's build **attestation** meets a requirement and turns
supply-chain integrity signals into `core::Finding`s. It produces the `scan::Provenance` value
that [`verdict`](verdict-design.md)'s `provenance_finding` reads, plus standalone A3/A24/A25
findings.

**Reuse:** `provenance.uses = [core, scan]`. Pure logic — no network; cosign/rekor wire-up
stays on the dynamic plane.

## f1 — attestation evaluation

[`Attestation`](../src/provenance.rs) (`provenance.rs:25`) is `{ signer_identity, slsa_level,
source_repo, source_sha }`; [`Attestation::signed_by(identity, slsa_level)`](../src/provenance.rs)
(`provenance.rs:38`) builds one. [`ProvenanceRequirement`](../src/provenance.rs)
(`provenance.rs:46`) is the policy `{ require_min_slsa, trusted_signers, expected_source_repo }`,
and [`evaluate(att)`](../src/provenance.rs) (`provenance.rs:60`) returns
`(scan::Provenance, Vec<Finding>)` — a missing/weak attestation (below min-SLSA, untrusted
signer, source-repo mismatch) yields findings; a satisfying one yields a signed `Provenance`.
This is A3 (provenance/signature) + the trusted-signer/min-SLSA gate.

## f2 — attestation delta

[`attestation_delta(old, new)`](../src/provenance.rs) (`provenance.rs:121`) reports **A25**: the
attestation appearing/disappearing, a builder-identity (signer) change, or a source-repo URL
change between two observations → `Vec<Finding>`. The account-takeover / build-tamper signal.

## f3 — Scorecard signals

![Scorecard ingest — parse_scorecard → ScorecardSignals → Finding[]](assets/scorecard-ingest.svg)

[`ScorecardSignals`](../src/provenance.rs) carries OSSF Scorecard facts
`{ subject, overall, maintained, branch_protection, signed_releases, dangerous_workflow }`;
[`findings()`](../src/provenance.rs) turns a poor score / missing branch
protection / dangerous workflow into advisory `Finding`s (A14 bus-factor, A24 maintainer
anomaly features):
`overall < 5` → Medium `MODGUNN-SCORECARD-LOW`, unmaintained → Medium
`…-UNMAINTAINED`, a dangerous workflow → **High** `…-DANGEROUS-WORKFLOW`, and missing
branch protection → Low `…-NO-BRANCH-PROTECTION`.

**Ingest** — [`parse_scorecard`](../src/provenance.rs) folds a raw `scorecard --format
json` report (aggregate `score`, `repo.name`, a `checks[]` of `{name, score}` on the
0..=10 scale) into that same `ScorecardSignals` — the read-side counterpart to `findings()`
(the exact `feeds::parse_*` shape: pure serde_json, offline, no twin of the model). A check
*passes* at Scorecard's Medium tier (`score >= 5`); **Dangerous-Workflow is inverted** — it
scores `10` when clean, `0` when a script/self-hosted-runner injection is found, so a present
score in `0..10` sets `dangerous_workflow`. Persisted via
[`warehouse`](warehouse-design.md)'s `append_scorecard` / `load_scorecard`.

**CLI** — `modgunn scorecard --path <report.json> [--subject <name>] [--warehouse <root>]`
drives that fold from the command line (mirroring the `ingest` verb): read a
`scorecard --format json` report, print the signals + the advisory findings they generate,
and — when `--warehouse` is given — persist them via `append_scorecard` so a later
`warehouse provenance-log` reads the same signals back (the append→load round-trip).
`--subject` overrides the report's `repo.name` to the fleet key; without `--warehouse` it is
a pure offline parse+print a consumer runs on a laptop.

## f4 — Sigstore / Rekor, the transparency-log leg

A cosign signature is only half the story: the other half is that the signature was written to a
**public, append-only transparency log** (Rekor), so a signer cannot quietly sign a malicious artifact
for one victim. [`provenance::parse_rekor_entries`](../src/provenance.rs) reads a Rekor
`/api/v1/log/entries/{uuid}` response **offline** into [`RekorEntry`](../src/provenance.rs)
`{uuid, log_index, integrated_time, kind, artifact_sha256, signer_identity, has_inclusion_proof,
has_signed_timestamp}` — the digest→identity→index binding a promotion gate compares against policy.

Two body kinds are decoded: **`hashedrekord`** (digest at `spec.data.hash.value`) and
**`intoto`/`dsse`** (`spec.content.payloadHash.value`, signer from
`spec.content.envelope.signatures[].keyid`). The base64 body is decoded by a **dozen lines of pure
Rust** rather than a `base64` dependency — the LEAN `scan` gate (holger / static-holger) stays
dependency-clean, which is the whole point of the feature split. That decoder is the collector's trust
boundary, so it rejects truncated groups, data-after-padding and stray characters with `None` instead
of returning partial bytes that would parse into a plausible-but-false record.

**What it does NOT claim.** [`RekorEntry::attestation`](../src/provenance.rs) returns
`slsa_level = 0`, always. A Rekor entry proves *this digest was logged by this identity*; it says
nothing about how the artifact was **built**, which is what a SLSA level asserts. Inferring a level
from log presence would be exactly the unearned claim `provenance-verify` exists to prevent — a caller
that also holds a verified SLSA provenance predicate raises it explicitly.

Likewise the `verification` block is **read, not assumed**: an entry with no `inclusionProof` is a
self-asserted record, and [`RekorEntry::findings`](../src/provenance.rs) grades it `High`
(`MODGUNN-REKOR-NO-INCLUSION-PROOF`) rather than treating "the JSON says it is in the log" as proof.
Verifying that proof against the log's signed tree head remains the dynamic plane's job (a live Rekor
client); modgunn owns the record and the honest statement of which parts are evidence-backed.

CLI: `warehouse rekor --entries <file|dir> [--expect-sha256 <hex>] [--trusted-signer <id>]`.
`--expect-sha256` asserts a specific artifact **is** in the log *with an inclusion proof* (a
self-asserted hit still fails); `--trusted-signer` asserts who logged it. Anti-silent-empty: a corpus
yielding 0 decodable entries is a MISSING-FEED error, never "the artifact is fine".