ADR 0025: The evidence bundle is a named, versioned format

Decision

The bundle is tik-evidence-bundle, version 1, and version 1 is what already shipped. The format published today is the layout the release pipeline has been producing, so every bundle in the wild is conformant and nothing has to be re-issued.

A bundle.edn manifest declares the format name and the version, and nothing else. The ticket, the head, the definition and the stage are all derivable from the files; a manifest that carried them would be inviting a reader to believe the producer instead of the evidence, which is the one thing a bundle exists to make unnecessary. A bundle without a manifest is version 1 — the baseline predates it.

The CHECKS are normative; verify.sh is one implementation of them that happens to travel inside the archive it checks. A reader who runs a stranger’s script has run a stranger’s code, so the checks are published as a list anyone can implement, and tik rederive implements them without executing anything from the bundle.

Re-derivation is tik rederive, and the service is that command in a loop. verify.sh proves the bytes are genuine with coreutils alone; saying what they ADD UP TO means running the pinned definition, which is what tik is. The HTTP face renders the same derivation as a badge and a page, and both print the commands that reach it without the service running.

A badge names the derivation and never grades it: the stages the pinned rules grant, the definition hash that judged them, and the instant the answer was computed. Not “compliant”, which means nothing until it names a policy.

A derivation may be cached on the bundle’s content address AND the minute it was computed for — never on content alone. Derivation is a function of (events, now). A guard with a freshness window is satisfied by evidence that is fresh and unsatisfied by the same bytes later, so memoizing on bytes alone would serve a stale answer with no way to notice. Keyed on both, an entry cannot go stale: every input is in the key, and the whole cache can be dropped at any moment with nothing lost but time (ADR 0013).

A definition’s schemas must be data. tik.process/schema-registry is malli’s default registry minus :fn and :multi, the two schemas that compile a child through m/eval. Derivation cannot evaluate a schema outside it — the reason is :schema/unsupported — and tik lint reports one as an error.

Context

The bundle was already the artifact; what it lacked was a name, a version and a written contract. The pressure to write one came from the release pipeline: evidence-<version>.tgz now ships on every release, tik import reads it, and the first outside reader will need to know what they may assume.

The bundle carries a different KIND of thing from an in-toto statement, which is why this is not a competing envelope. A statement attests facts about a build; a bundle carries the facts, the hash-pinned rules that judge them, and the means to recompute the verdict offline. in-toto’s nearest analogue is the layout, and a process definition already is one. tik bridge intoto reads their format; this publishes ours.

Three findings came out of building it, none of them anticipated.

A cache keyed by content alone is wrong. The idea as banked said the content hash “cannot go stale by construction”. That holds only for time-independent guards. :attested-within and :elapsed-since make the instant a second input, and the release process uses both — so the key had to carry the minute as well. That is not a weakening: it is what makes the memo a memo rather than a stored verdict.

The role register decides derivations, so it travels. :signed-by resolves a role to its members, and a store keeping a roles.edn overrides what the definition declares. A bundle without it re-derives under different membership than the store it came from, which is precisely the drift evidence exists to rule out.

A definition is read from material a stranger produced. A :malli guard carrying [:fn "(fn [_] …)"] executes that string on any runtime with sci — which babashka is. Nothing shipped uses :fn, and nothing could legitimately: a schema that calls a function is not offline, not reproducible, and not a pure function of (state, now, reached). The registry restriction enforces a law that was already written rather than adding one.

Consequences

see history · edit this page