pub struct Execution {
pub test: String,
pub runner: Runner,
pub source: String,
pub outcome: Outcome,
pub seconds: u64,
pub commit: String,
pub working_tree: String,
pub digest: String,
pub at: String,
pub origin: Origin,
pub command: String,
pub run: Option<RunRef>,
}Expand description
One recorded run of one test: the whole of what this repository durably remembers about it.
Every field is provenance. A result with no commit is an anonymous green, which is the thing a badge must never be derived from.
use majordomus_cli::evidence::{digest_of, Execution, Origin, Outcome, Runner, TestId};
let id = TestId::of("apps/majordomus-cli/tests/why.rs").unwrap();
let run = Execution {
test: id.as_string(),
runner: Runner::Crate,
source: id.source(),
outcome: Outcome::Pass,
seconds: 12,
commit: "0".repeat(40),
working_tree: "clean".into(),
digest: digest_of(b"fn main() {}"),
at: "2026-09-11T00:00:00Z".into(),
origin: Origin::Ci,
command: id.reproduce(),
run: None,
};
// it is a value in a tracked JSON file, so it has to survive the file unchanged
let back: Execution = serde_json::from_str(&serde_json::to_string(&run).unwrap()).unwrap();
assert_eq!(back, run);
assert_eq!(back.test, "crate:why");
assert_eq!(back.command, "cargo test --test why");§Example
One recorded run, with the provenance the runner did not carry: which commit, which tree state, the digest of the test’s own source, when, and how to run it again.
use majordomus_cli::evidence::{Execution, Origin, Outcome, Runner};
let e = Execution {
test: "suite:07_scope".into(),
runner: Runner::Suite,
source: "test/cases/07_scope.sh".into(),
outcome: Outcome::Pass,
seconds: 3,
commit: "a04b65c9".into(),
working_tree: "clean".into(),
digest: "sha256:00".into(),
at: "2026-09-11T00:00:00Z".into(),
origin: Origin::Local,
command: "bash test/run.sh 07_scope".into(),
run: None,
};
assert!(e.outcome.proves());Fields§
§test: StringThe test, in TestId::as_string form.
runner: RunnerWhich runner produced it.
source: StringThe test’s own source, repository-relative.
outcome: OutcomeWhat it said.
seconds: u64How long it took, in whole seconds.
commit: StringThe full commit id the run was made against.
working_tree: Stringclean, dirty or unknown: whether the tree the run measured was the commit.
digest: Stringsha256:<hex> of the test’s own source as it was when the run was recorded. An
execution whose test no longer hashes to this did not run the test that is there now.
at: StringWhen it was recorded, RFC 3339, UTC.
origin: OriginWhere the run happened.
command: StringThe exact command that runs this one test again.
run: Option<RunRef>The continuous-integration run the execution was recorded in, when it was recorded in one that could name itself. Absent for a local run, and absent from every row recorded before runs were named, which is why the ledger’s version did not change.
Implementations§
Source§impl Execution
impl Execution
Sourcepub fn digest_matches(&self, root: &Path) -> Option<bool>
pub fn digest_matches(&self, root: &Path) -> Option<bool>
Does the test’s source still hash to what was recorded?
None when the source is not there to hash — which is itself a finding, reported by
the path checks, not silently read as a match.
This is the staleness check that survives a revert: an edit made and undone around a run leaves no diff against any commit, and the recorded digest still says the test that ran is not the test that is there.
use majordomus_cli::evidence::{digest_of, Execution, Origin, Outcome, Runner, TestId};
let repo = tempfile::tempdir().unwrap();
let id = TestId::of("test/cases/07_scope.sh").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::Pass,
seconds: 1,
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_eq!(run.digest_matches(repo.path()), Some(true));
std::fs::write(repo.path().join(id.source()), "echo something else\n").unwrap();
assert_eq!(run.digest_matches(repo.path()), Some(false));
// and a source that is gone cannot be compared, which is not the same as agreeing
std::fs::remove_file(repo.path().join(id.source())).unwrap();
assert_eq!(run.digest_matches(repo.path()), None);Trait Implementations§
Source§impl<'de> Deserialize<'de> for Execution
impl<'de> Deserialize<'de> for Execution
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Execution
Source§impl JsonSchema for Execution
impl JsonSchema for Execution
Source§fn schema_id() -> Cow<'static, str>
fn schema_id() -> Cow<'static, str>
Source§fn json_schema(generator: &mut SchemaGenerator) -> Schema
fn json_schema(generator: &mut SchemaGenerator) -> Schema
Source§fn inline_schema() -> bool
fn inline_schema() -> bool
$ref keyword. Read moreSource§impl Ordered for Execution
A ledger holds one execution per test, so the test identifies it; the label and the
identity are the same string because there is nothing else to read it by.
impl Ordered for Execution
A ledger holds one execution per test, so the test identifies it; the label and the identity are the same string because there is nothing else to read it by.
impl StructuralPartialEq for Execution
Auto Trait Implementations§
impl Freeze for Execution
impl RefUnwindSafe for Execution
impl Send for Execution
impl Sync for Execution
impl Unpin for Execution
impl UnsafeUnpin for Execution
impl UnwindSafe for Execution
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.