provenance — design
Class:
design· Function axis:f0the check ·f1attestation eval ·f2attestation delta ·f3Scorecard signals ·f4Sigstore/Rekor transparency log. Strict: only the shape we run. The system view is indesign.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::Findings. It produces the scan::Provenance value that verdict'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 (provenance.rs:25) is { signer_identity, slsa_level, source_repo, source_sha }; Attestation::signed_by(identity, slsa_level) (provenance.rs:38) builds one. ProvenanceRequirement (provenance.rs:46) is the policy { require_min_slsa, trusted_signers, expected_source_repo }, and evaluate(att) (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) (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
ScorecardSignals carries OSSF Scorecard facts { subject, overall, maintained, branch_protection, signed_releases, dangerous_workflow }; findings() turns a poor score / missing branch protection / dangerous workflow into advisory Findings (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 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'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 reads a Rekor /api/v1/log/entries/{uuid} response offline into RekorEntry {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 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 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".