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

mikronova/modgunn

.nornir/provenance-policy-and-diff.md

provenance policy & ledger-diff — design

Class: design · Function axis: f0 verify→policy→ledger · f1 the ProvenanceRequirement policy + finding codes · f2 commit-pinning (A3) · f3 the attestation delta (A25) · f4 the ledger diff + regression gate · f5 the CDC/VEX scan plumbing that feeds it. Strict: only the shape we run. The base attestation model is in provenance-design.md; the warehouse it reads in warehouse-design.md; the system view in design.md.

f0 — the model: verify → policy → ledger → diff

modgunn's supply-chain integrity story is four layers, each pure and each testable without the one below it:

  1. VERIFY — cryptographically check a carried <artifact>.build-provenance.znippy seal against trusted roots (verify_sealed_provenance, feature provenance-verify). Produces a signed/rejected verdict. This is the only network-adjacent hop and it is optional at the crate boundary.
  2. POLICY — score the verified Attestation against a ProvenanceRequirement (provenance.rs:46): does it meet min-SLSA, come from a trusted signer, claim the expected repo AND the expected commit? Any shortfall is a core::Finding.
  3. LEDGER — append every verdict (verified or not) to the append-only build_provenance Iceberg table (Warehouse::append_build_provenance). Time-travellable.
  4. DIFF — compare the ledger's newest verdict per artifact at two snapshots and classify each artifact as added / removed / verdict-transitioned; a verified→not-verified transition is a regression — the gate-worthy signal (Warehouse::provenance_diff, warehouse.rs:797).

Layers 2 and 4 are the same idea (A25 supply-chain drift) at two altitudes: layer 2 compares one attestation to a policy; layer 4 compares the whole ledger at time from to the ledger at time to.

Reuse: the policy layer is provenance.uses = [core, scan] (pure logic); the diff layer is warehouse.uses = [core, provenance] plus skade's CDC. The pure diff fold provenance_ledger_delta (warehouse.rs:1346) takes two plain &[BuildProvenanceVerdict] slices, so it is exercised with no warehouse at all.

f1 — ProvenanceRequirement + the finding codes

ProvenanceRequirement::evaluate(att) (provenance.rs:66) returns (scan::Provenance, Vec<Finding>). signed on the returned Provenance is findings.is_empty() && signer.is_some() — a policy miss un-signs the artifact for Engine A. The policy knobs and the finding each raises:

Policy field / CLI flag Fires when … Finding id Severity
(no attestation at all) evaluate(None) MODGUNN-PROV-NO-ATTESTATION High
require_min_slsa / --min-slsa <n> att.slsa_level < n MODGUNN-PROV-SLSA-TOO-LOW High
trusted_signers / --trusted-signer <id> signer present but not in the trusted set MODGUNN-PROV-UNTRUSTED-SIGNER High
(any policy set, signer absent) attestation has no signer identity MODGUNN-PROV-NO-SIGNER Medium
expected_source_repo / --expected-repo att.source_repo ≠ expected MODGUNN-PROV-SOURCE-MISMATCH High
expected_source_sha / --expected-commit att.source_sha ≠ expected (or absent while pinned) MODGUNN-PROV-COMMIT-MISMATCH High

Each knob is independent and all fire together — a fully-wrong attestation raises four findings at once (the verify_tests::… fixtures assert exactly this stacking). Empty / zero = "don't care": the all-default ProvenanceRequirement accepts anything signed.

f2 — commit-pinning (A3): expected_source_sha

expected_source_sha (provenance.rs:58) is the new pin that closes the "right repo, wrong commit" gap. Verifying the repo alone lets a build off an unmerged branch or an attacker-pushed commit on the trusted repo pass; pinning the reviewed/tagged revision rejects it (provenance.rs:116). Matching is case-insensitive — git revisions are hex and producers differ on case, so DEADBEEF matches deadbeef (sha_eq, provenance.rs:142). An attestation that carries no commit while one is pinned is a mismatch, not a silent pass — the finding still fires with <none> as the observed value.

f3 — attestation delta (A25): the same-artifact drift

attestation_delta(old, new) (provenance.rs:153) compares two observations of the same artifact and reports what moved:

Change (old → new) Finding id Severity
attestation present → absent MODGUNN-PROV-ATTESTATION-DROPPED High
absent → present MODGUNN-PROV-ATTESTATION-ADDED Info
signer (builder) identity changed MODGUNN-PROV-SIGNER-CHANGED High
claimed source repo moved MODGUNN-PROV-SOURCE-CHANGED Medium
same repo, source commit changed MODGUNN-PROV-COMMIT-CHANGED Medium

COMMIT-CHANGED (provenance.rs:200) is the new signal: an immutable version ought to be reproducible from a single source commit, so a commit move for what should be the same artifact is a rebuild-from-elsewhere flag. It is deliberately narrow to avoid noise:

f4 — the ledger diff + the regression gate

The warehouse lifts A25 from "two attestations" to "two snapshots of the whole ledger".

A changed entry is a ProvenanceTransition (warehouse.rs:1274) { artifact_id, before, after }, raised only when same_verdict (warehouse.rs:1382) is false — i.e. the outcome moved (verified / signer / reason / blob / repo). ts_micros is a capture timestamp, not part of the verdict, so a re-verify that reproduces the same outcome is not a transition. The transition predicates:

ProvenanceLedgerDelta::regressions() (warehouse.rs:1317) is the gate-worthy subset; is_unchanged() is the "provenance held" fast path. The CLI cmd_provenance_diff (main.rs:451) exits 3 when --fail-on-regression is set and any regression fires — a promotion check blocks on it, distinct from a usage error (exit 2).

f5 — the scan plumbing this rides on: CDC --since + VEX

The same append-only-plus-CDC discipline powers warehouse scan (cmd_scan, main.rs:242):

Both are surfaced at fleet altitude by rollup's render_fleet_summary, printed by scan and by the standalone warehouse rollup.

Cross-references