Skip to main content

ADR-0010 — Evidence binds to three subjects, and recombination is the test

Status: PROPOSED (2026-08-28). Supersedes nothing. Decides where a claim lives.

The question

We want per-PR validation — compliance, security, value, each a pass plus the reasoning for the pass — collected and promoted up to the train. But a train is not the last assembly: its images are recombined into different bundles, per service, per channel. So: does an image carry this evidence, or does it carry only the train evidence it was part of?

The decision

Three subject types. A claim binds to the thing it is actually about, and to nothing larger.
Recombination is the test that decides the boundary, and it is not a thought experiment — it is what this channel does. Disassemble a set and reassemble it differently:
  • PR-level and image-level attestations follow each image. They are bound to identifiers that did not change. An image that moves into a new bundle brings its provenance, its SBOM, its scan, and — through the merge commit and the source chain — the compliance, security and value reasoning of the PRs that produced it.
  • set-level attestations do not transfer — but most of them are not really set-level. See the next section: prod validation decomposes to the image and is assigned there, so it does travel. What is left at the assembly level is only what is genuinely about the assembly.
So the answer to the question is both, and never each other’s: the image holds what is true of the image; the assembly holds what is true of the assembly.

Prod validation is assigned to the IMAGES

(Founder ruling, 2026-08-28: “prod validation keys can be directly assigned to those images — that’s the way.”) The first draft treated the prod soak as a set claim and concluded it could not be inherited by a recombined bundle. That conclusion was useless: it means a customer inherits nothing from our production running, which is the whole value we generate for them. The soak decomposes. Its subject is already an array of image digests (prod-soak-attestation.schema.json), so the claim “this digest was exercised in production during this window” is per-image on its face. Assign it there, and inheritance stops being a design problem: a bundle assembled from any combination of those images inherits each image’s prod evidence automatically, because the evidence is bound to the digest and the digest did not change. So the assembly level shrinks to what is genuinely about an assembly:

What must ride with the per-image claim, or it means nothing

“This image ran in prod” is too weak on its own: an image can run in production for a month and never have the code path a different configuration will exercise. So the per-image soak carries the configuration it was exercised under, and the vocabulary for that already exists — validation-contract.schema.json:
  • dependencies[] with kind, fidelity (“real by default”), and a justification REQUIRED for any fidelity that is not real;
  • proves — one line, what this fidelity genuinely establishes;
  • does_not_prove — one line, what a reader must NOT conclude. The schema calls this field “the point of the document”, and it is;
  • unproven_claims[] — claims an absent or dummy dependency makes unavailable, “written out so a receipt cannot accidentally assert them.”
A consumer running a configuration outside the one an image was soaked under does not get a weaker version of the claim. They get the claim, plus the explicit statement of what it does not prove for them — and their contract decides whether that is enough.

Why this is not a compromise

The mechanism for the recombined bundle to be honest about what it inherited already exists: evidence_absent[]. A bundle assembled from images that each carry provenance, an SBOM and a commit’s compliance reasoning, but which has never itself soaked, declares the soak absent — and the consumer’s contract decides whether that is acceptable (forbid_absent_evidence). The gap becomes visible data rather than silence. That is the same property that lets an edge entry ship with fewer attestations than a promoted one, using one contract language and two acceptance policies.

What a PR-level attestation asserts

Subject: the pull requestrepo#numberpinned to its merge commit SHA. Predicate: one leg per named check, each carrying a verdict and its basis. Why the PR and not the commit (founder correction, 2026-08-28: “PR has narrative — not commit”). A commit is a diff. A PR is a diff with an argument attached, and compliance and value are judgments about the argument, not about the bytes. Binding them to a commit would apply this ADR’s own test — a claim binds to the thing it is actually about — inconsistently, which the first draft of this ADR did. The durability objection that produced that first draft was simply wrong: a PR number is permanent and addressable forever. What is transient is the PR’s branch, not the PR. And Vexa-ai/vexa already decided this. merge-card is choke point 1 of the delivery constitution (ADR-0029 there): main accepts a PR only when both its value and its diff are acceptedpr-value green plus the state: value-signed human sign-off, and a fresh non-author review. It is a required status check. The unit at which value is judged is already the PR; this ADR now matches it instead of contradicting it. The merge commit is carried alongside as the binding, not the subject: it is how a PR’s claims reach an image, via the release archive and its SLSA provenance.

Two consequences of choosing the PR

  1. A commit that reaches main without a PR carries none of this. Direct pushes exist. Such a commit must show as unproven in the roll-up rather than being silently absent — a change that landed with no narrative, no value sign-off and no non-author review is precisely the thing a reviewer should be able to see.
  2. A release contains many PRs, so an entry’s evidence carries one attestation per PR in the range. That is a real growth in the evidence set and probably wants a roll-up object — a per-release summary that names each PR and its verdicts, with the individual attestations addressable beneath it.
Most of the machinery exists in Vexa-ai/vexa and is not yet attested — gates.yml (34 named gates including licenses, image-licenses, contract-version, contract-conformance, arch-report, isolation), pr-value.yml (a real compose stack driven through the full FSM by a contract-faithful bot), contribution-rights.yml, docs-current.yml. The work is not to build the checks. It is to bind their results to a commit, sign them, and carry them. Legs, with what already produces them:

⛔ The hazard, and the structural guard

The founder’s requirement is a pass and the reasoning for the pass. Reasoning is what makes this useful to a reviewer — “CI green” is not an argument. It is also how this becomes dangerous. A signed, plausible-sounding justification that nothing verified is worse than no attestation at all, because a regulated buyer will trust it. An LLM can produce an excellent paragraph explaining why a change is compliant without that paragraph being true. Therefore, structurally, not as a warning:
  1. The verdict is machine-derived. The reasoning is evidence, not prose. Each leg’s pass comes from a check that ran and exited. It is never inferred from the reasoning.
  2. Every reasoning field cites its basis — a run URL, a gate name and its output digest, a file and line, or a named human. A leg whose reasoning cites nothing is unproven, never pass.
  3. unproven is a first-class verdict and must be expressible alongside pass and fail, so a leg with no producer says so instead of being omitted. The four legs marked ⛔ above are unproven on every PR today, and the attestation must say that out loud.
  4. A human-authored leg names the human. Compliance and value judgments that rest on a person carry that person, the way published mode already requires --approved-by.

Consequences

  • Four legs have no producer. They are unproven until they do, and the attestation is honest about it rather than silent.
  • Commits that land without a PR have no PR-level evidence, by construction. The roll-up must show that rather than omit it.
  • The train’s evidence set becomes the union of what its images carry plus what the assembly earned — not a flat list, and consumers must be able to ask at which level a claim was made.
  • spec/channel-entry.schema.json needs a level for each evidence row. Today the rows are flat, which is exactly why this question was ambiguous.
  • A recombined bundle inherits automatically at two levels and must re-earn the third. That is a feature and should be documented as the reason the split exists.