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]>
5.8 KiB
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.0stays3.0— this is deliberately not RFC 8785/JCS, which would collapse it to3). Content addresses are SHA-256 over the canonical form's bytes, writtensha2-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_falseforindependent:false);undischargedserializes as an object carrying its reason. claimstrings 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'sframearray declares one obligation with synthesized text"frame: "+ entries joined by"+"(SPEC §5.1). energy_anchoris the calibration-attestation reference required forModelBasedreceipts; it isnullon aMeteredreceipt because the meter binding plus the contract's ceiling-derivation attestation carry the anchoring there.recourseis a free-string regime identifier ("ISAE3000","eIDAS-Art13", a bond reference); absence — not any particular value — is what serializesrecourse_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.