open-standards/wai/claim-conformance
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science 5ee4c7e28b
Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1m39s
standards workspaces build + test / aac-rs (pull_request) Successful in 32s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m8s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 37s
standards workspaces build + test / map-rs (pull_request) Successful in 40s
standards workspaces build + test / mesh-rs (pull_request) Successful in 35s
standards workspaces build + test / openpay (pull_request) Successful in 2m53s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 24s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Failing after 42s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 26s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 50s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m48s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m17s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m6s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m48s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m34s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 22s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m0s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 2m20s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 26s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 1m58s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 44s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 44s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 2m5s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m53s
wai formal verification (integer core) / kani (pull_request) Successful in 4m4s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 1m50s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 54s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 22s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Failing after 1m11s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 26s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 2m29s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 22s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 58s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 2m35s
wai cross-architecture byte-equality / windows-x86_64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · windows (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · windows (pull_request) Has been cancelled
wai: typed receipt classes and the soft binding class, reading OER through the OER module
A JWP receipt says what was delivered, by whom and at what energy cost, and
binds to its object one way. Other statements about the same objects had
nowhere to go, and energy-measurement §2 requires an acquisition class beside
every non-zero figure, which the object receipt has no member for.
jwp-receipts §2.4.3-§2.4.7 adds a `claim` kind with one envelope, one
canonical form (RFC 8785) and one signature rule for the classes energy,
lawful-basis and log-inclusion. §2.6 and §3.2 add a second, deliberately
weaker binding class: a publisher-signed soft binding with a resolution
endpoint, under the ancestor rule. §3.1 step 3 fixes a strict Ed25519 rule. A
declaration that a decision or a recovery record is checked against is
verified before anything is compared with it (`context_invalid`).

OER as settled on main is authoritative: this adds no second OER reader and
changes no OER rule, vector, verifier or document. An OER receipt an energy
claim names is read by oer::load under the document profile, verified by
oer::verify_receipt, and its figure read by the exact quantity reader the
composition checks use, through a new crate-private oer::rounds_to_micro.
claim_verify.py does the same with the OER reference's load, verify_receipt
and dec. The claim envelope keeps its own step-1 reader (claim_text.rs),
which reads claims only.

- Acquisition classes are the refined ones. The energy body carries the class
  as energy-measurement §2.7 writes it in JSON (energy_provenance,
  energy_uncertainty, energy_counter_filtering, energy_calibration_ref),
  present and null where a class has none, so a claim keeps one canonical
  form. Legacy HwShunt is refused, since no claim predates the refinement
  (energy_legacy_acquisition); missing, misplaced or sub-floor evidence is
  energy_acquisition_evidence. The grade correspondence is oer-receipts §2's,
  an exhaustive match over all five variants.
- §3.1 step 2's member tables give exactly the type step 2 checks, and step 2
  lists what the class rules add to a type as step 4's, never field_type. Both
  verifiers read a declaration in its table's order (they had read purposes
  before vocabulary and fragment, and every encoding after every member, so a
  declaration with two defects gave another reason than the text).
- oer::py_float_repr writes a double at a tie between two shortest decimals as
  CPython's repr does (round half to even at the shortest length). No content
  address or verdict depended on it: main's number profile keeps floats out of
  every receipt body. It changed the printed name of a vector named by such a
  number, which both OER verifiers print and the OER fuzzer compares. Fourteen
  doubles CPython 3.14.7 wrote are pinned.
- claim-conformance/: 154 vectors covering all 40 reasons, the canonical
  fixture recomputed by claim_verify.py and pinned, a second verifier
  (claim_verify.py) that shares no code with wai-rs, and differential.py,
  which runs both on inputs the corpus does not hold. receipt_claims joins the
  conformance library tests and both corpus loops.

Checked on this machine (macOS arm64) on the stream's tree:
- library tests with receipt_claims (dev; oer + receipt_claims also release);
- wai_claim_vectors and wai_oer_vectors verify their corpora;
- claim_verify.py reports every vector's declared outcome;
- differential.py: its default families and 20,000 random inputs (seed 7):
  no verdict differs and nothing crashes;
- oer2_verify.py passes every OER vector file and refuses every unreadable
  document, the C kernel's output equals firmware_vectors.json, and
  fuzz_differential.py (seed 1, 20,000 documents) finds no divergence.
And on this commit's tree, the per-link gate: build, library tests,
carriage, every corpus in the conformance loop, byte-exact, wasm32.

Not run here: the Linux and Windows legs (CI runs Linux; the Windows runner
is offline). The Python scripts are run by hand, not in CI.
Not done: key discovery (every claim signature presumes the verifier holds
the key), an authority class for who may sign which class, region- or
time-scoped soft bindings, and checking a log's receipt under its profile.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 01:42:45 -04:00
..
.gitattributes wai: typed receipt classes and the soft binding class, reading OER through the OER module 2026-09-30 01:42:45 -04:00
claim_verify.py wai: typed receipt classes and the soft binding class, reading OER through the OER module 2026-09-30 01:42:45 -04:00
differential.py wai: typed receipt classes and the soft binding class, reading OER through the OER module 2026-09-30 01:42:45 -04:00
README.md wai: typed receipt classes and the soft binding class, reading OER through the OER module 2026-09-30 01:42:45 -04:00
vectors.json wai: typed receipt classes and the soft binding class, reading OER through the OER module 2026-09-30 01:42:45 -04:00

claim-conformance

Conformance for typed receipt classes and the soft binding class (§2.4.3–§2.4.7, §2.6, §3.1–§3.2): claim receipts of class energy, lawful-basis, log-inclusion, soft-binding and binding-recovery, and recovery through a soft binding under the ancestor rule.

Each vector declares one outcome: accept — with the claim's link_hash, which pins the canonical form, or for a recovery the ancestor it attests — or reject at a named §3.1 step with a named reason. A verifier that rejects for another reason, or at another step, does not conform.

Files

file what it is
vectors.json 154 vectors: 27 accepted claims, 119 rejected claims, and 8 recoveries (2 accepted, 6 rejected). Every reason of §3.1 is exercised at least once.
claim_verify.py A second verifier, written from the extension text, that shares no code with wai-rs.
differential.py Generates inputs the corpus does not hold, runs both verifiers on them, and reports every input on which their verdicts differ.

Two procedures appear. claim verifies one receipt (§3.1), with optional context the verifier holds: previous (this signer's previous claim, step 5), object (the delivery receipt's content_hash and joules_micro), oer (an OER receipt and its signer keys), declaration (the claim a decision or a recovery record names), publisher (for a recovery record: the key of a verified delivery receipt for the declaration's subject — §2.6.3 makes the declaration context only together with it, so a vector that gives a recovery record one without the other is malformed), and exact_match_algs (the soft-binding algorithms the verifier has a matcher for; the corpus's matcher is byte equality). recover runs §3.2 from a declaration, the publisher key taken from a verified delivery receipt, the observed_value, and the object in hand's observed_hash (or null). Both procedures report recovery_signer_mismatch for a declaration the ancestor's publisher did not sign: recover_signer_mismatch and binding_recovery_declaration_not_publishers are the same defect, in a recovery performed and in one recorded.

Everything is synthetic. Keys are Ed25519 from fixed seeds (0x11 publisher, 0x22 meter, 0x33 operator, 0x44 sink, 0x55 resolver) and attest nothing; figures, uncertainties and calibration references are invented; algorithm identifiers, registries, endpoints, logs and calibration certificates use reserved example domains; vocabulary terms are illustrative and no verifier here checks them against a vocabulary.

The acquisition class

An energy claim carries its class as energy-measurement §2.7 writes a class in JSON: energy_provenance, with energy_uncertainty, energy_counter_filtering and energy_calibration_ref present and null where the class has no such evidence. Twenty-eight vectors are about the class and its evidence:

  • Accepted: an OnChipCounter figure with a declared uncertainty (2 % over 1 s) and its filtering state Unknown, and with Off; a CalibratedInstrument figure with a declared uncertainty (0.5 % over 1 s) and a calibration reference; a counter graded derived, below what it supports; a 1 µJ figure declared at exactly the resolution floor (288,676 ppm); and a counter graded measured that names an OER receipt grading the same figure measured.
  • Rejected at step 4: legacy HwShunt, alone and with evidence beside it (energy_legacy_acquisition); a label in another case (energy_unknown_acquisition); and, as energy_acquisition_evidence, a counter without its uncertainty or without its filtering state (null is not Unknown), with a state outside On/Off/Unknown, or with a calibration reference; an instrument without its reference, with a reference of white space only (a space and U+3000), or with a filtering state; a ModelBased figure with an uncertainty; an uncertainty of zero ppm, of a zero window, or of 2³² ppm; and a 1 µJ figure at 288,675 ppm, one below the floor. An Estimator graded simulated is energy_grade_exceeds_acquisition.
  • Rejected at step 2: an uncertainty without its window (missing_required_field), with a coverage factor beside it (unknown_member), with its relative part as a string, written as an array, and a filtering state written as a number (field_type).
  • Rejected at step 3: a ModelBased claim rewritten in transit to OnChipCounter with an uncertainty and a filtering state: the class and its evidence are signed.

Steps 1 and 2: types, and the order a body is read in

§3.1 step 2 checks exactly the type a member table gives, member by member in the table's order; whatever a class's rules add to a type is step 4's, with the rule's own reason. Four step-4 vectors pin that boundary from the rules' side: a zero figure (energy_zero_figure), a zero work count (energy_zero_work), an empty basis (lawful_basis_purpose_without_basis) and an http endpoint (soft_binding_endpoint_not_https). Eight more pin what the text settles on the other side, each signed again after its edit so the edit is its only defect:

  • Step 1: a byte-order mark before the text is malformed (RFC 8259 lets a parser ignore one); a class written as a number is field_type, not unknown_class.
  • Step 2, types: an empty alg is not a term; an endpoint with no scheme is not an absolute URI; a work unit holding U+3000, a White_Space character outside ASCII, is not a term. Each is field_type at step 2: alg is a term, not a string left whole to step 4's registry form; an endpoint's type is an absolute URI, and only https is step 4's; a term's white space is Unicode's White_Space, not ASCII's.
  • Step 2, order: a lawful-basis body whose record names neither record is field_type, since record is read first; a declaration whose vocabulary has no scheme and whose purpose has no basis is field_type, since the table reaches vocabulary first and its encoding is checked as it is read; and one whose first purpose's term holds a space and whose second carries an extra member is field_type, since each purpose is read whole before the next. A reader that checks every purpose's members before the other members, or every encoding after every member, reports these two as missing_required_field and unknown_member.

The OER receipts

The OER receipts in oer contexts are sealed with wai-rs's OER module (its canonical form and content address), and read and verified by each verifier's own OER verifier under oer-receipts: the reference verifier (../oer-conformance/oer2_verify.py) reports the valid ones valid, the one altered after sealing as receipt_id_mismatch and receipt_hash_mismatch, and as malformed the six whose figure is not a quantity oer-receipts §4 reads or whose body holds a number §4 forbids: joules_total as a decimal string of 35 significand digits, as the JSON number 0.0120005, as -0.0, as the integer 2⁵³, spelt +0.012000, and as true. Two valid ones state the claim's figure and grade but are not about its object (an unrelated object, and the object's hash spelt in uppercase); step 6 rejects both.

Seven vectors state a figure step 6 reads at its edges, as oer-receipts §4 reads a quantity and compares it exactly: 0.0120005, exactly half-way, which admits 12,000 and 12,001 µJ; the same decimal written 1.20005e-2; the integer 1, one joule; and two figures written with 34 significand digits, the most a decimal string holds — one exactly half-way, and one (0.0120004 followed by twenty-six nines) below half-way only in its last digits, which is 12,000 µJ and not 12,001, although rounding it to 28 significant digits would make it a tie.

Signatures

Seven vectors test §3.1 step 3 beyond an ordinary bad signature.

  • Six satisfy the verification equation — the cofactorless one, and so the cofactored one — with the key decoded leniently (y reduced mod p, the sign bit ignored when x = 0) and S reduced mod L. The generator asserts this for each. Step 3 refuses them on two different grounds:
    • RFC 8032's own rules, which step 3 restates: the identity key written with its sign bit set, and written with y = p + 1, each with a signature made without a private key (§5.1.3 decoding refuses both encodings; the point they spell is also of small order, so step 3's choice refuses them too); and S replaced by S + L (§5.1.7 requires S < L).
    • The choices step 3 fixes: the identity key and a key of order 8, each "signed" with no private key, and a genuine key signing with R the identity. An RFC 8032 verifier that accepts keys and R of small order accepts these three.
  • One does not (signer_key_y_not_reduced): a key of large order written with y = p + j. Nobody can sign for that point, so the vector carries another key's signature, which fails the equation under any decoding (the generator asserts that too). It pins only the reason — bad_signer_key, not signature_verify_failed — and does not show that a lenient verifier would accept a forgery.

The cryptography package's Ed25519 on its own is more lenient than RFC 8032's decoding. Called on each vector's key, signature and signing input (cryptography 50.0.1 over OpenSSL 3.6.4, Python 3.14.7, macOS on arm64), it accepted five of the six that satisfy the equation — both non-canonical spellings of the identity and the three vectors of the second group — and of the seven refused only S + L and the y = p + j vector. claim_verify.py therefore makes step 3's decoding, order and range checks itself, and leaves only the equation to cryptography.

Running

# wai-rs: the canonical fixture, byte-identical regeneration, and every vector
cargo run --no-default-features --features receipt_claims --bin wai_claim_vectors -- verify ../claim-conformance

# the second verifier (needs Python 3 and `cryptography`)
python3 claim_verify.py

# the two verifiers' verdicts on generated inputs the corpus does not hold
cargo build --no-default-features --features receipt_claims --bin wai_claim_vectors
python3 differential.py <target-dir>/debug/wai_claim_vectors [--random N] [--seed S] [--keep FILE]

CI runs the first on the four-platform conformance matrix. The second and third are run by hand; they are not CI steps.

differential.py writes its inputs in the corpus's format with no declared outcome and runs each verifier's verdicts mode (wai_claim_vectors verdicts <file>, claim_verify.py verdicts <file>), which prints one verdict per input. Its families: JSON numbers and strings at the limits of what parsers accept, nesting depth, calendar dates, URI authorities, acquisition classes with every combination of label, uncertainty, filtering state and calibration reference around energy-measurement §2.3–§2.5's rules (calibration references of every White_Space character, and of U+001C, U+001F, U+200B and U+FEFF, which are not White_Space), OER figures near a half-microjoule and at the edges of oer-receipts §4's quantities, OER receipts whose sums and floors sit at §6's tolerances, OER receipts holding numbers §4 forbids in a body (-0, fractions, exponents, integers beyond I-JSON's range) in members of every kind, a delivered object's microjoules spelt as the corpus never gives them, context receipts that do not themselves verify, bodies of every class with one defect from a fixed list and with every pair of those at two different positions (a member, or a member of a nested object), where the order §3.1 step 2 reads a body in decides the reason, and, with --random N, a seeded random sample of dates, endpoints, figures, sums and class combinations. Separately, each value that makes a whole file unreadable under oer-receipts §4 — a number beyond the double range, NaN, an unpaired surrogate escape, a repeated member name, nesting past 64 levels — goes in a file of its own beside readable twins, and the two verifiers must both refuse the file or give it the same verdicts. It exits 0 only if the two verdict lists are identical, every file is read or refused alike, and neither verifier crashed; any difference exits 1, at whatever step.

Both verifiers read a corpus-format file as oer-receipts §4 reads a vector document, since it holds OER receipts: wai-rs with oer::load, claim_verify.py with the OER reference's load. A difference where one side reports energy_oer_invalid is marked, since its cause is in the two OER verifiers rather than the claim rules, but it counts the same. Those two are compared on their own by ../oer-conformance/fuzz_differential.py.

What each check can and cannot show

The expected outcomes are declared per vector in the generator (wai-rs, claim_receipt::corpus), not copied from a verifier's output. The signed bytes, though, are produced by the same module that verifies them, so a canonicalisation defect would be baked into every signature and link hash and then confirmed. Two checks close that:

  • The canonical fixture. An OnChipCounter claim given with members out of order at three levels, whitespace, and \u escapes in the input text. Its canonical bytes, signature and link hash were computed by claim_verify.py fixture — Python's JSON serialiser with members ordered by UTF-16 code units, cryptography's Ed25519, and BLAKE3 written from its specification — and are pinned as literals in wai-rs (check_canonical_form), which wai_claim_vectors verify checks first. Reversing the member order, or changing the signing domain, fails it.
  • The second verifier. claim_verify.py reports every vector's declared outcome and every pinned link hash, including a claim whose canonical form spans three BLAKE3 chunks, and checks that the fixture it computes equals the literals in wai-rs. Its §3.1 step 3 checks other than the equation — RFC 8032 key decoding, the order of the key and of R, and S < L (which cryptography also checks) — are written from the curve's definition; the equation is cryptography's. Its acquisition-class rule is written from energy-measurement §2.3–§2.5, where wai-rs admits the class through crate::energy_class.

The corpus shows the two verifiers agree on its vectors and nothing more. differential.py checks agreement on inputs no vector declares an outcome for: that the two read the same inputs the same way. It does not show that either reading is the extension's, and it covers only the families it generates.

The second verifier and the differential were written by the same author as wai-rs, in a different language with no shared code. They catch disagreements in canonical form, hashing, signatures and the reading of JSON, dates, hosts, classes and figures; they are weaker evidence against a misreading of the extension that both verifiers share. Two of the order vectors above (lawful_basis_vocabulary_before_purposes, lawful_basis_purposes_in_order) exist because of one: both verifiers once read a declaration's purposes before its other members, which §3.1 step 2 does not say, and agreed with each other throughout. It was found by reading the text against the code; neither check could have shown it.

Regenerating

cargo run --no-default-features --features receipt_claims --bin wai_claim_vectors -- gen ../claim-conformance

Ed25519 signing is deterministic and the seeds are fixed, so the output is byte-identical on every machine; verify fails if the pinned file differs from it.