Skip to main content

WAI Extension: Route Record

Mirrored from the canonical text at commit 260cbd09 ().

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 §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/, 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). An invoice that bills a job from its route record is the receipt ledger’s (receipt-ledger §5).

Relationship to the core spec: wai.route.record is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

2. Terms and encodings

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.

memberform
kind"wai-route-request"
joba token: the job’s id
operationan operation
capabilitya capability
fallbacka capability other than capability, or null
quantities1 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
limitsan object of exactly the members below
preferan 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_usnull or an integer at least 1: the time after acceptance by which the job’s output must be served

limits, the hard limits:

memberform
regionsnull, or 1 to 64 tokens in strictly ascending order: the regions an endpoint may run in
residencynull, or 1 to 64 tokens in strictly ascending order: the places the job’s data may rest
no_traininga boolean: no endpoint that trains on its inputs
zero_retentiona boolean: no endpoint that keeps the job’s data
ga_onlya boolean: only endpoints generally available, not previews
consent_on_filea boolean: whether the job’s owner holds consent for what an endpoint that needs it does (a cloned voice)
max_price0 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_ceilingnull, 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.

memberform
kind"wai-route-pricebook"
version2 or 3: its version; a price book of version 1 has no such member
currencythree capitals A–Z
issueda time: when the prices were stated
entries1 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:

memberform
endpointa token: the endpoint’s id
operationversion 2: an operation: the one this entry prices
providera token: who runs it
capabilitya capability: what it produces
region, residencya token, or null when not known
trains_on_inputsa boolean, or null when not known
retention_daysan integer, or null when not known: how long it keeps a job’s data (0: not at all)
gaa boolean, or null when not known: generally available
needs_consenta boolean, or null when not known: whether what it does needs the consent of the person it imitates
unita unit: what it is priced by
divisoran integer at least 1
pricean integer: its price per divisor units
ttfb_msan integer, or null when not known: its time to first byte, in milliseconds
energy_ujan integer, or null when not stated: the microjoules it states per divisor units
energy_classthe class of that figure, or null exactly when energy_uj is null
energy_ppmversion 3: the figure’s declared uncertainty, in parts per million of it (energy-measurement §2.3): an integer from 1 to 2³² − 1 when energy_class is CalibratedInstrument or OnChipCounter, and null otherwise
qualityan integer 0 to 1000, or null when not known
failuresversion 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:

memberform
hopsan integer at least 1: the hops of the entry’s operation the router sent the endpoint in the period
from, totimes, from at most to and to at most the price book’s issued: the period those hops were sent in
failedan integer at most hops: the hops among them that failed (§5.2)
billed_pricean 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_msan 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.

memberform
kind"wai-route-policy"
namea token
versionan integer at least 1
keysthe four ranking keys price, ttfb, energy and quality, each once, in the policy’s order
max_attemptsan integer 1 to 16: the most survivors tried
exclude0 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:

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:

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):

limitremoves a candidate whenvalue
policyits provider is in the policy’s excludeprovider
unitthe request states no quantity in its unitunit
regionthe request’s regions is not null, and its region is null or not in itregion
residencythe request’s residency is not null, and its residency is null or not in itresidency
trainingno_training is true and its trains_on_inputs is not falsetrains_on_inputs
retentionzero_retention is true and its retention_days is not 0retention_days
gaga_only is true and its ga is not truega
consentconsent_on_file is false and its needs_consent is not falseneeds_consent
unit_pricemax_price has an entry for its unit, and price × that entry’s divisor exceeds that entry’s price × divisorprice
spendspend_ceiling is not null, and its currency is not the price book’s, or the candidate’s cost is null or exceeds its amountthe 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:

keyfirstin version 2
targetsthe one that meets the targetsthe same, the time to first byte read being the expected one
pricethe smaller costthe smaller expected cost, then the smaller cost
ttfbthe smaller ttfb_msthe smaller expected time to first byte, then the smaller ttfb_ms
energythe smaller energythe same
qualitythe larger qualitythe same
endpointthe smaller endpoint, by its bytesthe same

A null figure comes after every stated one, and two nulls 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:

memberform
kind"wai-route-record"
version1 or 2: its price book’s version (§3.4)
job, operation, capability, fallback, quantitiesthe request’s
requestthe request’s digest
policyan object of exactly name, version and digest: the policy’s
pricebookan object of exactly digest, currency and issued: the price book’s
candidatesthe candidates (§4.1), in their order, each an object of exactly the members below
orderthe order (§4.4): the survivors’ endpoints
decisionan object of exactly decided_by and runner_up (§4.5)
accepteda time: when the router accepted the job
deadlinenull when the request’s deadline_us is null; otherwise accepted + deadline_us
attempts0 to 16 objects of exactly the members of §5.2, in the order they were made
signerthe router’s key
sig§5.3

A candidate:

memberform
endpoint, provider, capability, region, residency, trains_on_inputs, retention_days, ga, needs_consent, unit, divisor, price, ttfb_ms, energy_uj, energy_class, qualitythe price book’s entry’s, as it stated them
operation, failuresversion 2: the entry’s, as it stated them
via_fallbacka boolean (§4.1)
cost, energyits estimates (§4.2)
expected_cost, expected_ttfb_msversion 2: its expected cost and expected time to first byte per successful job (§4.2), each an integer or null
removedits removals (§4.3): objects of exactly limit and value, in §4.3’s order; empty for a survivor
rankits 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:

memberform
hopits position among the attempts, from 1
endpointthe endpoint it was sent to
outcomeserved (the hop returned the job’s output) or failed
errornull when served; when failed, one of timeout, server_error, rate_limited, refused and unavailable
start, endtimes: when it was sent and when it ended
billeda boolean: whether the job is billed for it
not_billednull when billed; otherwise failed or deadline_missed
receiptthe receipt_id of the hop’s OER/2 receipt (oer-receipts §5): sha256: or blake3: and 64 lowercase hexadecimal digits
outputsversion 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)
deliveredversion 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:

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 §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: 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:

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:

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:

membervalue
kind"wai-route-third-party-hop"
version1
jobthe job’s id, as the route record states it
hopan integer at least 1: the attempt’s hop
endpointa token: the attempt’s endpoint
providera token: the provider of that endpoint’s candidate
receiptthe attempt’s receipt, its hop’s OER/2 receipt_id, in the form §5.2 gives it
originthe 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_joba string of 1 to 256 bytes, the provider’s id for the work, or null when it gave none
outcome"served" or "failed"
errornull 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
exchanges1 to 1024 objects of exactly the members below, in the order they were sent
outputswhen 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, []
usagewhat 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
chargewhat 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, endtimes: when the first exchange was sent, and when the last ended, start at most end
signer, sigthe router’s key and its signature

An exchange is one HTTP request and what came back:

membervalue
method1 to 16 uppercase ASCII letters
urlthe URL requested, https:// or http:// and then 1 to 2,040 bytes holding no ? and no #: its query and its fragment removed
start, endtimes: 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
sentan object of exactly sha256, 64 lowercase hexadecimal digits, the SHA-256 of the request’s body, and bytes, its length
statusthe response’s HTTP status, an integer 100 to 599, or 0 when no response came
receivedthe same of the response’s body; of no bytes, when no response came
jsonthe 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

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:

membervalue
kind"wai-route-delivery-decision"
version1 or 2 (§3.4)
jobthe job’s id, as its route record states it
parentthe job’s route record’s digest (§5.3)
seqan integer at least 1: its place among the job’s delivery decisions
prevnull, or 64 lowercase hexadecimal digits: the digest of the job’s decision of seq − 1, and null for its first (its chain, below)
objectsan 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)
deliverya token: the delivery’s request’s job, the delivery’s id
operation, capability, fallback, quantitiesthe delivery’s request’s, of the forms §3.1 gives them
request64 lowercase hexadecimal digits: the delivery’s request’s digest
policyan object of exactly name, version and digest: the delivery’s policy’s, of the forms §5.1 gives them
pricebookan object of exactly digest, currency and issued: the delivery’s price book’s, of the forms §5.1 gives them
candidatesthe 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
orderthe order (§4.4): 0 to 1024 tokens, the surviving routes’ endpoints
decisionan object of exactly decided_by and runner_up, of the forms §5.1 gives them (§4.5)
routea token, the route the delivery took: the first endpoint of the order; null when no route survives
passed0 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
offerversion 2: what the route was taken at (§6.4), or null when no route survives
decideda time: when the router decided
signer, sigthe 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:

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), 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:

membervalue
entryan integer: the position of the route’s entry in the delivery’s price book, from 0
unit, quantitythe entry’s unit, and the quantity the delivery’s request states in it
cost, energythe route’s candidate’s estimates (§4.2), each an integer or null
energy_class, energy_ppmthe entry’s, of the forms §3.2 gives them
gradethe 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:

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:

verdictwhen
withinthe receipt’s figure is not above the offer
exceededit is above the offer, and flagged
unmeteredthe receipt states the unmetered marker, so nothing shows the delivery within its offer, and a reader MUST NOT present it as within
no_figurethe offer states no energy, so nothing is held but the receipt’s delivery and grade
not_checkedno 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

8. Reasons

The codes this extension adds, each with where it is raised:

reasonraised 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/ 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:

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