Skip to main content

Module evidence

Module evidence 

Source
Expand description

Evidence: what actually ran, against which commit, and whether it still proves anything.

docs/CLAIMS.yaml binds a claim to a test with a path. A path is a declaration that a proof exists somewhere; it is not the proof. Until this module, a claim reading

status: guaranteed
test:   test/cases/84_distribution_model.sh

was rendered on the public site as a guarantee on the strength of that file existing. Nobody could answer, from the repository, whether the case had ever run, against which revision, with what result, or whether the result still applied. scripts/generate-site-data --check verified that the path resolves — which is a check that the reference is not broken, not that the claim is proven.

§The model

Three objects, and one derivation over them:

  • a TestId — the stable identity of a test, derived from the path a claim already names, so nothing is entered by hand and no claim has to be migrated;
  • an Execution — one recorded run of one test: its outcome, its duration, the commit it ran against, the digest of the test’s own source at the time, when, by which runner and from where;
  • a Ledger — the latest execution of every test, committed to the repository as durable semantic evidence. Not logs: one line’s worth of meaning per test.

and report, which joins the claims of the index with the ledger and decides, per claim, a ProofState that the surfaces render and the gate judges.

§The lifecycle, end to end

A claim’s path becomes an identity, a run becomes an Execution, the ledger keeps the latest one per test, and the digest is what notices that the test itself has moved since — without any commit having to change.

use majordomus_cli::evidence::{digest_of, Execution, Ledger, Origin, Outcome, Runner, TestId};

// the identity is derived from the path the claim already names; nothing is entered
let id = TestId::of("test/cases/84_distribution_model.sh").unwrap();
assert_eq!(id.as_string(), "suite:84_distribution_model");
assert_eq!(id.reproduce(), "bash test/run.sh 84_distribution_model");

let repo = tempfile::tempdir().unwrap();
std::fs::create_dir_all(repo.path().join("test/cases")).unwrap();
std::fs::write(repo.path().join(id.source()), "echo ok\n").unwrap();

let run = Execution {
    test: id.as_string(),
    runner: Runner::Suite,
    source: id.source(),
    outcome: Outcome::parse("ok"),
    seconds: 3,
    commit: "0".repeat(40),
    working_tree: "clean".into(),
    digest: digest_of(b"echo ok\n"),
    at: "2026-09-11T00:00:00Z".into(),
    origin: Origin::Local,
    command: id.reproduce(),
    run: None,
};
assert!(run.outcome.proves());

// the ledger is a tracked file, so the evidence survives the process that produced it
let mut ledger = Ledger::empty();
ledger.merge([run]);
ledger.save(repo.path()).unwrap();

let recorded = Ledger::load(repo.path()).unwrap();
let latest = recorded.latest(&id.as_string()).unwrap();
assert_eq!(latest.digest_matches(repo.path()), Some(true));

// edit the case afterwards and the recorded run is no longer a run of the test that is
// there now — which no diff against a commit would have shown, had the edit been reverted
std::fs::write(repo.path().join(id.source()), "echo something else\n").unwrap();
assert_eq!(latest.digest_matches(repo.path()), Some(false));

§What “current” means here, exactly

A passing run three commits ago is not automatically proof of the tree in front of you, and pretending otherwise is the failure this module exists to prevent. But declaring every result stale the instant anything anywhere changes is sound and useless — it would mean no claim is ever proven except in the seconds after a full run.

So the derivation is conservative and says which of two things it found:

  • ProofState::Proven — the test passed, and the tree in front of you is byte for byte the tree the run measured: the diff against the execution’s own commit is empty. The ledger’s own row is excluded from that diff, because the evidence is about the tree rather than part of what the tests measure — without that exclusion proven would be unreachable by construction, since recording dirties the tree and committing the record moves HEAD past the commit the record names.
  • ProofState::InputsUnchanged — the test passed, and nothing the claim itself names (its source document, its implementation, its test) differs between the recorded commit and the working tree. This is not proof at HEAD: something the claim does not name may have broken it. It is the absence of any known invalidation, and it is labelled as that everywhere it is shown.
  • ProofState::Stale — the test passed, and something the claim names has changed since. The changed paths are named.

The distinction is the point. A surface that collapsed the second into the first would be the green badge whose derivation cannot be inspected.

§What this is not

It is not tamper-proof, and it does not pretend to be. The ledger is a tracked file; a person can edit "outcome": "pass" into it, and the diff is what a reviewer sees. What the recorded digest does buy is staleness detection that survives a revert: an execution whose test source no longer hashes to the recorded digest did not run this test, whatever the commit says. Cryptographic ceremony against an attacker who can already commit to the repository would be ceremony with no threat model.

It is also not the execution control plane. crate::execution is about capability executions of a running server — in-flight work, progress, events, gone with the process. This is about test runs, durable, committed and read long after the process that produced them exited. Two different subjects that share an English word.

§Example

The vocabulary, without a repository: a test is named by the path a claim writes down, a result word is a pass only when the runner said so, and the proof states rank.

use majordomus_cli::evidence::{Outcome, ProofState, Runner, TestId};

let t = TestId::of("test/cases/07_scope.sh").unwrap();
assert_eq!(t.runner, Runner::Suite);
assert_eq!(t.as_string(), "suite:07_scope");
assert!(Outcome::parse("ok").proves());
assert!(!Outcome::parse("FAIL").proves());
assert!(ProofState::Proven < ProofState::Stale);

Re-exports§

pub use coverage::CommitId;
pub use coverage::EvidenceCoverage;
pub use coverage::EvidenceCoverageCount;
pub use coverage::EvidenceCoverageDimensions;
pub use coverage::EvidenceCoverageDomain;
pub use coverage::EvidenceCoverageFloors;
pub use freshness::aggregate;
pub use freshness::changed_between;
pub use freshness::compare;
pub use freshness::current;
pub use freshness::freshness;
pub use freshness::ledger_at;
pub use freshness::presented_commit;
pub use freshness::uncommitted;
pub use freshness::weakened_by;
pub use freshness::Comparison;
pub use freshness::Judgement;
pub use freshness::Presented;
pub use freshness::Recorded;
pub use freshness::Supplementary;
pub use freshness::TreeState;
pub use freshness::UNCOMMITTED_RUN;
pub use ledger::outcome_counts;
pub use ledger::Ledger;
pub use ledger::LedgerTarget;
pub use ledger::LEDGER_PATH;
pub use ledger::LOCAL_LEDGER_PATH;
pub use provenance::stamp;
pub use provenance::EvidenceHost;
pub use provenance::EvidenceProvenance;
pub use provenance::EvidenceReportArtifact;
pub use provenance::EvidenceToolchain;
pub use provenance::EvidenceToolchainSource;
pub use provenance::StampRequest;
pub use record::parse_crate_binaries;
pub use record::read_crate_output;
pub use record::record;
pub use record::CrateBinary;
pub use record::CrateRead;
pub use record::RecordOutcome;
pub use record::RecordRequest;
pub use run::run_id;
pub use run::weaken_by_records;
pub use run::weakened_by_records;
pub use run::EvidenceAbsent;
pub use run::EvidenceDropped;
pub use run::EvidenceProducer;
pub use run::EvidenceRunRecord;
pub use run::EvidenceRunTotals;
pub use run::RUN_RECORD_SCHEMA;

Modules§

coverage
A coverage summary bound to the commit and tree it was measured on.
freshness
Freshness: whether a recorded run still proves anything at the revision a reader is shown.
ledger
The ledger: the latest execution of every test, as a tracked file.
provenance
What a run measured about its own checkout when it ended.
record
The recorder: turning a run that happened into evidence that lasts.
run
What a run is made of, beyond the executions it recorded.
subject
Evidence subjects: every declaration a verdict can be asked about, what it is made of, and the tests it reaches.

Structs§

ClaimProof
One claim, joined to whatever the repository actually recorded about it.
EvidenceReport
The whole joined picture: every claim of the matrix against every execution recorded.
Execution
One recorded run of one test: the whole of what this repository durably remembers about it.
Finding
A claim whose declared status the evidence does not support.
LedgerSummary
What the ledger is and whether it is there at all.
PresentedRevision
What a report was judged at: the revision, its tree, and the runs the checkout holds that the revision’s own ledger does not.
RunRef
The continuous-integration run an execution was recorded in: enough to navigate back to the run that produced it.
Subject
Whether the report could see its whole subject.
TestId
The stable identity of a test: the runner that owns it and the name that runner knows it by.

Enums§

Origin
Where a run happened. The canonical model is provider-neutral: a CI adapter records Ci, and nothing here knows or cares which CI it was.
Outcome
What a run said about one test.
ProofState
What the repository can say about one claim’s proof, right now.
Runner
Which runner produced a result, and therefore how the test is named and re-run.

Functions§

capped_by_working_tree
What a state may still be once the tree the run measured is taken into account.
digest_of
sha256:<hex> of some bytes, the one spelling used in the ledger.
report
Join the claims of the index with the executions of the ledger and decide, per claim, what the repository can honestly say.
report_at
The same join, judged at a named revision: the working tree, or the checked-out commit as committed.