open-standards/joulecontract/conformance/README.md
Transaction Science 429dc59a83 joulecontract v0.8: the implementability amendment — round five seals the pack
Round five turned the adversarial method on the published artifacts
themselves: a clean-room auditor, forbidden from reading joulecontract-rs,
attempted to build a conformant validator from SPEC + conformance pack
alone. Verdict: receipt-local yes, composed no. Seventeen findings
(provenance/joulecontract-diff-v0.7-pack.md) — the bundle construction
was recoverable only by brute force, the negative vectors demanded
composed verdicts while omitting their sealed contexts, the key-log
event model showed one variant of two, the spec's literal quorum MUST
failed the golden vector, and RFC 8785 (the canonicalization any honest
implementer reaches for) produces the wrong digest.

SPEC v0.8.0 amends v0.7.0 (archived byte-exact; seven verified anchors):
bundle address = digest of LF-joined sorted member addresses; canonical
JSON defined precisely (float-ness preserved — deliberately not JCS);
DSSE PAE in the spec text; quorum scope stated (receipt-scoped k≥2 at
JR-3, per-obligation at JR-4); obligation matching exact with the frame
synthesis rule; the pack declared the normative wire form; timestamps
integer seconds; the KeyLog Added/Revoked enum on the wire.

Pack expands 13 → 22 vectors: evidence-bundle.json ships every member's
bytes (incl. the transition record), every negative embeds its sealed
context, four new mutation negatives (engagement_undisclosed,
recourse_undeclared, static_only_frame, unterminated_chain), three
context-variant vectors (timestamp_stale, breach_unamended,
verifier_revoked_window), and a JR-4 receipt making per-obligation
quorum visible. The EnB object is now sealed evidence and resolved by
the composed validator. Conformance README rewritten as an
implementer's guide; remaining unvectored behaviors stated explicitly.

Also: joule-code pre-existing clippy warnings fixed (workspace now
zero), binding emits 0.8.0.

76 joulecontract tests + 39 joule-code tests green, clippy zero on both
workspaces. Site: Round 5 card + seven-anchor chain, redeployed.

The round's lesson in the standard's own grammar: the pack was making
composed claims whose evidence was not sealed alongside — it was
running evidence:external. v0.8 seals the pack.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 16:21:44 -04:00

5.8 KiB
Raw Permalink Blame History

Conformance vectors — v1

Deterministic wire vectors for independent JouleContract implementations. Regenerate with:

cargo run -p joulecontract-conformance --bin gen_vectors

This pack is the normative wire form (SPEC §5.1): where prose and wire diverge, the wire rules. It was expanded at v0.8 after a clean-room implementability audit (round five, ../provenance/joulecontract-diff-v0.7-pack.md) found that the composed validation path could not be built from the published artifacts alone — every input a composed verdict needs now ships in the pack.

Reading the vectors

  • Canonical JSON (SPEC §7): UTF-8, code-point-sorted keys, no whitespace, shortest round-trip numbers with float-ness preserved (3.0 stays 3.0 — this is deliberately not RFC 8785/JCS, which would collapse it to 3). Content addresses are SHA-256 over the canonical form's bytes, written sha2-256:<hex>.
  • Expected demotions are an ordered multiset: per-discharge demotions appear once per affected discharge entry, in discharge order; receipt-level demotions appear once. A validator must reproduce the multiset; reproducing the exact order is RECOMMENDED but set-with-multiplicity equality is conformant.
  • Demotion wire tokens are the snake_case forms of the SPEC §15 names (independent_false for independent:false); undischarged serializes as an object carrying its reason.
  • claim strings are informative, not normative — match the verdict fields (demotions, max_profile, permits_claimed_profile), not the prose.
  • Timestamps are integer seconds. Obligation matching is exact (kind, text) equality; the contract's frame array declares one obligation with synthesized text "frame: " + entries joined by "+" (SPEC §5.1).
  • energy_anchor is the calibration-attestation reference required for ModelBased receipts; it is null on a Metered receipt because the meter binding plus the contract's ceiling-derivation attestation carry the anchoring there. recourse is a free-string regime identifier ("ISAE3000", "eIDAS-Art13", a bond reference); absence — not any particular value — is what serializes recourse_undeclared.
  • Verifier-key disjointness (SPEC §5.1.1) is computed against the effective generator key set; it does not require the verifier key to appear in the office key log — the log governs time coordinates (revocation windows, as-of membership), not the disjointness predicate.
Vector What a conformant implementation must do
contract.json Content-address it; derive seven obligations (two pre, two post, two inv, one frame).
receipt-conformant.json Two expected verdicts: receipt-local validation caps at JR-2 (it cannot witness the JR-3+ gates), and composed validation over the embedded sealed_context grades zero demotions, the stated max profile, and permits the claimed ⟨JR-3 · Metered · certified⟩.
receipt-jr4-conformant.json Grade JR-4: every obligation discharged twice by party-disjoint verifier entities. Quorum at JR-3 is receipt-scoped (k ≥ 2 distinct entities across the discharge log); at JR-4 it is per-obligation, expressed as multiple discharge entries per obligation (SPEC §10).
evidence-bundle.json Recompute every member address from the published bytes, recompute the bundle address (SHA-256 over the LF-joined, lexicographically sorted member address strings — SPEC §5.1.3), and match receipt.evidence. The transition record's bytes are included so receipt.transition is checkable too.
negative-*.json (11) Produce exactly the expected composed demotions and max profile against the embedded sealed_context. One case per load-bearing demotion: unbound meter, generator-key overlap (incl. the under-declared key set), entity-omission key-only independence, undischarged frame, stochastic-alone verdict, unattested generator, self-attested loop governance, undisclosed engagement, undeclared recourse, static-only frame, unterminated qualification chain.
context-*.json (3) The receipt is byte-identical to the conformant one; the demotion lives in the context: stale anchors (timestamp_stale), an open unamended breach (breach_unamended), and a key-log revocation window spanning the start anchor (verifier_revoked_window, exercising the two-timestamp Revoked event — SPEC §6.4).
malformed-missing-obligations.json Reject as malformed — an unmentioned obligation is a hole in the record, not a demotion.
malformed-inflated-allowance.json Reject as malformed — joules_allowed binds to the contract's ceiling; an inflated allowance is residual minting, not a demotion.
envelope-dsse.json Verify the DSSE envelope (Ed25519 over the PAE — formula in SPEC §7) with the published test key; recover the in-toto Statement; check _type, predicateType, and both subjects (in-toto digests use bare sha256 hex; native fields keep the sha2-256: prefix).
amendment-chain.json Verify each digest against the byte-exact files in ../archive/ — seven anchors, v0.1 through v0.7.
MANIFEST.json Verify every vector's content address before relying on it.

Not yet vectored (stated, not implied complete): composition/decomposition (SPEC §3.3), the ratchet (§5.3), re-anchor records (§3.1), sector-equivalence claims (§10), and the contract-amendment wire object (§5.2) — the reference implementation covers these in its test suite; pack vectors are future work.

The test key is not a trust root. Vectors must be reproducible, so envelope-dsse.json is signed with a published seed (0x2a × 32). It attests nothing; treat any receipt signed with it as generator_unattested everything. The pack itself is integrity-checked by MANIFEST.json and, in-repo, by the conformance crate's tests on every run.