Skip to content

evidence.claim

One claim with its proof state, the execution behind it — outcome, duration, commit, tree state, digest, time and origin — the command that reproduces it, and the other claims the same test proves. A claim the matrix does not declare is a not-found rather than an empty answer, because a typo that read as 'this claim has no evidence' is the one answer this capability must never give.

One claim with its proof state, the execution behind it — outcome, duration, commit, tree state, digest, time and origin — the command that reproduces it, and the other claims the same test proves. A claim the matrix does not declare is a not-found rather than an empty answer, because a typo that read as 'this claim has no evidence' is the one answer this capability must never give.

  • query
  • behaviorally_verified
  • module evidence
  • #evidence
  • #claims
  • #verification
  • #provenance

Exposure

Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.

surfaceas
MCPtool majordomus_evidence_claim
HTTPGET /api/v1/evidence/claim operationId evidence.claim
command linemajordomus evidence claim

Schemas

The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.

input · EvidenceClaimInput

One claim of the matrix, named by the id it is declared under.

The id is required, and there is no spelling of this input that means "every claim" — that question belongs to evidence.report. An id the matrix does not declare is a not-found from the handler rather than an empty proof.

use majordomus_cli::capability::builtin::evidence::EvidenceClaimInput;

let input: EvidenceClaimInput =
    serde_json::from_str(r#"{"claim": "evidence-both-directions"}"#).unwrap();
assert_eq!(input.claim, "evidence-both-directions");

// no id is a usage error, never a request for all of them
assert!(serde_json::from_str::<EvidenceClaimInput>("{}").is_err());
propertytyperequireddescription
claim string yes The claim id, as docs/CLAIMS.yaml spells it.
JSON Schema
{
  "additionalProperties": false,
  "description": "One claim of the matrix, named by the id it is declared under.\n\nThe id is required, and there is no spelling of this input that means \"every claim\" —\nthat question belongs to `evidence.report`. An id the matrix does not declare is a\nnot-found from the handler rather than an empty proof.\n\n```\nuse majordomus_cli::capability::builtin::evidence::EvidenceClaimInput;\n\nlet input: EvidenceClaimInput =\n    serde_json::from_str(r#\"{\"claim\": \"evidence-both-directions\"}\"#).unwrap();\nassert_eq!(input.claim, \"evidence-both-directions\");\n\n// no id is a usage error, never a request for all of them\nassert!(serde_json::from_str::<EvidenceClaimInput>(\"{}\").is_err());\n```",
  "properties": {
    "claim": {
      "description": "The claim id, as `docs/CLAIMS.yaml` spells it.",
      "type": "string"
    }
  },
  "required": [
    "claim"
  ],
  "title": "EvidenceClaimInput",
  "type": "object"
}

output · ClaimEvidence

One claim with everything the repository can say about its proof, and the way back out to the test that carries it.

The proof is flattened in, so a client reads one claim rather than an evidence wrapper around one; the executions and the other claims the same test proves sit beside it.

use majordomus_cli::capability::builtin::evidence::ClaimEvidence;
use majordomus_cli::evidence::ClaimProof;

let proof: ClaimProof = serde_json::from_value(serde_json::json!({
    "id": "evidence-both-directions",
    "claim": "a claim and its test answer for each other",
    "status": "guaranteed",
    "source": null,
    "implementation": null,
    "test_path": "test/cases/84_distribution_model.sh",
    "test": "suite:84_distribution_model",
    "state": "not_run",
    "meaning": "no run of this test has been recorded",
    "execution": null,
    "changed": [],
    "reproduce": "bash test/run.sh 84_distribution_model"
}))
.unwrap();

let answer = ClaimEvidence {
    proof,
    executions: Vec::new(),
    also_proves: vec!["evidence-record-is-not-networked".to_string()],
};
let json = serde_json::to_value(&answer).unwrap();
assert_eq!(json["id"], "evidence-both-directions");
assert!(json.get("proof").is_none(), "the claim is flattened in, not nested");
assert_eq!(json["also_proves"], serde_json::json!(["evidence-record-is-not-networked"]));
// a list even where the ledger keeps one: retention is a decision that may change,
// and a client that read a bare object would break when it did
assert_eq!(json["executions"], serde_json::json!([]));
propertytyperequireddescription
also_proves array yes The other claims the same test proves. A test shared by twelve claims is a test whose failure is twelve findings, and a reader of one of them should be able to see the other eleven without searching.
changed array yes The files this claim names that differ between the recorded commit and the working tree. Empty unless the state is ProofState::Stale.

Always emitted, empty included: the schema this capability publishes says the key is there, and a client that has to tell "no files changed" from "the server did not say" is a client reading two different answers as one.

claim string yes The claim sentence.
detail string | null no Why the state is what it is, in one sentence, when the state alone does not say: a test that declined to run, a run on a commit the presented revision does not contain, a tree that was not its commit, a failing run the checkout holds uncommitted.
execution one of 2 no The execution the state was derived from, when there is one.
executions array yes Every execution the ledger holds for this claim's test — today one, the latest, and a list because the ledger's retention is a decision that may change and a client that read a bare object would break when it did.
id string yes The claim's id, as docs/CLAIMS.yaml spells it.
implementation string | null no The file that implements it.
meaning string yes That state, in one sentence.
reproduce string | null no The command that produces the proof again, when a runner owns the test.
source string | null no The document that defines the behaviour.
state reference yes What the repository can say about the proof.
status string yes The status the claim declares: guaranteed, advisory, planned, rejected.
test string | null no The test's stable identity, when a runner owns it.
test_path string | null no The path the claim names as its test, verbatim.
JSON Schema
{
  "$defs": {
    "EvidenceExecution": {
      "description": "One recorded run of one test: the whole of what this repository durably remembers about\nit.\n\nEvery field is provenance. A result with no commit is an anonymous green, which is the\nthing a badge must never be derived from.\n\n```\nuse majordomus_cli::evidence::{digest_of, Execution, Origin, Outcome, Runner, TestId};\n\nlet id = TestId::of(\"apps/majordomus-cli/tests/why.rs\").unwrap();\nlet run = Execution {\n    test: id.as_string(),\n    runner: Runner::Crate,\n    source: id.source(),\n    outcome: Outcome::Pass,\n    seconds: 12,\n    commit: \"0\".repeat(40),\n    working_tree: \"clean\".into(),\n    digest: digest_of(b\"fn main() {}\"),\n    at: \"2026-09-11T00:00:00Z\".into(),\n    origin: Origin::Ci,\n    command: id.reproduce(),\n    run: None,\n};\n\n// it is a value in a tracked JSON file, so it has to survive the file unchanged\nlet back: Execution = serde_json::from_str(&serde_json::to_string(&run).unwrap()).unwrap();\nassert_eq!(back, run);\nassert_eq!(back.test, \"crate:why\");\nassert_eq!(back.command, \"cargo test --test why\");\n```\n# Example\n\nOne recorded run, with the provenance the runner did not carry: which commit, which\ntree state, the digest of the test's own source, when, and how to run it again.\n\n```\nuse majordomus_cli::evidence::{Execution, Origin, Outcome, Runner};\nlet e = Execution {\n    test: \"suite:07_scope\".into(),\n    runner: Runner::Suite,\n    source: \"test/cases/07_scope.sh\".into(),\n    outcome: Outcome::Pass,\n    seconds: 3,\n    commit: \"a04b65c9\".into(),\n    working_tree: \"clean\".into(),\n    digest: \"sha256:00\".into(),\n    at: \"2026-09-11T00:00:00Z\".into(),\n    origin: Origin::Local,\n    command: \"bash test/run.sh 07_scope\".into(),\n    run: None,\n};\nassert!(e.outcome.proves());\n```",
      "properties": {
        "at": {
          "description": "When it was recorded, RFC 3339, UTC.",
          "type": "string"
        },
        "command": {
          "description": "The exact command that runs this one test again.",
          "type": "string"
        },
        "commit": {
          "description": "The full commit id the run was made against.",
          "type": "string"
        },
        "digest": {
          "description": "`sha256:<hex>` of the test's own source as it was when the run was recorded. An\nexecution whose test no longer hashes to this did not run the test that is there now.",
          "type": "string"
        },
        "origin": {
          "$ref": "#/$defs/EvidenceOrigin",
          "description": "Where the run happened."
        },
        "outcome": {
          "$ref": "#/$defs/EvidenceOutcome",
          "description": "What it said."
        },
        "run": {
          "anyOf": [
            {
              "$ref": "#/$defs/EvidenceRun"
            },
            {
              "type": "null"
            }
          ],
          "description": "The continuous-integration run the execution was recorded in, when it was recorded in\none that could name itself. Absent for a local run, and absent from every row recorded\nbefore runs were named, which is why the ledger's version did not change."
        },
        "runner": {
          "$ref": "#/$defs/EvidenceRunner",
          "description": "Which runner produced it."
        },
        "seconds": {
          "description": "How long it took, in whole seconds.",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "source": {
          "description": "The test's own source, repository-relative.",
          "type": "string"
        },
        "test": {
          "description": "The test, in `TestId::as_string` form.",
          "type": "string"
        },
        "working_tree": {
          "description": "`clean`, `dirty` or `unknown`: whether the tree the run measured was the commit.",
          "type": "string"
        }
      },
      "required": [
        "test",
        "runner",
        "source",
        "outcome",
        "seconds",
        "commit",
        "working_tree",
        "digest",
        "at",
        "origin",
        "command"
      ],
      "type": "object"
    },
    "EvidenceOrigin": {
      "description": "Where a run happened. The canonical model is provider-neutral: a CI adapter records\n`Ci`, and nothing here knows or cares which CI it was.\n\n```\nuse majordomus_cli::evidence::Origin;\n\nassert_eq!(Origin::parse(\"CI\"), Some(Origin::Ci));\nassert_eq!(Origin::parse(\"local\"), Some(Origin::Local));\n// naming the provider is the adapter's business; the ledger records the kind of run,\n// so a repository that changes CI does not change what its old evidence says\nassert!(Origin::parse(\"github-actions\").is_none());\n```\n# Example\n\n```\nuse majordomus_cli::evidence::Origin;\nassert_eq!(Origin::parse(\"ci\"), Some(Origin::Ci));\nassert_eq!(Origin::parse(\"nowhere\"), None);\n```",
      "oneOf": [
        {
          "const": "local",
          "description": "Someone ran it on their own machine.",
          "type": "string"
        },
        {
          "const": "ci",
          "description": "A continuous-integration run.",
          "type": "string"
        },
        {
          "const": "release",
          "description": "A release run.",
          "type": "string"
        }
      ]
    },
    "EvidenceOutcome": {
      "description": "What a run said about one test.\n\n`NotRun` is deliberately absent: it is the absence of an execution, not an outcome one\nhad. Encoding it here would let a recorder write \"this did not run\" and have it counted\namong the things that did.\n\n```\nuse majordomus_cli::evidence::Outcome;\n\nassert!(Outcome::parse(\"ok\").proves());\nassert!(!Outcome::parse(\"timeout\").proves());\n// exactly one of the five is evidence of anything; the other four are reasons there\n// is none, kept apart because \"it declined\" and \"the harness broke\" are different bugs\nassert_eq!(Outcome::parse(\"skipped\"), Outcome::Skip);\nassert_eq!(Outcome::parse(\"nonsense\"), Outcome::Error);\n```\n# Example\n\n```\nuse majordomus_cli::evidence::Outcome;\nassert!(Outcome::parse(\"ok\").proves());\nassert_eq!(Outcome::parse(\"wat\"), Outcome::Error);\n```",
      "oneOf": [
        {
          "const": "pass",
          "description": "It passed.",
          "type": "string"
        },
        {
          "const": "fail",
          "description": "It failed.",
          "type": "string"
        },
        {
          "const": "skip",
          "description": "It declined to run and said so (an unmet precondition the case reports itself).",
          "type": "string"
        },
        {
          "const": "timeout",
          "description": "The bound fired before it finished.",
          "type": "string"
        },
        {
          "const": "error",
          "description": "The harness could not run it at all.",
          "type": "string"
        }
      ]
    },
    "EvidenceRun": {
      "description": "The continuous-integration run an execution was recorded in: enough to navigate back to\nthe run that produced it.\n\n`Origin` says what kind of run it was and deliberately names no provider. This names the\none run, in the words the provider addressed it with when it happened — its identifier,\nits attempt, the workflow and job, and the address a reader follows — because a link built\nlater from a template would point wherever the template's idea of the provider had drifted\nto. Only an adapter constructs it; nothing in the model reads the provider's name.\n\n```\nuse majordomus_cli::evidence::RunRef;\n\nlet actions = |k: &str| {\n    let v = match k {\n        \"GITHUB_ACTIONS\" => \"true\",\n        \"GITHUB_SERVER_URL\" => \"https://github.com\",\n        \"GITHUB_REPOSITORY\" => \"owner/repo\",\n        \"GITHUB_RUN_ID\" => \"42\",\n        \"GITHUB_RUN_ATTEMPT\" => \"2\",\n        \"GITHUB_WORKFLOW\" => \"validate\",\n        \"GITHUB_JOB\" => \"evidence\",\n        _ => return None,\n    };\n    Some(v.to_string())\n};\nlet run = RunRef::from_env(actions).unwrap();\nassert_eq!(run.id, \"42\");\nassert_eq!(run.attempt, 2);\nassert_eq!(run.url, \"https://github.com/owner/repo/actions/runs/42/attempts/2\");\n// a run that names neither its pull request's head nor its event still names itself\nassert_eq!((run.head_sha, run.event), (None, None));\n\nlet head = \"0123456789abcdef0123456789abcdef01234567\";\nlet pull = |k: &str| match k {\n    \"MJ_RUN_HEAD_SHA\" => Some(head.to_string()),\n    \"GITHUB_EVENT_NAME\" => Some(\"pull_request\".to_string()),\n    _ => actions(k),\n};\nlet run = RunRef::from_env(pull).unwrap();\nassert_eq!(run.head_sha.as_deref(), Some(head));\nassert_eq!(run.event.as_deref(), Some(\"pull_request\"));\n\n// outside a run there is nothing to name, and a half-described run is not a run\nassert!(RunRef::from_env(|_| None).is_none());\nassert!(RunRef::from_env(|k| (k == \"GITHUB_ACTIONS\").then(|| \"true\".to_string())).is_none());\n```",
      "properties": {
        "attempt": {
          "description": "Which attempt of the run, counted from 1.",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "event": {
          "description": "The event that started the run: `push`, `pull_request`, `schedule`,\n`workflow_dispatch`.",
          "type": [
            "string",
            "null"
          ]
        },
        "head_sha": {
          "description": "The pull request's head commit when the run was a pull-request run (its `commit` is\nGitHub's merge commit, the tree that ran). Carried beside the commit, never\nsubstituted for it.",
          "type": [
            "string",
            "null"
          ]
        },
        "id": {
          "description": "The provider's identifier of the run.",
          "type": "string"
        },
        "job": {
          "description": "The job within the run that recorded the execution.",
          "type": "string"
        },
        "provider": {
          "description": "The provider that ran it, as the adapter that recorded it names it.",
          "type": "string"
        },
        "url": {
          "description": "Where a reader finds the run, as the provider addressed it at the time.",
          "type": "string"
        },
        "workflow": {
          "description": "The workflow the run belongs to.",
          "type": "string"
        }
      },
      "required": [
        "provider",
        "id",
        "attempt",
        "workflow",
        "job",
        "url"
      ],
      "type": "object"
    },
    "EvidenceRunner": {
      "description": "Which runner produced a result, and therefore how the test is named and re-run.\n\nThe repository has exactly two that a claim may name today. A third would be a variant\nhere and a branch in `TestId::of`; a claim naming something neither runner produces is\nreported as `ProofState::Unrunnable` rather than silently counted as covered.\n\n```\nuse majordomus_cli::evidence::{Runner, TestId};\n\n// which runner owns a test is read off where the test lives, never declared\nassert_eq!(TestId::of(\"test/cases/07_scope.sh\").unwrap().runner, Runner::Suite);\nassert_eq!(\n    TestId::of(\"apps/majordomus-cli/tests/why.rs\").unwrap().runner,\n    Runner::Crate\n);\n// and a path under neither directory belongs to no runner at all\nassert!(TestId::of(\"test/lib.sh\").is_none());\n```\n# Example\n\n```\nuse majordomus_cli::evidence::Runner;\nassert_eq!(Runner::Suite.prefix(), \"suite\");\nassert_eq!(Runner::Crate.prefix(), \"crate\");\n```",
      "oneOf": [
        {
          "const": "suite",
          "description": "A behavioural case under `test/cases/`, run by `test/run.sh`.",
          "type": "string"
        },
        {
          "const": "crate",
          "description": "An integration test binary under `apps/majordomus-cli/tests/`, run by `cargo test`.",
          "type": "string"
        }
      ]
    },
    "ProofState": {
      "description": "What the repository can say about one claim's proof, right now.\n\nOrdered from strongest to weakest, so a summary that sorts by this reads as a ranking.\n\n```\nuse majordomus_cli::evidence::ProofState;\n\nlet mut states = vec![ProofState::NotRun, ProofState::Stale, ProofState::Proven];\nstates.sort();\nassert_eq!(\n    states,\n    vec![ProofState::Proven, ProofState::Stale, ProofState::NotRun]\n);\n\n// the two strongest are deliberately not one state: `inputs unchanged` is the absence\n// of a known invalidation, and collapsing it into `proven` is the unaccountable badge\nassert_ne!(ProofState::Proven, ProofState::InputsUnchanged);\nassert!(ProofState::Proven < ProofState::InputsUnchanged);\nassert!(ProofState::InputsUnchanged.passing());\n```\n# Example\n\n```\nuse majordomus_cli::evidence::ProofState;\nassert!(ProofState::Proven.passing());\nassert!(!ProofState::NotRun.passing());\nassert!(!ProofState::InputsUnchanged.meaning().is_empty());\n```",
      "oneOf": [
        {
          "const": "proven",
          "description": "A passing run, and nothing has changed since it — the diff against the execution's\nown commit is empty but for the ledger. Proof of the tree in front of you.",
          "type": "string"
        },
        {
          "const": "inputs_unchanged",
          "description": "A passing run, and nothing this claim names has changed since. Not proof at HEAD:\nsomething the claim does not name may have broken it.",
          "type": "string"
        },
        {
          "const": "stale",
          "description": "A passing run, but something this claim names has changed since. The proof is older\nthan its subject.",
          "type": "string"
        },
        {
          "const": "failing",
          "description": "The latest run of this claim's test did not pass.",
          "type": "string"
        },
        {
          "const": "not_run",
          "description": "The claim names a test a runner owns, and no run of it has ever been recorded.",
          "type": "string"
        },
        {
          "const": "unrunnable",
          "description": "The claim names a path no runner in this repository drives. Nothing can record it,\nso nothing can prove it this way.",
          "type": "string"
        },
        {
          "const": "no_test",
          "description": "The claim names no test at all. Correct for a `planned` or `rejected` claim, and a\ndefect for any other.",
          "type": "string"
        }
      ]
    }
  },
  "description": "One claim with everything the repository can say about its proof, and the way back out\nto the test that carries it.\n\nThe proof is flattened in, so a client reads one claim rather than an evidence wrapper\naround one; the executions and the other claims the same test proves sit beside it.\n\n```\nuse majordomus_cli::capability::builtin::evidence::ClaimEvidence;\nuse majordomus_cli::evidence::ClaimProof;\n\nlet proof: ClaimProof = serde_json::from_value(serde_json::json!({\n    \"id\": \"evidence-both-directions\",\n    \"claim\": \"a claim and its test answer for each other\",\n    \"status\": \"guaranteed\",\n    \"source\": null,\n    \"implementation\": null,\n    \"test_path\": \"test/cases/84_distribution_model.sh\",\n    \"test\": \"suite:84_distribution_model\",\n    \"state\": \"not_run\",\n    \"meaning\": \"no run of this test has been recorded\",\n    \"execution\": null,\n    \"changed\": [],\n    \"reproduce\": \"bash test/run.sh 84_distribution_model\"\n}))\n.unwrap();\n\nlet answer = ClaimEvidence {\n    proof,\n    executions: Vec::new(),\n    also_proves: vec![\"evidence-record-is-not-networked\".to_string()],\n};\nlet json = serde_json::to_value(&answer).unwrap();\nassert_eq!(json[\"id\"], \"evidence-both-directions\");\nassert!(json.get(\"proof\").is_none(), \"the claim is flattened in, not nested\");\nassert_eq!(json[\"also_proves\"], serde_json::json!([\"evidence-record-is-not-networked\"]));\n// a list even where the ledger keeps one: retention is a decision that may change,\n// and a client that read a bare object would break when it did\nassert_eq!(json[\"executions\"], serde_json::json!([]));\n```",
  "properties": {
    "also_proves": {
      "description": "The other claims the same test proves. A test shared by twelve claims is a test\nwhose failure is twelve findings, and a reader of one of them should be able to see\nthe other eleven without searching.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "changed": {
      "description": "The files this claim names that differ between the recorded commit and the working\ntree. Empty unless the state is `ProofState::Stale`.\n\nAlways emitted, empty included: the schema this capability publishes says the key is\nthere, and a client that has to tell \"no files changed\" from \"the server did not say\"\nis a client reading two different answers as one.",
      "items": {
        "type": "string"
      },
      "type": "array"
    },
    "claim": {
      "description": "The claim sentence.",
      "type": "string"
    },
    "detail": {
      "description": "Why the state is what it is, in one sentence, when the state alone does not say: a\ntest that declined to run, a run on a commit the presented revision does not\ncontain, a tree that was not its commit, a failing run the checkout holds uncommitted.",
      "type": [
        "string",
        "null"
      ]
    },
    "execution": {
      "anyOf": [
        {
          "$ref": "#/$defs/EvidenceExecution"
        },
        {
          "type": "null"
        }
      ],
      "description": "The execution the state was derived from, when there is one."
    },
    "executions": {
      "description": "Every execution the ledger holds for this claim's test — today one, the latest, and\na list because the ledger's retention is a decision that may change and a client\nthat read a bare object would break when it did.",
      "items": {
        "$ref": "#/$defs/EvidenceExecution"
      },
      "type": "array"
    },
    "id": {
      "description": "The claim's id, as `docs/CLAIMS.yaml` spells it.",
      "type": "string"
    },
    "implementation": {
      "description": "The file that implements it.",
      "type": [
        "string",
        "null"
      ]
    },
    "meaning": {
      "description": "That state, in one sentence.",
      "type": "string"
    },
    "reproduce": {
      "description": "The command that produces the proof again, when a runner owns the test.",
      "type": [
        "string",
        "null"
      ]
    },
    "source": {
      "description": "The document that defines the behaviour.",
      "type": [
        "string",
        "null"
      ]
    },
    "state": {
      "$ref": "#/$defs/ProofState",
      "description": "What the repository can say about the proof."
    },
    "status": {
      "description": "The status the claim declares: `guaranteed`, `advisory`, `planned`, `rejected`.",
      "type": "string"
    },
    "test": {
      "description": "The test's stable identity, when a runner owns it.",
      "type": [
        "string",
        "null"
      ]
    },
    "test_path": {
      "description": "The path the claim names as its test, verbatim.",
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "id",
    "claim",
    "status",
    "state",
    "meaning",
    "changed",
    "executions",
    "also_proves"
  ],
  "title": "ClaimEvidence",
  "type": "object"
}

Policies

benchmark
required — a target on every transport the exposure declares; the cases are the input type's
cache
checkout — up to 32 entries in the process, scoped by the registry fingerprint, the context's reading of the repository and the checkout's state (HEAD, the index, the refs, every changed path)

Benchmark targets

Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.

transportstatecasestargets
directcovered1first-claim cold+warm
mcpcovered1first-claim cold+warm
httpcovered1first-claim cold+warm

Claims implemented beside it

Attached by the file that implements each claim; every one links to its page with the implementation and the test that proves it.

Searches the titles and descriptions of every page this site publishes.