open-standards/wai/extensions/route-record.md
Transaction Science 658964c304 What a receipt declares of its own figure moves no verdict, and the corpus holds it
A figure is held to the offer's uncertainty, wider or narrower than the receipt's own. The texts
give that: a declared standard uncertainty bounds nothing (energy-measurement 2.1 and 2.3);
ModelBased, Estimator and a receipt of no class declare none, so a comparison has nothing to read
for them; and with the declaration in the rule, what a hop writes of its figure would buy a verdict
or cost one. A receipt graded below its offer is already held this way, its evidence read beside
the verdict.

The code read it so and no vector held the wider direction: every offer vector's counter declared
the offer's own 20,000 ppm, but for the largest uncertainty, which is narrower. A verifier taking
the wider of the two, or guarding a figure by a declaration past the offer's, passed the corpus.
Three vectors now hold both directions in both verifiers: a declaration of 500,000 ppm at the
figure is within, one of 1,000 ppm at the edge is within, and one of 500,000 ppm a microjoule past
the edge with no flag is refused. Every earlier vector is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 03:01:51 -05:00

1140 lines
79 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WAI Extension: Route Record
> Status: Draft. Registers the record `wai.route.record`: what a router considered for one
> media job and why it chose. A job names an operation of WAI's catalogue, a capability and
> a fallback ([SPEC](../SPEC.md) §4), the quantities it expects, hard limits that remove
> candidates and soft preferences that reorder the rest. A router reads its candidates from
> a **price book**, removes those a hard limit removes, ranks the survivors under a named
> and versioned **routing policy**, tries them in that order, and signs a **route record**:
> every candidate with the figures the price book stated for it and the limits that removed
> it, the order, the decision and why, and every attempt with its hop's OER/2 receipt and
> whether it was billed. A verifier given the record, the request, the price book and the
> policy recomputes the candidates, every removal, the ranking, the order and the decision,
> holds the attempts to the order and the billing rules, and, given the hops' receipts,
> holds each attempt to its receipt; it refuses a record those inputs do not give. No
> transport, no payment and no account: a route record is the recomputable account of a
> routing decision. The reference implementation is the `route_record` module of `wai-rs`
> (feature `route_record`); the corpus is
> [`live-conformance/routes/`](../live-conformance/routes/), and its second implementation
> is `live-conformance/route_record_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> This revision adds **version 2** of the price book and of the record (§3.4): an entry
> prices one operation of an endpoint, so an encode and a decode are estimated, capped and
> ranked at their own prices; it states the endpoint's measured failures with their basis, and
> the ranking reads the expected cost and the expected time to first byte per successful job
> they give (§4.2, §4.4), while the job pays the list price; and the attempt that served states
> its outputs, to which its hop's receipt is held (§6). Documents and records of version 1 are
> read and verified exactly as before.
>
> This revision adds **version 3** of the record (§3.4), on a price book of version 2: the
> attempt that served states what it delivered by its count and the root its hop's receipt
> names (§5.2, §6), not by a list, so a hop may deliver any number of objects and the record is
> the same size whatever it delivered; an object is shown to be one of them by its inclusion
> proof (§6.1). Records of versions 1 and 2 are read and verified exactly as before.
>
> This revision adds the **third-party hop record** (§6.2): what a router sent a third party for
> one hop, what came back and what the provider bills, signed by the router and bound to the
> hop's attempt and receipt. A verification document without one is read exactly as before.
>
> This revision adds the **delivery-decision record** (§6.3): why a delivery of a job's output
> took the route it took. A delivery is routed as a job is, from its own request, price book and
> policy (§3, §4); its record states every route considered with the figures its price book stated
> and the limits that removed it, the rule that chose and by which key each other route was passed
> over, and every input it read by digest. It is signed by the job's router, names the job's
> route record as its parent and the delivery decided before it, and holds what it delivers to
> what the job's served hop delivered. A verification document without one is read exactly as
> before.
>
> This revision adds **version 3 of the price book** and **version 2 of the delivery-decision
> record** (§3.4, §6.4): an entry states its energy figure's declared uncertainty; a decision
> states its **offer**, what its route was taken at; and a verifier given the route's OER/2 receipt
> holds it to the decision and to the offer: graded no higher than the offer, its figure within one
> declared uncertainty of the offer's, or flagged `price_exceeded` by the hop that signed it.
> Documents and records of earlier versions are read and verified exactly as before.
>
> Texts read for this revision: the WAI texts this one cites; RFC 8259; RFC 8785; RFC 8032;
> ISO 4217 (the alphabetic currency code); FIPS 180-4; the BLAKE3 specification.
## 1. Scope
In scope: the three documents a routing decision rests on, the request, the price book and
the policy (§3); how a router computes its candidates, their removals, their ranking and
its decision from them (§4); the record, its attempts and its signature (§5); how an
attempt is held to its hop's OER/2 receipt, and in versions 2 and 3 to the outputs it
delivered (§6); an output's inclusion proof (§6.1); a third party's hop (§6.2); a delivery's
decision, routed as a job is and chained to its job's record (§6.3); a delivery's offer and its
route's receipt (§6.4); verification (§7); and the reference implementation and corpus (§10).
Out of scope: a transport; how a router learns its prices and measures its endpoints'
failures, which the price book states and this extension takes as stated; accounts, credit
and payment; the contents of the job's output and its own receipts, which their extensions
define (a record of version 2 names the output by its content hash and nothing more, and one
of version 3 by its count and root); and
the energy of a hop, which its OER/2 receipt states ([oer-receipts](oer-receipts.md)). An
invoice that bills a job from its route record is the receipt ledger's
([receipt-ledger](receipt-ledger.md) §5).
Relationship to the core spec: `wai.route.record` is a **record** ([SPEC](../SPEC.md) §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
## 2. Terms and encodings
- **Router**: an Ed25519 key (RFC 8032) that signs route records. **Signer**: the record's
`signer`.
- **Time**: microseconds since 1970-01-01T00:00:00Z, an integer.
- **Amount**: an integer of millionths of a currency's minor unit (ISO 4217), as the
receipt ledger's amounts are (receipt-ledger §2). A **price** is an amount per `divisor`
units of a unit.
- **Document**: a JSON text (RFC 8259) read strictly: no member name repeated within an
object, no string holding an unpaired surrogate, arrays and objects nested at most 64
deep (the document counting as one), and every number an integer from 0 to 2⁵³ − 1
written in plain decimal (no sign, fraction or exponent). A text that is not such a
document is `malformed`. Every value of a document is therefore in the subset RFC 8785
canonicalises without a choice (the subset `wai::jcs` writes), and its **canonical form**
is RFC 8785's.
- **Digest**: SHA-256 (FIPS 180-4) over a document's canonical form, written as 64
lowercase hexadecimal digits.
- **Token**: a string of 1 to 128 bytes, each an ASCII letter or digit or one of `.`, `_`,
`-`, `:` and `/`. Endpoints, providers, regions, residencies and a policy's name are
tokens. Tokens are ordered by their bytes.
- **Unit**: a string of 1 to 64 bytes, a lowercase ASCII letter and then lowercase letters,
digits and `_` (`subpixels`, `seconds`, `packages`): the name of what a quantity counts.
- **Capability**: a string of 1 to 128 bytes of lowercase ASCII letters, digits, `_` and
`.`, beginning `wai.`: a capability of WAI's registry (SPEC §5), which a router **SHOULD**
name only when it is registered.
- **Operation**: the id of an operation of WAI's catalogue (`wai/catalogue/ops.json`): two
words joined by `.`, each a lowercase ASCII letter and then lowercase letters and digits
(`media.encode`).
- **Key** and **signature**: 64 and 128 lowercase hexadecimal digits.
- **Content hash**: an object's content hash as [jwp-receipts](jwp-receipts.md) §2.1 computes
it, BLAKE3-256 over the domain `moq-jwp:object\x01` and the object's bytes (a WAI
envelope's whole bytes, for an envelope), written as 64 lowercase hexadecimal digits.
- **Basis**: the hops a measured figure was read from and the period they were sent in
(§3.2.1).
- **Class**: an acquisition class of [energy-measurement](energy-measurement.md) §2, by its
label: `CalibratedInstrument`, `OnChipCounter`, `ModelBased` or `Estimator`.
- **Grade**: an evidential grade of [oer-receipts](oer-receipts.md) §2, by its label: `projected`,
`simulated`, `derived` or `measured`, weakest first.
- ⌊x⌋ is x rounded down; products are computed in integers wide enough not to overflow.
## 3. The documents
Each document is an object of exactly the members its table lists, each of its form; a
document that breaks a rule of this section is refused with its own reason
(`request_invalid`, `pricebook_invalid`, `policy_invalid`).
### 3.1 The request
What the job asked for, as the router received it.
| member | form |
|---|---|
| `kind` | `"wai-route-request"` |
| `job` | a token: the job's id |
| `operation` | an operation |
| `capability` | a capability |
| `fallback` | a capability other than `capability`, or `null` |
| `quantities` | 1 to 16 objects of exactly `unit` (a unit) and `quantity` (an integer at least 1), in strictly ascending order of `unit`: what the job is expected to need, unit by unit, before anything ran |
| `limits` | an object of exactly the members below |
| `prefer` | an object of exactly `sort` (`null` or one of `price`, `ttfb`, `energy`, `quality`), `ttfb_ms` (`null` or an integer: a time to first byte, in milliseconds) and `quality` (`null` or an integer 0 to 1000: a quality floor) |
| `deadline_us` | `null` or an integer at least 1: the time after acceptance by which the job's output must be served |
`limits`, the hard limits:
| member | form |
|---|---|
| `regions` | `null`, or 1 to 64 tokens in strictly ascending order: the regions an endpoint may run in |
| `residency` | `null`, or 1 to 64 tokens in strictly ascending order: the places the job's data may rest |
| `no_training` | a boolean: no endpoint that trains on its inputs |
| `zero_retention` | a boolean: no endpoint that keeps the job's data |
| `ga_only` | a boolean: only endpoints generally available, not previews |
| `consent_on_file` | a boolean: whether the job's owner holds consent for what an endpoint that needs it does (a cloned voice) |
| `max_price` | 0 to 16 objects of exactly `unit`, `price` and `divisor` (an integer at least 1), in strictly ascending order of `unit`: the most the job pays per `divisor` units of the unit |
| `spend_ceiling` | `null`, or an object of exactly `currency` (three capitals `A`–`Z`) and `amount`: the most the job may cost |
### 3.2 The price book
What each endpoint offers and costs, as the router read it. A price book is of version 1, 2 or
3 (§3.4); one of version 1 has no `version` member and its entries neither `operation` nor
`failures`, and only the entries of version 3 state `energy_ppm`.
| member | form |
|---|---|
| `kind` | `"wai-route-pricebook"` |
| `version` | `2` or `3`: its version; a price book of version 1 has no such member |
| `currency` | three capitals `A`–`Z` |
| `issued` | a time: when the prices were stated |
| `entries` | 1 to 1024 objects of exactly the members below, in strictly ascending order of `endpoint` in version 1, and of `endpoint` and then `operation` in versions 2 and 3 |
An entry:
| member | form |
|---|---|
| `endpoint` | a token: the endpoint's id |
| `operation` | version 2: an operation: the one this entry prices |
| `provider` | a token: who runs it |
| `capability` | a capability: what it produces |
| `region`, `residency` | a token, or `null` when not known |
| `trains_on_inputs` | a boolean, or `null` when not known |
| `retention_days` | an integer, or `null` when not known: how long it keeps a job's data (0: not at all) |
| `ga` | a boolean, or `null` when not known: generally available |
| `needs_consent` | a boolean, or `null` when not known: whether what it does needs the consent of the person it imitates |
| `unit` | a unit: what it is priced by |
| `divisor` | an integer at least 1 |
| `price` | an integer: its price per `divisor` units |
| `ttfb_ms` | an integer, or `null` when not known: its time to first byte, in milliseconds |
| `energy_uj` | an integer, or `null` when not stated: the microjoules it states per `divisor` units |
| `energy_class` | the class of that figure, or `null` exactly when `energy_uj` is `null` |
| `energy_ppm` | version 3: the figure's declared uncertainty, in parts per million of it ([energy-measurement](energy-measurement.md) §2.3): an integer from 1 to 2³² − 1 when `energy_class` is `CalibratedInstrument` or `OnChipCounter`, and `null` otherwise |
| `quality` | an integer 0 to 1000, or `null` when not known |
| `failures` | version 2: the failures measured at this endpoint and operation (§3.2.1), or `null` when none were measured |
A price book states each figure as its endpoint's provider gave it, and its failures as the
router measured them; a value that is not known is `null`, and §4.3 reads every `null` limit
as the most restrictive value.
In version 2 an entry prices one operation of one endpoint, and an endpoint has an entry for
each operation it serves, each with its own price, figures and failures. Within one
capability an encode (a motion search, then a codestream written) and a decode (a
reconstruction) spend different energy and are priced differently: one entry for both would
misstate one of them to the estimates (§4.2), to the unit-price limit (§4.3) and to the
ranking (§4.4).
#### 3.2.1 Failures (version 2)
`failures` states how often the endpoint's hops of the entry's operation failed, what the
provider billed for those failures, and how long they took, with the basis they were read
from. It is an object of exactly:
| member | form |
|---|---|
| `hops` | an integer at least 1: the hops of the entry's operation the router sent the endpoint in the period |
| `from`, `to` | times, `from` at most `to` and `to` at most the price book's `issued`: the period those hops were sent in |
| `failed` | an integer at most `hops`: the hops among them that failed (§5.2) |
| `billed_price` | an integer: what the provider billed the failed hops, per `divisor` units of the entry's `unit`: the sum it billed them, times `divisor`, over the sum of their quantities in that unit, rounded down; `0` when it billed none of them |
| `failed_ms` | an integer: the time a failed hop took from being sent to its end, when the next survivor could run, on average over the failed hops, in milliseconds, rounded down |
`hops`, `from` and `to` are the **basis** of the three figures: the failure rate is `failed`
over `hops`, and `billed_price` and `failed_ms` are read from the failed hops of the basis.
When `failed` is 0 there is no failed hop to read them from, and both are `0`: nothing was
billed and no time was spent. A `failures` object that breaks a rule of this table is
`pricebook_invalid`.
**A thin basis.** A basis of fewer than **100 hops** is too thin to mean anything. A failure
rate read from *n* hops has a 95% confidence interval of half-width up to 1.96 × ½ / √*n* (its
widest, at a rate of one half): ±0.098 at 100 hops, and wider than ±0.1 below 97. A rate
known no better than that cannot weigh one endpoint against another, and the rule rounds the
bound to 100. Failures of a thin basis, and `null` failures, are **unknown** (§4.2).
### 3.3 The policy
How survivors are ranked and how many are tried.
| member | form |
|---|---|
| `kind` | `"wai-route-policy"` |
| `name` | a token |
| `version` | an integer at least 1 |
| `keys` | the four ranking keys `price`, `ttfb`, `energy` and `quality`, each once, in the policy's order |
| `max_attempts` | an integer 1 to 16: the most survivors tried |
| `exclude` | 0 to 64 provider tokens in strictly ascending order: providers the policy never routes to |
A policy is named and versioned so that a deployment pins it: the record states its name,
version and digest, and a policy whose rules change takes a new version.
### 3.4 Versions
The request and the policy have one form. The price book has three: version 1, this
extension's first; version 2, which adds the members that price an operation and state an
endpoint's failures; and version 3, version 2's with `energy_ppm` in each entry, so that a
delivery's route states the uncertainty its energy is offered at (§6.4). What this text says of
a price book of version 2, its entries and its candidates, it says of one of version 3. The record
has three: version 1; version 2, which adds those members and names a hop's outputs by their
list; and version 3, version 2's but for naming them by their count and root (§5.2). A record of
version 1 rests on a price book of version 1, and one of version 2 or 3 on a price book of
version 2 (§7.2 step 5); no record rests on a price book of version 3. A delivery-decision record
of version 1 rests on a price book of version 2, and one of version 2 on a price book of version 3
(§7.2 step 10). A router writes one version of record over a price book of version 2; a reader
holds each as its version says.
A reader of version 1 reads every document of version 1 as before, and refuses one of
version 2 rather than misreading it: a price book with a `version` member, or an entry with
`operation` or `failures`, is not of version 1's members (`pricebook_invalid`), and a record
of version 2 is `unknown_version`; a reader of version 2 refuses a record of version 3 so. A
price book whose `version` is other than 2 and 3 is `pricebook_invalid`; a record whose `version`
is other than 1, 2 and 3 is `unknown_version`, and so is a delivery-decision record whose `version`
is other than 1 and 2.
Version 3 exists because a list bounds what a hop may deliver and grows with it: version 2
states at most 1024 outputs, and a package of a long title holds an object per piece and rung,
thousands of them. The count and the root are an integer and 32 bytes whatever was delivered,
and the root is the commitment the hop's receipt already names (§6), so nothing a hop signs
changes.
## 4. Routing
A router computes the following from the request, the price book and the policy, and a
verifier computes the same. Nothing in it depends on the order of a document's members, on
the time, or on anything the three documents do not state.
### 4.1 Candidates
The **candidates** are the price book's entries whose `capability` is the request's
`capability`, which are **direct** (`via_fallback` false), and, when the request names a
`fallback`, those whose `capability` is the fallback (`via_fallback` true), in the price
book's order. An entry of any other capability is not a candidate, and the record does not
list it. In a price book of version 2 a candidate's entry is also of the request's
`operation`: an entry of another operation is not a candidate either, and since an endpoint
has at most one entry of an operation, an endpoint is at most one candidate.
### 4.2 Estimates
For a candidate whose `unit` the request states a quantity `q` for:
- its **cost** is ⌊`q` × `price` / `divisor`⌋;
- its **energy** is ⌊`q` × `energy_uj` / `divisor`⌋ when `energy_uj` is stated.
An estimate is `null` when the request states no quantity in the candidate's unit, when
`energy_uj` is `null` (for the energy), and when it exceeds 2⁵³ − 1, which no document can
state. Units are never converted: a candidate priced in `seconds` is estimated from the
request's `seconds` and from nothing else. An estimate is the router's, from the request's
own quantities; what a job costs is what its invoice bills from its receipts.
In a price book of version 2 a candidate also has two **expected** figures, per successful
job, which count its failures. An attempt at the endpoint fails with probability
*f* = `failed` / `hops` and serves with *s* = 1 − *f*, attempts taken as independent; a job
sent there until it is served meets *f* / *s* = `failed` / (`hops` − `failed`) failures on
average before it is (a geometric count). Each failure costs the router what the provider
bills for it, and costs the job the time it took before the next survivor ran. So, per
successful job:
- its **expected cost** is `cost` + ⌊*F* × `failed` / (`hops` − `failed`)⌋, where
*F* = ⌊`q` × `billed_price` / `divisor`⌋ is what the provider bills one failed hop of this
job;
- its **expected time to first byte** is `ttfb_ms` + ⌊`failed_ms` × `failed` / (`hops` −
`failed`)⌋, in milliseconds.
An expected figure is `null` when the candidate's failures are unknown (§3.2.1), when the
figure it adds to (`cost`, `ttfb_ms`) is `null`, when every hop of the basis failed (there is
no finite expectation), and when *F* or the figure exceeds 2⁵³ − 1, as an estimate is.
**Unknown failures** are not read as no failures, which would rank an endpoint nobody
measured ahead of one measured to fail rarely, nor as a worst case, which this text states
nowhere: the expected figures are `null`, and the rules this text gives every `null` figure
apply to them unchanged. §4.4 ranks a `null` after every stated figure and then, within the
same key, by the list figure, as version 1 ranks every candidate; and since no limit reads
failures, unknown failures remove nothing.
**The job pays the list price.** The expected cost is the router's figure for ranking, of
what failures cost it; the job is never charged it. The limits of §4.3 read the list `price`
and the `cost`, what the job pays, and an invoice bills a job from its billed attempt alone
(§5.2, receipt-ledger §5), so no failure reaches the job's line.
### 4.3 Hard limits
Each candidate is held to every limit in this order, and its **removals** list each limit
that holds, in this order, each with the candidate's value the limit read (`null` when the
price book does not know it):
| limit | removes a candidate when | value |
|---|---|---|
| `policy` | its `provider` is in the policy's `exclude` | `provider` |
| `unit` | the request states no quantity in its `unit` | `unit` |
| `region` | the request's `regions` is not `null`, and its `region` is `null` or not in it | `region` |
| `residency` | the request's `residency` is not `null`, and its `residency` is `null` or not in it | `residency` |
| `training` | `no_training` is true and its `trains_on_inputs` is not `false` | `trains_on_inputs` |
| `retention` | `zero_retention` is true and its `retention_days` is not 0 | `retention_days` |
| `ga` | `ga_only` is true and its `ga` is not `true` | `ga` |
| `consent` | `consent_on_file` is false and its `needs_consent` is not `false` | `needs_consent` |
| `unit_price` | `max_price` has an entry for its `unit`, and `price` × that entry's `divisor` exceeds that entry's `price` × `divisor` | `price` |
| `spend` | `spend_ceiling` is not `null`, and its `currency` is not the price book's, or the candidate's cost is `null` or exceeds its `amount` | the cost |
So a value the price book does not know counts as the most restrictive one: an endpoint that
does not say it never trains on its inputs is removed by `no_training`, one with no stated
retention by `zero_retention`, and a cost that cannot be estimated, or a ceiling in another
currency, by `spend`. A candidate with no removal is a **survivor**. In version 2 the limits
read the candidate's own entry, its operation's, and `unit_price` and `spend` read the list
`price` and the `cost`, never the expected cost (§4.2).
### 4.4 Ranking
The survivors are ordered by a list of keys, the **key list**:
1. `targets`, when the request's `prefer.ttfb_ms` or `prefer.quality` is not `null`: a
survivor **meets** the targets when, for each target stated, its `ttfb_ms` is stated and
at most `prefer.ttfb_ms`, and its `quality` is stated and at least `prefer.quality`;
2. the request's `prefer.sort`, when it is not `null`;
3. the policy's `keys`, in its order, without `prefer.sort`;
4. `endpoint`.
Two survivors compare by the first key of the list on which they differ, and the one that
comes first under that key ranks first:
| key | first | in version 2 |
|---|---|---|
| `targets` | the one that meets the targets | the same, the time to first byte read being the expected one |
| `price` | the smaller cost | the smaller expected cost, then the smaller cost |
| `ttfb` | the smaller `ttfb_ms` | the smaller expected time to first byte, then the smaller `ttfb_ms` |
| `energy` | the smaller energy | the same |
| `quality` | the larger `quality` | the same |
| `endpoint` | the smaller `endpoint`, by its bytes | the same |
A `null` figure comes after every stated one, and two `null`s do not differ; a key of two
figures compares the second only when the first does not differ. Endpoints are distinct, so
the order is total. The **order** is the survivors' endpoints in it, and each survivor's
**rank** is its position there, from 1. The energy compared is each figure as its provider
states it, whatever its class: the record states every class beside its figure, so a reader
sees what was compared.
**Why per successful job.** A router tries survivors in turn until one serves. For one
quantity, cost to the router or time to the job, let an attempt at survivor *i* spend *Cᵢ* on
average (*sᵢ* × its list figure + *fᵢ* × its failure's) and serve with probability *sᵢ*. The
attempts of an order spend *C*₁ + *f*₁*C*₂ + *f*₁*f*₂*C*₃ + … on average, each attempt
weighted by the chance that every one before it failed. Exchanging two neighbours *i*, *j*
changes that sum by a term whose sign is that of *Cᵢ* / *sᵢ* − *Cⱼ* / *sⱼ*, so the order that
minimises it ranks by *C* / *s* = the list figure + (*f* / *s*) × the failure's: the expected
figure per successful job (§4.2). An endpoint that fails often is tried first only where it is
cheaper, or faster, per job served. The policy is unchanged: it names its keys, and a price
book of version 2 gives `price` and `ttfb` these figures.
### 4.5 The decision
`decided_by` is `none` when there is no survivor, `only_survivor` when there is one, and
otherwise the first key of the key list on which the first two survivors of the order
differ: the reason the first was tried before the second. `runner_up` is the second
survivor's endpoint, or `null` when there are fewer than two.
The number of survivors the router may try is the smaller of the survivors' number and the
policy's `max_attempts`, the **allowed** attempts.
## 5. The record
### 5.1 Members
A route record is a document of exactly these members:
| member | form |
|---|---|
| `kind` | `"wai-route-record"` |
| `version` | `1` or `2`: its price book's version (§3.4) |
| `job`, `operation`, `capability`, `fallback`, `quantities` | the request's |
| `request` | the request's digest |
| `policy` | an object of exactly `name`, `version` and `digest`: the policy's |
| `pricebook` | an object of exactly `digest`, `currency` and `issued`: the price book's |
| `candidates` | the candidates (§4.1), in their order, each an object of exactly the members below |
| `order` | the order (§4.4): the survivors' endpoints |
| `decision` | an object of exactly `decided_by` and `runner_up` (§4.5) |
| `accepted` | a time: when the router accepted the job |
| `deadline` | `null` when the request's `deadline_us` is `null`; otherwise `accepted` + `deadline_us` |
| `attempts` | 0 to 16 objects of exactly the members of §5.2, in the order they were made |
| `signer` | the router's key |
| `sig` | §5.3 |
A candidate:
| member | form |
|---|---|
| `endpoint`, `provider`, `capability`, `region`, `residency`, `trains_on_inputs`, `retention_days`, `ga`, `needs_consent`, `unit`, `divisor`, `price`, `ttfb_ms`, `energy_uj`, `energy_class`, `quality` | the price book's entry's, as it stated them |
| `operation`, `failures` | version 2: the entry's, as it stated them |
| `via_fallback` | a boolean (§4.1) |
| `cost`, `energy` | its estimates (§4.2) |
| `expected_cost`, `expected_ttfb_ms` | version 2: its expected cost and expected time to first byte per successful job (§4.2), each an integer or `null` |
| `removed` | its removals (§4.3): objects of exactly `limit` and `value`, in §4.3's order; empty for a survivor |
| `rank` | its rank (§4.4), or `null` for a candidate a limit removed |
So a record of version 2 states, for every candidate, the failures it was ranked by with
their basis, and the terms the ranking derived from them.
### 5.2 Attempts
An attempt is one hop the router sent the job to:
| member | form |
|---|---|
| `hop` | its position among the attempts, from 1 |
| `endpoint` | the endpoint it was sent to |
| `outcome` | `served` (the hop returned the job's output) or `failed` |
| `error` | `null` when served; when failed, one of `timeout`, `server_error`, `rate_limited`, `refused` and `unavailable` |
| `start`, `end` | times: when it was sent and when it ended |
| `billed` | a boolean: whether the job is billed for it |
| `not_billed` | `null` when billed; otherwise `failed` or `deadline_missed` |
| `receipt` | the `receipt_id` of the hop's OER/2 receipt (oer-receipts §5): `sha256:` or `blake3:` and 64 lowercase hexadecimal digits |
| `outputs` | version 2: the content hashes (§2) of the objects the hop delivered, in the order it delivered them: when served, 1 to 1024, no two the same; when failed, none (an empty list) |
| `delivered` | version 3: an object of exactly `count`, an integer, and `root`, 64 lowercase hexadecimal digits: when served, the number of objects the hop delivered, at least 1, no two the same, and their commitment in the order it delivered them (§6); when failed, `0` and 64 zeros |
The attempts **MUST** follow the order: the attempt at position *i* names the *i*-th survivor
of the order, and there are at most the allowed attempts (§4.5). So no attempt names a
removed candidate, and no survivor is tried before one ranked above it. Only the last attempt
may be `served`; a job whose last attempt failed was tried on every allowed survivor before
the router gave up. The times are in order: `accepted` is at most the first attempt's
`start`, each attempt's `start` is at most its `end`, and each attempt starts no earlier than
the one before it ended.
Billing:
- a failed attempt is not billed, `not_billed` `failed`: a hop that returned no output costs
the job nothing, whatever the provider charged the router;
- a served attempt that ended after the `deadline` is not billed, `not_billed`
`deadline_missed`: a job that missed the deadline it declared is not billed;
- every other served attempt is billed.
So at most one attempt of a record is billed, and a reader finds the hop that served, whether
it was billed and why not, and the receipt of every hop, failed ones too.
### 5.3 Signature and digest
The record's `sig` is Ed25519 by `signer` over the 17 bytes `wai:route-record` (ASCII) and
0x01, followed by the canonical form of the record without `sig`. It is verified strictly, as
[SPEC](../SPEC.md) §7 *Signatures* requires: a key that fails step 1 or 2 is `bad_key`, and a
signature that fails a later step is `bad_signature`. The record's **digest**, which names it
(receipt-ledger §5.2), is the digest of the whole record, `sig` included.
## 6. Hop receipts
Every hop is receipted under [oer-receipts](oer-receipts.md): a `job` receipt whose
`capability` is `oer.job.` followed by the record's `operation` (oer-receipts §3), whose
`work.status` is `1` for a hop that served and `-1` for one that failed, and whose figure is
the hop's energy, or the unmetered marker where nobody measured it (oer-receipts §3.1). When
a verifier is given the receipts and the keys they are published under (oer-receipts §5), it
holds each attempt to its receipt:
- a receipt whose `receipt_id` is the attempt's `receipt` is given: otherwise
`receipt_missing`;
- it verifies under oer-receipts on its own, with no reason (§7 there): otherwise
`receipt_invalid`;
- it is a `job` receipt of the record's operation whose `work.status` is the attempt's
outcome's: otherwise `receipt_mismatch`;
- in a record of version 2, the receipt of the attempt that served names the outputs the
attempt states: its `subject.output` is their commitment, as oer-receipts §3 *Output*
defines it, the content hash of the one output, or the jwp-receipts §2.2 root over their
content hashes in their order, every leaf of the unlabelled form, as 64 lowercase
hexadecimal digits: otherwise `receipt_output_mismatch`;
- in a record of version 3, its `subject.output` is the attempt's `root`: otherwise
`receipt_output_mismatch`.
So a receipt cannot be moved to vouch for an output it did not produce. The hop signs what it
produced, in its receipt; the router signs what the job delivered and which receipt is the
hop's, in its record; and a record that pairs a hop's receipt with outputs the receipt does
not name is refused. A holder of the job's output checks the remaining step, that its
objects' content hashes are the record's outputs (§7.3), or in version 3 that their count is
the record's and their commitment its root: the receipt names the root alone, so the count is
the router's statement, which the objects check. The attempt that served is held to
its outputs whether or not it is billed: one that served past the deadline delivered them
too. A failed attempt delivered nothing, and its receipt is not held to an output.
### 6.1 An output's inclusion proof (version 3)
A record of version 3 does not list what the hop delivered. A reader that must show one
object to be among them, as a receipt ledger shows a billed output to be (receipt-ledger
§5.1), gives that object's **inclusion proof**: an object of exactly `index`, an integer, the
object's place in the order the hop delivered, from 0, and `siblings`, a list of content
hashes' texts (§2). The proof holds the object whose content hash is *h* to an attempt that
delivered `count` objects under `root` when:
- `index` is below `count`, and `siblings` holds exactly ⌈log₂ `count`⌉ hashes, none for
one object;
- for one object, *h* is the root (oer-receipts §3 *Output*); otherwise, from *h*'s leaf
(jwp-receipts §2.2, the unlabelled form), each sibling in turn combined with the value so far
by jwp-receipts §2.2's node, the sibling on the left where bit *i* of `index` is 1 and on the
right where it is 0, at level *i* from 0, ends at `root`.
A router makes it from the tree §6 commits to: the sibling at each level, the last node of an
odd level paired with itself. The count and the bits fix the place, as jwp-receipts §3 step 5
fixes an object's, so a node paired with itself is proved at its own index only, and two
proofs at one index are of one object. The leaf and node domains alone keep a walk of another
length from ending at the root; the number of siblings is checked all the same, as jwp-receipts
checks it, so such a proof is refused before anything is hashed. This extension's verification (§7) is given no object
and checks no proof; the reader that is given them checks it, with its own refusal.
A receipt states the hop's energy with its grade, and, in minor version 1, its class or the
unmetered marker; the record states no figure of its own. A reader of the hops' energy reads
it from their receipts, and an invoice bills a job from them (receipt-ledger §5).
### 6.2 A third party's hop
A hop a router sends to a third party's endpoint is work it did not do: the provider did it,
behind an API the router called. Its receipt (§6) is the router's, and says the hop served or
failed; what the router sent, to whom, what came back, when, and what the provider bills, it
states in a **third-party hop record**, a document (§2) of exactly these members:
| member | value |
|---|---|
| `kind` | `"wai-route-third-party-hop"` |
| `version` | `1` |
| `job` | the job's id, as the route record states it |
| `hop` | an integer at least 1: the attempt's `hop` |
| `endpoint` | a token: the attempt's `endpoint` |
| `provider` | a token: the `provider` of that endpoint's candidate |
| `receipt` | the attempt's `receipt`, its hop's OER/2 `receipt_id`, in the form §5.2 gives it |
| `origin` | the provider's API: `https://`, then a host of 1 to 253 lowercase letters, digits, `.` and `-`, then optionally `:` and a port, and nothing else |
| `account` | `"operator"`, the hop ran on the router's operator's own account with the provider, or `"customer"`, on the account of the job's owner |
| `provider_job` | a string of 1 to 256 bytes, the provider's id for the work, or `null` when it gave none |
| `outcome` | `"served"` or `"failed"` |
| `error` | `null` when served; when failed, an object of exactly `class`, an error of §5.2, and `code`, a string of 1 to 256 bytes: the provider's own code or status, as it gave it |
| `exchanges` | 1 to 1024 objects of exactly the members below, in the order they were sent |
| `outputs` | when served, 1 to 1024 lists of exactly two strings, a name of 1 to 255 bytes holding no `/`, and the object's content hash, in the order the hop delivered them, no name and no content hash twice; when failed, `[]` |
| `usage` | what the provider bills for the hop: an object of exactly `unit`, a unit, `quantity`, an integer, and `source`, a string of 1 to 512 bytes saying where the quantity was read; when served, such an object; when failed, `null`, or such an object when the provider bills the failure |
| `charge` | what the provider itself reported charging, or `null` when it reported nothing: an object of exactly `amount`, a string of 1 to 64 bytes, the figure as the provider wrote it, `unit`, a string of 1 to 64 bytes, its currency or unit in its own terms, and `source`, a string of 1 to 512 bytes |
| `start`, `end` | times: when the first exchange was sent, and when the last ended, `start` at most `end` |
| `signer`, `sig` | the router's key and its signature |
An exchange is one HTTP request and what came back:
| member | value |
|---|---|
| `method` | 1 to 16 uppercase ASCII letters |
| `url` | the URL requested, `https://` or `http://` and then 1 to 2,040 bytes holding no `?` and no `#`: its query and its fragment removed |
| `start`, `end` | times: when it was sent, and when its response ended or it failed, `start` at most `end`, both from the record's `start` to its `end`, and `start` at least the previous exchange's |
| `sent` | an object of exactly `sha256`, 64 lowercase hexadecimal digits, the SHA-256 of the request's body, and `bytes`, its length |
| `status` | the response's HTTP status, an integer 100 to 599, or `0` when no response came |
| `received` | the same of the response's body; of no bytes, when no response came |
| `json` | the response's body when it is JSON, read as a document after each number that is not an integer 0 to 2⁵³ − 1 is written as its decimal text and each string that begins `https://` or `http://` is cut at its first `?` or `#`; otherwise `null` |
A record that is not of these members, each of its form, is `third_party_invalid`, and so is
one whose `json` holds a string that begins `https://` or `http://` and holds a `?` or a `#`.
**No secret is in it.** The key the hop ran on is never stated, only whose account it is; an
exchange states no header, since a key travels in one; and every URL is stated without its
query, since a provider's signed download link is a bearer token, its query the key to the
object. What was sent and received is named by digest and length, which a holder of the bytes
checks, and the response is kept as JSON where the provider answered with JSON, so that what it
said about the job (its id, its status, the usage it billed) can be read from the record itself.
The record is signed by the router, over the domain `wai:route-third-party-hop\x01` and the
canonical form of the record without `sig`, under the strict rule of SPEC §7, and named by its
digest (§2), over its canonical form with `sig`. It is bound to its hop: given the route record
and the hop receipts (§7.2 step 9), a record is held to
- the route record's router: its `signer` is the record's, and its `job` the record's;
- its attempt: `hop` names one, whose `endpoint`, `receipt` and `outcome` are the record's
and, when it failed, whose `error` is the record's `error.class`; whose candidate's
`provider` is its `provider`, and whose candidate's `unit` is its `usage.unit` when it states
a usage; and whose `start` is at most the record's `start` and whose `end` at least its
`end`. No two records are of one hop. Otherwise `third_party_mismatch`;
- what the hop delivered: when it served, the commitment of its outputs' content hashes in
their order (oer-receipts §3 *Output*) is its receipt's `subject.output`, to which §6 holds
the attempt's `outputs` or `root`, and in a route record of version 3 their number is the
attempt's `count`, which the receipt does not name. Otherwise
`third_party_output_mismatch`.
So the provider's work is tied to the hop that paid for it: the receipt says the hop served
these objects, the record says which calls produced them and what the provider bills, and the
route record names both. The `charge` is the provider's own figure, in its own terms, beside the
usage and never added to it; a reader who compares what the provider says it charged with what
a job was billed reads both from signed documents. A tariff of version 6 bills a billed hop by the
record's usage (receipt-ledger §4.9). The record states no energy figure: the
hop's receipt states it, or the unmetered marker where the provider gave none
(oer-receipts §3.1).
### 6.3 A delivery's decision
A job's output is then delivered: sent to who asked for it, by one of the routes that could carry
it, an origin, a cache or a bearer among them. A router that routes a delivery reads the
delivery's own documents, of §3's forms: its **request**, whose `job` is the delivery's id and
whose `quantities` are what the delivery carries, unit by unit (`bytes`, for one); its **price
book**, of version 2, whose entries are the routes, each priced for the operation it performs;
and its **policy**. From them §4 computes the delivery's candidates, every limit that removes one,
the ranking, the order and the decision, as it computes a job's. The router states why the
delivery took the route it took in a **delivery-decision record**, a document (§2) of exactly these
members:
| member | value |
|---|---|
| `kind` | `"wai-route-delivery-decision"` |
| `version` | `1` or `2` (§3.4) |
| `job` | the job's id, as its route record states it |
| `parent` | the job's route record's digest (§5.3) |
| `seq` | an integer at least 1: its place among the job's delivery decisions |
| `prev` | `null`, or 64 lowercase hexadecimal digits: the digest of the job's decision of `seq` − 1, and `null` for its first (*its chain*, below) |
| `objects` | an object of exactly `count`, an integer at least 1, and `root`, 64 lowercase hexadecimal digits: what the delivery carries, the objects the job's served hop delivered, by their count and their commitment (§6) |
| `delivery` | a token: the delivery's request's `job`, the delivery's id |
| `operation`, `capability`, `fallback`, `quantities` | the delivery's request's, of the forms §3.1 gives them |
| `request` | 64 lowercase hexadecimal digits: the delivery's request's digest |
| `policy` | an object of exactly `name`, `version` and `digest`: the delivery's policy's, of the forms §5.1 gives them |
| `pricebook` | an object of exactly `digest`, `currency` and `issued`: the delivery's price book's, of the forms §5.1 gives them |
| `candidates` | the delivery's candidates (§4.1), in their order, 0 to 1024 objects each of the members and forms of a candidate of a record of version 2 (§5.1) and, in a decision of version 2, `energy_ppm`, of the form §3.2 gives it |
| `order` | the order (§4.4): 0 to 1024 tokens, the surviving routes' endpoints |
| `decision` | an object of exactly `decided_by` and `runner_up`, of the forms §5.1 gives them (§4.5) |
| `route` | a token, the route the delivery took: the first endpoint of the order; `null` when no route survives |
| `passed` | 0 to 1023 objects of exactly `endpoint`, a token, and `key`, a key of the key list (§4.4): for each survivor after the first, in the order, the first key on which it and the first differ, by which it was passed over |
| `offer` | version 2: what the route was taken at (§6.4), or `null` when no route survives |
| `decided` | a time: when the router decided |
| `signer`, `sig` | the router's key and its signature |
A record that is not of these members, each of its form, is `delivery_invalid`; the relations
between its members, and between it and its documents, its parent and the decisions before it, are
those below.
**Why, in full.** The record restates each route as its price book stated it, with its estimates,
its expected figures and every limit that removed it; it names the rule that chose, the policy by
its name, version and digest, and for every survivor the key that ranked the route the delivery
took above it, the runner-up's being the decision's `decided_by`; and it names by digest every
input the rule read: the delivery's three documents and, by its parent, the job and the objects it
delivers. A verifier recomputes all of it (§7.2 step 10), so a router cannot state a route the
rule did not choose, nor a reason it did not have.
**Signed and chained.** The record is signed by the job's router, over the domain
`wai:route-delivery-decision\x01` and the canonical form of the record without `sig`, under the
strict rule of SPEC §7, and named by its digest (§2), over its canonical form with `sig`. It is
chained to its job: `parent` names the job's route record, and each decision after the first
names the one before it by `prev`, so a job's decisions are one sequence from its record, in which
a decision left out before a later one, moved or given out of order is refused. The sequence proves
nothing of decisions after its last: a verifier is not told how many deliveries were decided, and a
list that ends early verifies.
Given the job's route record and its decisions, from the first and in order (§7.2 step 10), a
decision is held to:
- **its documents**, as step 5 holds a record to its job's: its `delivery`, `operation`,
`capability`, `fallback`, `quantities` and `request` are the delivery's request's `job`,
`operation`, `capability`, `fallback`, `quantities` and digest (`request_mismatch`); its `policy`
the delivery's policy's (`policy_mismatch`); and its `pricebook` the delivery's price book's
digest, currency and `issued`, the book of the version its own rests on (§3.4:
`pricebook_mismatch`);
- **§4 over them**, as step 6 holds a record's candidates, order and decision (`candidate_mismatch`,
`removal_mismatch`, `rank_mismatch`, `decision_mismatch`); and its `route` the first of the order,
or `null` for none, and its `passed` each later survivor's endpoint with its key, in the order
(`delivery_route_mismatch`);
- **its parent**: its `signer` and `job` are the route record's, its `parent` the route record's
digest, and its `objects` what the route record's served attempt delivered: in a record of
version 3, its `delivered`; in one of version 2, the count of its `outputs` and their commitment
(§6). A record of version 1 states no output, and one no hop served delivered nothing, so neither
has a delivery. Otherwise `delivery_parent_mismatch`;
- **its chain**: its `seq` is its place in the list, from 1; its `prev` is `null` for the first and
the digest of the decision before it for every other; its `decided` is at least the served
attempt's `end`, when the objects first existed, and at least the `decided` of the decision before
it; and its `delivery` is not that of a decision before it. Otherwise `delivery_chain_broken`.
The route a delivery took is a decision, not a delivery: whether the route carried the objects is
for the delivery's own receipts to state ([bearer-receipts](bearer-receipts.md)), and what it costs
for the tariff that bills it (receipt-ledger §4). The record states no energy figure and no amount
of its own, only the figures its price book stated and the estimates §4 derives from them.
### 6.4 A delivery's offer and its route's receipt
A decision of version 2 rests on a price book of version 3 and states its **offer**: what the
delivery took its route at, as the route's entry states it and §4.2 estimates it. Its `offer` is
`null` when no route survives, and otherwise an object of exactly these members:
| member | value |
|---|---|
| `entry` | an integer: the position of the route's entry in the delivery's price book, from 0 |
| `unit`, `quantity` | the entry's `unit`, and the quantity the delivery's request states in it |
| `cost`, `energy` | the route's candidate's estimates (§4.2), each an integer or `null` |
| `energy_class`, `energy_ppm` | the entry's, of the forms §3.2 gives them |
| `grade` | the grade `energy_class` supports (oer-receipts §2): `measured` for `CalibratedInstrument` and `OnChipCounter`, `derived` for `ModelBased`, `projected` for `Estimator`; `null` when `energy_class` is `null` |
The decision's `pricebook` digest and the offer's `entry` name what was offered, and the rest
restates it, so a holder of the decision reads what the route was taken at without the book. An
offer that is not of these members, each of its form, is `delivery_invalid`; one that is not what
the route's entry and §4.2 give, or `null` while a route survives, or not `null` while none does,
is `delivery_offer_mismatch`.
**The route's receipt.** A route is one hop, and every hop is receipted (§6). A verification
document may give a delivery's **receipt**, the OER/2 receipt of the hop that carried it. It comes
after the decision, which the hop names; no signed document of the router's states it. It is held
to its delivery:
- it verifies under oer-receipts on its own, with no reason: otherwise `receipt_invalid`;
- it is a `job` receipt of the decision's `operation` whose `work.status` is `1`, whose
`chain.signer` is the decision's `route`, and whose `subject.inputs` holds the text `sha256:` and
the decision's digest: otherwise `receipt_mismatch`;
- its `subject.output` is the `root` of the decision's `objects`: otherwise
`receipt_output_mismatch`.
So the receipt is the route's, of this delivery. The hop signs which decision it carried out, and a
receipt moved from another delivery of the same objects names another decision. A decision of
version 1 states no offer, and a delivery no route took has no receipt: a receipt given with either
is `delivery_offer_mismatch`.
**Conformance.** The receipt is then held to the offer, in this order:
1. **Its grade.** Its `provenance.how` is no higher than the offer's `grade` in oer-receipts §2's
order, when `grade` is not `null`: otherwise `delivery_grade_exceeded`, whatever its figure and
its flag. A route was taken at the evidence its entry states, and a receipt does not raise that
after the fact: a route that reads better evidence states it in its next price book. A receipt
graded below the offer is held like any other, and its `grade` is stated beside its verdict.
2. **Its figure.** The receipt's **figure** is its `energy.joules_total` in microjoules, rounded to
the nearest and a half to the even one, as receipt-ledger §5.1 reads a hop's. The unmetered marker
(oer-receipts §3.1) states none; a figure that is negative or past what a u64 holds is
`receipt_mismatch`. With *u* the offer's `energy_ppm`, or 0 when it is `null`, a figure *J* is
**above the offer** when the offer's `energy` is not `null` and *J* × 10⁶ exceeds `energy` ×
(10⁶ + *u*): more than one declared uncertainty above the offer's figure, and for a class that
declares none, more than the figure.
3. **Its flag.** A receipt whose figure is above the offer states `price_exceeded` as its
`work.outcome`, and no other receipt does: otherwise `delivery_price_unflagged` and
`delivery_price_misflagged`.
The flag is the hop's, in what it signs, and a verifier recomputes it, so a receipt neither hides a
figure above its offer nor claims one. A flagged receipt is accepted: it says what happened. Its
**verdict** is one of:
| verdict | when |
|---|---|
| `within` | the receipt's figure is not above the offer |
| `exceeded` | it is above the offer, and flagged |
| `unmetered` | the receipt states the unmetered marker, so nothing shows the delivery within its offer, and a reader **MUST NOT** present it as within |
| `no_figure` | the offer states no `energy`, so nothing is held but the receipt's delivery and grade |
| `not_checked` | no receipt was given |
One declared uncertainty is a term of the offer, as rung-price §6.4 takes it for a ranking, and not
a limit on an error: a standard uncertainty bounds nothing (energy-measurement §2.1), so a figure
above the offer is shown past what the route was taken at and not shown wrong. The offer holds the
receipt's figure and grade, and not the uncertainty the receipt declares of its own figure
(`energy.class`), which this section does not compare with `energy_ppm`: a figure is held to the
offer's uncertainty, wider or narrower than its own. So a verdict turns on the figure alone: what a
hop declares of its figure neither buys a verdict nor costs one, and a receipt that declares
nothing, as a `ModelBased` or `Estimator` class and a receipt of no class do, is held by the same
rule. A declaration wider than the offer's is weaker evidence, as a lower grade is, and a reader
finds it in the receipt the verdict names. The offer is for the quantity the delivery's request
states, and the receipt's figure is the hop's for the whole delivery. What a flagged delivery costs
is the tariff's to say (receipt-ledger §4).
## 7. Verification
### 7.1 Inputs
A verifier is given a **verification document**: a JSON object of exactly `request`,
`pricebook`, `policy` and `record`, each a string holding that document's text, `receipts`,
`null` or a list of strings each holding an OER/2 receipt's text, and `keys`, `null` or an
object of a receipt's `chain.signer` names to keys, as oer-receipts §5 publishes them. It may
also have `third_party`, a list of strings each holding a third-party hop record's text (§6.2).
It may also have `deliveries`, a list of objects of exactly `request`, `pricebook`, `policy` and
`decision`, each a string holding that document's text: a delivery's three documents and its
delivery-decision record (§6.3), the job's decisions from the first, in order. Each may also have
`receipt`, a string holding the text of the OER/2 receipt of its route (§6.4).
The texts are carried as strings so that each is read as written. A verification document that
is not such an object, whose `receipts` is a list while its `keys` is `null`, or that has
`third_party` while its `receipts` is `null`, is `malformed`, and so is one whose `deliveries` is
not such a list or that gives a `receipt` while its `keys` is `null`.
### 7.2 Steps
A verifier runs these steps and stops at the first refusal, which it reports with its reason
and where (`at`, a list: the member, then indexes or members within it):
1. **The request** (§3.1), at `request`: `malformed`, `request_invalid`.
2. **The price book** (§3.2), at `pricebook`: `malformed`, `pricebook_invalid`.
3. **The policy** (§3.3), at `policy`: `malformed`, `policy_invalid`.
4. **The record** (§5), at `record`: a text that is not a document, or whose `kind` is not
`wai-route-record`, is `malformed`; a `version` that is an integer other than `1`, `2` and
`3` is `unknown_version`, at `record`, `version`; one that is not of §5.1's and §5.2's
members for its version, each of the form its table gives it (a restated member of a
candidate, the form §3.2 gives it for the record's price book version, and of `failures`,
each member an integer and `hops` at least 1), is `record_invalid` (an attempt whose `hop`
is not its position, or whose `error`, `outputs` or `delivered` is not as its `outcome`
requires, among them; the relations between a
candidate's members, such as an energy figure and its class, or a failure count and its
hops, are step 6's); then its signature (§5.3): `bad_key`, `bad_signature`, at `record`,
`sig`.
5. **The binding**, at `record` and the member: its `job`, `operation`, `capability`,
`fallback`, `quantities`, `request` and `deadline` are not the request's
(`request_mismatch`); its `policy` is not the policy's name, version and digest
(`policy_mismatch`); its `version` is not one that rests on the price book's (§3.4:
`pricebook_mismatch`, at `record`, `version`), or its `pricebook` is not the price book's
digest, currency and `issued` (`pricebook_mismatch`).
6. **The candidates**, at `record`, `candidates` and the index, recomputed by §4, each in
turn and for each these three in this order: a candidate missing, added or out of order,
or with a member other than `removed` and `rank` not what §4 gives it
(`candidate_mismatch`); its `removed` not its removals (`removal_mismatch`); its `rank` not
its rank (`rank_mismatch`). A list of the wrong length is a `candidate_mismatch` at the
first index past the shorter. Then, at `record`, `order`, the order (`rank_mismatch`); and
at `record`, `decision`, the decision (`decision_mismatch`).
7. **The attempts**, at `record`, `attempts` and the index, each in turn, by §5.2: an attempt
past the allowed number (`attempts_exceeded`); one that names an endpoint that is not a
survivor (`attempt_removed`); one that names a survivor not at its position in the order
(`attempt_out_of_order`); a served attempt that is not the last (`served_not_last`); times
out of order (`times_invalid`); `billed` and `not_billed` not what §5.2 gives
(`billing_invalid`). Then, at `record`, `attempts`: a job whose last attempt failed, or
that made none while a survivor was allowed, with fewer attempts than allowed
(`attempts_incomplete`).
8. **The hop receipts** (§6), when `receipts` is not `null`: first each receipt's text, at
`receipts` and its index, which a receipt that is not a document under oer-receipts §4
fails (`receipt_invalid`); then each attempt in turn, at `record`, `attempts`, its index and
`receipt`: `receipt_missing`, `receipt_invalid`, `receipt_mismatch`,
`receipt_output_mismatch`, the first receipt of the list whose `receipt_id` is the
attempt's being its receipt.
9. **The third-party hop records** (§6.2), when the document has `third_party`, each in turn,
at `third_party` and its index: a text that is not a document, or whose `kind` is not
`wai-route-third-party-hop`, is `malformed`; a `version` that is an integer other than `1`
is `unknown_version`, at `version` within it; one that is not of §6.2's members and forms
is `third_party_invalid`; then its signature, `bad_key`, `bad_signature`, at `sig` within
it; then its binding, `third_party_mismatch`, at the member; then what its hop delivered,
`third_party_output_mismatch`, at `outputs` within it.
10. **The delivery decisions** (§6.3), when the document has `deliveries`, each in turn, at
`deliveries`, its index and the member: its `request`, `pricebook` and `policy`, as steps 1 to 3
read the job's (`malformed`, `request_invalid`, `pricebook_invalid`, `policy_invalid`); then its
`decision`: a text that is not a document, or whose `kind` is not
`wai-route-delivery-decision`, is `malformed`; a `version` that is an integer other than `1` and
`2` is `unknown_version`, at `version` within it; one that is not of §6.3's and §6.4's members
and forms is `delivery_invalid`; then its signature, `bad_key`, `bad_signature`, at `sig` within
it; then, at the member within it, its documents, §4 over them (a candidate at `candidates` and
its index), its route, its offer (§6.4), its parent and its chain, in that order and each as
§6.3 holds it; then, when the entry has `receipt`, that receipt, as §6.4 holds it, at `receipt`
but for a decision of version 1, at `version` within the decision.
### 7.3 Outcome
- **refuse**: the reason and where.
- **accept**: the record's digest; the job and its operation; the served endpoint and the hop
that served it, and in a record of version 2 the content hashes of its outputs, in their
order, or in one of version 3 their `delivered` count and root, or `null`s for a job no hop
served; the billed hop, or `null`; the deadline,
`none` when none was declared and otherwise `met` or `missed` by the served hop, or `none`
too when no hop served; the order; the decision; each removed candidate's endpoint with the
limits that removed it; whether the hop receipts were `checked` or `not_checked`; and, when
checked, the hops whose receipts state the unmetered marker (oer-receipts §3.1), which a
reader **MUST NOT** present as hops of zero joules. When the document has `third_party`, each
record's hop, digest, `account`, `usage` and `charge`, in the order of their hops. When it has
`deliveries`, each decision's `seq`, `delivery`, digest, `route`, `decision` and `passed`, in
their order, and of a decision of version 2 its `offer` and its `conformance`, an object of
exactly `verdict` (§6.4), `receipt`, the receipt's `receipt_id`, `energy`, its figure, and
`grade`, its `provenance.how`, each `null` where no receipt or no figure gives it.
## 8. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `request_invalid` | §3.1, §7.2 step 1 |
| `pricebook_invalid` | §3.2, §7.2 step 2 |
| `policy_invalid` | §3.3, §7.2 step 3 |
| `record_invalid` | §5.1, §5.2, §7.2 step 4 |
| `request_mismatch` | §7.2 step 5 |
| `policy_mismatch` | §7.2 step 5 |
| `pricebook_mismatch` | §7.2 step 5 |
| `candidate_mismatch` | §4.1, §4.2, §7.2 step 6 |
| `removal_mismatch` | §4.3, §7.2 step 6 |
| `rank_mismatch` | §4.4, §7.2 step 6 |
| `decision_mismatch` | §4.5, §7.2 step 6 |
| `attempts_exceeded` | §5.2, §7.2 step 7 |
| `attempt_removed` | §5.2, §7.2 step 7 |
| `attempt_out_of_order` | §5.2, §7.2 step 7 |
| `served_not_last` | §5.2, §7.2 step 7 |
| `times_invalid` | §5.2, §7.2 step 7 |
| `billing_invalid` | §5.2, §7.2 step 7 |
| `attempts_incomplete` | §5.2, §7.2 step 7 |
| `receipt_missing` | §6, §7.2 step 8 |
| `receipt_invalid` | §6, §7.2 step 8 |
| `receipt_mismatch` | §6, §7.2 step 8 |
| `receipt_output_mismatch` | §6, §7.2 step 8 |
| `third_party_invalid` | §6.2, §7.2 step 9 |
| `third_party_mismatch` | §6.2, §7.2 step 9 |
| `third_party_output_mismatch` | §6.2, §7.2 step 9 |
| `delivery_invalid` | §6.3, §7.2 step 10 |
| `delivery_route_mismatch` | §6.3, §7.2 step 10 |
| `delivery_parent_mismatch` | §6.3, §7.2 step 10 |
| `delivery_chain_broken` | §6.3, §7.2 step 10 |
| `delivery_offer_mismatch` | §6.4, §7.2 step 10 |
| `delivery_grade_exceeded` | §6.4, §7.2 step 10 |
| `delivery_price_unflagged` | §6.4, §7.2 step 10 |
| `delivery_price_misflagged` | §6.4, §7.2 step 10 |
A verifier of this extension also raises `malformed` for a text that is not a document (§2)
or a verification document that is not §7.1's, `unknown_version` for a record of another
version, and `bad_key` and `bad_signature` for its signature (SPEC §7).
## 9. Operation
The catalogue's operation `route.verify` (`wai route verify <document>`) verifies a
verification document (§7.1) and prints its outcome (§7.3), offline; it exits 0 when the
record is accepted and 1 when it is refused.
## 10. Conformance
[`live-conformance/routes/`](../live-conformance/routes/) holds the corpus, in one file,
`vectors.json`, written by `wai_route_record_vectors gen` from fixed seeds: a router's key and
a hop's, and the OER/2 receipts of minor version 1 the hops sign. Each vector is a
verification document with the outcome §7.3 gives it:
- **Accepted**: a job served by its first survivor, billed; a failover after a server error,
the failed hop unmetered and not billed; a served hop past the deadline, not billed; a job
every allowed survivor failed, with the policy's `max_attempts` below the survivors' number;
a job no candidate survives; a ranking decided by the targets, by the request's sort, by a
key of the policy and by the endpoint; an unknown energy and time to first byte ranked last;
every limit of §4.3 removing a candidate, a `null` value among them, and a candidate removed
by several; a ceiling in another currency; the fallback's candidates; and a record verified
without its receipts.
- **Accepted, version 2**, over one price book whose endpoint prices its encode and its decode
apart and whose encodes' failures run from none in 1,000 hops to all of 500: a ranking by the
expected cost, which puts an endpoint whose billed failures make it dearer after one of the
same list price, a cheaper one that fails more after both, and the unmeasured endpoint, the
basis of 99 hops and the basis whose every hop failed after every measured one, a basis of
exactly 100 hops counting; a decode at its own price, under a unit-price cap its endpoint's
encode exceeds; a ranking and a target by the expected time to first byte, and one by the
list cost where no failure was measured; a spend ceiling that reads the list cost; a failover
whose failed hop delivered nothing; a hop that served three outputs, one that served past the
deadline, its outputs bound all the same; and a record verified without its receipts.
- **Refused**: each reason of §8 at least once, each vector a control of one defect, its
record signed again over the defect where the defect is not in the signature; and each code
§8 reuses. In version 2, each rule of §3.2 and §3.2.1 broken once (an entry out of order by
its operation, without its operation or its failures; more hops failed than were sent; a
period ending after the book was issued, or reversed; a billed price or a time with no
failed hop; a basis of no hop), an operation in a price book of version 1 and a price book
of version 4; a record of either version on a price book of the other; an expected figure
and a basis restated, and the other operation's entry listed; a served hop with no output,
a failed one with one, an output twice and one in capitals; and a served hop's receipt moved
from another job, a late hop's too, outputs restated in another order, one output named by
its leaf and three by the first of them (`receipt_output_mismatch`).
- **Accepted, version 3**, on version 2's price book: a hop that served one output, whose root
is its content hash; three; and 5,000, more than a list of version 2 holds, its record the
size of the others; a failover whose failed hop delivered nothing; a hop that served past the
deadline; and a record verified without its receipts.
- **Refused, version 3**: a served hop's count of 0; a failed hop with a count, and with a root;
a root in capitals; a count as text; a member besides the count and the root; outputs listed
as version 2 lists them; `delivered` in a record of version 2 (`record_invalid`); a record of
version 3 on a price book of version 1 (`pricebook_mismatch`); and a served hop's receipt
moved from another job, and a root not its receipt's (`receipt_output_mismatch`). A record of
version 4 is `unknown_version`.
- **Accepted, third-party hops**, under records of versions 2 and 3: a hop a third party served,
on the operator's account, whose provider reported no charge; one whose provider reported a
charge and a provider's own job id; a failed hop the provider bills beside a served one; a
failed hop it does not bill; and a hop of the customer's account.
- **Refused, third-party hops**: a document with `third_party` and no receipts (`malformed`); a
record of version 2 (`unknown_version`); each form and bound of §6.2 past its limit; a member missing and a member added, a header among
them; a URL with a query, one with a fragment, a response keeping a signed link and an origin
with a path; a served record with no outputs and no usage, a failed one with outputs and with no
error; a name twice and an object twice; exchanges out of order, before the record's start
and past its end (`third_party_invalid`);
a changed record (`bad_signature`); and another router's, another job's, a hop that is not
an attempt, another endpoint's, provider's, receipt's, outcome's and error's, a usage in
another unit, times outside the attempt's and a second record of a hop
(`third_party_mismatch`); and outputs that are not what the receipt names, the same in another
order, and another number than the attempt's count (`third_party_output_mismatch`).
- **Accepted, delivery decisions**, under records of versions 2 and 3: a job's output delivered to
Europe by the faster of two caches of one price, the dearer cache and the origin passed over by
price, the second cache by time to first byte; a second delivery of it to another region by the
one route that keeps no data there, chained to the first; a delivery no route survives; a record
verified without its receipts; and a document whose `deliveries` is empty.
- **Refused, delivery decisions**: a `deliveries` that is not a list, an entry without its policy
and a decision not as text (`malformed`); the delivery's request, price book and policy each
invalid; a decision that is not a document, of another kind and of version 3; a member missing and
one added, a `seq` of 0, a `prev` in capitals, no object, `objects` with a member besides, a route
passed by no key, a route as a number, a candidate of version 1's members, `decided` as text and
more routes passed than a list holds (`delivery_invalid`); a decision changed after it was signed,
a signer of small order and one signed under the route record's domain; another delivery's id,
operation, capability, quantities and request digest, and a fallback the request does not name
(`request_mismatch`); another policy version, name and digest (`policy_mismatch`); another price
book's `issued`, digest and currency, and one of version 1 (`pricebook_mismatch`); a route's price
restated, a route left out, a removal left out, two ranks swapped, the order reversed and another
`decided_by`; another route, none while one survives, a route passed by another key, a passed
route left out, a removed route passed, and a route where none survives
(`delivery_route_mismatch`); another router's decision, another job's, another parent, another
count and root, and a decision of a job no hop served and of a record of version 1
(`delivery_parent_mismatch`); and a first decision at `seq` 2 and one naming a `prev`, a second
with no `prev` and one naming another, decisions out of order, the first left out before the
second, one decided before the output existed and one before the decision before it, and one
delivery decided twice (`delivery_chain_broken`).
- **Accepted, offers**, over a delivery of 6 J offered by a counter's figure declared to 20,000 ppm,
by a model's figure, by an estimate and with no figure: a receipt below the figure, at it, at one
declared uncertainty above it, and one that rounds down to that edge; one a microjoule past it and
one that rounds up past it, each flagged; a model's figure a microjoule past its own, flagged; an
estimate at its figure; a receipt graded below its offer; one declaring more uncertainty than its
offer at the figure, and one declaring less at the edge; the unmetered marker; an offer of no
figure; a decision with no receipt given and one no route survives; the largest uncertainty; a
decision of version 2 after one of version 1; two deliveries each held to its own; and a delivery
under a record of version 2.
- **Refused, offers**: each rule of `energy_ppm` broken once, and a price book of version 2 that
states one (`pricebook_invalid`); a record on a price book of version 3, and a decision of either
version on the other's (`pricebook_mismatch`); an offer left out, one in a decision of version 1,
and each of its forms past its limit (`delivery_invalid`); a route's uncertainty restated
(`candidate_mismatch`); each member of the offer another, the figure by one and the uncertainty
by one each way, and an offer `null` while a route survives and stated while none does
(`delivery_offer_mismatch`); a receipt not as text and one given with no keys (`malformed`); one
that is not a receipt, changed after it was signed and under a key not published
(`receipt_invalid`); one of another kind, operation and route, of a failed delivery, naming no
decision and another, moved from another delivery of the same objects, and of a figure that is
negative and one past what a u64 holds (`receipt_mismatch`);
one of other objects (`receipt_output_mismatch`); one given with a decision of version 1 and one no
route survives (`delivery_offer_mismatch`); a grade above the offer's, a flagged one among them
(`delivery_grade_exceeded`); a figure above the offer with no flag, by a microjoule, by rounding,
of each class, under a declaration wider than the figure's distance past the offer and under
another outcome (`delivery_price_unflagged`); and a flag below the figure,
at the edge, on the unmetered marker and with no figure offered (`delivery_price_misflagged`).
`wai_route_record_vectors verify` and the test `tests/route_record.rs` check that the file
regenerates byte for byte, that `wai-rs` gives every vector its outcome, that every reason of
§8 refuses a vector, and that §8 lists exactly the reference's codes.
`live-conformance/route_record_verify.py` is the second implementation, written from this
text: it shares no code with `wai-rs`, verifies the router's signature with WAI's strict rule
written from RFC 8032 (`claim-conformance/_ed25519.py`), verifies the hop receipts with the
OER/2 reference verifier (`oer-conformance/oer2_verify.py`), computes the outputs'
commitment with BLAKE3 written from its specification (`claim-conformance/_blake3.py`),
recomputes every candidate, removal, expected figure, rank, order, decision and attempt
itself, and every delivery's routing, route, passed routes, offer, parent, chain and receipt, gives
every vector the same outcome, requires every mutated copy of the corpus to
fail, and requires the corpus to catch the verifier with each of its checks dropped.
## 11. Security and privacy considerations
- **What the record proves.** That the router, holding these three documents, chose by their
rules, and tried and billed as it says. It does not prove that the price book states what
the providers charge, or that an endpoint's stated retention or training is true: the price
book is the router's statement, pinned by digest, and a buyer who holds the published price
book of that digest compares it. A record whose candidates differ from its price book's
entries is refused (`candidate_mismatch`), so a router cannot list a price it did not
state.
- **Removed means never tried.** An attempt that names a removed candidate is refused
(`attempt_removed`): a job whose limits removed an endpoint was not sent there, by this
router's own signed account. Whether a hop's provider kept the data it was sent is outside
any record; the limit that removed a provider that says it keeps data is not.
- **Failed hops are not billed.** A record that bills a failed attempt, or a served one past
the declared deadline, is refused (`billing_invalid`); the receipt ledger bills a job from
the billed attempt alone.
- **Unknown is restrictive.** Every `null` a limit reads removes the candidate (§4.3), so a
price book that omits what a buyer cares about removes the endpoint rather than admitting
it.
- **Failures are measured, and stated with their basis.** A price book of version 2 states
each endpoint's failures as the router measured them, with the hops and the period they
were read from, and the record restates them beside the expected figures the ranking read.
The record does not prove the measurement, as it does not prove a price: a reader sees what
the order rests on, and a basis too thin to mean anything is read as no measurement at all
(§3.2.1). Leaving an endpoint unmeasured gains it nothing: its expected figures are `null`,
ranked after every measured one (§4.2).
- **The job pays the list price.** What failures cost is the router's: it orders the
survivors and is never billed to the job, whose limits and line read the list price
(§4.2).
- **A receipt is bound to its output.** In version 2, a served hop's receipt names the outputs
the record states it delivered (§6), so a receipt cannot vouch, in another record, for an
output its hop did not produce; and a holder of the output who finds its content hashes in
the record knows which hop's receipt is about it.
- **A third party's record holds no secret.** A third-party hop record states whose account the
hop ran on, never the key; no header, since a key travels in one; and no URL's query, since a
provider's signed link is a bearer token (§6.2). A record that keeps one is refused, so a
router that signs one has signed something a verifier will not read.
- **What a third-party record proves.** That the router says it made these calls and read these
answers: the provider signs nothing, so the record is the router's statement of what passed
between them, bound by digest to the bytes, which a holder of them checks. A provider's
`charge` is what the provider reported, as the router read it, and is never a price.
- **A delivery's route is the rule's.** A delivery-decision record whose route, or whose reason for
passing a route over, is not what §4 gives over the delivery's own documents is refused
(`delivery_route_mismatch`), so a router that sends a delivery to a route its rule did not choose
cannot sign a record that verifies. Each decision names the job's route record and the decision
before it, so one left out before a later one, moved or put in another job's deliveries is refused
(`delivery_chain_broken`, `delivery_parent_mismatch`). The decisions after the last one given are
not proved absent: a verifier is not told how many there were.
- **What a delivery decision proves.** That the router, holding the delivery's three documents,
chose its route by their rules, for the output the job's served hop delivered. It does not prove
that a route's terms are as its price book states, which a holder of the published price book of
that digest compares, nor that the route carried the objects, which the delivery's own receipts
state.
- **A delivery is held to its offer.** A decision states what its route was taken at, and the
route's receipt is held to it: a figure above the offer that its hop does not flag is refused
(`delivery_price_unflagged`), and so is a flag on a figure that is not (`delivery_price_misflagged`),
so what a hop signs says which it was. A receipt names its decision and is signed by the route, so
it cannot be moved to another delivery of the same objects, and a receipt graded above the offer
is refused whatever it states.
- **What conformance proves.** That the route's own receipt, under the key published for it, states
a figure within or above the offer. It does not prove the figure, which is the hop's reading at
the grade it states: a receipt graded below its offer is held all the same, and the outcome states
its grade beside its verdict. A delivery with no receipt given is `not_checked`, and a verifier is
not told that a receipt exists.
- **What a record reveals.** Its job id, its operation and its quantities, every endpoint and
provider that could have served the job with their prices, and when each hop ran; in
version 2, every candidate's measured failures and the content hashes of the job's
outputs. A third-party hop record reveals, besides, what the provider answered about the job,
and a delivery decision every route that could have carried the job's output, with its terms,
and where the delivery was to go, by the regions and residencies its request names.
They travel between the router, the job's owner and an auditor they choose.