route-record: a third party's hop has a signed record of what was sent, what came back, and what the provider bills #572

Merged
dcharlot merged 3 commits from wai/third-party-hop into main 2026-11-02 11:04:26 -05:00
Owner

A hop sent to a third party's endpoint is work the router did not do. Its OER/2 receipt says the hop served or failed. Nothing WAI defines says what was sent, to whom, what came back, or what the provider bills, so an implementation has to carry that in a record of its own that WAI's verifier cannot read. This adds WAI's record for it.

The record (route-record §6.2)

wai-route-third-party-hop, signed by the router under its own domain and named by its digest:

  • Who and how: the provider and its API origin, whose account the hop ran on (operator or customer, never the key), and the provider's id for the work.
  • Each exchange: method, URL, times, status, and the bodies sent and received by SHA-256 and length. A response is kept where it was JSON.
  • What came of it: the outputs by name and content hash, the usage the provider bills in the endpoint's unit, and what it reported charging, in its own terms and never added to anything.

It holds no secret. No header has a member, since a key travels in one, and no URL states its query, since a provider's signed link is a bearer token. third_party::kept_response and url_without_query make a response and a URL what a record may state, and a record that keeps one is refused.

Three design choices

  • No route record changes. The record names the hop's receipt, which the route record already names, so the chain closes without a new record version.
  • No energy member. The hop's receipt already states the figure, or the unmetered marker where the provider gave none.
  • Usage in the entry's unit. A record's usage.unit must be the unit of its endpoint's candidate. A ledger line can then bill the record's usage at the entry's price, which is the next step.

Verification (§7.2 step 9)

A verification document may carry third_party, the records' texts, beside its receipts. Each record is held to:

  • the route record's signer and job;
  • its attempt's endpoint, receipt, outcome, error and times;
  • that endpoint's candidate's provider and unit;
  • when it served, its outputs to what the hop's receipt names and the attempt's count.

Three new reasons: third_party_invalid, third_party_mismatch, third_party_output_mismatch. A document without third_party reads exactly as before, and its outcome has no such member.

Tests

Routes corpus 126 → 236 vectors: nine accepted and 101 controls of one defect each, including every form and bound of §6.2 at its first value past the limit.

  • Rust: the corpus regenerates byte for byte and tests/route_record.rs passes. Each of 79 mutations of third_party.rs and of verify's step 9, one per rule, is caught by the corpus, and the restored tree passes. wai breaking reports nothing unacknowledged.
  • Python: route_record_verify.py, written from the text, gives every vector its outcome. All 1,119 mutated copies fail, and the corpus catches each of its 103 checks when dropped. It now stops a mutated copy at its first failure, since it only asks whether one fails.

Not covered: the record states what the router says it sent and received. The provider signs nothing, so the record proves the router's statement and binds it by digest to the bytes, which only a holder of the bytes can check. §11 says so.

Not in this PR

Ledger kind 17, a job line billing the record's usage, and the provider's charge beside the line come next, then the catalogue operations and kinds for exact upscale and tonemap. This record is usable and verifiable without them.

🤖 Generated with Claude Code

A hop sent to a third party's endpoint is work the router did not do. Its OER/2 receipt says the hop served or failed. Nothing WAI defines says what was sent, to whom, what came back, or what the provider bills, so an implementation has to carry that in a record of its own that WAI's verifier cannot read. This adds WAI's record for it. ## The record (route-record §6.2) `wai-route-third-party-hop`, signed by the router under its own domain and named by its digest: - **Who and how:** the provider and its API origin, whose account the hop ran on (`operator` or `customer`, never the key), and the provider's id for the work. - **Each exchange:** method, URL, times, status, and the bodies sent and received by SHA-256 and length. A response is kept where it was JSON. - **What came of it:** the outputs by name and content hash, the usage the provider bills in the endpoint's unit, and what it reported charging, in its own terms and never added to anything. **It holds no secret.** No header has a member, since a key travels in one, and no URL states its query, since a provider's signed link is a bearer token. `third_party::kept_response` and `url_without_query` make a response and a URL what a record may state, and a record that keeps one is refused. ## Three design choices - **No route record changes.** The record names the hop's receipt, which the route record already names, so the chain closes without a new record version. - **No energy member.** The hop's receipt already states the figure, or the unmetered marker where the provider gave none. - **Usage in the entry's unit.** A record's `usage.unit` must be the unit of its endpoint's candidate. A ledger line can then bill the record's usage at the entry's price, which is the next step. ## Verification (§7.2 step 9) A verification document may carry `third_party`, the records' texts, beside its receipts. Each record is held to: - the route record's signer and job; - its attempt's endpoint, receipt, outcome, error and times; - that endpoint's candidate's provider and unit; - when it served, its outputs to what the hop's receipt names and the attempt's count. Three new reasons: `third_party_invalid`, `third_party_mismatch`, `third_party_output_mismatch`. A document without `third_party` reads exactly as before, and its outcome has no such member. ## Tests Routes corpus 126 → 236 vectors: nine accepted and 101 controls of one defect each, including every form and bound of §6.2 at its first value past the limit. - **Rust:** the corpus regenerates byte for byte and `tests/route_record.rs` passes. Each of 79 mutations of `third_party.rs` and of `verify`'s step 9, one per rule, is caught by the corpus, and the restored tree passes. `wai breaking` reports nothing unacknowledged. - **Python:** `route_record_verify.py`, written from the text, gives every vector its outcome. All 1,119 mutated copies fail, and the corpus catches each of its 103 checks when dropped. It now stops a mutated copy at its first failure, since it only asks whether one fails. **Not covered:** the record states what the router says it sent and received. The provider signs nothing, so the record proves the router's statement and binds it by digest to the bytes, which only a holder of the bytes can check. §11 says so. ## Not in this PR Ledger kind 17, a job line billing the record's usage, and the provider's charge beside the line come next, then the catalogue operations and kinds for exact upscale and tonemap. This record is usable and verifiable without them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A hop sent to a third party's endpoint is work the router did not do. Its OER/2 receipt says the
hop served or failed; nothing WAI defines says what was sent, to whom, what came back, or what
the provider bills, so an implementation carries that in a record of its own that WAI's verifier
cannot read.

`wai-route-third-party-hop`, signed by the router and named by its digest: the provider and its
API origin, whose account the hop ran on (never the key), the provider's id for the work, each
exchange (method, URL without its query, times, status, the bodies by SHA-256 and length, the
response where it was JSON, with links' queries removed), the outputs by name and content hash,
the usage the provider bills, in the endpoint's unit, and what it reported charging, in its own
terms.

A verification document may carry the records beside its receipts (§7.2 step 9). Each is held to
the route record's signer and job, to its attempt and that attempt's candidate, and when it served,
its outputs to what the hop's receipt names and the attempt's count. No route record changes, since
the record names the receipt the route record already names.

routes corpus: 126 -> 202 vectors, from fixed seeds, every control refused at its own place.
route_record_verify.py verifies each from the text, shares no code with wai-rs, and catches each of
its 85 checks dropped and all 1,051 mutated copies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Rust mutations showed a usage's quantity of the wrong type went uncaught, and the same held of
other forms and bounds the text states. Each now has a control, at its first value past the limit,
so the two implementations are held to the same limits. A clause `unhex` already made redundant
is gone. The document kind is in the registry.

The Python verifier stops a mutated copy at its first failure: it only asks whether one fails.

routes corpus: 236 vectors. route_record_verify.py: 103 checks, 1,119 mutated copies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
CHANGELOG: Reason::ALL's length is acknowledged as breaking
All checks were successful
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m24s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 52s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 5m16s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 10m49s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m33s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 17m12s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 9m51s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 17m15s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 7m24s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 7m56s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 21m9s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 7m45s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m51s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 19m36s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 35s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m40s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 13m38s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m28s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 21m45s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 11m20s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 11m44s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 6m18s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m3s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 13m3s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m2s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m46s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 8m22s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m32s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h9m59s
010c66d01e
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot force-pushed wai/third-party-hop from 010c66d01e
All checks were successful
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m24s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 52s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 5m16s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 10m49s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m33s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 17m12s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 9m51s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 17m15s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 7m24s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 7m56s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 21m9s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 7m45s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m51s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 19m36s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 35s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m40s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 13m38s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m28s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 21m45s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 11m20s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 11m44s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 6m18s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m3s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 13m3s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m2s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m46s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 8m22s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m32s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h9m59s
to 2e3f09bcf0
All checks were successful
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 16m11s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m47s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m26s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m50s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 15m6s
wai-pack / wai-pack · macos (pull_request) Successful in 6m39s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 13m19s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 7m57s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 5m20s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 18m12s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 12m31s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 7m54s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 19m27s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 24m48s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 1m19s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 14m57s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 30m0s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 18m39s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m51s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m3s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m4s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 10m32s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 15m17s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m34s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 1m22s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m47s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 3m18s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 8m25s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h18m30s
2026-11-02 08:03:26 -05:00
Compare
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!572
No description provided.