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
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]>
434 lines
27 KiB
Markdown
434 lines
27 KiB
Markdown
# WAI Extension: OER Receipts (outcome and energy)
|
||
|
||
> Status: Draft. Ingests **OER/2 draft 0.1**, with the departures in §9. An OER
|
||
> receipt records what one unit of work was, what it cost, **how that cost is
|
||
> graded**, and what it rests on — canonicalised, content-addressed, hash-chained
|
||
> and Ed25519-signed, so a third party can check every figure without trusting the
|
||
> producer. Conformance is **reason-set-equivalence**: for every published vector,
|
||
> a verifier reports exactly the reasons the vector declares. Verifiers: the
|
||
> reference, `oer-conformance/oer2_verify.py`, and `wai-rs` (`crate::oer`, the `oer`
|
||
> feature); corpus: `oer-conformance/`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
|
||
## 1. Scope and model
|
||
|
||
[`energy-measurement.md`](energy-measurement.md) fixes *how a joule figure is
|
||
acquired* — the acquisition class. It does not say how much weight the figure can
|
||
bear. An OER receipt adds that: every figure carries an **evidential grade**, and
|
||
the receipt as a whole is canonical, content-addressed and chained, so the grade
|
||
cannot be quietly upgraded after the fact. An OER/2 receipt carries the grade and
|
||
no acquisition class; the two are different axes (§2).
|
||
|
||
A receipt describes one unit of work of a declared `kind`, one of the strings
|
||
`decision`, `task`, `reconstruction`, `calibration`, `job`, `compile`, `decode`,
|
||
`mitigate`, `rearrange`, `anchor`, or `bundle` (of other receipts). Receipts form a
|
||
chain; the chain, the records its decisions relied on, and a ledger identity that
|
||
ties the chain to a meter are verified together.
|
||
|
||
Three independent implementations check the same corpus: the reference verifier
|
||
(`oer-conformance/oer2_verify.py`), the portable C kernel that *produces* receipts
|
||
(`oer-conformance/settle/`), and `wai-rs`. They share no canonicalisation code.
|
||
|
||
## 2. The evidential grade (REQUIRED)
|
||
|
||
`provenance.how` **MUST** be one of four grades, weakest first:
|
||
|
||
| grade | meaning |
|
||
|---|---|
|
||
| `projected` | an estimate awaiting a measurement |
|
||
| `simulated` | produced by emulation or a model run, not by the system itself |
|
||
| `derived` | follows from measured or published figures by stated arithmetic |
|
||
| `measured` | read from an instrument, a datasheet, or a published measurement |
|
||
|
||
A figure's grade is its **evidential standing**; its acquisition class
|
||
([`energy-measurement.md`](energy-measurement.md) §2: `CalibratedInstrument`,
|
||
`OnChipCounter`, `ModelBased`, `Estimator`, and the legacy `HwShunt`) is **how it
|
||
was obtained**. They are different axes and a receipt **SHOULD** carry both where
|
||
it carries an acquisition class at all. An OER/2 receipt has no member for the
|
||
class, and cannot gain one without becoming non-canonical (§3). Its descriptive
|
||
members name none. `provenance.device` is text; the corpus writes `hardware`,
|
||
`emulation` and `simulation`. `energy.method` is text too; the corpus writes
|
||
`hardware_meter` and `model_estimated`. In particular, neither `hardware` nor
|
||
`hardware_meter` is `HwShunt` or either refined hardware class.
|
||
|
||
[`energy-measurement.md`](energy-measurement.md) governs the receipt profiles
|
||
WAI defines (§1 there), not an OER/2 receipt. No rule of that extension binds an
|
||
OER receipt or its producers and verifiers: an OER receipt that carries a
|
||
non-zero `energy.joules_total` and no class breaks none of them, and that
|
||
extension's publication gate (§4 there) and reporting contract (§6 there) do not
|
||
apply to it either. Its evidential axis is the grade, under this document's
|
||
rules.
|
||
|
||
The two axes correspond in one direction:
|
||
|
||
| acquisition class | supports the grade |
|
||
|---|---|
|
||
| `CalibratedInstrument` | `measured` |
|
||
| `OnChipCounter` | `measured` |
|
||
| `HwShunt` (legacy) | `measured`: a figure sealed before the hardware class was refined, or a refined hardware figure mapped into the three-name vocabulary (below) |
|
||
| `ModelBased` | `derived`, by [`energy-measurement.md`](energy-measurement.md)'s own definition |
|
||
| `Estimator` | `projected` |
|
||
|
||
- Both refined hardware classes are readings of an instrument, so both support
|
||
`measured`. The grade does not carry what separates them: whether the figure's
|
||
uncertainty is traceable to a calibration
|
||
([`energy-measurement.md`](energy-measurement.md) §2.2). The class and its
|
||
declared uncertainty (§2.3 there) say that; `measured` does not.
|
||
- Legacy `HwShunt` is what WAI receipts sealed before the refinement say. It
|
||
ranks below both refined classes, and
|
||
[`energy-measurement.md`](energy-measurement.md) §2.6 forbids it in a new WAI
|
||
receipt, so a hardware figure a conforming WAI receipt seals today names
|
||
`OnChipCounter` or `CalibratedInstrument`. Mapped into a vocabulary that has
|
||
only `HwShunt` / `ModelBased` / `Estimator`, both refined classes become
|
||
`HwShunt` (§2 there); the grade does not change, so a `HwShunt` figure
|
||
produced that way supports `measured` too. An OER receipt records no class
|
||
at all, so a figure graded `measured` in one says nothing of which hardware
|
||
class, if any, is behind it.
|
||
- The correspondence does not run the other way: `measured` also covers a
|
||
datasheet or a published figure, which no acquisition class names, and
|
||
`simulated` has no acquisition-class counterpart at all.
|
||
|
||
A producer **MUST NOT** grade `measured` a figure whose acquisition class is
|
||
`Estimator`. A receipt carries no acquisition-class member (§3), so no reason in
|
||
§7 checks this; it binds the producer.
|
||
|
||
`provenance.weakest_input` names the lowest-graded input the figure rests on, so a
|
||
`measured` total built on a `projected` constant is visible as such.
|
||
|
||
The `energy` claim class of [`jwp-receipts.md`](jwp-receipts.md) §2.4.5 carries both
|
||
axes for a delivery receipt's figure: the acquisition class with its evidence, and a
|
||
grade, which it rejects when it ranks above what the class supports under the
|
||
correspondence above. It may name the OER receipt that records the figure. A verifier
|
||
holding that receipt then checks that it verifies under this document, is about the
|
||
same object (its `subject.output` is the object's jwp-receipts §2.1 content hash, as
|
||
the claim writes it), and states the same grade and, to the microjoule, the same
|
||
figure, read as §4 reads a quantity. That is where the producer rule above becomes
|
||
checkable, against the class the claim's signer states: a verifier that checks an
|
||
accepted `energy` claim against the OER receipt it names holds a receipt graded no
|
||
higher than that class supports. It changes nothing about the OER receipt itself, which
|
||
still carries no class.
|
||
|
||
## 3. Receipt members
|
||
|
||
Members are fixed per path, and **only** these members may appear:
|
||
|
||
| path | members, in canonical order |
|
||
|---|---|
|
||
| *(root)* | `oer`, `receipt_id`, `kind`, `capability`, `intent`, `subject`, `work`, `energy`, `provenance`, `settlement`, `floor`, `efficiency`, `bundle`, `chain`, `hash_alg`, `sig_alg` |
|
||
| `subject` | `inputs`, `output` |
|
||
| `work` | `status`, `outcome`, `latency_s`, `proficiency` |
|
||
| `energy` | `joules_total`, `boundary`, `breakdown`, `method`, `temperature_k` |
|
||
| `energy.breakdown` | `compute_j`, `movement_j`, `idle_alloc_j`, `shared_alloc_j`, `upstream_j` |
|
||
| `provenance` | `how`, `device`, `weakest_input`, `hardware_profile`, `corpus` |
|
||
| `provenance.weakest_input` | `how`, `input` |
|
||
| `settlement` | `question_key`, `served_from_record`, `record_id`, `record_hash`, `reuse_count`, `derivation_j`, `valid_until` |
|
||
| `floor` | `bits_committed`, `bits_delivered`, `floor_j`, `decades_above_floor` |
|
||
| `efficiency` | `completed_units`, `wasted_units`, `joules_per_completed`, `waste_fraction`, `value_per_joule` |
|
||
| `bundle` | `stages`, `provenance_root` |
|
||
| `bundle.stages[]` | `kind`, `receipt_hash`, `joules` |
|
||
| `chain` | `prev_hash`, `receipt_hash`, `signer`, `signature`, `anchor` |
|
||
| `chain.anchor` | `path`, `delivered_at`, `root` |
|
||
|
||
An object at any other path orders its members by key. A member not listed for its
|
||
path makes the receipt non-canonical.
|
||
|
||
`oer` is the profile version, a string (`"2.0"`); its major version is the part
|
||
before the first `.`. A verifier **MUST** reject a major version other than `2`, and
|
||
an `oer` that is not a string, before any check but whether the receipt is an object
|
||
(§7) — a version written as a JSON number would have a major that depends on how
|
||
each parser rounds it. `energy.boundary` **MUST** be stated: a joule figure without
|
||
its measurement boundary cannot be compared with any other.
|
||
|
||
**Capability.** `capability` names what the receipt is about, in the receipt's own
|
||
namespace: a decision receipt names its decision class under `oer.decision.*`
|
||
(`oer.decision.ev_charging_authorization`), and bundles and anchors use `oer.bundle.*`
|
||
and `oer.anchor.*`. These are not WAI payload capabilities, which name formats a sink
|
||
reconstructs ([`SPEC.md`](../SPEC.md) §5), and a receipt **MUST NOT** name them in
|
||
the `wai.` namespace.
|
||
|
||
**Status, escalation and abstention.** `work.status` is `1` for a unit of work that
|
||
completed and `-1` for one that was wasted; a bundle's `efficiency` counts them as
|
||
`completed_units` and `wasted_units`. Two outcomes are easy to confuse:
|
||
|
||
- `escalate` — the question was routed to a higher authority and answered there.
|
||
It is a verdict: `subject.output` carries the answer and the receipt may store a
|
||
record that later receipts are served from. This unit did not derive the answer,
|
||
and says so.
|
||
- `abstain` — no answer. The receipt makes no verdict claim: `subject.output` is
|
||
empty, and it stores no record (`settlement.served_from_record` false,
|
||
`record_id` and `record_hash` empty), so nothing can ever be served from it as
|
||
settled. A verifier reports `abstention_settled` for an abstention that carries
|
||
any of them. Empty and false here mean absent, `null`, `false`, `0`, `""`, `[]` or
|
||
`{}`; any other value is a claim.
|
||
|
||
A producer **MUST** receipt a decision it did not earn — one closed by a stand-in, a
|
||
default or a guess — as an abstention, never as a verdict. An unsettled answer is a
|
||
first-class result. An abstention is completed work (`status` `1`), so
|
||
`efficiency.joules_per_completed` counts it; a figure per *settled* decision
|
||
**MUST NOT**.
|
||
|
||
## 4. Canonical form
|
||
|
||
The canonical body is the receipt **without** `chain` and `receipt_id`, with every
|
||
object's members in the order of §3, serialised as JSON with `,` and `:` as
|
||
separators and no other whitespace, and with non-ASCII characters written
|
||
unescaped (UTF-8). Within strings, `"` `\` and the controls `\b \f \n \r \t` take
|
||
their short escapes and every other character below U+0020 is written `\u00XX` in
|
||
lower-case hex.
|
||
|
||
**Numbers.** A receipt body carries two kinds of number and no others. Integers are
|
||
written in plain decimal, with no fraction or exponent, and lie within ±(2⁵³ − 1):
|
||
the I-JSON range (RFC 7493 §2.2), which any reader of IEEE 754 binary64 or wider
|
||
reads exactly. Every non-integer quantity — every joule figure, temperature and ratio
|
||
— is a **decimal string** (`"14.960786694120"`). A number with a fraction or an
|
||
exponent (`1.0`, `1e2`), negative zero (`-0`), and an integer outside that range
|
||
**MUST NOT** appear in a body, and a verifier **MUST** report `malformed` for one.
|
||
The canonical form therefore never prints a float. That matters because parsers
|
||
disagree on exactly these shapes — one reads `-0` and 2⁶⁴ as exact integers, another
|
||
as floats — and two verifiers that hashed them would compute two receipt ids for one
|
||
receipt. The rule is about how a number is **written**: a verifier whose parser reads
|
||
`1.0` and `1` as the same value needs a reader that sees the text. Records (§6) are
|
||
not receipt bodies and are unaffected.
|
||
|
||
**The document.** A vector document is read before any receipt in it is verified,
|
||
and one that cannot be read under this profile is refused whole:
|
||
|
||
- a number whose nearest binary64, rounding half to even, is infinite — magnitude
|
||
2¹⁰²⁴ − 2⁹⁷⁰ or more, in any spelling. A reader **MUST** round correctly: one that
|
||
does not refuses finite numbers just below the midpoint, or reads ones past it;
|
||
- `NaN`, `Infinity` or `-Infinity`, which are not JSON;
|
||
- a string holding an unpaired surrogate, which is not Unicode (RFC 7493 §2.1);
|
||
- a member name repeated within one object (RFC 7493 §2.3) — readers disagree on
|
||
which value wins, so a signed receipt could carry a second, hidden figure;
|
||
- nesting deeper than 64 arrays and objects, the document itself counting as one.
|
||
|
||
The corpus holds one document for each in `oer-conformance/unreadable/`, among them a
|
||
repeated figure inside a receipt and nesting far past any parser's limit. A vector in
|
||
`edge_vectors.json` holds the extremes a document may carry — the largest binary64 as
|
||
a float and as an integer, a float just below the midpoint, a surrogate pair, nesting
|
||
exactly 64 deep — so a verifier that refuses too much fails too.
|
||
|
||
**Decimal strings.** A decimal string is JSON's own number grammar (RFC 8259 §6)
|
||
carried as a string, in ASCII: an optional `-` and no other sign, no whitespace, no
|
||
leading zero, digits on both sides of any `.`, and an exponent of one to three
|
||
digits. The significand — every digit before the exponent, leading zeros included —
|
||
has at most 34 digits, so every decimal string is exact in IEEE 754 decimal128.
|
||
Where a check reads a quantity it accepts a decimal string or an integer in the range
|
||
above, and nothing else. A receipt member that fails is `malformed`; a ledger figure
|
||
that fails makes the identity fail.
|
||
|
||
**The canonical form is pinned independently of the vectors.** The reference
|
||
generator uses the reference verifier's canonicaliser, so a defect in the field
|
||
order would be baked into every receipt id and then confirmed by the same code —
|
||
reversing the declared order and regenerating still yields a passing corpus. Every
|
||
verifier therefore also checks a **canonical fixture**: an input whose members are
|
||
given in the wrong order at two levels, against literal expected bytes and their
|
||
`sha-256` id (`sha256:364718c7…84ed30`). The cross-check against the C kernel,
|
||
which shares no code with either verifier, proves the same property from the other
|
||
side.
|
||
|
||
A second fixture pins the stage list that `bundle.provenance_root` addresses (§6),
|
||
which is serialised by the same rules as the body. Every stage list in the corpus is
|
||
ASCII, and on ASCII text an escaped and an unescaped serialisation agree, so no
|
||
vector can tell them apart. The fixture's stages carry a two-byte and a four-byte
|
||
character, with their members out of order, against literal bytes and their
|
||
`sha-256` id (`sha256:c1caa0ce…f2638b`).
|
||
|
||
## 5. Identity, chain and signature
|
||
|
||
- `receipt_id` = `H(canonical body)`.
|
||
- `chain.receipt_hash` = `H(chain.prev_hash ‖ canonical body)`, the previous hash as
|
||
UTF-8 bytes. Every receipt after the first **MUST** carry a `prev_hash` equal to
|
||
the previous receipt's `receipt_hash`, both strings — a hash that is not a string
|
||
links nothing, so the receipt after it is a `chain_break`.
|
||
- `chain.signature` is Ed25519 (RFC 8032) over the UTF-8 bytes of
|
||
`chain.receipt_hash`, verified against the key the vector file publishes under
|
||
`chain.signer`. A published key is decoded strictly (RFC 8032 §5.1.3): it **MUST**
|
||
be the canonical encoding of a point, and the point **MUST NOT** be of small
|
||
order, which verifies a signature over any message. Every signature under a key
|
||
that fails either is `signature_invalid`.
|
||
|
||
`H` is named by `hash_alg`: `sha-256` writes `sha256:<hex>`, `blake3` writes
|
||
`blake3:<hex>`. A verifier that lacks the named algorithm **MUST** report
|
||
`missing_capability` — never a mismatch, which would blame the producer for the
|
||
verifier's gap. `sig_alg` **MUST** be `ed25519`.
|
||
|
||
## 6. Composition checks
|
||
|
||
Every check is evaluated in **exact** decimal arithmetic: no sum, product or
|
||
comparison is rounded, so no verdict is decided by rounding noise and two
|
||
conforming verifiers cannot disagree at a tolerance's edge. "Within `t`" means
|
||
`|a − b| ≤ t` — a difference exactly at the tolerance passes, and a miss counts the
|
||
same on either side — and every relative tolerance scales by the magnitude of the
|
||
figure it names, so a negative figure is judged exactly as a positive one.
|
||
|
||
- **Sum.** The `energy.breakdown` terms **MUST** sum to `joules_total` within
|
||
`1e-9 × max(1, |sum|)`.
|
||
- **Floor.** `floor.floor_j` **MUST** equal `bits_committed · k_B · temperature_k ·
|
||
ln 2` within 1% of that product's magnitude — the Landauer floor for the bits the
|
||
decision committed — with `k_B = 1.380649e-23 J/K` (exact by definition) and `ln 2`
|
||
taken as the decimal `0.6931471805599453`, so the product itself is exact.
|
||
- **Bundle.** `bundle.provenance_root` = `H` of the stage list, each stage in
|
||
`bundle.stages[]` member order, serialised by §4's rules exactly as the body is;
|
||
the stages' `joules` **MUST** sum to `joules_total` within `1e-9 × max(1, |sum|)`;
|
||
a bundle **MUST** carry `efficiency.completed_units`.
|
||
- **Records.** A receipt served from a record (`settlement.served_from_record`,
|
||
with any `record_id` but `unverified`) cites `settlement.record_hash`, which
|
||
**MUST** equal `sha256("<key>|<answer>|<created>")` over the record the vector
|
||
publishes, with `created` written to one decimal place rounded half-to-even on
|
||
its binary value. A record's `key` and `answer` are strings and its `created` a
|
||
number (not a boolean); a record that is not, a `record_id` that is absent, empty
|
||
or not a string, and a record store that is not an object cannot vouch for
|
||
anything, so the receipt citing it is a `record_mismatch`. The check runs for a
|
||
vector that publishes `records`; one that publishes none makes no claim about
|
||
them and runs no record check.
|
||
- **Ledger.** The receipts' `joules_total` plus the ledger's `unallocated_joules` and
|
||
`carried_forward_joules` (`0` when absent) **MUST** equal its `meter_joules`
|
||
within `1e-9 × max(1, |meter|)`. A ledger figure that is not a quantity (§4)
|
||
makes the identity fail.
|
||
|
||
## 7. Reasons
|
||
|
||
A verifier reports **every** reason a receipt fails, from this vocabulary, and
|
||
nothing else, and a receipt too broken to check is a reason, never a verifier
|
||
error. Some checks stop at the first failure and report it **alone**, discarding
|
||
anything found before it; they run in this order, so a receipt with more than one
|
||
names the same reason everywhere:
|
||
|
||
1. the receipt is not a JSON object — `malformed`;
|
||
2. `oer` is absent, not a string, or of another major — `unknown_major` (§3);
|
||
3. a member not listed for its path, `chain` included — `canonical_failure`;
|
||
4. the body carries a number §4 forbids — `malformed`;
|
||
5. `hash_alg` names an algorithm the verifier lacks — `missing_capability`;
|
||
6. any other member a check needs is absent or mistyped, `hash_alg` and
|
||
`bundle.stages` (not a list) included — `malformed`.
|
||
|
||
Everything else accumulates. Some members are judged on their value, so a wrong
|
||
one is a named reason rather than `malformed`:
|
||
|
||
- judged whether absent or mistyped — `kind` that is not a listed string is
|
||
`unknown_kind`; `energy.boundary` that is not a non-empty string is
|
||
`boundary_missing`; a bundle's `efficiency` that is not an object holding
|
||
`completed_units` is `efficiency_missing`; `sig_alg` other than `ed25519` is
|
||
`missing_capability`, which, unlike an unsupported `hash_alg`, stops nothing;
|
||
- judged when present but mistyped, and `malformed` (step 6) when absent — a
|
||
`receipt_id` that is not a string is `receipt_id_mismatch`; a `chain.receipt_hash`
|
||
that is not a string is `receipt_hash_mismatch` and, since the signature is over
|
||
it, `signature_invalid`; a `chain.signer` that is not a string, or names a key that
|
||
is absent or not a string, is `signer_unknown`; a `provenance.how` that is not a
|
||
string is `grade_unknown`.
|
||
|
||
| reason | raised when |
|
||
|---|---|
|
||
| `unknown_major` | `oer` is absent, not a string, or of a major other than `2` (reported alone) |
|
||
| `unknown_kind` | `kind` is not one of §1's kinds |
|
||
| `canonical_failure` | a member not listed for its path (reported alone) |
|
||
| `malformed` | the receipt is not an object, a member a check needs is absent or mistyped (a quantity that is not a §4 decimal string or integer included), or the body carries a number §4 forbids (reported alone) |
|
||
| `missing_capability` | the verifier lacks `hash_alg` (reported alone), or `sig_alg` is not `ed25519` |
|
||
| `receipt_id_mismatch` | `receipt_id` ≠ `H(body)` |
|
||
| `receipt_hash_mismatch` | `chain.receipt_hash` ≠ `H(prev_hash ‖ body)` |
|
||
| `chain_break` | `prev_hash` ≠ the previous receipt's `receipt_hash`, or either is not a string |
|
||
| `sum_mismatch` | breakdown does not sum to `joules_total` |
|
||
| `boundary_missing` | `energy.boundary` is absent, empty or not a string |
|
||
| `floor_mismatch` | `floor_j` is not the Landauer floor within 1% |
|
||
| `grade_unknown` | `provenance.how` is not a §2 grade |
|
||
| `abstention_settled` | an `abstain` receipt carries an answer or a record (§3) |
|
||
| `bundle_root_mismatch` | `provenance_root` does not address the stages |
|
||
| `bundle_sum_mismatch` | stages do not sum to `joules_total` |
|
||
| `efficiency_missing` | a bundle without `efficiency.completed_units` |
|
||
| `signer_unknown` | `chain.signer` is not a string naming a published key that is a string |
|
||
| `signature_invalid` | the signature does not verify, or the published key fails §5's strict decoding |
|
||
| `record_mismatch` | a cited record does not rehash to `record_hash`, or cannot be rehashed (§6) |
|
||
| `ledger_mismatch` | the ledger identity does not hold |
|
||
|
||
Reasons for receipts that share a `receipt_id` **accumulate**; a repeated id
|
||
**MUST NOT** replace, and so hide, the reasons of an earlier receipt.
|
||
|
||
## 8. Conformance
|
||
|
||
> **reason-set-equivalence.** For every vector in `oer-conformance/`, a conforming
|
||
> verifier reports exactly the declared set of reasons — the valid vectors none,
|
||
> each tampered vector exactly its own — and both fixtures (§4) hold.
|
||
|
||
| file | vectors | produced by |
|
||
|---|---|---|
|
||
| `vectors.json` | 9 | the reference generator: a simulated corridor day, two valid vectors and seven tampered twins |
|
||
| `firmware_vectors.json` | 1 | the portable C kernel (`settle/`): 400 receipts, independent SHA-256 and Ed25519 |
|
||
| `edge_vectors.json` | 99 | `generate_edge_vectors.py`: one case for each reason the upstream nine never exercise, each number shape and decimal-string defect §4 forbids and each limit from the accepting side, each bound and tolerance edge, each step of the order of the reasons reported alone, each member judged on its value, each claim an abstention cannot make, each key a signature cannot rest on, and the extremes a document may hold |
|
||
| `unreadable/` | 11 documents | one for each way §4 refuses a document whole; every verifier must refuse all of them |
|
||
|
||
The upstream vectors cover eight reason names; the edge vectors pin the rest,
|
||
including `malformed`, `canonical_failure` and `abstention_settled`, the
|
||
repeated-id rule, and every edge a verifier could draw in the wrong place: each
|
||
integer bound from both sides; each decimal-string limit from both sides; each of
|
||
the four tolerances — sum, floor, bundle, ledger — exactly at its edge and just past
|
||
it, on both sides, for negative and small figures, and with the figure that sets its
|
||
base; for each of the four, a verdict that flips if it is computed to 34 digits
|
||
(and for the sum, to 28); each step of the order of the reasons reported alone; and
|
||
keys that are small-order or non-canonical encodings, each under a signature that a
|
||
lenient verifier accepts. Their expectations are written by hand from this document
|
||
— none is copied from a verifier's output, which would only prove the code agrees
|
||
with itself.
|
||
|
||
Two rules bind the producer and no reason in §7 can enforce them: the capability
|
||
namespace, and receipting an unearned decision as an abstention (§3). A verifier
|
||
cannot tell from a receipt's bytes whether a verdict was earned — only that an
|
||
abstention claims none.
|
||
|
||
```text
|
||
wai_oer_vectors verify oer-conformance # wai-rs, --features oer: every file and unreadable/
|
||
python3 oer-conformance/oer2_verify.py <file> # reference verifier, per file
|
||
python3 oer-conformance/oer2_verify.py --unreadable oer-conformance/unreadable
|
||
python3 oer-conformance/fuzz_differential.py # the two verifiers against each other
|
||
make -C oer-conformance/settle && oer-conformance/settle/settle_host out.json 400 # the C kernel
|
||
```
|
||
|
||
The vectors pin reason-set-equivalence one case at a time. Between them,
|
||
`fuzz_differential.py` mutates corpus documents and requires the reference and
|
||
`wai-rs` to report the same reasons, or the same refusal, for every one, and neither
|
||
to crash.
|
||
|
||
CI runs `wai_oer_vectors` — every vector file and every document in `unreadable/` —
|
||
with the other cargo-class corpora, rebuilds the C kernel, and checks its output
|
||
byte-for-byte against the pinned `firmware_vectors.json` and through
|
||
`wai_oer_vectors`.
|
||
|
||
## 9. Departures from draft 0.1
|
||
|
||
This extension settles the points the draft left open. Each changes what a
|
||
conforming producer writes or what a verifier accepts; none changes how a receipt
|
||
is chained or signed.
|
||
|
||
- **Capability namespace.** The draft's vectors named decisions `wai.settle.*`, a
|
||
namespace WAI reserves for payload formats. They are `oer.decision.*` here (§3),
|
||
and the corpus is regenerated under the new names.
|
||
- **One serialisation.** The draft computed `bundle.provenance_root` with ASCII
|
||
escaping and the body without it. Both use the body's serialisation here (§4, §6).
|
||
Every stage list in the corpus is ASCII, where the two agree, so this rule alone
|
||
moves no root — the corpus's roots differ from the draft's only because the rename
|
||
changes every stage's `receipt_hash`. The stage-list fixture holds the rule.
|
||
- **The number profile is normative.** Both producers already wrote only integers
|
||
and decimal strings; the draft did not require it. It is a **MUST** here (§4):
|
||
integers within I-JSON's range, decimal strings in JSON's number grammar, anything
|
||
else `malformed`, and a document outside §4's document profile refused whole. That
|
||
removes disagreements between JSON parsers the draft's vectors never exercised.
|
||
- **The checks are exact.** The draft compared figures in whatever precision its
|
||
reference happened to use; here every check is exact, with `ln 2` pinned as a
|
||
decimal (§6), so a verdict never depends on rounding.
|
||
- **One tolerance rule.** The draft's reference scaled the sum tolerance by the
|
||
sum's magnitude but the floor, bundle and ledger tolerances by the signed figure,
|
||
so a negative figure could fail a check it met. All four use the magnitude here
|
||
(§6).
|
||
- **Every shape has one reading.** The draft's reference crashed, or reported
|
||
something other than its reason, on members of the wrong type — a list where a
|
||
string belongs, an object where a list does — and let a repeated member name hide
|
||
a second value. §4 refuses repeated names, §7 names the reason for each wrong
|
||
type, §5 says a link is a string, and §3's member lists hold inside `chain` too.
|
||
- **Keys are decoded strictly** (§5). The draft's reference accepted a small-order
|
||
public key, and non-canonical encodings of one, under which a single signature
|
||
verifies over every message.
|
||
- **`oer` is a string** (§3), so the major version never depends on a float parse.
|
||
- **Abstention is its own outcome.** The draft's `escalate` receipts carry the answer
|
||
the escalation returned and store it as a record: they are verdicts, and §3 says
|
||
so. A decision that was not earned is `abstain`, which claims no answer and stores
|
||
no record, and `abstention_settled` holds it to that.
|