Some checks failed
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 23s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 19s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 13s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 21s
wai-app and Create / wai-app · does this change touch it (push) Successful in 17s
wai-audit collector and player integration / does this change touch it (push) Successful in 38s
WAI operation catalogue / does this change touch it (push) Successful in 1m6s
wai-fleet service / does this change touch it (push) Successful in 42s
wai-pack / wai-pack · does this change touch it (push) Successful in 26s
wai-quantum for JavaScript / does this change touch it (push) Successful in 24s
wai-quantum studio and desktop / does this change touch it (push) Successful in 27s
wai-sink-android native sink library / does this change touch it (push) Successful in 14s
wai-sink-swift native sink package / does this change touch it (push) Successful in 16s
WAI Studio / does this change touch it (push) Successful in 56s
wai-ledger service / does this change touch it (push) Successful in 2m28s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-pack / wai-pack · linux (push) Has been cancelled
wai-pack / wai-pack · linux-arm64 (push) Has been cancelled
wai-pack / wai-pack · macos (push) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (push) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (push) Has been cancelled
wai-pack / wai-pack · dependency licences (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
WAI examples / WAI examples · does this change touch them (push) Has been cancelled
WAI examples / WAI examples · portable · linux (push) Has been cancelled
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Has been cancelled
WAI examples / WAI examples · android-app · linux-arm64 (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Has been cancelled
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Has been cancelled
receipt-ledger §4.14 (new), §4.1, §4.3, §6.1, §6.8, §6.9, §7.2 step 4, §7.3, §9, §10, §11, §12. A tariff of version 9 states `follows`, after `storage`: zero, or the digest of the tariff whose chain it continues; both parties sign it with the rest of the body. Everything version 8 bills, version 9 bills as it does, and an invoice of version 9 is version 8's layout. A tariff that follows another continues that tariff's chain: its first invoice names in `prev` the last invoice of the chain it follows, and every rule that reads the invoice before reads that one, across the two tariffs: a package it lists is held and billed its byte-days (§4.7), a record it lists that is billed once is billed never again (record_rebilled, §6.9), and its period ends where the next begins (invoice_overlap). §7.2 step 4 accepts a previous invoice of the invoice's tariff or of the one it follows (tariff_mismatch otherwise) and refuses an invoice of a following tariff that names none before it, chain_restarted (new, §10). An accepted invoice of a following tariff states the tariff it follows. So a key made or retired, a packager or a holder added, is a tariff the buyer signs that keeps the chain, its held storage and its billed-once history; a retired key's records bill nothing under the tariff that retires it. Versions 1 to 8 read, verify and bill exactly as before. Reference: receipt_ledger VERSION_9, may_follow, Tariff::follows and Tariff::followed, continues, Reason::ChainRestarted; make_invoice refuses a following tariff's invoice with no previous and a previous of a tariff not followed. The reference ledger (wai/wai-ledger, §9) accepts a following tariff when it holds the tariff followed, of its buyer, followed by no other, with an invoice; closes it (a source or an invoice under it is tariff_mismatch); and takes over its chain: its last invoice, its billed records, and every source its next invoice would bill, verified again under the following tariff, a held package whose packager the new scope drops left behind, and any other source refused or billing less (outside_scope) refusing the following tariff at that source. It rebuilds each chain after the one it continues on start, and its test issues the corpus's three invoices byte for byte across a restart. Corpus live-conformance/ledger/: one buyer's chain across three tariffs of version 9, a stitcher key made then another retired, the package the first day lists billed its byte-days under each tariff after, beside the new key's interstitials (a new source, stitched_interstitial, from the stitched corpus's hls_interstitial); the same scope under a tariff that follows none, billing no storage; controls for chain_restarted, a previous invoice of a tariff not followed (tariff_mismatch), an overlap across the tariffs (invoice_overlap), a record billed again across them (record_rebilled), a retired key's records billed (quantity_not_recorded), and an invoice of version 8 under a tariff of version 9; the control of an undefined invoice version moves from 9 to 10. It regenerates byte for byte on x86_64 (Framework) and aarch64 (Thor); wai-rs, the service and the Python second implementation each remake the chain's three invoices byte for byte. Second implementation receipt_ledger_verify.py mirrors the rule (a tariff's follows, step 4's previous of the tariff followed, chain_restarted, the follows an outcome states): all 321 vectors agree; the month, the two days of storage and the chain across three tariffs, 10 invoices, are written again byte for byte; every reason of §10 refuses a vector (24 listed); 389 of 389 mutated copies fail (five new: the invoice before not given, another tariff stated followed, none stated followed, another chain's invoice given as the one before, the held package's storage left out); 90 of 90 weakened verifiers are caught (two new checks: a restarted chain, a previous invoice of a tariff not followed). Planted faults, each caught by tests/receipt_ledger.rs: an invoice of a following tariff naming none accepted; any tariff's invoice taken as the one before; the tariff followed not read in step 4; follows left out of the signed body; a held package read only under one tariff; the billed records of another tariff's invoice not read; the tariff followed not stated; version 10 read as defined. Decision D62: a following tariff that both parties sign, not an amendment of a scope; fail closed on a restarted chain; what it does not do (a stitched record's time). Breaks acknowledged in the change note. Co-Authored-By: Claude Opus 5.5 <[email protected]>
85 lines
6 KiB
Markdown
85 lines
6 KiB
Markdown
# wai-ledger
|
|
|
|
The WAI receipt ledger ([`extensions/receipt-ledger.md`](../extensions/receipt-ledger.md)) as a
|
|
service. A seller and a buyer both sign a tariff. The ledger verifies each record it is given by
|
|
the extension that defines it: a room's session receipt, a delivering party's settlement statement
|
|
with the acknowledgements its downstream parties signed, a package receipt with its indexes, an ad
|
|
room's receipts with the players' presentation claims. For a period, it signs the invoice whose
|
|
every line cites the records it came from, with the joules those records state per scope and per
|
|
acquisition class, and two reports: the ad decisions against the presentation claims, and the
|
|
delivery of each party with the bytes two parties were acknowledged for twice.
|
|
|
|
Every invoice is served with its **verification document**: the tariff, the invoice, the invoice
|
|
before it in its chain, and the sources. Any verifier of the extension checks it without this
|
|
service, the corpus's second implementation among them:
|
|
|
|
```sh
|
|
python3 -I ../live-conformance/receipt_ledger_verify.py --document document.json
|
|
```
|
|
|
|
Payments and tax are not here: an invoice is the figures a payment rests on.
|
|
|
|
## Running it
|
|
|
|
```sh
|
|
cargo build --locked --release
|
|
WAI_LEDGER_DIR=/path/to/ledger WAI_LEDGER_KEY=/path/to/seller.key WAI_LEDGER_ADDR=127.0.0.1:8251 \
|
|
./target/release/wai-ledger serve
|
|
```
|
|
|
|
| variable | meaning |
|
|
|---|---|
|
|
| `WAI_LEDGER_DIR` | the directory the service owns (required); created when absent |
|
|
| `WAI_LEDGER_KEY` | the seller key's file: 32 bytes, the Ed25519 seed; made with mode 0600 when absent |
|
|
| `WAI_LEDGER_ADDR` | the address it listens on (default `127.0.0.1:8251`) |
|
|
|
|
`wai-ledger key` prints the seller key of `WAI_LEDGER_KEY`, which a buyer's tariff names as its
|
|
seller. `wai-ledger verify FILE` prints a verification document's outcome.
|
|
|
|
The directory:
|
|
|
|
```text
|
|
LOCK held exclusively while a process serves the directory
|
|
tariffs/<digest>/tariff.wlt the tariff, both signatures
|
|
tariffs/<digest>/sources/<n>.json the sources held, in the order they arrived
|
|
tariffs/<digest>/invoices/<n>.wli the chain, in order
|
|
tariffs/<digest>/invoices/<n>.json each invoice's verification document
|
|
```
|
|
|
|
Every file is written whole: to a temporary name, synced, renamed into place, and its directory
|
|
synced. On start the service takes the lock (a second process on the same directory is refused),
|
|
then reads every tariff, source and invoice back and verifies each again, each chain after the chain
|
|
it continues, and refuses to start on a chain that does not link.
|
|
|
|
## Endpoints
|
|
|
|
| | |
|
|
|---|---|
|
|
| `GET /v1` | the service, its seller key and its limits |
|
|
| `GET /v1/health` | `ok` |
|
|
| `POST /v1/tariffs` | `{"tariff": <hex>}`: a tariff whose seller is the service's key, both signatures verified; `201` and its digest. A tariff that follows another (version 9, receipt-ledger §4.14) takes over the chain of a tariff the service holds, of its buyer, that no other follows and that has an invoice (`record_missing` or `tariff_mismatch` at `["tariff", "follows"]` otherwise): its first invoice names that chain's last, every record the chain billed stays billed, and every source the followed tariff would bill next is held under it, verified again under its scope (a held package whose packager it does not name is left behind; any other source it refuses, or would bill less of, refuses the tariff at `["tariff", "follows", "source", n]`, `outside_scope` for the latter). The followed tariff is closed: a source or an invoice under it is `tariff_mismatch` |
|
|
| `GET /v1/tariffs/{digest}` | the tariff, its chain of invoices, and the sources held and not yet billed |
|
|
| `POST /v1/sources` | `{"tariff": <digest>, "source": {...}}`: the source verified by its extension, in the tariff's scope, under no other tariff, within the tariff's period, and checked against every source held (`record_duplicate`, `record_overlap`) and every record billed (`record_rebilled`); `201` and the records it holds |
|
|
| `POST /v1/invoices` | `{"tariff": <digest>, "start": <µs>, "end": <µs>}`: the invoice of the period over the sources held, not yet billed, that lie in it (a package always does), chained after the last, signed, verified and kept; `201`, the invoice, its outcome with the reports, and its verification document |
|
|
| `GET /v1/invoices/{digest}` | the invoice, its outcome and its verification document |
|
|
| `POST /v1/verify` | a verification document: its outcome |
|
|
| `GET /metrics` | the service's counters in the text exposition format (`../wai-metrics`): requests by route, method and status (`wai_ledger_requests_total`), the tariffs, sources and invoices held, and the verifications running. No tariff, party, key or client is a label. It is the operator's, not an operation of the extension: a request that came through a front is answered `404`, and a front that sets no forwarding field is configured not to pass the path on |
|
|
|
|
A refusal is `422` with `{"outcome": "refuse", "reason", "at"}`, the extension's reason and place;
|
|
a source refused by its own extension names that extension's reason and its place after
|
|
`"source"`. A body that is not what the endpoint takes is `400`; an unknown tariff or invoice is
|
|
`404`. A request body is at most 64 MiB. Requests that change the ledger run one at a time;
|
|
`POST /v1/verify` and `GET /v1/invoices/{digest}` verify at most two documents at a time.
|
|
|
|
The service has no accounts: a tariff binds a buyer by the buyer's own signature, and a source is
|
|
accepted only when it verifies and the tariff's scope names its party, whoever submits it.
|
|
|
|
## Checks
|
|
|
|
```sh
|
|
cargo test --locked --release # the corpus's invoices issued byte for byte, the chain
|
|
# recovered after a restart, each source billed once,
|
|
# one process per directory
|
|
bash smoke.sh http://127.0.0.1:18251 # over HTTP, against an instance started with the corpus's
|
|
# seller key (see .github/workflows/wai-ledger.yml)
|
|
```
|