All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 15s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 7s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 7s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 14s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 8s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 8s
WAI energy receipts / does this change touch it (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 16s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 7s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 8s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 7s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 7s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 13s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 7s
wai-web wasm32 / does this change touch it (pull_request) Successful in 13s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 3m43s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m10s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 3m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m54s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 1m3s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m20s
wai-fleet service / service · linux (pull_request) Successful in 1m59s
wai-pack / wai-pack · linux (pull_request) Successful in 3m21s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m39s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 50s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m44s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m8s
wai-ledger service / service · linux (pull_request) Successful in 4m30s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 2m46s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m26s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m36s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m53s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m46s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 6m10s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 15m48s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m24s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 12m43s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 23m53s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m16s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 4m0s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 19s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 43s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 1m25s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 7m16s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 27s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m9s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 12m29s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 10m34s
wai-ledger service / service · macos (pull_request) Successful in 48s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m47s
wai-fleet service / service · macos (pull_request) Successful in 1m3s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 29s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m30s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 34s
wai-pack / wai-pack · macos (pull_request) Successful in 2m30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m25s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m52s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 10m38s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 4m50s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m58s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m54s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 25s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 15m5s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 7m37s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 11m59s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m23s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 15m57s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m16s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h13m0s
The change of wai/hls-cues-delivery-decision (98192fe63..c42fa8c9c, 4 commits), on the fix branch: its changelog entries as files of wai/changes/, its wai-conformance.yml edits as step files, corpus files and test tags, and every other file as the branch has it. - 8b9a32067 channel-lineup §7.4: a channel's breaks marked in its HLS Media Playlist, from its signed schedule - af5947f31 route-record §6.3: a delivery's decision records why it took the route it took - 558a6764f channel-lineup §7.4: a window ends before the year 10000, and every rule of the cue tags has a vector - c42fa8c9c route-record §6.3: a control for each member that binds a decision to its documents, and what the chain proves Co-Authored-By: Claude Opus 5.5 <[email protected]>
996 lines
67 KiB
Markdown
996 lines
67 KiB
Markdown
# 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.
|
||
>
|
||
> 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); 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`.
|
||
- ⌊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 or
|
||
version 2 (§3.4); one of version 1 has no `version` member and its entries neither
|
||
`operation` nor `failures`.
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `kind` | `"wai-route-pricebook"` |
|
||
| `version` | version 2: `2`; 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 version 2 |
|
||
|
||
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` |
|
||
| `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 two: version 1, this
|
||
extension's first, and version 2, which adds the members that price an operation and state an
|
||
endpoint's failures. 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). 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 is `pricebook_invalid`; a record whose `version` is
|
||
other than 1, 2 and 3 is `unknown_version`.
|
||
|
||
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` |
|
||
| `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) |
|
||
| `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 |
|
||
| `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 version 2 (`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.
|
||
|
||
## 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.
|
||
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.
|
||
|
||
### 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` is
|
||
`unknown_version`, at `version` within it; one that is not of §6.3'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 parent and its chain, in that order and each as §6.3 holds it.
|
||
|
||
### 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.
|
||
|
||
## 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 |
|
||
|
||
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 3; 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 2; 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`).
|
||
|
||
`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, parent and chain, 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.
|
||
- **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.
|