proofbundle conformance corpus
September 18, 2026 · View on GitHub
Versioned, digest-pinned test vectors that a proofbundle verifier (this one, or an
independent implementation) must agree on. Every case is verified offline by
run_conformance.py: no calendar contact, no network — the specific Bitcoin block
merkle root a case needs is frozen inside its case.json, independently sourced and
byte-reproducible.
Run it:
make conformance # skips anchor sub-checks if opentimestamps is absent
python conformance/run_conformance.py --require-anchors # full run (needs the [anchors] extra)
The summary reports the executed scope, not a pass ratio. The shape, with placeholders rather than a snapshot of one branch's case count — a number printed here goes stale the moment the corpus grows, and a stale example is exactly the quotable half-truth rule 7 exists against:
[conformance] <N> cases · <full> fully checked · <partial> partially checked · <not run> not run · <failed> failed
<k> check(s) in <m> case(s) did NOT run in this environment:
- <caseId>: <why this check could not run here>
to execute them: pip install -e '.[anchors]' && python conformance/run_conformance.py --require-anchors
A case that did not run is labelled NOT RUN, one whose anchor sub-check skipped is PARTIAL.
Neither is ever counted as a pass. scope and skipped bind each other in both directions: only a
fully checked case may name no skip, and anything else must name what it skipped — which is what
makes the disclosure hold for any missing optional dependency, not just for opentimestamps.
See CONFORMANCE.md rule 7.
Case format
manifest.json lists case directories. Each holds a case.json:
{caseId, kind, input, expected: {...}, specRefs[], rationale, attribution}
expected is the whole point of the corpus: a case declares what it proves and what it
does not. A green run therefore never overclaims — a case that is canonicalization-correct
but not schema-conformant records the exact finding count as an expected-fail, it does not
hide it.
Kinds
decision_crossimpl
A decision-receipt statement pair (decision + referenced evidence) produced by a second, independent implementation, checked for cross-implementation agreement:
.jcsbytes are byte-identical with proofbundle's RFC 8785 canonical output;- both content roots (
statement_content_root, jcs-sha256-v1) recompute to theMANIFEST.jsonvalues and toexpected; decision.evidenceRefs[*].digestbinds the evidence content root;validate_decision_predicatereturns exactlyexpected.decision_predicate_findings(an expected-fail when the external predicate does not yet matchdecision-receipt/v0.1);- the OpenTimestamps anchor, when present, resolves to the expected status offline
(
confirmedagainst a frozen merkle root, orpending). The verifier rejects a wrong frozen root (block_mismatch), so confirmation is not a blind pass — that negative is exercised bytests/test_anchors_ots.py; the corpus itself runs the positive check.
native_bundle
A native proofbundle bundle checked against the CLI verify exit-code contract
(0 crypto OK · 1 verification failure · 2 malformed · 3 policy unmet). The exit code is
the conformance contract, so each case declares the exact code it must produce; the fail-closed
floor requires a native_bundle case to declare exitCode. These lock core verifier properties
onto the gate — a valid bundle verifies, a duplicate JSON key is rejected as malformed (the C1
Bishop-Fox parser-differential defense), a single flipped payload byte fails the signature.
cap1_document
A coverage-attestation document of draft-hillier-coverage-attestation-00 (profile cap/1),
checked through proofbundle.cap1. The single axis is expected.cap1Rules: the exact set of rule
ids (R0 to R8) that must fire, [] for a conformant document. The fifteen cases under
conformance/cap1/ are the draft author's own vectors (PV-01 to PV-05, NC-01 to NC-10; Apache-2.0,
conformance/cap1/vectors/LICENSE.author), and the expected sets come from the author's recorded
conformance run, including NC-05 firing R1 and R5 together. They are generated by
conformance/cap1/_generator/build_cases.py; a test keeps the generated bytes and the committed
ones identical.
Cases today
| caseId | proves | does NOT prove |
|---|---|---|
decision-crossimpl-schema-conformant | full end-to-end decision-receipt/v0.1 conformance cross-impl: RFC 8785 canonicalization + content-root binding, the predicate validates clean in NORMAL and STRICT mode (0 findings), and a confirmed Bitcoin anchor at block 958761 (OTS-committed root matches the real block merkle root, independently fetched from blockstream.info + blockchain.info, verified offline) | that the decision was correct — the anchor fixes existence and evidence binding, never correctness |
decision-crossimpl-confirmed-anchor-lifecycle | RFC 8785 canonicalization + content-root binding cross-impl, and a confirmed Bitcoin anchor at block 957504 (OTS proof committed root matches the real block merkle root, independently fetched, verified offline) | decision-receipt/v0.1 schema conformance (predicate reports 12 findings) |
decision-crossimpl-canonicalization-root-binding (historical — superseded by decision-crossimpl-schema-conformant) | RFC 8785 canonicalization + content-root binding cross-impl | schema conformance (12 findings, expected-fail) and a confirmed anchor (still pending) |
All three vectors are contributed by MarkovianProtocol / Colin (audit-anchor), vendored digest-pinned
as pure data, credited (MIT / SigmaSynth LLC). The gap between "canonicalization proven" and "full v0.1
conformance", recorded in audit_artifacts/crossimpl_fixture_gap_20260711.md, is now closed: full
decision-receipt/v0.1 conformance: done (graduated 2026-07-19, block 958761) with
decision-crossimpl-schema-conformant. The canonicalization-only iteration is retained as a historical
case (CONFORMANCE.md rule 5, no silent changes: a regeneration is a new case, the old one kept, so its
historical green run stays reproducible).
Adding a case
Never edit an accepted vector's bytes in place — a fixture change must be a new case (or an
explicit, reviewed re-pin), so accepted expectations cannot drift silently. Add the directory,
its case.json, and a line in manifest.json.