diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index df7f89479..fbe876e82 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -2017,7 +2017,9 @@ jobs: # from the sources it takes from the metering, settlement, ads, presentation, stitched and # routes corpora and the packs of pack-conformance, and no other file is there; wai-rs gives every vector its # stated outcome; every reason of §10 and every reused code refuses a vector, and so does - # each source kind's own extension; §10 lists exactly the reference's codes); then the + # each source kind's own extension; §10 lists exactly the reference's codes; under a tariff + # of version 3, jobs priced per operation and a chain that bills a job's work once, its + # output's receipt cited again by a re-run, with version 2 billing as before); then the # corpus's second implementation, receipt_ledger_verify.py, written from the extension's # text and sharing no code with wai-rs, which verifies each source with that corpus's own # second implementation (live_metering_verify.py, settlement_verify.py, live_ads_verify.py diff --git a/.github/workflows/wai-ledger.yml b/.github/workflows/wai-ledger.yml index e05ff5586..db49e3509 100644 --- a/.github/workflows/wai-ledger.yml +++ b/.github/workflows/wai-ledger.yml @@ -2,7 +2,8 @@ name: wai-ledger service # The WAI receipt ledger as a service (wai/wai-ledger, extensions/receipt-ledger.md §9): its # tests (the corpus's invoices issued byte for byte, the chain recovered from the directory -# after a restart, a source billed once, one process per directory), clippy with warnings as +# after a restart, a source billed once, under a tariff of version 3 a job's work billed once and +# its re-run billed, one process per directory), clippy with warnings as # errors, and its smoke checks over HTTP against an instance started here with the corpus's # seller key, the invoice's verification document checked by receipt_ledger_verify.py; on # Linux and on the macOS runner (the host the service would be deployed to). diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 3a65dfcba..aa213428b 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,44 @@ wire format may change and such changes are recorded here. ## Unreleased +### The receipt ledger prices media jobs per operation, and bills the work done + +- **Tariff version 3** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.6): each job price + also names an `operation` of the catalogue (`media.encode`, `media.decode`), and a job is priced by its serving + capability **and** its operation, the one its route record names. Within one capability an encode (a motion search, + then a codestream written) and a decode (a reconstruction) spend different energy for the same output units, so one + price per capability overpriced one or underpriced the other, and a decode could not be billed at its own price. A + job whose capability and operation the tariff does not price bills nothing. A version-2 reader cannot read a price + that names an operation (`op_len` and `operation` follow `capability`), so this is a version of its own: a tariff and + its invoices of version 3 (`WLT1`, `WLI1` with version byte 3, the invoice laid out as version 2's). +- **A chain bills a job's work, not its output's identity** (§5.2, §5.3, §6.9, §12): under a tariff of version 3 + the records billed once for a job are its route record and its hops' OER/2 receipts, new on every run; its output's + decode receipt (kind 15) is cited, and carried in its lines' citation digests, but is not billed once. The codecs + are deterministic, so the same request run again delivers the same output receipt from a new hop with new energy: + that re-run is billed, in the invoice that holds it or a later one, and its output receipt is cited again. A replayed + hop receipt or route record is refused as before (`record_rebilled` across the chain, `record_duplicate` within an + invoice), so two lines of one invoice cite one output receipt only under different hop receipts; two route records + of one job are two runs of it, no longer `record_overlap`. A tariff of version 2 reads, verifies and bills exactly as + before, an output once; a package keeps its own rule in every version (a delivered product, once per receipt). +- **The corpus** adds nine vectors: encode and decode of `int_motion` at their own prices, a re-run billed in the + next invoice of its chain, one output under two hops of one invoice billed for each, an operation the tariff does not + price, a replayed hop receipt refused, one hop under two lines refused, prices out of order by operation, a version-2 + invoice under a version-3 tariff, and the re-run refused under version 2. Its version-3 jobs are routed by the route + corpus's router under that corpus's seeds; `routes/vectors.json` is unchanged. `receipt_ledger_verify.py` reads + version 3 and its rules, requires the new mutated copies to fail, and drops two more of its checks (`operation`, + `output-once`); the ledger service (`wai/wai-ledger`) applies the chain rule of its tariff's version. +- **Fixed:** a job price whose capability (or, in version 3, operation) is not UTF-8 text is refused + `tariff_invalid`, as §4.3 reads it: its form is a rule, not the layout. The reference read it `malformed`, unlike the + text and `receipt_ledger_verify.py`; no vector held such a price. +- **Breaking:** `api:rust:wai::receipt_ledger::JobPrice::operation`: a job price states its operation, empty in + version 2. A struct literal adds `operation: String::new()`. +- **Breaking:** `api:rust:wai::receipt_ledger::JobFacts::operation`: a job source's operation, its route record's. +- **Breaking:** `api:rust:wai::receipt_ledger::check_across`: takes the tariff's version first, which decides what a + chain bills once and whether two records of one job overlap. +- **Breaking:** `record:corpus:wai/live-conformance/ledger/vectors.json`: the control `invoice_version` gives an + invoice of version 4, since version 3 is now defined, and is refused `unknown_version` as before; every other vector + keeps its outcome, and the version-3 vectors are added. + ### Media jobs in the receipt ledger, and the receipts of `wai.video.int_motion` written down - **The receipt ledger prices media jobs** ([extensions/receipt-ledger.md](extensions/receipt-ledger.md) §4.5): a diff --git a/wai/extensions/receipt-ledger.md b/wai/extensions/receipt-ledger.md index 2d661c3c5..3810907d5 100644 --- a/wai/extensions/receipt-ledger.md +++ b/wai/extensions/receipt-ledger.md @@ -23,7 +23,10 @@ > the delivery of each party under the one tariff with the bytes two parties were acknowledged > for twice. A tariff of version 2 also prices **media jobs** (§4.5): a job is billed from > its route record ([route-record](route-record.md)), its hops' OER/2 receipts and WAI's -> receipt of its output, in the units those receipts state. No new transport, no payment +> receipt of its output, in the units those receipts state. A tariff of version 3 prices a +> job by its capability **and its operation** (§4.6), since an encode and a decode spend +> different energy, and bills a job's **work** once, its route record and its hops' receipts, +> its output's receipt being the evidence a re-run may cite again (§6.9). No new transport, no payment > and no tax: an invoice is the recomputable figures a payment rests on. The reference implementation is the > `receipt_ledger` module of `wai-rs` (feature `receipt_ledger`) and the service > `wai/wai-ledger`; the corpus is [`live-conformance/ledger/`](../live-conformance/ledger/), @@ -112,7 +115,7 @@ records recomputes all three. | field | size | meaning | |---|---|---| | `magic` | 4 | `WLT1` (ASCII) | -| `version` | 1 | 1, or 2 for a tariff that prices jobs (§4.5) | +| `version` | 1 | 1; 2 for a tariff that prices jobs (§4.5); 3 for one that prices them per operation and bills their work (§4.6, §6.9) | | `seller` | 32 | the seller's key, which signs the tariff and every invoice under it | | `buyer` | 32 | the buyer's key, which accepts the tariff by signing it | | `start`, `end` | 8 each | u64: the period the tariff is good for | @@ -121,7 +124,7 @@ records recomputes all three. | `delivery` | 32 | four u64: millionths of a minor unit per 10^9 acknowledged bytes of each object class, in class order (settlement §7.1) | | `package` | 24 | three u64: millionths of a minor unit per package receipt, per entry, and per 10^9 stored bytes | | `ads` | 16 | two u64: millionths of a minor unit per presented impression, and per billable impression | -| `job` | 2 + Σ (18 + *c*) | version 2 only, absent in version 1: a u16 count *j*, then each job price (§4.5): `cap_len` (u8), `capability` (`cap_len` bytes), `unit` (u8), `divisor` (u64) and `price` (u64) | +| `job` | 2 + Σ (18 + *c*), in version 3 2 + Σ (19 + *c* + *o*) | versions 2 and 3, absent in version 1: a u16 count *j*, then each job price (§4.5, §4.6): `cap_len` (u8), `capability` (`cap_len` bytes), in version 3 `op_len` (u8) and `operation` (`op_len` bytes), then `unit` (u8), `divisor` (u64) and `price` (u64) | | `scope` | 2 + 33 × *n* | a u16 count *n*, then each entry: `kind` (u8) and `id` (32), in strictly ascending order of (`kind`, `id`) | | `seller_sig` | 64 | §4.2 | | `buyer_sig` | 64 | §4.2 | @@ -140,16 +143,17 @@ tariff the buyer did not sign bills nobody. A reader reads exactly the bytes it is given as one tariff, in the order of §4.1, and refuses at the first of these that holds: fewer than 4 bytes, `truncated`; a magic other than `WLT1`, -`malformed`; no fifth byte, `truncated`; a version other than 1 and 2, `unknown_version`; the +`malformed`; no fifth byte, `truncated`; a version other than 1, 2 and 3, `unknown_version`; the bytes end before a field the layout requires, `truncated`; bytes after `buyer_sig`, `malformed`. It then refuses as `tariff_invalid` a tariff that breaks any of these: - `start` is below `end`; `currency` is three capitals `A`–`Z`; - `seller` and `buyer` are not zero, and not the same key; - `scope` has at least one entry, in strictly ascending order of (`kind`, `id`), each `kind` 1 to - 4 (1 to 5 in version 2) and each `id` not zero; + 4 (1 to 5 in versions 2 and 3) and each `id` not zero; - in version 2, `job` has at most 255 prices, each as §4.5 requires, in strictly ascending - order of (`capability`'s bytes, `unit`). + order of (`capability`'s bytes, `unit`); in version 3, at most 255, each as §4.6 requires, + in strictly ascending order of (`capability`'s bytes, `operation`'s bytes, `unit`). Then the seller's signature, then the buyer's: `bad_key`, `bad_signature`. @@ -163,7 +167,7 @@ A scope entry names whose records the tariff bills: | 2 | a party's key | a settlement statement's `party` is the `id` | | 3 | a packager's key | a package receipt's `signer_pubkey` is the `id` | | 4 | an ad decider's or a stitcher's key | every receipt of an ads source has `signer_pubkey` the `id` (live-ads §3.4); a stitched source's records are accepted only when their `signer` is such an `id`: the verifier's **signers** (stitched-presentation §7.1) are the scope's entries of kind 4 | -| 5 | a router's key (version 2) | a job source's route record has `signer` the `id` (route-record §5.1) | +| 5 | a router's key (versions 2 and 3) | a job source's route record has `signer` the `id` (route-record §5.1) | The scope is what the buyer agreed to pay for: a record signed by a party the tariff does not name is refused `outside_scope` (§7.2 step 5), whoever submits it. @@ -185,6 +189,25 @@ A job the tariff has no price for, by its serving capability, is billed nothing, pays for a capability only at a price it signed. A package job stays a package source (§5.1), billed by §4.1's package prices. +### 4.6 Job prices per operation (version 3) + +A tariff of version 3 prices jobs as §4.5 does, and each job price also names an `operation`: +an operation of WAI's catalogue in route-record §2's form (two words joined by `.`, each a +lowercase ASCII letter and then lowercase letters and digits: `media.encode`, `media.decode`), +of at most 128 bytes. A job is priced by its serving capability **and** its operation, which is +its route record's `operation`, the one its request named (route-record §5.1). The reason is the +work: within one capability, an encode (a motion search, then a codestream written) and a +decode (a reconstruction) spend different energy and compute for the same output units, so one +price per capability overprices one of them or underprices the other, and a buyer pays each at +the price it signed for it. A job the tariff has no price for, by its serving capability and its +operation, is billed nothing, as a job whose capability a tariff does not price is (§4.5): a +price for one operation never bills another. + +Version 3 is a version of its own, not a change to version 2: a reader of version 2 cannot read +a job price that names an operation, and the buyer of a version-2 tariff signed version 2's +rules. Version 3 also bills a job's work rather than its output's identity (§5.2, §5.3, §6.9). +A tariff of version 2, its invoices and its sources read, verify and bill exactly as before. + ## 5. Sources ### 5.1 Kinds and their documents @@ -199,7 +222,7 @@ extension's verifier is given. Bytes are lowercase hexadecimal strings. | `package` | `receipt` (the text of `package.receipt.json`), `indexes` (the index files in the receipt's component order) | packaging §9, a receipt of version 2 against its indexes | | `ads` | `descriptor`, `grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts` (form-2 receipts in their JSON carriage), `pods`, `claims`, `creatives`, `issuers` | live-ads §9, with ad-presentation §9 under a policy that requires claims | | `stitched` | `records` (pod-input records), `claims` (stitched claims), `manifests`, `responses` (VAST and VMAP documents), `receipts` (form-1 receipts in their JSON carriage), `creatives`, `issuers` | stitched-presentation §7, its signers the tariff's (§4.4) | -| `job` (version 2) | `request`, `pricebook`, `policy`, `record`, `receipts`, `keys` (a route record's verification document, route-record §7.1, its `receipts` and `keys` not `null`) and `outputs`, a list of objects of exactly `receipt` (the text of a `wai.video.int_motion` decode receipt's JSON form, integer-payloads §15.2) and `codestream` (the `WIV1` payload it describes) | route-record §7, accepted with its hop receipts checked; then each output receipt read by integer-payloads §15.2 and re-derived from its `codestream` (§15.1 there), or `output_invalid` at `outputs` and its index | +| `job` (versions 2 and 3) | `request`, `pricebook`, `policy`, `record`, `receipts`, `keys` (a route record's verification document, route-record §7.1, its `receipts` and `keys` not `null`) and `outputs`, a list of objects of exactly `receipt` (the text of a `wai.video.int_motion` decode receipt's JSON form, integer-payloads §15.2) and `codestream` (the `WIV1` payload it describes) | route-record §7, accepted with its hop receipts checked; then each output receipt read by integer-payloads §15.2 and re-derived from its `codestream` (§15.1 there), or `output_invalid` at `outputs` and its index | A source is accepted when its extension's verifier accepts it; for `ads`, when the outcome is **accept** (live-ads §9.4), a **gap** being `source_gap`. A `stitched` source is always @@ -211,8 +234,9 @@ refused `malformed`. Under a tariff of version 1, a `job` source is not a kind, `malformed`. A job source's **billed hop** is the attempt of its route record that is billed (route-record -§5.2), if any; its **serving capability** is that attempt's candidate's `capability`, and its -**figure** is the figure of that attempt's OER/2 receipt: the unmetered marker +§5.2), if any; its **serving capability** is that attempt's candidate's `capability`, its +**operation** is its route record's `operation`, and its **figure** is the figure of that +attempt's OER/2 receipt: the unmetered marker (oer-receipts §3.1), or `energy.joules_total` in microjoules, rounded to the nearest and a half to the even one, with the receipt's `energy.class`. A billed hop's receipt whose figure has no class, or rounds below 1 µJ or past what a u64 holds, cannot be stated in a row @@ -241,9 +265,9 @@ the others are the ledger's own: | 11 | an invoice, `WLI1` | its digest (§6.7, as [revenue-share](revenue-share.md) §7.4 cites it) | no source: cited by revenue-share §7.4 | | 12 | a cue event | its event digest ([opportunity-report](opportunity-report.md) §7.3, live-events §3.2) | no source: cited by opportunity-report §7.3 | | 13 | a room policy | its digest ([opportunity-report](opportunity-report.md) §7.3, live-ads §3.2) | no source: cited by opportunity-report §7.3 | -| 14 | an OER/2 receipt | the 32 bytes its `receipt_id` names (oer-receipts §5) | a `job` source (version 2): the receipt of each attempt of its record | -| 15 | a `wai.video.int_motion` decode receipt | its receipt hash (integer-payloads §15.1) | a `job` source (version 2): each output receipt | -| 16 | a route record | its digest (route-record §5.3) | a `job` source (version 2): its record | +| 14 | an OER/2 receipt | the 32 bytes its `receipt_id` names (oer-receipts §5) | a `job` source (versions 2 and 3): the receipt of each attempt of its record | +| 15 | a `wai.video.int_motion` decode receipt | its receipt hash (integer-payloads §15.1) | a `job` source (versions 2 and 3): each output receipt | +| 16 | a route record | its digest (route-record §5.3) | a `job` source (versions 2 and 3): its record | Kinds 17 to 127 are unassigned. Kinds **128 to 255 are reserved for private and provisional use**: an implementation's own records, such as a hosted service's provisional records carried @@ -255,9 +279,13 @@ digest WAI defines covers one. A source's **head** is its receipt, statement or package receipt; an `ads` source's heads are its receipts, a `stitched` source's its accepted records, and a `job` source's its route record. A head of one source that another source of the invoice also contributes is refused `record_duplicate`; a per-hop record, which a room receipt and a statement can both name, is -contributed once. An OER/2 receipt or an output receipt a `job` source contributes that an earlier -`job` source of the invoice also contributes is refused `record_duplicate` too: one hop serves one -job, and one output is one job's, so neither is billed for two. +contributed once. An OER/2 receipt a `job` source contributes that an earlier `job` source of the +invoice also contributes is refused `record_duplicate` too: one hop serves one job, so no hop is +billed for two. Under a tariff of version 2, so is an output receipt: one output is one job's. +Under a tariff of version 3 it is not: an output receipt is the evidence of what a job delivered, +not the work, and the same request run again on a deterministic codec delivers the same output +under the same receipt from a new hop that did the work again (§6.9). Two lines of one invoice +that cite one output receipt therefore cite different hop receipts, whichever lines they are. ### 5.3 Placement in time @@ -272,9 +300,11 @@ A source is **in** a period when: - `job`: its route record's `accepted` is within it. Two room receipts of one room whose sessions overlap, two statements of one party whose periods -overlap, two receipts of the invoice's ads sources naming one break and one session, and two -route records of one router naming one `job`, bill one thing twice: the later is refused -`record_overlap`. +overlap, two receipts of the invoice's ads sources naming one break and one session, and, under +a tariff of version 2, two route records of one router naming one `job`, bill one thing twice: +the later is refused `record_overlap`. Under a tariff of version 3, two route records of one job +are two runs of it, each billed from its own hop, and no hop twice (§5.2): a job's id names what +was asked, not the work, and a request run again is worked again. ## 6. The invoice @@ -283,7 +313,7 @@ route records of one router naming one `job`, bill one thing twice: the later is | field | size | meaning | |---|---|---| | `magic` | 4 | `WLI1` (ASCII) | -| `version` | 1 | its tariff's version, 1 or 2 | +| `version` | 1 | its tariff's version, 1, 2 or 3 | | `seller`, `buyer` | 32 each | the tariff's | | `tariff` | 32 | the tariff's digest | | `prev` | 32 | the digest of the invoice before it in its chain (§6.9); zero for the first | @@ -292,7 +322,7 @@ route records of one router naming one `job`, bill one thing twice: the later is | `records` | 4 + 33 × *r* | a u32 count, then each record (§5.2): `kind` (u8) and `id` (32), in strictly ascending order of (`kind`, `id`) | | `lines` | 2 + 94 × *l* | a u16 count, then each line (§6.3), in strictly ascending order of (`source_kind`, `source`, `unit`) | | `energy` | 1 + 18 × *e* | a u8 count, then each row (§6.6): `scope` (u8), `tag` (u8), `figures` (u32), `joules` (u64, µJ), `max_ppm` (u32), in strictly ascending order of (`scope`, `tag`) | -| `unmetered` | 16 or 20 | four u32: the unmetered figures of scopes 1 to 4 (§6.6); in version 2, five, scopes 1 to 5 | +| `unmetered` | 16 or 20 | four u32: the unmetered figures of scopes 1 to 4 (§6.6); in versions 2 and 3, five, scopes 1 to 5 | | `amount` | 8 | u64: the sum of the lines' amounts | | `sig` | 64 | §6.7 | @@ -307,7 +337,7 @@ A line is 94 bytes: | field | size | meaning | |---|---|---| -| `source_kind` | 1 | 1 room, 2 delivery, 3 package, 4 ads, 5 job (version 2) | +| `source_kind` | 1 | 1 room, 2 delivery, 3 package, 4 ads, 5 job (versions 2 and 3) | | `source` | 32 | the room id, the delivering party, the packager, the ad decider or the router | | `unit` | 1 | the unit (below) | | `quantity` | 8 | u64: the quantity of the unit the records state | @@ -332,7 +362,7 @@ each: | 3 | 3 | `stored_bytes` | their `stored_bytes` | `package` 2 | 10^9 | | 4 | 1 | `presented_impressions` | under a policy that requires claims, the impressions that count (ad-presentation §9.2); of a stitched source, the presentations of its accepted records' ads (stitched-presentation §7.4), one per session | `ads` 0 | 1 | | 4 | 2 | `billable_impressions` | under a policy that does not, the billable impressions (quartile at least 1) | `ads` 1 | 1 | -| 5 | *k* | the *k*-th job price's unit | over the router's job sources with a billed hop whose serving capability is that price's `capability`: one per job (`jobs`), or their output receipts' `work_pixel_ops` (`subpixels`) | the *k*-th job price's `price` | its `divisor` | +| 5 | *k* | the *k*-th job price's unit | over the router's job sources with a billed hop whose serving capability is that price's `capability` (and, in version 3, whose operation is its `operation`): one per job (`jobs`), or each job's output receipts' `work_pixel_ops` (`subpixels`) | the *k*-th job price's `price` | its `divisor` | There is one line for each source and unit whose quantity is above 0, a price of 0 included, and none for a quantity of 0. A delivery line's bytes are those its party's downstream parties signed @@ -360,12 +390,17 @@ Each line cites the records it came from: for `presented_impressions`, each counted impression's own claim; of a stitched source, its stitcher's accepted records, and each presented own claim of their ads; - a job line: for each job it counts, its route record and its billed hop's OER/2 receipt, and, - for `subpixels`, each of its output receipts whose `work_pixel_ops` is above 0. + for `subpixels`, each of its output receipts whose `work_pixel_ops` is above 0. In version 3, + an output receipt that two jobs of the line delivered is cited once and its `work_pixel_ops` + counted for each (§6.9). `cited` is their number, and `cites` is SHA-256 over the 17 bytes `wai:ledger-cites` (ASCII) and 0x01, followed by each cited record's `kind` and identity, in ascending order of (`kind`, identity). Every cited record is in `records`. A buyer recomputes a line from the records it -cites alone, and shows one line to an auditor with its records and nothing else. +cites alone, and shows one line to an auditor with its records and nothing else. One line is +the exception: a version-3 job line two of whose jobs delivered one output receipt names each job +and that output once, and how many of its jobs delivered the output is stated by their sources, +each of which pairs its job with its outputs (§5.1). ### 6.6 Energy @@ -377,7 +412,7 @@ Each record that states an energy figure states it once, in one **scope**: | 2 `delivery_hops` | the per-hop records named by the statements and by no room receipt of the invoice | | 3 `packaging` | the package receipts' `joules_micro` and class | | 4 `ad_decisions` | the ad receipts' `joules_micro` and class; a pod-input record states no figure | -| 5 `jobs` (version 2) | each job source's billed hop's figure (§5.1): the hop that served the job, in the class its OER/2 receipt states; a job with no billed hop states none | +| 5 `jobs` (versions 2 and 3) | each job source's billed hop's figure (§5.1): the hop that served the job, in the class its OER/2 receipt states; a job with no billed hop states none | For each scope, `unmetered` counts its figures that are the unmetered marker, which are in no row and **MUST NOT** be presented as zero joules. For each scope and each tag among its metered @@ -408,10 +443,11 @@ invoice with a row of tag 0. It refuses as `private_kind` an invoice with a reco - `start` is below `end`; `currency` is three capitals `A`–`Z`; - each list is in the strictly ascending order §6.1 gives it; each record's `kind` is 1 to 9 (in - version 2, also 14 to 16: kinds 10 to 13 are no ledger source's, §5.2); -- each line's `source_kind` is 1 to 4 (1 to 5 in version 2), its `unit` a unit of its kind (§6.3; - for kind 5, at least 1), its `quantity` and `cited` above 0; -- each row's `scope` is 1 to 4 (1 to 5 in version 2), its `tag` 2 to 5 and its `figures` above 0. + versions 2 and 3, also 14 to 16: kinds 10 to 13 are no ledger source's, §5.2); +- each line's `source_kind` is 1 to 4 (1 to 5 in versions 2 and 3), its `unit` a unit of its kind + (§6.3; for kind 5, at least 1), its `quantity` and `cited` above 0; +- each row's `scope` is 1 to 4 (1 to 5 in versions 2 and 3), its `tag` 2 to 5 and its `figures` + above 0. Then the signature (§6.7): `bad_key`, `bad_signature`. @@ -419,9 +455,29 @@ Then the signature (§6.7): `bad_key`, `bad_signature`. The invoices of one tariff form a **chain**: each names in `prev` the invoice before it, the first names zero, and their periods do not overlap. A ledger keeps one chain per tariff. No record is in -the `records` of two invoices of a chain: a record billed once is billed never again, whatever -period a later invoice states, and a package receipt, which states no time, is billed in the -invoice that first holds it. +the `records` of two invoices of a chain, but, under a tariff of version 3, an output receipt +(kind 15): a record billed once is billed never again, whatever period a later invoice states, and +a package receipt, which states no time, is billed in the invoice that first holds it. + +What a chain bills once for a job is its **work**, and its output's receipt is the **evidence**. +A run of a job is a hop that did it: new energy, and a new OER/2 receipt and route record, new +on every run. An output receipt is not the work: the codecs are deterministic, so the same request +run again delivers the same output under the same receipt. Under a tariff of version 3, therefore: + +- the records a chain bills once for a job are its route record and its hops' OER/2 receipts, + the billed hop's among them; +- an output receipt is cited, and carried in its lines' citation digests, but is not billed once: + a later invoice may list it again for another job's hop; +- a re-run whose route record and hop receipt are new is billed, in the invoice that holds it; +- a source with a hop receipt or a route record the previous invoice holds is refused + `record_rebilled`, and one with a hop receipt an earlier source of the invoice gives is refused + `record_duplicate` (§5.2): a replay of either is never billed; +- two lines of one invoice may cite one output receipt only under different hop receipts. + +Under a tariff of version 2, an output receipt is billed once as well, and a re-run of a job +whose output an invoice of the chain holds is refused, as that tariff's buyer signed it. A +package keeps its rule in every version: it is billed as a delivered product, once per package +receipt, which is a rule of its own and not a job's. ## 7. Verification @@ -453,9 +509,10 @@ It runs these steps and stops at the first refusal, which it reports with the re that states a tariff states this one (§3, `tariff_conflict`); and it is in the invoice's period (§5.3, `outside_period`). 6. **Across sources**, at `source` and the index of the later: a head contributed twice - (`record_duplicate`); a receipt, statement or ad receipt overlapping an earlier one (§5.3, - `record_overlap`); and a record that is in the previous invoice's `records` - (`record_rebilled`). + (`record_duplicate`); a receipt, statement or ad receipt overlapping an earlier one, or, in + version 2, a route record of the router and job an earlier one names (§5.3, + `record_overlap`); and a record that is in the previous invoice's `records`, in version 3 an + output receipt excepted (§6.9, `record_rebilled`). 7. **The figures**, at `invoice`, recomputed from the sources as §6.2 to §6.6 compute them and compared in this order: 1. a line whose source and unit no source gives, or that states a larger quantity than the @@ -472,9 +529,10 @@ A figure that cannot be held in its field is a difference. - **refuse**: the reason and where. - **accept**: the invoice's digest; the tariff's digest; the currency and the period; the number of records; each line with its source kind, source, unit name (and, for a job line, the job - price's `capability`), quantity, price, amount and its cited records; each energy row by scope and class label with its figures, joules and `max_ppm`; each - scope's unmetered figures and coverage (scopes 1 to 4, and in version 2 scope 5); the amount; and - the reports of §8. + price's `capability`, and in version 3 its `operation`), quantity, price, amount and its cited + records; each energy row by scope and class label with its figures, joules and `max_ppm`; each + scope's unmetered figures and coverage (scopes 1 to 4, and in versions 2 and 3 scope 5); the + amount; and the reports of §8. ## 8. Reports @@ -586,7 +644,8 @@ from the corpora of the extensions it reads (`live-conformance/metering/`, `live-conformance/stitched/`, `live-conformance/routes/` and `pack-conformance/packs/`), with delivery statements of two parties signed for it and, for each media job, a sink's `wai.video.int_motion` decode receipt of its output over a payload of -`wai-rs/tests/vectors/int_video/`. The sources are +`wai-rs/tests/vectors/int_video/`, and, for version 3, the route records of a decode, a re-run, a +replayed hop receipt and a claimed one, routed and sealed under the route corpus's own seeds. The sources are held once, by name, and each vector is a verification document (§7.1) naming its sources, with the outcome §7.3 gives it: @@ -600,7 +659,12 @@ outcome §7.3 gives it: version 2, a month of media jobs one router routed, beside a package: an `int_motion` job billed per job and per subpixel of its output's receipt, a failover billed at the fallback's price, a job served past its deadline and one every allowed hop failed, which bill nothing, and a job - whose serving hop was unmetered, counted unmetered in the jobs' scope. + whose serving hop was unmetered, counted unmetered in the jobs' scope; and, under a tariff of + version 3, the same jobs priced per operation with an `int_motion` decode beside the encodes, + each at its own price; the next invoice of that chain, billing the first job run again, its + route record and hop receipt new and its output receipt one the first invoice holds; the job + and its re-run in one invoice, their shared output receipt cited once and its subpixels billed + for each; and a decode under a tariff that prices only the encode, which bills nothing. - **Refused**: each reason of §10 at least once, each vector a control of one defect; a source of each kind outside the scope, a room receipt and a statement under other prices, and a source of each timed kind outside the period; a refusal of each source kind by its own extension; and, for @@ -608,7 +672,10 @@ outcome §7.3 gives it: version-1 tariff, a version-1 invoice under a version-2 tariff, a job given twice, routed twice, and claiming another job's output, and a job line and a jobs row that do not recompute; and an invoice of each version listing a record of a kind reserved for private use, and one listing - another text's kind. + another text's kind; and, for version 3, job prices out of order by operation, a version-2 + invoice under a version-3 tariff, a hop receipt the previous invoice billed brought again + under a new route record, and one hop receipt claimed by two jobs billed on two lines; and, + under a tariff of version 2, the same re-run, refused because its output receipt was billed. `wai_receipt_ledger_vectors verify` and the test `tests/receipt_ledger.rs` check that the file regenerates byte for byte, that `wai-rs` gives every vector its outcome, that every reason of §10 @@ -633,6 +700,16 @@ copy of the corpus to fail. statement period and one viewer's break once (`record_overlap`), and a record once in a chain (`record_rebilled`). A verifier given one invoice checks it against the one before it; a buyer holding the chain checks every link. +- **Work, not identity.** Under a tariff of version 3 a job is billed for the work done, its + route record and its hops' OER/2 receipts, once per chain, and its output's receipt is the + evidence of what it delivered (§6.9). A deterministic re-run does the work again, a new hop, + new energy, a new receipt, and is billed; a replayed hop receipt or route record is refused, + which is what keeps a job from being billed twice. A seller cannot bill a job again by citing + its output again: every billing needs a hop receipt no invoice of the chain holds. What a re-run + rests on is that hop's receipt: a router and a hop that collude can bill a re-run that never + ran, as a party and a downstream key can bill bytes (settlement §13). Whether a request is + worked again or answered from outputs still kept is the router's decision, not the ledger's; + the ledger bills what was worked. A tariff of version 2 keeps billing an output once. - **What the figures rest on.** An invoice is as true as its records: a delivery line rests on acknowledgements, so a party and a downstream key that collude can bill bytes that never moved, as settlement §13 says; a room line rests on per-hop records and the room's events; an ad line diff --git a/wai/live-conformance/ledger/README.md b/wai/live-conformance/ledger/README.md index bc9f60566..3e7c6bf44 100644 --- a/wai/live-conformance/ledger/README.md +++ b/wai/live-conformance/ledger/README.md @@ -43,7 +43,18 @@ The accepted vectors: billed hop and their output's decode receipt (`work_pixel_ops`); the failover is billed at the fallback's price; the job served past its deadline and the one every allowed hop failed bill nothing; the jobs' energy is the serving hops' figures, a counter row and a model row, and the - job whose serving hop was unmetered is counted unmetered (coverage partial). + job whose serving hop was unmetered is counted unmetered (coverage partial); +- `jobs_priced_per_operation` (version 3, §4.6): the same jobs under a tariff of version 3, which + prices each job by its serving capability and its operation, with a decode of + `wai.video.int_motion` beside the encodes: the encodes and the decode are billed per job and per + subpixel at their own prices, and the failover at the fallback's encode price; +- `a_rerun_billed` (version 3, §6.9): the next invoice of that chain bills `job-0001` run again + two days later, a new route record and a new hop receipt, its output the decode receipt the first + invoice already lists: the work is billed once, and the output is the evidence, cited again; +- `one_output_under_two_hops` (version 3): `job-0001` and its re-run in one invoice: both billed, + the output receipt they share cited once and its subpixels billed for each hop; +- `an_operation_the_tariff_does_not_price` (version 3): a tariff that prices `int_motion`'s encode + and not its decode: the decode bills nothing, and its hop's energy is stated. Every other vector is refused, a control of one defect: each reason of §10, the reused codes, and a refusal of each source kind by its own extension. @@ -62,7 +73,10 @@ manifests, responses and receipts they name, by their bytes), media jobs from `served_after_the_deadline`, `served_by_an_unmetered_hop`, `every_allowed_survivor_failed`, `served_by_a_hop_whose_figure_states_no_class`, `a_failed_hop_billed`, `an_unknown_energy_ranks_last`), each `int_motion` job's output a decode receipt sealed here over -`wai-rs/tests/vectors/int_video/int_video_bitstream.wiv1`, and the +`wai-rs/tests/vectors/int_video/int_video_bitstream.wiv1`; the version-3 jobs (`job_decode`, +`job_rerun`, `job_rerun_replays_its_hop`, `job_claims_another_hop`) routed here by that corpus's +router under its price book and policy, their hop receipts sealed under its hop key, the replayed +and the claimed one being `served_first_try`'s own; and the packages `video-base-01` and `video-staged-01` of [`pack-conformance/packs/`](../../pack-conformance/packs/). When one of those corpora is regenerated, so is this one. Nothing here was fetched from outside the repository. diff --git a/wai/live-conformance/ledger/vectors.json b/wai/live-conformance/ledger/vectors.json index 394512aa7..327dc0c47 100644 --- a/wai/live-conformance/ledger/vectors.json +++ b/wai/live-conformance/ledger/vectors.json @@ -1074,6 +1074,40 @@ }, "outputs": [] }, + "job_claims_another_hop": { + "kind": "job", + "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0008\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[{\"divisor\":1,\"price\":150000000,\"unit\":\"seconds\"}],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.encode\",\"prefer\":{\"quality\":null,\"sort\":null,\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", + "pricebook": "{\"currency\":\"EUR\",\"entries\":[{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\"},{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"region\":\"us-east\",\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\"},{\"capability\":\"wai.audio.opus\",\"divisor\":1,\"endpoint\":\"opus.alpha\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":1000,\"provider\":\"prov-alpha\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":50,\"unit\":\"seconds\"}],\"issued\":1792996400000000,\"kind\":\"wai-route-pricebook\"}", + "policy": "{\"exclude\":[\"prov-x\"],\"keys\":[\"price\",\"quality\",\"ttfb\",\"energy\"],\"kind\":\"wai-route-policy\",\"max_attempts\":3,\"name\":\"lowest-cost\",\"version\":3}", + "record": "{\"accepted\":1793000000000000,\"attempts\":[{\"billed\":false,\"end\":1793000002000000,\"endpoint\":\"im.alpha\",\"error\":\"server_error\",\"hop\":1,\"not_billed\":\"failed\",\"outcome\":\"failed\",\"receipt\":\"sha256:788f5f9baac2a827b270d23fe24ace09851cd9a5fdb3d69fc71eaa4fc6c130a1\",\"start\":1793000000001000},{\"billed\":true,\"end\":1793000009000000,\"endpoint\":\"av1.alpha\",\"error\":null,\"hop\":2,\"not_billed\":null,\"outcome\":\"served\",\"receipt\":\"sha256:e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8\",\"start\":1793000002000100}],\"candidates\":[{\"capability\":\"wai.video.av1\",\"cost\":480000000,\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy\":10000000,\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"rank\":2,\"region\":\"eu-west\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.av1\",\"cost\":800000000,\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit_price\",\"value\":200000000}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.int_motion\",\"cost\":1036800,\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy\":31104000,\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"rank\":1,\"region\":\"eu-central\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":691200,\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"retention\",\"value\":null},{\"limit\":\"ga\",\"value\":false}],\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":null,\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit\",\"value\":\"frames\"},{\"limit\":\"spend\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":345600,\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"policy\",\"value\":\"prov-x\"}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":518400,\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"rank\":null,\"region\":\"us-east\",\"removed\":[{\"limit\":\"region\",\"value\":\"us-east\"},{\"limit\":\"residency\",\"value\":null},{\"limit\":\"training\",\"value\":null},{\"limit\":\"retention\",\"value\":30}],\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":1555200,\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy\":24192000,\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"rank\":null,\"region\":\"eu-central\",\"removed\":[{\"limit\":\"consent\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\",\"via_fallback\":false}],\"capability\":\"wai.video.int_motion\",\"deadline\":1793000030000000,\"decision\":{\"decided_by\":\"price\",\"runner_up\":\"av1.alpha\"},\"fallback\":\"wai.video.av1\",\"job\":\"job-0008\",\"kind\":\"wai-route-record\",\"operation\":\"media.encode\",\"order\":[\"im.alpha\",\"av1.alpha\"],\"policy\":{\"digest\":\"09d08acf6a76fa45d822f10a6806b21ad1c41172bcccfcd08548e59567c4c741\",\"name\":\"lowest-cost\",\"version\":3},\"pricebook\":{\"currency\":\"EUR\",\"digest\":\"af132fd169de93674a59d1735c840e539f9144f3a26dc18bee089efe57bcf108\",\"issued\":1792996400000000},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}],\"request\":\"e5327072dc33d75bc48f5fdc86b5924b239389131ab2b2537b97cef0190eec22\",\"sig\":\"ab8db2417d7bf7e17fc2b59cb09daf76b88f95bf6260f7ed86305583d526aa3e144b8e565f64a4c09e52c8c1ff92dc703c3fe005961c4fce5af9d2ce0dcfc007\",\"signer\":\"5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb\",\"version\":1}", + "receipts": [ + "{\"oer\":\"2.1\",\"receipt_id\":\"sha256:788f5f9baac2a827b270d23fe24ace09851cd9a5fdb3d69fc71eaa4fc6c130a1\",\"kind\":\"job\",\"capability\":\"oer.job.media.encode\",\"intent\":\"replicate\",\"subject\":{\"inputs\":[\"sha256:8c8e2058048597568a20e258937051b02f67ae21f539797fe7b166522a581560\"],\"output\":\"\"},\"work\":{\"status\":-1,\"outcome\":\"failed\",\"latency_s\":\"1.0\"},\"energy\":{\"joules_total\":null,\"boundary\":\"the hop's host\",\"breakdown\":{\"compute_j\":null,\"movement_j\":null,\"idle_alloc_j\":null,\"shared_alloc_j\":null,\"upstream_j\":null},\"method\":\"unmetered\",\"temperature_k\":\"300\"},\"provenance\":{\"how\":\"projected\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"projected\",\"input\":\"im.alpha\"}},\"floor\":{\"bits_committed\":0,\"bits_delivered\":\"0\",\"floor_j\":\"0\",\"decades_above_floor\":\"0\"},\"chain\":{\"prev_hash\":\"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\"receipt_hash\":\"sha256:4c63ec697f60ab65213359f7a25d65fc77645b03c351720c9f96b094d289db15\",\"signer\":\"ed25519:route-corpus-hops\",\"signature\":\"adec31913b0b4846655b81c7db325aa24b9c77b1fcde23bd2a3139e196ac4f24f7f94ee1ede9743d5a9ad50768b3aef54e4c10c3ad0973f0acb68f269c25260b\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}", + "{\"oer\":\"2.1\",\"receipt_id\":\"sha256:e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8\",\"kind\":\"job\",\"capability\":\"oer.job.media.encode\",\"intent\":\"replicate\",\"subject\":{\"inputs\":[\"sha256:0d69497c0597b96e23e625790c9c89527c1e1d50744c9acc9b901a0694060588\"],\"output\":\"blake3:8c05ad23f29dac4fa0cb00b9cb758eab315e7cbd6458cb04ad8eaaae24ae67d5\"},\"work\":{\"status\":1,\"outcome\":\"completed\",\"latency_s\":\"1.0\"},\"energy\":{\"joules_total\":\"31.104\",\"boundary\":\"the hop's host\",\"breakdown\":{\"compute_j\":\"31.104\",\"movement_j\":\"0\",\"idle_alloc_j\":\"0\",\"shared_alloc_j\":\"0\",\"upstream_j\":\"0\"},\"method\":\"hardware_meter\",\"temperature_k\":\"300\",\"class\":{\"label\":\"OnChipCounter\",\"relative_ppm\":20000,\"window_us\":4000000,\"filtering\":\"Unknown\"}},\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"measured\",\"input\":\"im.alpha\"}},\"floor\":{\"bits_committed\":0,\"bits_delivered\":\"0\",\"floor_j\":\"0\",\"decades_above_floor\":\"0\"},\"chain\":{\"prev_hash\":\"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\"receipt_hash\":\"sha256:519736649ceb37a7bec8a0db6c87cb6e80fdb7e6a079fc7bd990aaddd7152a3b\",\"signer\":\"ed25519:route-corpus-hops\",\"signature\":\"80569744db45809f85f0d80de50d473419cbdfa6603e02a44f3c060be515e9d9579baee9eedd7c1162dcce2817d2b941a854b7930290edc77a51b2dbde1d1508\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}" + ], + "keys": { + "ed25519:route-corpus-hops": "733470a6f6824eef91aa6b6fe4094e4e3b864ce6a368122223cf718fdc57fc57" + }, + "outputs": [] + }, + "job_decode": { + "kind": "job", + "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0007\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[{\"divisor\":1,\"price\":150000000,\"unit\":\"seconds\"}],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.decode\",\"prefer\":{\"quality\":null,\"sort\":null,\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", + "pricebook": "{\"currency\":\"EUR\",\"entries\":[{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\"},{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"region\":\"us-east\",\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\"},{\"capability\":\"wai.audio.opus\",\"divisor\":1,\"endpoint\":\"opus.alpha\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":1000,\"provider\":\"prov-alpha\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":50,\"unit\":\"seconds\"}],\"issued\":1792996400000000,\"kind\":\"wai-route-pricebook\"}", + "policy": "{\"exclude\":[\"prov-x\"],\"keys\":[\"price\",\"quality\",\"ttfb\",\"energy\"],\"kind\":\"wai-route-policy\",\"max_attempts\":3,\"name\":\"lowest-cost\",\"version\":3}", + "record": "{\"accepted\":1793000000000000,\"attempts\":[{\"billed\":true,\"end\":1793000004000000,\"endpoint\":\"im.alpha\",\"error\":null,\"hop\":1,\"not_billed\":null,\"outcome\":\"served\",\"receipt\":\"sha256:a5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd45\",\"start\":1793000000001000}],\"candidates\":[{\"capability\":\"wai.video.av1\",\"cost\":480000000,\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy\":10000000,\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"rank\":2,\"region\":\"eu-west\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.av1\",\"cost\":800000000,\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit_price\",\"value\":200000000}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.int_motion\",\"cost\":1036800,\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy\":31104000,\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"rank\":1,\"region\":\"eu-central\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":691200,\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"retention\",\"value\":null},{\"limit\":\"ga\",\"value\":false}],\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":null,\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit\",\"value\":\"frames\"},{\"limit\":\"spend\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":345600,\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"policy\",\"value\":\"prov-x\"}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":518400,\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"rank\":null,\"region\":\"us-east\",\"removed\":[{\"limit\":\"region\",\"value\":\"us-east\"},{\"limit\":\"residency\",\"value\":null},{\"limit\":\"training\",\"value\":null},{\"limit\":\"retention\",\"value\":30}],\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":1555200,\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy\":24192000,\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"rank\":null,\"region\":\"eu-central\",\"removed\":[{\"limit\":\"consent\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\",\"via_fallback\":false}],\"capability\":\"wai.video.int_motion\",\"deadline\":1793000030000000,\"decision\":{\"decided_by\":\"price\",\"runner_up\":\"av1.alpha\"},\"fallback\":\"wai.video.av1\",\"job\":\"job-0007\",\"kind\":\"wai-route-record\",\"operation\":\"media.decode\",\"order\":[\"im.alpha\",\"av1.alpha\"],\"policy\":{\"digest\":\"09d08acf6a76fa45d822f10a6806b21ad1c41172bcccfcd08548e59567c4c741\",\"name\":\"lowest-cost\",\"version\":3},\"pricebook\":{\"currency\":\"EUR\",\"digest\":\"af132fd169de93674a59d1735c840e539f9144f3a26dc18bee089efe57bcf108\",\"issued\":1792996400000000},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}],\"request\":\"8ba0fa17fde6a701c28299e56ea306e00451aa54eda34231f9e67d690290fa30\",\"sig\":\"60b6cab0e8e3319fb23163e616b5c356e9e08c5c5e41bb4574acaaa529b2f9a53a9eb96cd36b673586932e77959df9ac28382f3b9e7ea3357627e15e7ad98609\",\"signer\":\"5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb\",\"version\":1}", + "receipts": [ + "{\"oer\":\"2.1\",\"receipt_id\":\"sha256:a5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd45\",\"kind\":\"job\",\"capability\":\"oer.job.media.decode\",\"intent\":\"replicate\",\"subject\":{\"inputs\":[\"sha256:61143413782993a03b76d1722775ddde0f5a81727afc6a0dcfddbe4bb00bd94f\"],\"output\":\"blake3:8c05ad23f29dac4fa0cb00b9cb758eab315e7cbd6458cb04ad8eaaae24ae67d5\"},\"work\":{\"status\":1,\"outcome\":\"completed\",\"latency_s\":\"1.0\"},\"energy\":{\"joules_total\":\"12.5\",\"boundary\":\"the hop's host\",\"breakdown\":{\"compute_j\":\"12.5\",\"movement_j\":\"0\",\"idle_alloc_j\":\"0\",\"shared_alloc_j\":\"0\",\"upstream_j\":\"0\"},\"method\":\"hardware_meter\",\"temperature_k\":\"300\",\"class\":{\"label\":\"OnChipCounter\",\"relative_ppm\":20000,\"window_us\":4000000,\"filtering\":\"Unknown\"}},\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"measured\",\"input\":\"im.alpha\"}},\"floor\":{\"bits_committed\":0,\"bits_delivered\":\"0\",\"floor_j\":\"0\",\"decades_above_floor\":\"0\"},\"chain\":{\"prev_hash\":\"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\"receipt_hash\":\"sha256:f6eb9db930138563fd5d1e4b67f9e145325b155fbbc9e306b165c789bc8875d1\",\"signer\":\"ed25519:route-corpus-hops\",\"signature\":\"027f4f3ffbf2f2b311fa4775ac727764c7eff3ee5d840dd8419bca2556a34f890a16ae36a64581d9eef5a63b1c11f222f81cb0d1dcefc51c3f4ffd88a3abe801\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}" + ], + "keys": { + "ed25519:route-corpus-hops": "733470a6f6824eef91aa6b6fe4094e4e3b864ce6a368122223cf718fdc57fc57" + }, + "outputs": [ + { + "receipt": "{\"profile\":\"wai.video.int_motion/receipt\",\"codestream_hash\":\"277865b574029f700f4fac023de3ab06ae598ee6f8ed726444e91442a6bd59e9\",\"clip_hash\":\"3492566e8e2682e3b3bac083b4db02ef8eba9a7f15d9faf0e27feee0f6eb488c\",\"h\":48,\"w\":48,\"frames\":[\"e1d8e8f8b5c534be1421e5858ed9137808ddb849bdff2bf397beb32568d8eb69\",\"ae7214babf5bbd6ec279434740f3271be43d2392b8167813fa557b5c4426fa14\",\"b3ef86db27b82d303bc6b612cb224ad55259b4f2e2c0461c74dcce188cd64b99\",\"3906f91010bf1b9ca14f6e2ac9b499589ac02ff4465e1d9764a22443a8b29c22\",\"bedb1538c2071d705d2d3ab2df2c569ffed440278494649f9bf1ec881561c519\"],\"merkle_root\":\"0f3fe4aa201c275c10289c22f31e7211b555627b33a251bbe33d094abada6508\",\"work_pixel_ops\":34560,\"joules_micro\":0,\"uj_per_kilopixel_op\":null,\"energy_provenance\":null,\"parent_receipt_hash\":null,\"signer_pubkey\":\"4a29b9b42b4c7b8d947b226d503cfa1ee8b69a5cd3e4cb88b30db023d6d2bb8c\",\"signer_id\":\"did:example:sink/job-0007\",\"sig\":\"1f15b01db9c2cfc6aa737c720f9269bf14cea7de770ca80b2c7ea1a524165f2f34483bbbfaec155afd586384d3e80bf15821385f426d0a3509eff7a9cc57100b\",\"receipt_hash\":\"901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f\"}", + "codestream": "57495631010100000500000030000000300000000800000006000000d5ffffff590000000000000004000000090000000f000000160000001e00000028000000330000004100000051000000640000007a00000094000000b3000000d8000000030100003601000073010000bb0100001002000074020000ea020000750300001a040000dd040000c3050000d2060000130800008e0900004e0b00005f0d0000d00f0000b21200001a160000201a0000e11e00007e240000202b0000f6320000383c00002747000012540000556300005c750000a38a0000aa9c0000edab0000d8b80000c7c3000009cd0000dfd4000081db00001ee10000dfe50000e5e900004ded00002ff00000a0f20000b1f4000071f60000ecf700002df900003cfa000022fb0000e5fb00008afc000015fd00008bfd0000effd000044fe00008cfe0000c9fe0000fcfe000027ff00004cff00006bff000085ff00009bff0000aeff0000beff0000ccff0000d7ff0000e1ff0000e9ff0000f0ff0000f6ff0000fbff0000ffff00000000010000000000feff0100ffff01000000000000000000000000000200fdfffbfffbfffcfffbfffdfffbff00000000000000000300fdfffbfffbfffbfffbfffcfffbfffffffbff000000000100fffffdfffbfffbfffbfffbfffbff00000000000000000000000000000000fcfffdfffbfffcff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0400fbff0400fcfffbff0100000000000000feff02000500fbfffbfffbfffbfffbfffdfffbff00000000ffff01000500fcfffefffbfffbfffbfffbfffbff000000000000000000000000fdfffbfffcfffbfffbfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdff0200fcff0300000000000000000000000000fbfffcfffbfffbfffcfffbfffffffdff00000000000000000500fbfffbfffbfffbfffbfffffffbff00000000000000000300fefffdfffbfffbfffbfffbfffbff000000000000000000000000fdfffdfffcfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0200fbff0400ffff010000000000000000000300fefffbfffbfffbfffcff0100feff00000000000000000100fffffffffbfffbfffbfffbfffbff00000000000000000000000001000000fdfffcfffbfffbff74420000280b35593c000000a214bea9451501b9ad0cbdbd9a309164812c7ece531911d14717c3dbbc1d27782217989f7218f20d121d7bd4ca1f3eb3ef1f71131429e2d1f626c3e7ab249c33af4fe2d2c042a6d2222fb9b3c460c33836571567e55a40c90777634123135bfb8e1357858f0dfd6cd3316844ae2fdb041518b067b9167f09231b1d6f1c16b7df311ea6450d1ea29eed1ecc320c2b747de0206b34c82a708a2525b67967473d53e643c54f6f2f42e6355aaafd6534aabbdf5fe1a866406fac26314f6ad812ef006e0dd2c39e124439f5306ba6e0396f65ff1627ef771e0071f21b088eae1efd0a7d22d53ada205042fa23135ba627dfdecc3c473a7353bf1ea7435671e53209bdc9581998ef568e94f260c2af006595d31a5d0301d30b0e96f22e27c43d162997a218be088f12dc9d0915dff4db17246c1a3c61066e3807ec553cb509743e78e21d250c19332ef30e3d2d4f94a0275d0ead4e59f7ff403f44c02ed1b145563b075b55c0ece2599ea8038a52c4e77287a40b3015595e178405f01716799f12508739158ce39537c6364439c2c61335c774823feb0e4a46484b29464146093e52ba6e2e3e5a0631121b992d5a46a848c8478e518c794638949ff05b4c57d23967cc4b57d3bb164147bebb2f4cd1452f40e1701817cec818f564ff13f4072317feda4f17504f253558977544d057484615df8028fbee312a5a55ed2c1c72d1257130d62be71dda51a0a97748680d2735181dc85721d3923f8d192f8794fcc96be998eb135e56840f812d952b26dc2f2f8537db1701de931a9e0b811c99606b1d7a72b81fe6796623b2e3442266abe726bfe71c26713b582970ff964812b3a7511b88474ad1a39d60fe77fb5b93faa43aa689e37f22cf656dcdde8d4a529b091869c19a199bf5af108ce21a33eb8ae43a8485f63a7c0ae7f3e9d6d3fbf35320f44e2572fb823070f38da5d5fb4cd37f3e167531443d7027341943de2d2e0dbe4918ce0f53b1e5a7368b2c3c6ff4ff9044663d54810e945168c3b1d019852e9412c0563e10ce0287359612c8365ce0fd34c5cdb9f290f0e017571aaef2f889ca179debd4f3065bae18380425f4d7c9dd18bb2cf626ea6faa24e3f4384b1da15a3d6df8b52cc90b935da2e4c23b9597355cee7f1945393e249b7b14ad729ff91432962e6218224895175df6f11b30546af34ef81f1840d4b4f24290fa19e90d41f3792fbd17b307eff2702189161f1552f429b1fc18154682320160642d6a8c304b9a74645336beb060762fd36780e26160b5380e4500bf316ab3b6594b76993031ace92b2efad9cb178983121b9d4845f4ba36071a9d404af3f9d4f2199f14cff37ed44717baad75f448628d19af3421f46a8b0f19184461f3ef7bc1178d00634bcf58624c6725ac619927f15545490f360e0fb877c6814c6b9ea2f952ccf3a3abad91da10ef63f3144a0f23340223ff35814709f4d3516c16e5bb75f3a3574719c25228f4b5a03e1636db84f49a58781762e821f35742a2181bc2f7f397ef311637054630ad43d250643a45395291b55b725ae5723bdd1b62eae7a445bd65319f19c63568c61a5b1206b34d3bed1dd63786de01f49a49affb2a9cebf3518ed9fb7f061bf402f2bdfb0bb9c0f344eaa4fb1bb58df3fb10a4fb6bcbb8f3bb45a0fbfbd11ef477204efb6ff5815087d668629a2a0c5d17e9236a698d0061d79fdb460b2f26a9c24f33529ee32911b3aa1515876dab37a91b8339f13feff22ac0af17a839caf36b8b36183cace0f27ca7e419927205f46713bd1740d4a5f49da36019a6baa1f4ceac2c19b530c1f2eaf4de19c6322751ef91df5b92e05f6533f2a760db246a7d1551e266a304d149e19ef5ab9b67c917279abc17e5eb69fbf214f2182014b4fb9ffe3e163705e1fba23dfe17e466a2fb4cd37c16dcc16ffbaaa6f61663923efb92173218345ca6fb512703175f6a95fb422b751962216358fc9aad68a047125962cd87430577849addf21174af2dc5499c4fcf8778b8293377e4ed3b12154cf3afbf46fb6ab6baf3dc41b0fb4cbe91f32c0b39fb2ef0f3f2262a42fbe21813f3cd5230fb476af8f3870f92fbefea62f47a7a92fb7234cdf297d11f5d8e12023c55bf666f4ef6005c8d58bc9549953d730699fd4ff8264fb23cf0ac1886051a1c4d05891af4edc2fbf792baf24163bdfbe896c3f32f65e3fb6023ebf2ca07d7fb66774bf4993ad1fbd99a01f30be2a6fbd34630f3275768fb7ef1a1f2f78972fbe65735554ebaa9390610c05a5315734087513d8c09862074f653884a096d36bad118d01b4762691789a9a4fb84456418eb0ca3fb4aa9b419eedf99fb865a481883a740fbccb302171e6b31fbdcc4a318d918a3fb2772ae179e15aefb5dcf1b17b3873ffb7673f4189ca65c5d080bdc43f1e1b278e9bbf0411aed639db279b76db39473596dd293164dfc0317fddbabfb866f2cf49fee5afb376ea4f3351975fb9baa96f394a45afbd65b6bf333ddaffb9de9fdf212c3d7fbeeead4f2262aa9fbf329a1f4fa10ccfbaee586f41f6791fbe442375aab92fb3ebf0cd287434e9d6e5b5143a22f6941660f17d858c6629c18cba73c167536ce1e8e78a81944d168f419b32118417e79f37ee3a4168ea062f3684ff8160f7514f3d3335e16d0c984f4e44f5716db178bf3f1e44f1765e6d5f39b589417ffd4263cb1b60b56568bf7410b9931975f374f710f5c7f4fa42325bcbf1b8b7648524f17d3a2bc1d17f181fb389846f3eaf47dfb8dede9f2ef7b22fb6cb646f4429fd8fb1ab5a5f4cd8e2ffb7dc74ef4164ac2fb955678f3aea1abfb511264f4825bd7fb2cdbf5f268318f5c44110177dd73146b7a24ef4d9557b1ac71c8ac855c5302606910468a110d2833d76a203d85cc1c16f25d02f36cc7671902a893f3c335ff186fdffbf23bdcd317eae2b3f3dfb9001a3c8153f3a8edd118ccf62bf491591a17bed596f344cc8916cbf73c423ef6c45d15bc03651b1912a83850df6979ba2a6292c2eebd5d91e87574332d1ec8640e210884defb523cdff2811f84fb900283f397e066fbb5636bf4b5af3afbe896e9f2ed4ea3fbaabb7cf42cfc51fb744940f4fc3dc7fb444010f3ebf46cfb40019ff4ba45685818c3743cc9991ca49c3d626e6cdb6d54ca28d67ab141e2599329871efca9c71b6444e22057e352f45dd8b917feaa52f42bfc1119743121f3694f9818db6c99f3d79c4618989917f426394b16467b0df3b1fb8f19521d21f48db4ca175a186e3dbffda754ced3533d92a3eea77ad2e668dd293056741d36bf0ff6d877943fff1c5c2be22145ecd620c546c1f3241b6b1627e58ef43e187e1951f342f42ab1b917ec3602f4f8a719192312a0f21e3ac51704a731f4981c4f168ba270f48c969c181fea6d40d92c977aaa69c464c1fe0b4fe4ac6e6dca35ef5f6aa19dc29fe940765b492821c377c822552bbe2132b22720a4a99cfbe66ccb18874f90fb5de48618e3bcc8fb7a9dfc174fca80fb033b2e19226885fb5cbaa018cf7481fb4bbe3b1690f45071cf3b545fc7dc4c460fb904a5f494e4710bb28c59b37201bc93c3b85cedf29ed26f8cac231756f41e282b6e208e484d416f57f1f2d56f3617c0a984f402e336196a0dacf401f23a182db681f4e431cd161e6be5f31d3a03177b0239f33755483d85c9d2624864d23f6bb7c5a69c7e2164d4f8ed6167d7fdbd05c2405ff28607d169ffc3238fcc5423c78c9346c614a93f473a2b17750683fbeed5a017f40664fb11227117579a43fbf768d91957ef44fb4994591639592ffb3898db867e356462c5292e4270ba30a3145c994dc63817c2b506ff790c88855f7484a1d36b3cb022b49dc01f39897e3db627e7453b7eaa47ee9c181a325d08f3aea14718447cc5f3ce9d6d187dabeef346352a168c4c9bf38ff00e189a54f0817d9e5867e164264acf9f89a0468af5865b1d0f5d44650780f7a37565a8f4c0ceb96346217425be3f42ddfb460186a443dc4f4f39e726fd2e11ac22fba65269162b0d79fbccdde017447c95fbeeead617a64c9a609f26b64105687099d3e8f96edd6e714832a030810d602862a462e0c908e95486467e4f657a255646fefc1b3d6612ce46f05c183a0aa2e63122e07a4fe77fc847dfdb81f4981cdd5a7e25095e2496ff7c0d982d61f1b45a40c98a5da72c8cd36f9bae985ea04c49826f6e0159848c46d189116c7425229a29dca5753180914230f7d87f2cd1e2294a23bd653c8f2c033180eb015dc4796a35ffb37e5d2a91a83e040b6f9551375c7318d897491c79c9bfd5a26b563d7258cbb89e8e7b06429a6fdbbb519019578d2cf852092d88bd0f2a78859a2f1ffe22519dd8cb507fba725b0e80ca5d8ed18c6087f6007e90e4ac6bd0224e504285c6ad5e2a4885c47c8e5608003a80cdac406c2de6e9d9c33b3d881826ea313c35a52f98773329023e2e2f644eed4c9fd91548d5819c5845a9c356a9e281411746cb75d8b9686bb07213513a6827af9e2d2582d3aaee5d0a1e1582463d496d83be81d6d0003b8586b2b42e814ecb2eb4b31b288dd08d4a6d71253f6f90872d7f2ee65a725a8f582404ef5c515ac37806644d66e187784bf25f74a43c7dd98747933f580caf12c97de8796af508f6ce38d753833fa7e3df673b922672b38b4a3750af52b2e56f46a293d2301edf675a87c40637fa24ff5de154a43f0bf38b907403487346618150454a15be8935547faf98945cf4502f7649b7fb6993be0d924ba7e87e5c44373d6a05774e1f29b0405671ec2d2912145352a9f45d41c46d625518ce670cea41617a3a074385e691abd1d9156a1d614d5fb54f097d82d80c614b6b92cfc8bbf18ecd7c70d5e7049d93d1e01d3208d0df28de97dd2f92b2384b83a3414fd99c8754c10b9e5dd1be2056f367ff898dec986d36884b48e0487aa87651128106a3085ec747b37f462bcf65f40a63d5ef3e7f7cceecdee0471af487ef9a024980f7d83f5964932f413bab5da896195fea92a65bb5e08a7a9bd68e57b2e1444310b9ef9f9b1b0772aea0435d832578761abacc584d3588cdec05e37241fdd2de96b2b67eb007b4e5f9d4ef2a8742af5235a45147c3cab7323fd344569e919373f12bd5554e858d3c3b21c295a1d79c4a4fb2a7b79f3ab4817fe0416134b576cce565e369d8770ed9c2924f8be0dfa9dd680fe77af006c4499b2cf442c3a8c83284915263e978a0343afd905502056046dc7ab6773c132468e7578f4cfde095720b8e785a720e4bc40228f788b0f27669314a5f931b46bd7903017f8e10890fe23b330b2f9651075346d3114f7bd0563a61aa2b540a0abd34d4772278bec85e703c9d214d3cb2b767c686f94e8433cfbe3fab6a77d952576510c5cc764230fd772df1fee062380a787e5e1da520a88c3e08b7c330d28d614ff959b861ecc7795fe87c22712ae536609e6bee407518e19d641c5b653da09e59b8800e89d17848545190aad2538d869bd717dbd8356a67798d9ec5e38da75c9caf09b6ab547b3941643edf2f02e64254d50e36362c41616048da6c425bf9318c40ad18669821964ec7cbd2b8714e8f5dc27626caff71a47bf64d476ce899f18c24556f7820202b9b3fc5f4e77ac5be8c1bd2e3492682645389b4b359578edf64a5a823571c07547829153e647579c44813fa75b8937cd5797249dd5803ee0386f9cab266917789d973c3c989dff5e0de1a65817d238899a57e9e48eb67c3d0321d4dee57cc778c5525bf8f542e90828a8b72085cd25067411b57e1a7d11a4f67da559757ad36fbb9f6f3c55cf6723acf5d58348dc055fce03fd929925ba3f6e40db6df8f0358c8a335945849649d783b52b0006124cf4042c3f16a854dd4a7672206614e215921735710d162219312c171da4b7649882772eb6ac18293790f8a6ad0eb8a98988789622911a626f368e825d45f92441e6949bc1b3e57a98a2136eda230713b4b6c6175aa129315646973336e21535ebbb8b8a549687da77a9c603751abd14723da67adc45dd8ecffb37995aabce4d743db9059e992aa435f87ebf2069785b20d1282e032f87ec90a5977b5b6eb7ce5e14e76816f6d8aa2a99d88a41cda754a8eed7955bdc677bda57b7c9308d8853619ea8795f972807be4487ad3c6ba8177c1897f6ea77077cd107d7b0c6eb9775068bd82b5994983546a8b789ca4fc7e30fe328a23afde89fc09b97d0dc4b878a796bc885f381176c0dedd7c3009868708763778074f1483b056e47b2d2e6f775e060d80613be47905e3c28197a2b2867f2087840da05978d324ca77d0d2c3838b448282d320027d0815b57a46e25386e546797aea5eba75c4748a77a68fe6769eb2d488d27ee978dcf669752152f679d5d4447e23d3767a076168784a958b79e1e6cb7e174abe7e0952b07a7e773b7e902dfe83891d937b603f948047799278c0e56181423661814ddcad7e50682c7bd2708e8791bd117bd3ad5d764ab9e9851e6116883d29db86051203825f38767ecf081c7edad64780ccc1007a419fa88156cbfb8877f0068a366cf17ab74967805410d1775c0ec084f283567a15ff0480a7bab185479db782a0e0027c86b9e58487498f89cbd316877cf25b801767548ac2714878a7a81c89caa1d885c0633589a0289b7edbef3e82b44ae88619e8d979c7e36585e1ac7d81e3dea681258e21841709f6844a668580588df77ba9ec97766b4321899d7c8f84f0b64b880840f485a1533d851767a57d4a544c83b06ffa7dc8b02f8747de5c7fbfcc1387f25f5a8a96d15289712fee8898a5408dfef27fb1fe337a96b4d9119e22eb87a7befdb497371c3793e2cc0aac6839ae8fdf2214934812e67a0fae55765e185b83fc9293793110d187dcce0e868708c67c2d76639bf921f880fcbef0956f67119f67b9559a35d5fb9b7c1ecc997a8cb6b78d5cf38c291b8a8b95baa28623c1f77ac0c1d18300ec467e9c6e94869bba197d07b41b8215ed717c33a3518a2a69078e6c91f6ad16430393da5506a55aa5d0037315fafc42ea2304936366d8c6a231a413a65288914a4878fdecb7772cc2ad83e9a63a83e6830079d593e17e3d6ab47b4116c192d8e6c68cfe19c9af3ec9c39005755d013a19f177bd9ea67a4d820f7a4e487c042afd998ae5808088048d1c7cc641628a98e6237ed84a2e7b4c74f67ceebe667d388172975a9ae8afa684c0785fc93092ba7ee176bb07db75b140997b504ba975669b2c865791fb8b03a2327b42ee2879af368c80edb7ac827a8a198220b7b9783bfb7779379079828ed03eb60c7aed938ef44a98ff16557824090280d15731762a53287703a3b77fe63bd287bed734819a8f2080aac48c8253cc6a75084b27795ed0db7599b7f18705651188868021a7a69f5eb33e38d97dc4fd1985411d5076c1516a76d8de15829ba8e8861fdc0f88aa783f7b9aac607cc4b52478d9b68f7d03c7a57974203779ea94e0764e1d8f9d8ac846a5eb023c84085245853201b782e10ac287e06f07774edcdb8465e7e67b4f201381f6363f8327e4cc7f49584576e49dec82aad62b849a18e57ddab9dfa1dfa972a6a2b7127f6d3b3278c7a2f881f0af98824535538250c2998900cf9b88288d2778e3a5a29607b80c8af1e1f989ebb4ae7dd81bfe750a478a76390a5b7b16d7b9993e2d42cf578d667a5a367b78e9f23b7b6d993680088c68756815f1891602298a214723abf3ed7f8f93785d8a60b62d7f9eb218890b14197c055eff793efab48717d3f3b1cef2697a0b0daf7db98d5c89fd22457b7ea64a823c2d0d7cbbea0f763d9900fbcd7c0993dd268a901da4f37cdd3adc772d0a257608a94f6af2ecfe8990c841a548ad0f0355bd0b81c8cd508356dde386276d8987e4c8a17eae24597cac2c9685aa9cc780e78af9a7128b35a7e9e43f76e95770804540f4893590636813988480ba2f139c9f0c55cf83ba19860744d489391892880cfe0388f03fa186e898e57bdd04e379cba8b8fab785aa7d307981941cf013891e2b397be66ac778effb7b751ce013894acb3e846196ee03de219d897ead1781e16f55890797767732717b7de9e02981710be977bbb463fcf62c2695d6343a977160de77eb897f7530e5f883ab1320711d583f8542c6327aa93f847c26e8ae8708649b7e8188db85b068b58886efc983bb205d897fa99c8468f85ef91cde28a52f53e29aed1c0d7f8d8119889b02c485d946ab664e23bf880482d580c720ae03112885d3da3ef5804946a079c54cd5818cf8d489cd3b628376f4f882b02727872cc97d22c71cbb9c815fe9843299d185b9f9268620db128aaa5432880e540f7cb833b28a1d5a6dd08ba32078d731a87a3e5420770bf03f8511f95e7a99760a8607a9747f5599b1fab4e9db81987a0e879b025b7c8d6f6978d1bc797d09ed717cc600de87c2131b8498b4da03fa05068478a4b287491ec17eaf7e0c897045e87ea6f48782d212087c4ae8261d0f6f8a88c0755084709f6979ab8ade7818d3fd752beacb841a37c8786bccc77c149311847b83808ad929b8893b3819847bd2c27b19348181d8928287ace0be81a2481c1b8b644980cf780f771cc794839d461e7a85872d8038e63b7e25abcd8744d4f87b76233aa0aa52ebd7dcf395882d352a8351529c88f9ed62882f1b3d8a2e67d87b6dfe6bf8af405a83b1c9a7781e89ca76b46e807b4dd55f83761f5e88a1414e8af230b1851248a69d78bd977f239dc87a30b68d7f59940889c67e1177fd92fd861dee88864cb1591be244838471e77587929508875f678889cfd22b885a6c6081052f387e616a7a95bd77bbb1b62c138a067367797a4de184bef4d981d4d4f285ee3ca07ddad63c7a8003257fe6a788793612258267cd397e8529538850e641762ba213796a18c57ed784b7a52fd508a5ff20d6869efabf75d4df5f8a0e37078a9a432585d194c478d9b3b2786337658a8ca57d878c9eac7a19cf377e01f38482fee8c97926b98085796ae89e949cc590dc73e87d162af77c02e8088786b96a808534d2869636ba7c3117c086a46f587ac74f0e7ad36cd0806677b183ef845e870b1448822f4aa087dbd2be7aca2314810d587d869815da803de1e7855f800b850d17f47e7a1e32f90724a69a3f96087f43ea127b6a182a7bfd348b830732597c4f97457bcdbd9182687a998096417287ac455787b3a8cc8246a11e03b6260486f3a3837effe43b89e505de9b95289f9334b3ab840b670f7df76c63884d12b084dc574579ebb4018104286881d85c12861f1510767af346872d88fb86b01c9176e2bb40dccaa58cfa8517f60409c52a9ecaebf394ed2204a1499d827e71164f818c8ce57bf993528a8dff547c5a128f78fdab6e8acf492e82caa1737939ff837903492c9e15b105837ccb6c96d364cea13718beab45cc63a4dbb267872ee9ba80433d0f89b982877b7aa0437c178bc57cc9db49812ee96d839b96ec7f3087a979a9fe4f8942f577a8b8aa46a5009723940ef30e8fdd242eaf54b30298a5b8d579ef96f8844b5bf77d5e8fd989c2dd558aa5f488833c105b7734aa5889366c7f787df91d806ecb87859e787d9079ff13a63b6b2a8b01851c8c0015a1a35783748ad81b147ec9a56b894303be88adf98775b82cea775b4f787ba9ec7675b3313d7e4d8de588bcf81d8969f4238d5e69be94a58fecaa4121839ae315a69706ddf68a3c56d97b031a4487a743597a04bca87a08df6284aa30628a2da15488948dab86f283667e2ded9e895a7ef0864a1ece89683dfe7946884da33af14bac9579888c54d36097cbe562899b6efe818e3cda7c6f139b800d6a0c842c6fd87c625ffc79621e5084fe4da9803ad79080c31a237694b56d762587fe7576d051879db28578da6af183a7ba867b838b1388eb4f7384144be37e35e7437bd647c37642b8047bac91fd8509359281e07abc84e38bcd7b2ef4177e16d7e4829677b57b6a3c1c86ffaeea7554a4c381e629cd76f4b5647af3c05781599421804f32568846ba047bf5f23485aa42797d24c8e1823a8f7d7ef5c3137cb9f9f17d9b79c5771320817ec486607559163f8aad94d487d96a8478604ae689faf40f884899338a71ca6f8052b3ca7934c7b279ef72b6833c2dcf87157d32833183f87e41530f850d5f2f7f6271647a08dfcc76e52240864fd8967f99ff6c81e8509d75436ca985506f117690e56b82115360820063ec842f79887b035b9e81b91d167da253ea76c2179082a27e977eeebe1482cc8030886afb707ce3c56a868b4f457870aa77776ea76c77fd1b65780504aa7812bf8d87f5700f7f4a42167a2d9aa485e8eba27a02a7f98750398e754c7f1c887ba3f17f01a0b27dcbba247c8da525856b67a37ac57b168437f122843eef50897e485d7de3cc4683d97ca77da1c35f7b1e65eb79f4b56a88bc1597889e305e7b62cb8975661d3d7e1b6dd27fe8bc4079c2f3808adaa7f8865c81887f9797c6852b613681a2003e7ca021df7d2683247de48b3d806d107e75b2c9a0758575197e3457fb84c129f88193ae7b80eb7e668391225c87e994eb86a62a357d2003e5822c5d6989eb3d3d89e4e5fb82447a8c7eab8af6799e71b775277fe87aca187d82215976893f7f4277bcbb3d7aeab82e82a4b01789650b0179d29442896852507cef31227c8015c47a1ebf568a159a7d7d797c5080c1995f7b5cdfcf7e7743fc81c01b817e6dfeb0770e1e5479b355ae7be5cf2d8905addf7b8d5d8e83f57b35834e903f77ed5de57da439b5794eca49779e6a8d7f03743b88c0da998279b2597da81b237ca9466478589160773815fa845326708352a80576e0dfed75c23b5d863773717622b332872989b17f3d4d2a786f7f6586858e6981eb61547e87ae438220d4b781ca47f88066367f77171f6a88bd47c281d086d77c69edcb7881ed067b7a423b84e9e73d85d3a99788c01bc985b61e8d76b1b04178483ff5807d0b4b7ddf077f82ea658b811eb49c8866adfa7e3dc41d861bf6b97b1da66c8463379688ba6c5778bf607b82eed76178484af37837732a8261abaf7cfe95a67f144ba6784c62e087cae92979238bb68159c3217f4d16e97d58695287eda5f579b737557e8a4804765d5d4888738cf9790689308957215e8069a5c6889500347e88bc947e6cb65577c719ef8680d45c7d6f8aef86feb97182e5059a7b7b08898a5ff7cc7ae2be8b81c89ebd89ad77c57532305b85370ef17ad5c9d68482fba7880ffdf18283140b8a546afd7f4809617e5e7dd8788e9a0c83ce42227ada17358838520b785fcca87d83ccb57e3cec797c81ed177d3797b3796f6d557c59827e879f99c49d4094599d8f2e11a25021b88741121578fcf0f187ab6d9d86fe9540800201cb80da5f0f8072dce8867182408610c3e687b517098405940177a3c62f87a11de7a140c6518b772ffe90d0ff517a6337bc7a4731e884f009e481c9ff5d7e10aaaf85640f837a9ddae585f677827570a3df88fd176c7fb821e581e3141a880da01185b9c74d91a8d0adb5a830b98a98d596854b503a7f71f93979f199b87b576d82856732b6802081e17513731f86aa71de7bab9c0b7b9504b77dd33dc588c3081d8711a6d69ce9d132b70f581690eeb4c387ef1f3f891488ff850c6ad17f65c33c83fb556d7a7593da766c6ea289b8f2d4773175507ff665ba7b1b1a07889b55d5872eb3a3ad2635c08f9e5cb1abf8cd4880b438bd8207fc1880c7852a825ff03b799761ba7d45ff6e83f3c032836bd33c88f9528b7842413a8acd5f41791241c6797e00768626b90a94288aa1a5666113b2936c9e8674c93378bd4b018435dcbc896f86f58183f0878944c2787e8bc6a3800e95f28030ab2f84eff0738796e3f086a3bb4a787ab2db8e8605e8029dba41fc0fec099202a3146465e9e48541ca737910aaee828218f280c17cd777ba82bb7f86543685b164ef7c2ba9438786dd3a8a7b507f0163f5d885402166773f8ac7876b9d607ce33c1c7e19b2be8724cf6d7c03e4fc8980c2947b38276678a01a957f5b20d979fbb3b980449eb677a3f51a7f85875e977409adb1d85985923d3d9285a373648968b7367a99c2bf777a4d5e8bec6933b49ebb36866d3b2a857b913e801439228606bf4879af072c7846471082c4eb50792a9bacd048f7eaaf50a4298c43f866892c81747939ed307b3117b0807c33b579b10ac6ab76fefd84ed4008863e54757ee60c6c84d8e57b7d6f137d7befe98b81fc80327e5272b490077466a3fa75cc7bd93f9b809600d4816a061682318eb88537eacc9f21a31596839c47862c81877795452d83768f2486d13aef84288d59805cf1fc7da8802b7d5c2bd79efe2c8a7ba4035a8adce0c37c9689f17f1addd17ffb0d3d76923ba99a5f20aa87138ca276bc035f79cd58d37a36caff7de1d482802d1158980da6d278e6c8b2b0243f45784809ef7ebfcca47fc88c2e7670912e824b6d88813c80618ac0073991fe161885080abf80bf4366782a53be7d00c88f7c081ca67d279ca488d6e2fc838529d8da6786987e49e16a8672369b85ea9bab7632300484fc094a834564e5821320058750819d787050477ac7c6fc85a97c4c77ce05b385d731aa81ec7a6b7b21116d9f402da080b61ed37b640f807b9557458322ba2980bf02c67f26cbf57f844a31f92599597dcabe038254c80a7c11f9247eda30877de65f277dacaa7c751e2fb2862d35b4d8ab39677e7c740f822d9a4b8286fa6d88fb38668752b38b8981ac3286cae9891c02f27487ab62bb7a1fbbd7830cdae57b0009c67d972b7b7f732b0b8a8c270a7926b9557e26b965778754407bdb421988008793830761d8810386eb891e616e7dcdfeb21daf446f7f4d284f88033e8284a97cc4898d76f6794403cf75b181cc8384b694886398b385b1ed06868003e785b80f6c789337238259b1cf8602d2ab82d17b4076c8df6b76b19eec7db223cb7af598fb8416b355867f0e5387afea5e8336d12c79d8f7827d10ceb685f72b9b76943ec675625f6c7b8ed07d7bf0e51289590b7789512ae7f942a87d193bc3b7860cfe8b756a23c6798e6bac77e4c1b776f9a53082692e80857c69bd0294a0e67cb08c0388f86fa6869b434e7c16188e8164bc08860b8beb8269e2dd7fec151e79bc9e1880227d888822e976806f499c7e152e227e8bf5fe7c00c82481e898fade846be38140e0277b84f7417c4f07417e06246179c3130784a5407a7d9868321b0570a17582be397afa231e825cc2eb795e065278485135838804da8666c69177ecf1107f5355087c41e7a28673c63484afc6d57e43856c7d52fb6b813af4d0844c1f6f76d44f8783c1e17f7db42da482acf24e771cfd3281b546ac7526d60b8748fe1f983c79bc753462cc825ed78578bda51c8887083f82eb0eeb84495f067e0fe4491cabd286868a4831815475fd818f38ea777403c98871e7ef7ea3c6678512d81d865d110f8c34698d7554afad851fcdd8810c87aa7b7576bd87b58e8d783051d57750323d8688983780655e137f06774080977ead773b79897760af1f7ab713d97c320134a58490f57fc391ec771c748778eeed3a83d8e5cd7b0c1b317650397e751e2d439514250f8869f46e7e32011f7b923bd47f1415e87cca2ae676abd23088e0933786f3370faf90fc5c7a11700077b044bb7d6b67398893c04976bb6ce7844caacb8e90150b8a47387d87a7267a8092e8f383250cb87f92304877e0684c79be2311806aa14e90e6c4b184e8d9447a36415e7e985d2b781cce728a5505d084eb7edd94a0c6317d564da07d912216873720188445ffa3887be4cc850b4ac98349c4fd77b0035ab57743148037b193853c104580ad82d77514159f7eb4c19d8ad4e6aaa8f7030ab738f7d0840fe4f07f92831f80181bbd80af198a8857912a874cc3538aa3a9b184886909a28f67ac8db04c29208044f37b744b1d85543f9976bb1265857df9789baf534c97fb418d816624f780bb2b107f3e951479f5987581800a257985c88e7e6c757b7600a4c1a0b45b9c9448f01495730fb57c8b73cf849c925379f7070f94c5f476aef6b56c92385d8c87d66b3c89f469477c88869387e26060883f981786026df37e2a19ef88f051149ed7d571ab211d0696fa4b4c925b06cdf90b12b2acea4cd994d5f38fa9096b1baa1006b2833d1705846fd93c7608d46e834a25df80a5acbf794cb5a77e37326d8a1b3e6a75d1efd3932d0e509fdb5a679bdeb55f839591fa9214bbeeb30790b58de9e509994ae8207b4929a68874c20377692e2b81b23c58827e0750779f499b816087c97dc582d977e65fd27dfad0327dae652276a0e4e380c964e3814df92e81d70d058694216f790b798d810e953b86987a5c76f1631e7a3199107cecbb598a1b5000879416c67a433d1e807fa9d0788b7e6b7d6b1f6b80f450b3784ad6cb7e7f3d7789b09e05783ff6838450b7a67c10fd09819ea75b84483429889117d079eb12ff7a64ce877e86dd3a7e44c2e576c4e4c78992dd2184b41b1083ad8de681a2659884516b99797ea68b8a7c3a96812140577d7050d186273ea2868625a48003f62486497c2d88cde11c82c797597a68273a86fda03f7a72d19a8025cf8e85c3e48a868b3d3c8216a1b67b863b8c7b9eb9ee81410b977d9bde3c7bcb1f817ad4cd4283a743b689f40f92893852d2824564128963b528848678487e4cd9178a6b7933817714a683b4a4d1794593ff83b695d47e01c4fa7facb56682ef67a086e7080681fb67627f9dcf917c8438338385bd647cffdd427886d68784084b84788f721284ccc1ae804a8a5f76b0731179ec61a875d319af7742add57f49588b781ff5878382a1be75ad9fc286be0da075eab1bf8001057f821ebf5179a17bce7f95c3768523d345893eef997f9803a383b5587d7ea65984813428377ccb4377750e1eb88273b48686eb199e7d47431b837e5a8c76a2000e8217f7a989b529a2871d00c0889cab9b8092fa3982b886b4760832407b0732107918d3578636b48d81ad9fce7f8b4f658a51ac688904d5e378be8fcd7758862e7960512d88e2602684f06ed37ad84ad7875f38518621ac3d84c5f94f83c105c17efc6e887f88864f8349f38381003f2e7a1c0f90805953987e2d11df7a75b7c276c87a1d7633d23b83f3c07b7f271a99754485fd89ae19697fcf49c475db428f75355e16875ac6f27f986fb77704451081b5ab4f8199c9fb85ddd5ff815bcdc98699d0da7e40aa8478d7a8217de64da08891b21a7b6a4e477f162aa184ef848f7963e4ae7f55b2747ee8aae78113973e85bc15dd7d10eb9576c3541b853c511277f7e38d8129ee7c8277de3d8425f39989f3ae567b08c297791d942b7743eacb864c9f9581eaee987a2bfc8e89b6255189a1a604884780c6894f322383d6c503779d874487740ab27a1987977ecd05c47d9e42d37e9582c97e93a3827a8b07197b60807e88ec27cb89c33ed2779cf7a38491b29d87e8ce317c7606967c1cbc427ff2a0a4895ca50c84099aba75434fd6808ec5db790bbae582d59a267b2193377c198e02882392d9786ce58185cd223a764444b782d5ed938493f6fa88497cbb88a547368407df8277524747800ac52479c3ef9379b7ef637d0a7d448ae505fb85d2000b81f89e868a14eded78354c89764d69d6883a67ea761f730d7c1660c57e4b910c7f4265d77adf24907994d9e97d9790c1794b25d48978b6ca805ba2e27bc9e65c825d47877a0075ac86e4f7ef885260a5894e07c382873e7f7a765976775ddb657c9d6a5c867224b68795a61e8a8b32bc854df9c77e2bc6047b471f6c88dafa7576b252ba86dfb46f7752befd880ec0ba89b9c3e67b955e41806d104688ebeaee817da6207ebb5ae182a6f4a0802140548aa224798a7d8296828dffb27c268397866c75ac893ee4f184c092557816be177d6cf78b8489c31a7a8b90237e0af46883a3bb458803d91f7f9653b476079058880c6ad07ed731e88555f31a8873442379ff13398a14f8d9820ffd8f882093ef7c55feb27f2c15658a7e072788a4931b7b7afaf276a3bfcb7fe65f8b848b0e817c89f27c89ae2b6f7653721d7e3a842777da587b7e47a8b17d85eceb7e2451a7855fde4e78d14ceb786f37007e717006847899d67a8015b87736eefa797b3712870022057a8176d47c810a5b88fc1b51832ae3317c6844e189980a4f86a13a727f5a5a8779c873bb79335b6c7f58e0be819283c283f594d77c459a187c4f3dca88dac04689497cdb7571e07b76cc02387cb3de3e7e1d82c389e2b3d77e79e1c187681c76786295c77a50157483bd35367f2dbec389cbba5088dae83a7827781a7e48f7e084e31fb586c9359a77f83d0b7e02cb9a89ada61b7f89b1e87a67d88c7e6707427b99938e781946a582d18214818af57b892ff7ab7ee8989c83f05c297f6f49ac7b60c87b87f337348746433583d576bc75c06e6582276dbb7d4045f27f54ec7b8095e77f8111b8f19346d17daba9fa6da11cf467aa3859288f3cc3728b9eed0699dce1c498a94081abcc74dc93adc02d80a611e69689d47e76d8e5ef81f8df167bee356686fa47db8079296890fe180ca68ea14a8fd585aea5e57284b721d15690735e838c7fa9fc90139b619ebd8bbb885e595787155da79923dc97853e25f6867c169a89cd4d7d8155b2fb7ef283a7b6a01fc59039f30d8be3f2fcaa038f8c914e98bba90e3c390475cfe121366e6c9c95282d8c24ddfb76fcd3e8a12ce52f8a9e9c2b7dd731f887e5e10678796af378db30477dd29b5e91d6efb598e5c645a6c33d249705e638963a36d927ed7b3f7b468fde7fe61793872174c38c3c56c8806533e4946850c47de95ea0881d24437ecf66a68911ca7183257cf48036e333b14fed0a94b7f8369e40c24ab15e273afbf4242f7bc014267d0d46218697689281f3c0707d42369a96dd92ef8486a7e285868a19877702ab7e74c2ba75911089820964c58cc1bb5a8d0d6168ab7fe96395f8046584913b5e84dcbc9d7d691c4f781880ac7fa4270798fc66e48569596e7919b2f9850b92ac7fbf720d7f5d9a7475b3b304b5ae023e9c438c2ca7b54f16ae68e4fd8838b7e27e9bfb787efa2ae5838a890d7e50f8278074ddea87b8a32d8a8360f575be9a587cd02c0c7fb07aaf7fe7789076ac2c098d48130bb71a90919c94084e8ca2e23e82e93a1c8744d405766af43176f3135e8af881de871356c17b23c142a4336d307fe8ebea7646c51f7bbff01d762d648178ba9bac76c395cd88e8eb12057ce8bfdfa887df81df2b0b8a88218882fc269a7918b6a085ab2ca1813feb8f860249749eda88c17d924dfb855f5cae8124b6bd8722e96f79f4a3c37677f0507ba3a6290509290add8ea2a189b00a0c83699ad087dfcdd688bd35d082444b5381864d367773a2068d65335879d21dee7c9cd324817c45bd793abe9a874f689495c77f489a9d579b87bc5d58813cc8ad7b4385387c0e66b7818d118a7d66129b8af10519864c0458a4fb9f2586dd3a327a2bd82c8087913a76c34221860ecb6b77001bd794f5078d84332c2a7b60e553865d064881db78b57dc3e43185337fb481f4b58376b372c08951e20a857ead35856815ff87070e4e8afdc4007de575328529774a79921e897d22a158fa6e65bb79c5579677223ce77887ef4a8381acdc861a660d851bf6ad8548a4ac16a756c877f57b6a7858b56a879a0d6186631a0281c6bf6a87194d5c8932e4969bfa8ccbf9895f23810a3cf07f65286c78f45bbc7524f7aa84178bd07e3f08cf83d9ab8fa3c8e08576fc02a08a8bcd767df2d64f85af6114827e7ee886805d0476e6a01e83358240fc52a107896e9cf08443329d7538f1f7777befdb8471d5da79adc3ef7bb534d317d69949a6c15000822b97cf78ba65ae790bafbd89fd4d7388d731618871772b831631ba7fbe213a7d233828890a8baf80122b39868a89188a677a57843f5b4c7b895ef2f6a8600f8da00b8d7672a6f5894c7b4788a22bf5863b8bcd7b36eeae7cbc7aa388c0c8e27cb9b1a97f18a43e7ea4155487f1c4797fae5a397d739757783cdaa27b49bdd0b0f72f2684eba23585974f5582e0bbcd7f89f90d7b1d5eef7f7c0f586bc4f47e797fd19e1aa42fcf7d7b08a1832ad13d7c08f1da80f612927d6ea7ea825ba91582df3d60b877428d883129448aa6a1a8846f91f9772b61cd819ba8488817d32b85f3e4827b464e3d1ca62b8d86045e467d770d3e781c5064851cc3268677d3407e6ad78d8027eb5c9da485877c3964d1773c6e7f80b9287481069b7c7d71e72d768671e38039e2f47c8737c1849a065e82acc70280fddad8898f09ac893398c088fd17828828f2fa8ff3a7b978bddbfd891bbcce7cd8a43685f421b5879893427abe2a5d7697bf85825123907933e4917512ad6c7efe17e689528444878994ab7dd2121686a3203218f9ccfbb539f89d83848054883afbea7bea82be837a784089d74359823632c07c7fc62489ba9b2b86aacbd77b6fef3787ddf98777f641ef79bf6031824e12818604e00ca3a529537affef3e821e12c889d10b2787e00a2c7a784ac0764444d9838b73ee81f4aa2a97f456c188aa1e7d789f49e37e23558979e30dd37d36d11778d02c98aa1cca728171e797839ed69f840b094e7c2efbb8805fd3ff8517a4d181051d8582ceb2c3800cfe9eae3e68e2814702ba7e21befd811b7f9383e9b1567e24925198062adda32dbbb07f51d0827d6122a880e0f14c850dfa87893da07f8314d4a184ff784e83bc277b7d83883109d35d4a8746c5a0792c44ad75dae83c7b9eb9a0825c7a1b7a5d52ce8506078986a9466684c7fcf1898c81f8824fb4347dd606fd81d8a42c8af1053179539681856a7dcf8dd362e41904e8497e9462097bc026c016948e1b8652962278811c51853e421278d9878688614d6086cd29d0821c21a97c8adcec7c6d87147671ce407bac0f55806881268f947260a23b13310b5edfd2a1c0c7f8940b8d7877b8c70b775a94d57fa43916770c1b2089645e357d593a168549cf977be181487c77cc1386e845a07535f2b68cc3f93d8f3201b17ac14abc8a8e117eb15ff74f93059ec07cd0aa487e5ed0d4894d0bfc8508db8380e04b67780134877ff9a5b77605c37786ff0130841ad252763105e8a7cdf2757929c75685b799a78a41b690a64996109aa444b7781e368c7c151c1476b3433378a7557c7dc6249381bb4f0d7e01e8c384f2d645887c0fc07c865f978c4b78dd89d64e48a8f454d57ddbf32d96a030748c39814a86ace04b8015be4b7eb8fde777fb7df485a3973f81e1881f89a6ba96887bfded79b98d907d9993f17ac0a40595b5684078cf95c1a99852d1ac70f49fa4fb34fe9073a422856b26b07974203a7613ea107f83cc6088d6e2587dc16a4c80414cba885367f67e9707a4890619d8850c673880f670ad8be94c86962f2c84a966d482915be34883e82c40784062b17e5ee96e76f09d1b8650b0cc7b4784d3877a25967d8c441d7df761db7f97a93080897025826283207be7378684332cfd7e206f437a63498e87e99f8375b4f75189bf8b8b7be32af98687c08479b7aef885f88c6b7d8f846f7bb3e9da7fe558957f0009dc8643a96c7e9b148879dbc08a8215ff388aaa42dd7f4a5ff0811ff1f5818e06e47ac8cd4a86f6eeee82aa30b87abdf8cb8432542b87761f5d86f3b5f486b23c8077ee9ac2777d4ca1751ab2a078171f08853d0527878355ec8757913e781726a784ea9b1c7e45db0980bfba027771e0358258eb2e89aaa7b47f5db73d8a47802a7e321e8879cd346383673d8e7ac4b55884a2772188b2a1717e6976c180ae9b567859579c761d69ee7556b9cb7c9bcc00847e071c82ac50d27a3433ac7948af2d881523bf894b91237b1a1e3c852750dc82ee23dc7840452f7e8cb0057b6d34a688a7f0a275f08be77df90aca76059f577c7397527c7d94e488f744fb83783fed85d5939b8708c94688cfb55b87f8686577fd51968287f6f77f337fbe86165543771acbf4779a06947deba203803e013b8a49172984a08d288449cf7d851a308c8172891783a59ab7898231ee7b1d704d848613c57ef62f6b81dcfd4f8026b9d276339c607d0b676a83f0d3d784e1acfc795372c67f94212d875410a28113cda37a1ad69d85a7d3d47ab6e8107627012f8823792085b2c5b689fe4230843eaee281838b5c7c92a70687ec03157bee18507a9a351d8019295b81ca605a7d1004fa819dab4179e63b4178def95b83a56493860e428676c7480081dc339c7f00feb180301bc287f90a0378d8c1e17a6993787ebc03b389350030881cb133894851ac754fe3c9757a3bce77bb5ac287ec6f8c823ae93f7c3e78068122c5217cfa0680878cf8e678ead59586af858e76f8fc64824caa7889d3a99581f55e177d90c1ee85430ef689c81cb975e0b45e7ef2e8f1820350b67a2d52c081925437839935b97f93ae69818a7e838367fc8384b5e10477daf33b87a7c180893f7f5b8100a4f081c6bf058280f88b85273e0b7f0f374584dcc70782d2891d7f00ab0b7f1e19ad813f50de75676c0f7db93a287f5e4e7583c117797af75a9e773e951688613b787bc217e47769ede279fc26547a405786859d4d487e9e54ac83ace08276357bf07c9d87be829271698a439ec77bef84018a124f4d88fbccb8778beaef890c879076ae2fe2885dff2a7aba41178a9b8b9689aacbe97de235658477559f819271118213ea9089aebfd081aead5a7ad987af758288d4891aef73819d8e8378a9c80579c0f0cc7805d523830a2a2c7b34aaa67d9c09937502cb4f8399110585a810cf7aa48c18812e3174836bd3ca81b4c8bf7ba55280851385bf7a23cc9c88e111248a3f7ff9836565b37b2886a389ad82808143d8d67b0fc02a7ded52378414f8ea85680a7c82021ef280d13ab77ecc26c478415e777595212b7637ea107fdf85e97da2d1d57877a85a8f054fd87817cc73785eacd181fd4d1e7ae8507c8608649675ab49ef753ed6a17d82594b8a75be6684e994417ed008b18982d7a68085346c75527dca7e4cfd827c925fb18a26fb9c7d83969e80d5c962781e2426808f9d187f1c7b6b81bd23737d36ad907c5ccd9b88c213bc7a2044cb7da30eb3848296f97a858edc7e66f5c1804dca5c97e631ac8383d3e9924d704e7be766177fc283e37e0940a081f5c3a577effb0f78d128dc7ef283a3849ff6ac768db7d7864141ba88f4fd0f85ae908783c86fdf7aea1d9976c06eac76d5395c7bff787578c2e8a180a0864e7b18d3047f8188748722b30178ddbcf278d68f468626d2a6818176b87618c81588a141c77d688c5799bc773b7706b86a762f8fd0795bd82a762766388564453c856c63237a9993867c28980d81e372fd82d659ef7c1d1de3817f0e7e7dd36cf77e48e5af80200a027c2b7ea4805127f67540e4ce77fd0900783fae4978ff664c801c57cb775701257d12f56581c313ff8387e42676cdc80a7e0541767dcfefb37c209eec7ab27d24874b3e52908b18f993d8534d932c7e948a6a2a6a81bce6f381587bb77d9feff67ebd766a7e14d49a88bf55a3791535c2888326787a68a97579943e53898613d986d8a4c189c1bdcc85633e9d80a826da754c1a851dfd44aef8d62ba41e5d0958f764995a18c37b007fb563ce7f82183f7a42d1b57d7f0edb788f604c89a6c53f83c6f508863d712182a796a8b87bfc419c5c4d928c461498b3c64e467df0f00577cbb3bd7cf9b0af84652f06779b180e7630af657d3e83c487f88c648321596d777838ed8534b14086f6483682494d52a54b994fb14cdf889674ed6fe45cd01677fac9c77cdae8f77eb63b028737324d82d2775d7bfb0d5d7ce1a574786c8be787446fbe7dd4cdba808b3d3e7f6f081176c736929aec6cdb931c365da2f60b657a63e42f894e61fd7ffa064277e70c1080eba22c7eaff558792e18c983a481be75246e828625a70d7787dd30804b1a267b30c8b7907cb36899915363b8a8da7c832c2e1c881eeaeb78400f5279d4a987769820c27d35d5827d56787d7d35e7d0838aee81787bf6f485b379f08946ba9e85f36d258811ad0cb5713fe3b036c48f803b9dee809a182a7ef8bbb47a816fa786238b207b02837b866815257d73f59f7c2c27247a456f3f8a7df9bd7a65d5ae7c446817839bde42b42288a1ae2704c77caf078d7b2ee94780d796e57625ec0d828d2e037ae850fb8070577a87aa8a7f88b9a6d080448c2a77e563cc89ce9c0e79f6a63779b5abe08996362dab3a1b5295f3a0c788b0816586d5b7c7830c990c78718d2d7ab1520c7945c9ee76279178835aadc77e936d187b67e3087a32c44e7ec7d1317758fd8d7c7db13a9d7462d78fddc781813ad7a88792a79383b4fea2758dc2c989eb612b78cda49f880c754177d7a15284f6e3418453794d7fe8624288e2fbbb792480f88789534188638656a9a84e11e2c2212b82fac52c83c91c0177bc320c81463c8a83a6d73f848fa8f3827ef5c586fee8e884bae36783cb26f87d28fd03897ee3f78490229887bbc6f09981c9e595298684870f9102839f9c44890022577c7403908286427e86c83960881696aa8591a06683b7b5f18627b56288c3c7a28517f01f898dbed87729f9d37fd8d3af9a36f8527b9f8ac97fa81b897950df3377dc74ff7d172686884b2c5e7ce34ea786a10be987fb8b28814606588577141781d0ceb5780080dc7b35b8089af50605a632ae6380b113b9841f448b7c53af8e82629c8e7b6d462c84b529f8853fa33e81f3541a7d5da56888347b008ad8f76f797a7175779db675760aa19a88e6fa9187b1590baab17eb086d71fb08185b2638ab04017856421707e37d805881cb15d88724ffa759038b588f69ba18984bddd7b47cc747938328d8319a72d7e1ae8ad862912c98eb6d158a54eb704887212bc76e02002850249b477f2f3da80020c547fa9222488c3b52c7c5b7edd815e06c4897fd88d7b30ecaf79f2faae8243c6a888761f9395281ded91edd4141ed0d5b978f28ecf8091ab527827aee97c6e424082b75bd187f7195d7c9a7df67ac230ce8419a0958948fe517f51c93877cbd7fe7fec03fd81652fc6882e1c87996f89b81c7afb5c7a5f31277e51b72685560c9a7f80e6cf7a86cbcc7cb64d1778f2c4f377c4eb0b83f3ae66890e01a978324d7d80597000884858f28894e06b6e7f37fc75b98dd887eae34880108d7b877db89d7722e2de869b79b580e99f9e822fd347852df4697514cd9d7fe032f18769db56862380f97f929572899eaeb77bb3670d85bded4318a4fda9827219bb87696b6f79f05cbd858a7e6d8901e84b76f7a2c97cda5fd17fbc7a1e8244d4ec8081234387b7494478ca6b05859607b68926aee289951d3d82e6b2a1769dab9f826582fa75eac33276de3abf77bf1f7a824bd2867eee2a257e93912887daa739834ffce37db83364850947b6875cd437897c1d8d7fa90cc4855ef0627bc0e5568409ac0d7fea82ce85b07afd7eff01eb78a8802189e84568772ccd0c80b2060e7fc271587d5343288876315283a33df2894dad79822b0e6e80b9b1dca037593b873416db7a3e4db47f87d20c89e8ce2aaf8d2b4d87b39d3f7908f86678bf43fe821016298197ea5188eadc098aeff09e7b3ee8858894b85f7d11b12c782ef4e396200a39180823cc7dd0a3477c6ad73278c7c6d4ee5bf2d17fcd6a4382b4c84583ab5b6581b8d90a7d0d46737e1dd5007aa0083076bdf83a7b62a7228138a59679ac983ba1759f3791f434af93ec47b7156889baedb06e6a8c964b139ff953427a3de10f7917afb4882cc93682cec40c7da128338a331abb76fc26618946cc1588e546a6877ff118a6a39c919a6e8d7598e435f3adadf59f9050ddb694d615" + } + ] + }, "job_failed": { "kind": "job", "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0004\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.encode\",\"prefer\":{\"quality\":null,\"sort\":null,\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", @@ -1142,6 +1176,44 @@ } ] }, + "job_rerun": { + "kind": "job", + "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0001\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[{\"divisor\":1,\"price\":150000000,\"unit\":\"seconds\"}],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.encode\",\"prefer\":{\"quality\":null,\"sort\":null,\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", + "pricebook": "{\"currency\":\"EUR\",\"entries\":[{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\"},{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"region\":\"us-east\",\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\"},{\"capability\":\"wai.audio.opus\",\"divisor\":1,\"endpoint\":\"opus.alpha\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":1000,\"provider\":\"prov-alpha\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":50,\"unit\":\"seconds\"}],\"issued\":1792996400000000,\"kind\":\"wai-route-pricebook\"}", + "policy": "{\"exclude\":[\"prov-x\"],\"keys\":[\"price\",\"quality\",\"ttfb\",\"energy\"],\"kind\":\"wai-route-policy\",\"max_attempts\":3,\"name\":\"lowest-cost\",\"version\":3}", + "record": "{\"accepted\":1793172800000000,\"attempts\":[{\"billed\":true,\"end\":1793172804000000,\"endpoint\":\"im.alpha\",\"error\":null,\"hop\":1,\"not_billed\":null,\"outcome\":\"served\",\"receipt\":\"sha256:1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15\",\"start\":1793172800001000}],\"candidates\":[{\"capability\":\"wai.video.av1\",\"cost\":480000000,\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy\":10000000,\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"rank\":2,\"region\":\"eu-west\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.av1\",\"cost\":800000000,\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit_price\",\"value\":200000000}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.int_motion\",\"cost\":1036800,\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy\":31104000,\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"rank\":1,\"region\":\"eu-central\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":691200,\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"retention\",\"value\":null},{\"limit\":\"ga\",\"value\":false}],\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":null,\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit\",\"value\":\"frames\"},{\"limit\":\"spend\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":345600,\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"policy\",\"value\":\"prov-x\"}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":518400,\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"rank\":null,\"region\":\"us-east\",\"removed\":[{\"limit\":\"region\",\"value\":\"us-east\"},{\"limit\":\"residency\",\"value\":null},{\"limit\":\"training\",\"value\":null},{\"limit\":\"retention\",\"value\":30}],\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":1555200,\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy\":24192000,\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"rank\":null,\"region\":\"eu-central\",\"removed\":[{\"limit\":\"consent\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\",\"via_fallback\":false}],\"capability\":\"wai.video.int_motion\",\"deadline\":1793172830000000,\"decision\":{\"decided_by\":\"price\",\"runner_up\":\"av1.alpha\"},\"fallback\":\"wai.video.av1\",\"job\":\"job-0001\",\"kind\":\"wai-route-record\",\"operation\":\"media.encode\",\"order\":[\"im.alpha\",\"av1.alpha\"],\"policy\":{\"digest\":\"09d08acf6a76fa45d822f10a6806b21ad1c41172bcccfcd08548e59567c4c741\",\"name\":\"lowest-cost\",\"version\":3},\"pricebook\":{\"currency\":\"EUR\",\"digest\":\"af132fd169de93674a59d1735c840e539f9144f3a26dc18bee089efe57bcf108\",\"issued\":1792996400000000},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}],\"request\":\"84f9305c1a97b1fe8c4c865a604e9509743d39b6c5fe4daf715b0e4fc69c2139\",\"sig\":\"bdfb9213a623693cf42b9546c437a6c63dd07761e0ff1b987fcca46cbafa0a152b1b7f2033d6f8c7e3f138b469c24082349b6612ac52ce2ad43dca0ebc5b8a08\",\"signer\":\"5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb\",\"version\":1}", + "receipts": [ + "{\"oer\":\"2.1\",\"receipt_id\":\"sha256:1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15\",\"kind\":\"job\",\"capability\":\"oer.job.media.encode\",\"intent\":\"replicate\",\"subject\":{\"inputs\":[\"sha256:0d69497c0597b96e23e625790c9c89527c1e1d50744c9acc9b901a0694060588\"],\"output\":\"blake3:8c05ad23f29dac4fa0cb00b9cb758eab315e7cbd6458cb04ad8eaaae24ae67d5\"},\"work\":{\"status\":1,\"outcome\":\"completed\",\"latency_s\":\"1.0\"},\"energy\":{\"joules_total\":\"30.875\",\"boundary\":\"the hop's host\",\"breakdown\":{\"compute_j\":\"30.875\",\"movement_j\":\"0\",\"idle_alloc_j\":\"0\",\"shared_alloc_j\":\"0\",\"upstream_j\":\"0\"},\"method\":\"hardware_meter\",\"temperature_k\":\"300\",\"class\":{\"label\":\"OnChipCounter\",\"relative_ppm\":20000,\"window_us\":4000000,\"filtering\":\"Unknown\"}},\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"measured\",\"input\":\"im.alpha\"}},\"floor\":{\"bits_committed\":0,\"bits_delivered\":\"0\",\"floor_j\":\"0\",\"decades_above_floor\":\"0\"},\"chain\":{\"prev_hash\":\"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\"receipt_hash\":\"sha256:1e0a9beba4127a69dd543f6ae74774f6c22017664b48785769175da72be8fdd1\",\"signer\":\"ed25519:route-corpus-hops\",\"signature\":\"df68f13d91d9a650f6db809f864651c06e5af46d20c73f04a1a9797383e8153d4fd9b7a0f3629ea76b4ecefd14c97508b64b13ffc356d6df77cb1ad85674350d\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}" + ], + "keys": { + "ed25519:route-corpus-hops": "733470a6f6824eef91aa6b6fe4094e4e3b864ce6a368122223cf718fdc57fc57" + }, + "outputs": [ + { + "receipt": "{\"profile\":\"wai.video.int_motion/receipt\",\"codestream_hash\":\"277865b574029f700f4fac023de3ab06ae598ee6f8ed726444e91442a6bd59e9\",\"clip_hash\":\"3492566e8e2682e3b3bac083b4db02ef8eba9a7f15d9faf0e27feee0f6eb488c\",\"h\":48,\"w\":48,\"frames\":[\"e1d8e8f8b5c534be1421e5858ed9137808ddb849bdff2bf397beb32568d8eb69\",\"ae7214babf5bbd6ec279434740f3271be43d2392b8167813fa557b5c4426fa14\",\"b3ef86db27b82d303bc6b612cb224ad55259b4f2e2c0461c74dcce188cd64b99\",\"3906f91010bf1b9ca14f6e2ac9b499589ac02ff4465e1d9764a22443a8b29c22\",\"bedb1538c2071d705d2d3ab2df2c569ffed440278494649f9bf1ec881561c519\"],\"merkle_root\":\"0f3fe4aa201c275c10289c22f31e7211b555627b33a251bbe33d094abada6508\",\"work_pixel_ops\":34560,\"joules_micro\":0,\"uj_per_kilopixel_op\":null,\"energy_provenance\":null,\"parent_receipt_hash\":null,\"signer_pubkey\":\"4a29b9b42b4c7b8d947b226d503cfa1ee8b69a5cd3e4cb88b30db023d6d2bb8c\",\"signer_id\":\"did:example:sink/job-0001\",\"sig\":\"50bcbf089a0f15151ff0573d3fdb210636f66997ea2d23d08864ab2f1ed6b4529383206c88f30973b2314e8ceb2db80f888af5a201b494e0aba18507000db30c\",\"receipt_hash\":\"35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f\"}", + "codestream": "57495631010100000500000030000000300000000800000006000000d5ffffff590000000000000004000000090000000f000000160000001e00000028000000330000004100000051000000640000007a00000094000000b3000000d8000000030100003601000073010000bb0100001002000074020000ea020000750300001a040000dd040000c3050000d2060000130800008e0900004e0b00005f0d0000d00f0000b21200001a160000201a0000e11e00007e240000202b0000f6320000383c00002747000012540000556300005c750000a38a0000aa9c0000edab0000d8b80000c7c3000009cd0000dfd4000081db00001ee10000dfe50000e5e900004ded00002ff00000a0f20000b1f4000071f60000ecf700002df900003cfa000022fb0000e5fb00008afc000015fd00008bfd0000effd000044fe00008cfe0000c9fe0000fcfe000027ff00004cff00006bff000085ff00009bff0000aeff0000beff0000ccff0000d7ff0000e1ff0000e9ff0000f0ff0000f6ff0000fbff0000ffff00000000010000000000feff0100ffff01000000000000000000000000000200fdfffbfffbfffcfffbfffdfffbff00000000000000000300fdfffbfffbfffbfffbfffcfffbfffffffbff000000000100fffffdfffbfffbfffbfffbfffbff00000000000000000000000000000000fcfffdfffbfffcff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0400fbff0400fcfffbff0100000000000000feff02000500fbfffbfffbfffbfffbfffdfffbff00000000ffff01000500fcfffefffbfffbfffbfffbfffbff000000000000000000000000fdfffbfffcfffbfffbfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdff0200fcff0300000000000000000000000000fbfffcfffbfffbfffcfffbfffffffdff00000000000000000500fbfffbfffbfffbfffbfffffffbff00000000000000000300fefffdfffbfffbfffbfffbfffbff000000000000000000000000fdfffdfffcfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0200fbff0400ffff010000000000000000000300fefffbfffbfffbfffcff0100feff00000000000000000100fffffffffbfffbfffbfffbfffbff00000000000000000000000001000000fdfffcfffbfffbff74420000280b35593c000000a214bea9451501b9ad0cbdbd9a309164812c7ece531911d14717c3dbbc1d27782217989f7218f20d121d7bd4ca1f3eb3ef1f71131429e2d1f626c3e7ab249c33af4fe2d2c042a6d2222fb9b3c460c33836571567e55a40c90777634123135bfb8e1357858f0dfd6cd3316844ae2fdb041518b067b9167f09231b1d6f1c16b7df311ea6450d1ea29eed1ecc320c2b747de0206b34c82a708a2525b67967473d53e643c54f6f2f42e6355aaafd6534aabbdf5fe1a866406fac26314f6ad812ef006e0dd2c39e124439f5306ba6e0396f65ff1627ef771e0071f21b088eae1efd0a7d22d53ada205042fa23135ba627dfdecc3c473a7353bf1ea7435671e53209bdc9581998ef568e94f260c2af006595d31a5d0301d30b0e96f22e27c43d162997a218be088f12dc9d0915dff4db17246c1a3c61066e3807ec553cb509743e78e21d250c19332ef30e3d2d4f94a0275d0ead4e59f7ff403f44c02ed1b145563b075b55c0ece2599ea8038a52c4e77287a40b3015595e178405f01716799f12508739158ce39537c6364439c2c61335c774823feb0e4a46484b29464146093e52ba6e2e3e5a0631121b992d5a46a848c8478e518c794638949ff05b4c57d23967cc4b57d3bb164147bebb2f4cd1452f40e1701817cec818f564ff13f4072317feda4f17504f253558977544d057484615df8028fbee312a5a55ed2c1c72d1257130d62be71dda51a0a97748680d2735181dc85721d3923f8d192f8794fcc96be998eb135e56840f812d952b26dc2f2f8537db1701de931a9e0b811c99606b1d7a72b81fe6796623b2e3442266abe726bfe71c26713b582970ff964812b3a7511b88474ad1a39d60fe77fb5b93faa43aa689e37f22cf656dcdde8d4a529b091869c19a199bf5af108ce21a33eb8ae43a8485f63a7c0ae7f3e9d6d3fbf35320f44e2572fb823070f38da5d5fb4cd37f3e167531443d7027341943de2d2e0dbe4918ce0f53b1e5a7368b2c3c6ff4ff9044663d54810e945168c3b1d019852e9412c0563e10ce0287359612c8365ce0fd34c5cdb9f290f0e017571aaef2f889ca179debd4f3065bae18380425f4d7c9dd18bb2cf626ea6faa24e3f4384b1da15a3d6df8b52cc90b935da2e4c23b9597355cee7f1945393e249b7b14ad729ff91432962e6218224895175df6f11b30546af34ef81f1840d4b4f24290fa19e90d41f3792fbd17b307eff2702189161f1552f429b1fc18154682320160642d6a8c304b9a74645336beb060762fd36780e26160b5380e4500bf316ab3b6594b76993031ace92b2efad9cb178983121b9d4845f4ba36071a9d404af3f9d4f2199f14cff37ed44717baad75f448628d19af3421f46a8b0f19184461f3ef7bc1178d00634bcf58624c6725ac619927f15545490f360e0fb877c6814c6b9ea2f952ccf3a3abad91da10ef63f3144a0f23340223ff35814709f4d3516c16e5bb75f3a3574719c25228f4b5a03e1636db84f49a58781762e821f35742a2181bc2f7f397ef311637054630ad43d250643a45395291b55b725ae5723bdd1b62eae7a445bd65319f19c63568c61a5b1206b34d3bed1dd63786de01f49a49affb2a9cebf3518ed9fb7f061bf402f2bdfb0bb9c0f344eaa4fb1bb58df3fb10a4fb6bcbb8f3bb45a0fbfbd11ef477204efb6ff5815087d668629a2a0c5d17e9236a698d0061d79fdb460b2f26a9c24f33529ee32911b3aa1515876dab37a91b8339f13feff22ac0af17a839caf36b8b36183cace0f27ca7e419927205f46713bd1740d4a5f49da36019a6baa1f4ceac2c19b530c1f2eaf4de19c6322751ef91df5b92e05f6533f2a760db246a7d1551e266a304d149e19ef5ab9b67c917279abc17e5eb69fbf214f2182014b4fb9ffe3e163705e1fba23dfe17e466a2fb4cd37c16dcc16ffbaaa6f61663923efb92173218345ca6fb512703175f6a95fb422b751962216358fc9aad68a047125962cd87430577849addf21174af2dc5499c4fcf8778b8293377e4ed3b12154cf3afbf46fb6ab6baf3dc41b0fb4cbe91f32c0b39fb2ef0f3f2262a42fbe21813f3cd5230fb476af8f3870f92fbefea62f47a7a92fb7234cdf297d11f5d8e12023c55bf666f4ef6005c8d58bc9549953d730699fd4ff8264fb23cf0ac1886051a1c4d05891af4edc2fbf792baf24163bdfbe896c3f32f65e3fb6023ebf2ca07d7fb66774bf4993ad1fbd99a01f30be2a6fbd34630f3275768fb7ef1a1f2f78972fbe65735554ebaa9390610c05a5315734087513d8c09862074f653884a096d36bad118d01b4762691789a9a4fb84456418eb0ca3fb4aa9b419eedf99fb865a481883a740fbccb302171e6b31fbdcc4a318d918a3fb2772ae179e15aefb5dcf1b17b3873ffb7673f4189ca65c5d080bdc43f1e1b278e9bbf0411aed639db279b76db39473596dd293164dfc0317fddbabfb866f2cf49fee5afb376ea4f3351975fb9baa96f394a45afbd65b6bf333ddaffb9de9fdf212c3d7fbeeead4f2262aa9fbf329a1f4fa10ccfbaee586f41f6791fbe442375aab92fb3ebf0cd287434e9d6e5b5143a22f6941660f17d858c6629c18cba73c167536ce1e8e78a81944d168f419b32118417e79f37ee3a4168ea062f3684ff8160f7514f3d3335e16d0c984f4e44f5716db178bf3f1e44f1765e6d5f39b589417ffd4263cb1b60b56568bf7410b9931975f374f710f5c7f4fa42325bcbf1b8b7648524f17d3a2bc1d17f181fb389846f3eaf47dfb8dede9f2ef7b22fb6cb646f4429fd8fb1ab5a5f4cd8e2ffb7dc74ef4164ac2fb955678f3aea1abfb511264f4825bd7fb2cdbf5f268318f5c44110177dd73146b7a24ef4d9557b1ac71c8ac855c5302606910468a110d2833d76a203d85cc1c16f25d02f36cc7671902a893f3c335ff186fdffbf23bdcd317eae2b3f3dfb9001a3c8153f3a8edd118ccf62bf491591a17bed596f344cc8916cbf73c423ef6c45d15bc03651b1912a83850df6979ba2a6292c2eebd5d91e87574332d1ec8640e210884defb523cdff2811f84fb900283f397e066fbb5636bf4b5af3afbe896e9f2ed4ea3fbaabb7cf42cfc51fb744940f4fc3dc7fb444010f3ebf46cfb40019ff4ba45685818c3743cc9991ca49c3d626e6cdb6d54ca28d67ab141e2599329871efca9c71b6444e22057e352f45dd8b917feaa52f42bfc1119743121f3694f9818db6c99f3d79c4618989917f426394b16467b0df3b1fb8f19521d21f48db4ca175a186e3dbffda754ced3533d92a3eea77ad2e668dd293056741d36bf0ff6d877943fff1c5c2be22145ecd620c546c1f3241b6b1627e58ef43e187e1951f342f42ab1b917ec3602f4f8a719192312a0f21e3ac51704a731f4981c4f168ba270f48c969c181fea6d40d92c977aaa69c464c1fe0b4fe4ac6e6dca35ef5f6aa19dc29fe940765b492821c377c822552bbe2132b22720a4a99cfbe66ccb18874f90fb5de48618e3bcc8fb7a9dfc174fca80fb033b2e19226885fb5cbaa018cf7481fb4bbe3b1690f45071cf3b545fc7dc4c460fb904a5f494e4710bb28c59b37201bc93c3b85cedf29ed26f8cac231756f41e282b6e208e484d416f57f1f2d56f3617c0a984f402e336196a0dacf401f23a182db681f4e431cd161e6be5f31d3a03177b0239f33755483d85c9d2624864d23f6bb7c5a69c7e2164d4f8ed6167d7fdbd05c2405ff28607d169ffc3238fcc5423c78c9346c614a93f473a2b17750683fbeed5a017f40664fb11227117579a43fbf768d91957ef44fb4994591639592ffb3898db867e356462c5292e4270ba30a3145c994dc63817c2b506ff790c88855f7484a1d36b3cb022b49dc01f39897e3db627e7453b7eaa47ee9c181a325d08f3aea14718447cc5f3ce9d6d187dabeef346352a168c4c9bf38ff00e189a54f0817d9e5867e164264acf9f89a0468af5865b1d0f5d44650780f7a37565a8f4c0ceb96346217425be3f42ddfb460186a443dc4f4f39e726fd2e11ac22fba65269162b0d79fbccdde017447c95fbeeead617a64c9a609f26b64105687099d3e8f96edd6e714832a030810d602862a462e0c908e95486467e4f657a255646fefc1b3d6612ce46f05c183a0aa2e63122e07a4fe77fc847dfdb81f4981cdd5a7e25095e2496ff7c0d982d61f1b45a40c98a5da72c8cd36f9bae985ea04c49826f6e0159848c46d189116c7425229a29dca5753180914230f7d87f2cd1e2294a23bd653c8f2c033180eb015dc4796a35ffb37e5d2a91a83e040b6f9551375c7318d897491c79c9bfd5a26b563d7258cbb89e8e7b06429a6fdbbb519019578d2cf852092d88bd0f2a78859a2f1ffe22519dd8cb507fba725b0e80ca5d8ed18c6087f6007e90e4ac6bd0224e504285c6ad5e2a4885c47c8e5608003a80cdac406c2de6e9d9c33b3d881826ea313c35a52f98773329023e2e2f644eed4c9fd91548d5819c5845a9c356a9e281411746cb75d8b9686bb07213513a6827af9e2d2582d3aaee5d0a1e1582463d496d83be81d6d0003b8586b2b42e814ecb2eb4b31b288dd08d4a6d71253f6f90872d7f2ee65a725a8f582404ef5c515ac37806644d66e187784bf25f74a43c7dd98747933f580caf12c97de8796af508f6ce38d753833fa7e3df673b922672b38b4a3750af52b2e56f46a293d2301edf675a87c40637fa24ff5de154a43f0bf38b907403487346618150454a15be8935547faf98945cf4502f7649b7fb6993be0d924ba7e87e5c44373d6a05774e1f29b0405671ec2d2912145352a9f45d41c46d625518ce670cea41617a3a074385e691abd1d9156a1d614d5fb54f097d82d80c614b6b92cfc8bbf18ecd7c70d5e7049d93d1e01d3208d0df28de97dd2f92b2384b83a3414fd99c8754c10b9e5dd1be2056f367ff898dec986d36884b48e0487aa87651128106a3085ec747b37f462bcf65f40a63d5ef3e7f7cceecdee0471af487ef9a024980f7d83f5964932f413bab5da896195fea92a65bb5e08a7a9bd68e57b2e1444310b9ef9f9b1b0772aea0435d832578761abacc584d3588cdec05e37241fdd2de96b2b67eb007b4e5f9d4ef2a8742af5235a45147c3cab7323fd344569e919373f12bd5554e858d3c3b21c295a1d79c4a4fb2a7b79f3ab4817fe0416134b576cce565e369d8770ed9c2924f8be0dfa9dd680fe77af006c4499b2cf442c3a8c83284915263e978a0343afd905502056046dc7ab6773c132468e7578f4cfde095720b8e785a720e4bc40228f788b0f27669314a5f931b46bd7903017f8e10890fe23b330b2f9651075346d3114f7bd0563a61aa2b540a0abd34d4772278bec85e703c9d214d3cb2b767c686f94e8433cfbe3fab6a77d952576510c5cc764230fd772df1fee062380a787e5e1da520a88c3e08b7c330d28d614ff959b861ecc7795fe87c22712ae536609e6bee407518e19d641c5b653da09e59b8800e89d17848545190aad2538d869bd717dbd8356a67798d9ec5e38da75c9caf09b6ab547b3941643edf2f02e64254d50e36362c41616048da6c425bf9318c40ad18669821964ec7cbd2b8714e8f5dc27626caff71a47bf64d476ce899f18c24556f7820202b9b3fc5f4e77ac5be8c1bd2e3492682645389b4b359578edf64a5a823571c07547829153e647579c44813fa75b8937cd5797249dd5803ee0386f9cab266917789d973c3c989dff5e0de1a65817d238899a57e9e48eb67c3d0321d4dee57cc778c5525bf8f542e90828a8b72085cd25067411b57e1a7d11a4f67da559757ad36fbb9f6f3c55cf6723acf5d58348dc055fce03fd929925ba3f6e40db6df8f0358c8a335945849649d783b52b0006124cf4042c3f16a854dd4a7672206614e215921735710d162219312c171da4b7649882772eb6ac18293790f8a6ad0eb8a98988789622911a626f368e825d45f92441e6949bc1b3e57a98a2136eda230713b4b6c6175aa129315646973336e21535ebbb8b8a549687da77a9c603751abd14723da67adc45dd8ecffb37995aabce4d743db9059e992aa435f87ebf2069785b20d1282e032f87ec90a5977b5b6eb7ce5e14e76816f6d8aa2a99d88a41cda754a8eed7955bdc677bda57b7c9308d8853619ea8795f972807be4487ad3c6ba8177c1897f6ea77077cd107d7b0c6eb9775068bd82b5994983546a8b789ca4fc7e30fe328a23afde89fc09b97d0dc4b878a796bc885f381176c0dedd7c3009868708763778074f1483b056e47b2d2e6f775e060d80613be47905e3c28197a2b2867f2087840da05978d324ca77d0d2c3838b448282d320027d0815b57a46e25386e546797aea5eba75c4748a77a68fe6769eb2d488d27ee978dcf669752152f679d5d4447e23d3767a076168784a958b79e1e6cb7e174abe7e0952b07a7e773b7e902dfe83891d937b603f948047799278c0e56181423661814ddcad7e50682c7bd2708e8791bd117bd3ad5d764ab9e9851e6116883d29db86051203825f38767ecf081c7edad64780ccc1007a419fa88156cbfb8877f0068a366cf17ab74967805410d1775c0ec084f283567a15ff0480a7bab185479db782a0e0027c86b9e58487498f89cbd316877cf25b801767548ac2714878a7a81c89caa1d885c0633589a0289b7edbef3e82b44ae88619e8d979c7e36585e1ac7d81e3dea681258e21841709f6844a668580588df77ba9ec97766b4321899d7c8f84f0b64b880840f485a1533d851767a57d4a544c83b06ffa7dc8b02f8747de5c7fbfcc1387f25f5a8a96d15289712fee8898a5408dfef27fb1fe337a96b4d9119e22eb87a7befdb497371c3793e2cc0aac6839ae8fdf2214934812e67a0fae55765e185b83fc9293793110d187dcce0e868708c67c2d76639bf921f880fcbef0956f67119f67b9559a35d5fb9b7c1ecc997a8cb6b78d5cf38c291b8a8b95baa28623c1f77ac0c1d18300ec467e9c6e94869bba197d07b41b8215ed717c33a3518a2a69078e6c91f6ad16430393da5506a55aa5d0037315fafc42ea2304936366d8c6a231a413a65288914a4878fdecb7772cc2ad83e9a63a83e6830079d593e17e3d6ab47b4116c192d8e6c68cfe19c9af3ec9c39005755d013a19f177bd9ea67a4d820f7a4e487c042afd998ae5808088048d1c7cc641628a98e6237ed84a2e7b4c74f67ceebe667d388172975a9ae8afa684c0785fc93092ba7ee176bb07db75b140997b504ba975669b2c865791fb8b03a2327b42ee2879af368c80edb7ac827a8a198220b7b9783bfb7779379079828ed03eb60c7aed938ef44a98ff16557824090280d15731762a53287703a3b77fe63bd287bed734819a8f2080aac48c8253cc6a75084b27795ed0db7599b7f18705651188868021a7a69f5eb33e38d97dc4fd1985411d5076c1516a76d8de15829ba8e8861fdc0f88aa783f7b9aac607cc4b52478d9b68f7d03c7a57974203779ea94e0764e1d8f9d8ac846a5eb023c84085245853201b782e10ac287e06f07774edcdb8465e7e67b4f201381f6363f8327e4cc7f49584576e49dec82aad62b849a18e57ddab9dfa1dfa972a6a2b7127f6d3b3278c7a2f881f0af98824535538250c2998900cf9b88288d2778e3a5a29607b80c8af1e1f989ebb4ae7dd81bfe750a478a76390a5b7b16d7b9993e2d42cf578d667a5a367b78e9f23b7b6d993680088c68756815f1891602298a214723abf3ed7f8f93785d8a60b62d7f9eb218890b14197c055eff793efab48717d3f3b1cef2697a0b0daf7db98d5c89fd22457b7ea64a823c2d0d7cbbea0f763d9900fbcd7c0993dd268a901da4f37cdd3adc772d0a257608a94f6af2ecfe8990c841a548ad0f0355bd0b81c8cd508356dde386276d8987e4c8a17eae24597cac2c9685aa9cc780e78af9a7128b35a7e9e43f76e95770804540f4893590636813988480ba2f139c9f0c55cf83ba19860744d489391892880cfe0388f03fa186e898e57bdd04e379cba8b8fab785aa7d307981941cf013891e2b397be66ac778effb7b751ce013894acb3e846196ee03de219d897ead1781e16f55890797767732717b7de9e02981710be977bbb463fcf62c2695d6343a977160de77eb897f7530e5f883ab1320711d583f8542c6327aa93f847c26e8ae8708649b7e8188db85b068b58886efc983bb205d897fa99c8468f85ef91cde28a52f53e29aed1c0d7f8d8119889b02c485d946ab664e23bf880482d580c720ae03112885d3da3ef5804946a079c54cd5818cf8d489cd3b628376f4f882b02727872cc97d22c71cbb9c815fe9843299d185b9f9268620db128aaa5432880e540f7cb833b28a1d5a6dd08ba32078d731a87a3e5420770bf03f8511f95e7a99760a8607a9747f5599b1fab4e9db81987a0e879b025b7c8d6f6978d1bc797d09ed717cc600de87c2131b8498b4da03fa05068478a4b287491ec17eaf7e0c897045e87ea6f48782d212087c4ae8261d0f6f8a88c0755084709f6979ab8ade7818d3fd752beacb841a37c8786bccc77c149311847b83808ad929b8893b3819847bd2c27b19348181d8928287ace0be81a2481c1b8b644980cf780f771cc794839d461e7a85872d8038e63b7e25abcd8744d4f87b76233aa0aa52ebd7dcf395882d352a8351529c88f9ed62882f1b3d8a2e67d87b6dfe6bf8af405a83b1c9a7781e89ca76b46e807b4dd55f83761f5e88a1414e8af230b1851248a69d78bd977f239dc87a30b68d7f59940889c67e1177fd92fd861dee88864cb1591be244838471e77587929508875f678889cfd22b885a6c6081052f387e616a7a95bd77bbb1b62c138a067367797a4de184bef4d981d4d4f285ee3ca07ddad63c7a8003257fe6a788793612258267cd397e8529538850e641762ba213796a18c57ed784b7a52fd508a5ff20d6869efabf75d4df5f8a0e37078a9a432585d194c478d9b3b2786337658a8ca57d878c9eac7a19cf377e01f38482fee8c97926b98085796ae89e949cc590dc73e87d162af77c02e8088786b96a808534d2869636ba7c3117c086a46f587ac74f0e7ad36cd0806677b183ef845e870b1448822f4aa087dbd2be7aca2314810d587d869815da803de1e7855f800b850d17f47e7a1e32f90724a69a3f96087f43ea127b6a182a7bfd348b830732597c4f97457bcdbd9182687a998096417287ac455787b3a8cc8246a11e03b6260486f3a3837effe43b89e505de9b95289f9334b3ab840b670f7df76c63884d12b084dc574579ebb4018104286881d85c12861f1510767af346872d88fb86b01c9176e2bb40dccaa58cfa8517f60409c52a9ecaebf394ed2204a1499d827e71164f818c8ce57bf993528a8dff547c5a128f78fdab6e8acf492e82caa1737939ff837903492c9e15b105837ccb6c96d364cea13718beab45cc63a4dbb267872ee9ba80433d0f89b982877b7aa0437c178bc57cc9db49812ee96d839b96ec7f3087a979a9fe4f8942f577a8b8aa46a5009723940ef30e8fdd242eaf54b30298a5b8d579ef96f8844b5bf77d5e8fd989c2dd558aa5f488833c105b7734aa5889366c7f787df91d806ecb87859e787d9079ff13a63b6b2a8b01851c8c0015a1a35783748ad81b147ec9a56b894303be88adf98775b82cea775b4f787ba9ec7675b3313d7e4d8de588bcf81d8969f4238d5e69be94a58fecaa4121839ae315a69706ddf68a3c56d97b031a4487a743597a04bca87a08df6284aa30628a2da15488948dab86f283667e2ded9e895a7ef0864a1ece89683dfe7946884da33af14bac9579888c54d36097cbe562899b6efe818e3cda7c6f139b800d6a0c842c6fd87c625ffc79621e5084fe4da9803ad79080c31a237694b56d762587fe7576d051879db28578da6af183a7ba867b838b1388eb4f7384144be37e35e7437bd647c37642b8047bac91fd8509359281e07abc84e38bcd7b2ef4177e16d7e4829677b57b6a3c1c86ffaeea7554a4c381e629cd76f4b5647af3c05781599421804f32568846ba047bf5f23485aa42797d24c8e1823a8f7d7ef5c3137cb9f9f17d9b79c5771320817ec486607559163f8aad94d487d96a8478604ae689faf40f884899338a71ca6f8052b3ca7934c7b279ef72b6833c2dcf87157d32833183f87e41530f850d5f2f7f6271647a08dfcc76e52240864fd8967f99ff6c81e8509d75436ca985506f117690e56b82115360820063ec842f79887b035b9e81b91d167da253ea76c2179082a27e977eeebe1482cc8030886afb707ce3c56a868b4f457870aa77776ea76c77fd1b65780504aa7812bf8d87f5700f7f4a42167a2d9aa485e8eba27a02a7f98750398e754c7f1c887ba3f17f01a0b27dcbba247c8da525856b67a37ac57b168437f122843eef50897e485d7de3cc4683d97ca77da1c35f7b1e65eb79f4b56a88bc1597889e305e7b62cb8975661d3d7e1b6dd27fe8bc4079c2f3808adaa7f8865c81887f9797c6852b613681a2003e7ca021df7d2683247de48b3d806d107e75b2c9a0758575197e3457fb84c129f88193ae7b80eb7e668391225c87e994eb86a62a357d2003e5822c5d6989eb3d3d89e4e5fb82447a8c7eab8af6799e71b775277fe87aca187d82215976893f7f4277bcbb3d7aeab82e82a4b01789650b0179d29442896852507cef31227c8015c47a1ebf568a159a7d7d797c5080c1995f7b5cdfcf7e7743fc81c01b817e6dfeb0770e1e5479b355ae7be5cf2d8905addf7b8d5d8e83f57b35834e903f77ed5de57da439b5794eca49779e6a8d7f03743b88c0da998279b2597da81b237ca9466478589160773815fa845326708352a80576e0dfed75c23b5d863773717622b332872989b17f3d4d2a786f7f6586858e6981eb61547e87ae438220d4b781ca47f88066367f77171f6a88bd47c281d086d77c69edcb7881ed067b7a423b84e9e73d85d3a99788c01bc985b61e8d76b1b04178483ff5807d0b4b7ddf077f82ea658b811eb49c8866adfa7e3dc41d861bf6b97b1da66c8463379688ba6c5778bf607b82eed76178484af37837732a8261abaf7cfe95a67f144ba6784c62e087cae92979238bb68159c3217f4d16e97d58695287eda5f579b737557e8a4804765d5d4888738cf9790689308957215e8069a5c6889500347e88bc947e6cb65577c719ef8680d45c7d6f8aef86feb97182e5059a7b7b08898a5ff7cc7ae2be8b81c89ebd89ad77c57532305b85370ef17ad5c9d68482fba7880ffdf18283140b8a546afd7f4809617e5e7dd8788e9a0c83ce42227ada17358838520b785fcca87d83ccb57e3cec797c81ed177d3797b3796f6d557c59827e879f99c49d4094599d8f2e11a25021b88741121578fcf0f187ab6d9d86fe9540800201cb80da5f0f8072dce8867182408610c3e687b517098405940177a3c62f87a11de7a140c6518b772ffe90d0ff517a6337bc7a4731e884f009e481c9ff5d7e10aaaf85640f837a9ddae585f677827570a3df88fd176c7fb821e581e3141a880da01185b9c74d91a8d0adb5a830b98a98d596854b503a7f71f93979f199b87b576d82856732b6802081e17513731f86aa71de7bab9c0b7b9504b77dd33dc588c3081d8711a6d69ce9d132b70f581690eeb4c387ef1f3f891488ff850c6ad17f65c33c83fb556d7a7593da766c6ea289b8f2d4773175507ff665ba7b1b1a07889b55d5872eb3a3ad2635c08f9e5cb1abf8cd4880b438bd8207fc1880c7852a825ff03b799761ba7d45ff6e83f3c032836bd33c88f9528b7842413a8acd5f41791241c6797e00768626b90a94288aa1a5666113b2936c9e8674c93378bd4b018435dcbc896f86f58183f0878944c2787e8bc6a3800e95f28030ab2f84eff0738796e3f086a3bb4a787ab2db8e8605e8029dba41fc0fec099202a3146465e9e48541ca737910aaee828218f280c17cd777ba82bb7f86543685b164ef7c2ba9438786dd3a8a7b507f0163f5d885402166773f8ac7876b9d607ce33c1c7e19b2be8724cf6d7c03e4fc8980c2947b38276678a01a957f5b20d979fbb3b980449eb677a3f51a7f85875e977409adb1d85985923d3d9285a373648968b7367a99c2bf777a4d5e8bec6933b49ebb36866d3b2a857b913e801439228606bf4879af072c7846471082c4eb50792a9bacd048f7eaaf50a4298c43f866892c81747939ed307b3117b0807c33b579b10ac6ab76fefd84ed4008863e54757ee60c6c84d8e57b7d6f137d7befe98b81fc80327e5272b490077466a3fa75cc7bd93f9b809600d4816a061682318eb88537eacc9f21a31596839c47862c81877795452d83768f2486d13aef84288d59805cf1fc7da8802b7d5c2bd79efe2c8a7ba4035a8adce0c37c9689f17f1addd17ffb0d3d76923ba99a5f20aa87138ca276bc035f79cd58d37a36caff7de1d482802d1158980da6d278e6c8b2b0243f45784809ef7ebfcca47fc88c2e7670912e824b6d88813c80618ac0073991fe161885080abf80bf4366782a53be7d00c88f7c081ca67d279ca488d6e2fc838529d8da6786987e49e16a8672369b85ea9bab7632300484fc094a834564e5821320058750819d787050477ac7c6fc85a97c4c77ce05b385d731aa81ec7a6b7b21116d9f402da080b61ed37b640f807b9557458322ba2980bf02c67f26cbf57f844a31f92599597dcabe038254c80a7c11f9247eda30877de65f277dacaa7c751e2fb2862d35b4d8ab39677e7c740f822d9a4b8286fa6d88fb38668752b38b8981ac3286cae9891c02f27487ab62bb7a1fbbd7830cdae57b0009c67d972b7b7f732b0b8a8c270a7926b9557e26b965778754407bdb421988008793830761d8810386eb891e616e7dcdfeb21daf446f7f4d284f88033e8284a97cc4898d76f6794403cf75b181cc8384b694886398b385b1ed06868003e785b80f6c789337238259b1cf8602d2ab82d17b4076c8df6b76b19eec7db223cb7af598fb8416b355867f0e5387afea5e8336d12c79d8f7827d10ceb685f72b9b76943ec675625f6c7b8ed07d7bf0e51289590b7789512ae7f942a87d193bc3b7860cfe8b756a23c6798e6bac77e4c1b776f9a53082692e80857c69bd0294a0e67cb08c0388f86fa6869b434e7c16188e8164bc08860b8beb8269e2dd7fec151e79bc9e1880227d888822e976806f499c7e152e227e8bf5fe7c00c82481e898fade846be38140e0277b84f7417c4f07417e06246179c3130784a5407a7d9868321b0570a17582be397afa231e825cc2eb795e065278485135838804da8666c69177ecf1107f5355087c41e7a28673c63484afc6d57e43856c7d52fb6b813af4d0844c1f6f76d44f8783c1e17f7db42da482acf24e771cfd3281b546ac7526d60b8748fe1f983c79bc753462cc825ed78578bda51c8887083f82eb0eeb84495f067e0fe4491cabd286868a4831815475fd818f38ea777403c98871e7ef7ea3c6678512d81d865d110f8c34698d7554afad851fcdd8810c87aa7b7576bd87b58e8d783051d57750323d8688983780655e137f06774080977ead773b79897760af1f7ab713d97c320134a58490f57fc391ec771c748778eeed3a83d8e5cd7b0c1b317650397e751e2d439514250f8869f46e7e32011f7b923bd47f1415e87cca2ae676abd23088e0933786f3370faf90fc5c7a11700077b044bb7d6b67398893c04976bb6ce7844caacb8e90150b8a47387d87a7267a8092e8f383250cb87f92304877e0684c79be2311806aa14e90e6c4b184e8d9447a36415e7e985d2b781cce728a5505d084eb7edd94a0c6317d564da07d912216873720188445ffa3887be4cc850b4ac98349c4fd77b0035ab57743148037b193853c104580ad82d77514159f7eb4c19d8ad4e6aaa8f7030ab738f7d0840fe4f07f92831f80181bbd80af198a8857912a874cc3538aa3a9b184886909a28f67ac8db04c29208044f37b744b1d85543f9976bb1265857df9789baf534c97fb418d816624f780bb2b107f3e951479f5987581800a257985c88e7e6c757b7600a4c1a0b45b9c9448f01495730fb57c8b73cf849c925379f7070f94c5f476aef6b56c92385d8c87d66b3c89f469477c88869387e26060883f981786026df37e2a19ef88f051149ed7d571ab211d0696fa4b4c925b06cdf90b12b2acea4cd994d5f38fa9096b1baa1006b2833d1705846fd93c7608d46e834a25df80a5acbf794cb5a77e37326d8a1b3e6a75d1efd3932d0e509fdb5a679bdeb55f839591fa9214bbeeb30790b58de9e509994ae8207b4929a68874c20377692e2b81b23c58827e0750779f499b816087c97dc582d977e65fd27dfad0327dae652276a0e4e380c964e3814df92e81d70d058694216f790b798d810e953b86987a5c76f1631e7a3199107cecbb598a1b5000879416c67a433d1e807fa9d0788b7e6b7d6b1f6b80f450b3784ad6cb7e7f3d7789b09e05783ff6838450b7a67c10fd09819ea75b84483429889117d079eb12ff7a64ce877e86dd3a7e44c2e576c4e4c78992dd2184b41b1083ad8de681a2659884516b99797ea68b8a7c3a96812140577d7050d186273ea2868625a48003f62486497c2d88cde11c82c797597a68273a86fda03f7a72d19a8025cf8e85c3e48a868b3d3c8216a1b67b863b8c7b9eb9ee81410b977d9bde3c7bcb1f817ad4cd4283a743b689f40f92893852d2824564128963b528848678487e4cd9178a6b7933817714a683b4a4d1794593ff83b695d47e01c4fa7facb56682ef67a086e7080681fb67627f9dcf917c8438338385bd647cffdd427886d68784084b84788f721284ccc1ae804a8a5f76b0731179ec61a875d319af7742add57f49588b781ff5878382a1be75ad9fc286be0da075eab1bf8001057f821ebf5179a17bce7f95c3768523d345893eef997f9803a383b5587d7ea65984813428377ccb4377750e1eb88273b48686eb199e7d47431b837e5a8c76a2000e8217f7a989b529a2871d00c0889cab9b8092fa3982b886b4760832407b0732107918d3578636b48d81ad9fce7f8b4f658a51ac688904d5e378be8fcd7758862e7960512d88e2602684f06ed37ad84ad7875f38518621ac3d84c5f94f83c105c17efc6e887f88864f8349f38381003f2e7a1c0f90805953987e2d11df7a75b7c276c87a1d7633d23b83f3c07b7f271a99754485fd89ae19697fcf49c475db428f75355e16875ac6f27f986fb77704451081b5ab4f8199c9fb85ddd5ff815bcdc98699d0da7e40aa8478d7a8217de64da08891b21a7b6a4e477f162aa184ef848f7963e4ae7f55b2747ee8aae78113973e85bc15dd7d10eb9576c3541b853c511277f7e38d8129ee7c8277de3d8425f39989f3ae567b08c297791d942b7743eacb864c9f9581eaee987a2bfc8e89b6255189a1a604884780c6894f322383d6c503779d874487740ab27a1987977ecd05c47d9e42d37e9582c97e93a3827a8b07197b60807e88ec27cb89c33ed2779cf7a38491b29d87e8ce317c7606967c1cbc427ff2a0a4895ca50c84099aba75434fd6808ec5db790bbae582d59a267b2193377c198e02882392d9786ce58185cd223a764444b782d5ed938493f6fa88497cbb88a547368407df8277524747800ac52479c3ef9379b7ef637d0a7d448ae505fb85d2000b81f89e868a14eded78354c89764d69d6883a67ea761f730d7c1660c57e4b910c7f4265d77adf24907994d9e97d9790c1794b25d48978b6ca805ba2e27bc9e65c825d47877a0075ac86e4f7ef885260a5894e07c382873e7f7a765976775ddb657c9d6a5c867224b68795a61e8a8b32bc854df9c77e2bc6047b471f6c88dafa7576b252ba86dfb46f7752befd880ec0ba89b9c3e67b955e41806d104688ebeaee817da6207ebb5ae182a6f4a0802140548aa224798a7d8296828dffb27c268397866c75ac893ee4f184c092557816be177d6cf78b8489c31a7a8b90237e0af46883a3bb458803d91f7f9653b476079058880c6ad07ed731e88555f31a8873442379ff13398a14f8d9820ffd8f882093ef7c55feb27f2c15658a7e072788a4931b7b7afaf276a3bfcb7fe65f8b848b0e817c89f27c89ae2b6f7653721d7e3a842777da587b7e47a8b17d85eceb7e2451a7855fde4e78d14ceb786f37007e717006847899d67a8015b87736eefa797b3712870022057a8176d47c810a5b88fc1b51832ae3317c6844e189980a4f86a13a727f5a5a8779c873bb79335b6c7f58e0be819283c283f594d77c459a187c4f3dca88dac04689497cdb7571e07b76cc02387cb3de3e7e1d82c389e2b3d77e79e1c187681c76786295c77a50157483bd35367f2dbec389cbba5088dae83a7827781a7e48f7e084e31fb586c9359a77f83d0b7e02cb9a89ada61b7f89b1e87a67d88c7e6707427b99938e781946a582d18214818af57b892ff7ab7ee8989c83f05c297f6f49ac7b60c87b87f337348746433583d576bc75c06e6582276dbb7d4045f27f54ec7b8095e77f8111b8f19346d17daba9fa6da11cf467aa3859288f3cc3728b9eed0699dce1c498a94081abcc74dc93adc02d80a611e69689d47e76d8e5ef81f8df167bee356686fa47db8079296890fe180ca68ea14a8fd585aea5e57284b721d15690735e838c7fa9fc90139b619ebd8bbb885e595787155da79923dc97853e25f6867c169a89cd4d7d8155b2fb7ef283a7b6a01fc59039f30d8be3f2fcaa038f8c914e98bba90e3c390475cfe121366e6c9c95282d8c24ddfb76fcd3e8a12ce52f8a9e9c2b7dd731f887e5e10678796af378db30477dd29b5e91d6efb598e5c645a6c33d249705e638963a36d927ed7b3f7b468fde7fe61793872174c38c3c56c8806533e4946850c47de95ea0881d24437ecf66a68911ca7183257cf48036e333b14fed0a94b7f8369e40c24ab15e273afbf4242f7bc014267d0d46218697689281f3c0707d42369a96dd92ef8486a7e285868a19877702ab7e74c2ba75911089820964c58cc1bb5a8d0d6168ab7fe96395f8046584913b5e84dcbc9d7d691c4f781880ac7fa4270798fc66e48569596e7919b2f9850b92ac7fbf720d7f5d9a7475b3b304b5ae023e9c438c2ca7b54f16ae68e4fd8838b7e27e9bfb787efa2ae5838a890d7e50f8278074ddea87b8a32d8a8360f575be9a587cd02c0c7fb07aaf7fe7789076ac2c098d48130bb71a90919c94084e8ca2e23e82e93a1c8744d405766af43176f3135e8af881de871356c17b23c142a4336d307fe8ebea7646c51f7bbff01d762d648178ba9bac76c395cd88e8eb12057ce8bfdfa887df81df2b0b8a88218882fc269a7918b6a085ab2ca1813feb8f860249749eda88c17d924dfb855f5cae8124b6bd8722e96f79f4a3c37677f0507ba3a6290509290add8ea2a189b00a0c83699ad087dfcdd688bd35d082444b5381864d367773a2068d65335879d21dee7c9cd324817c45bd793abe9a874f689495c77f489a9d579b87bc5d58813cc8ad7b4385387c0e66b7818d118a7d66129b8af10519864c0458a4fb9f2586dd3a327a2bd82c8087913a76c34221860ecb6b77001bd794f5078d84332c2a7b60e553865d064881db78b57dc3e43185337fb481f4b58376b372c08951e20a857ead35856815ff87070e4e8afdc4007de575328529774a79921e897d22a158fa6e65bb79c5579677223ce77887ef4a8381acdc861a660d851bf6ad8548a4ac16a756c877f57b6a7858b56a879a0d6186631a0281c6bf6a87194d5c8932e4969bfa8ccbf9895f23810a3cf07f65286c78f45bbc7524f7aa84178bd07e3f08cf83d9ab8fa3c8e08576fc02a08a8bcd767df2d64f85af6114827e7ee886805d0476e6a01e83358240fc52a107896e9cf08443329d7538f1f7777befdb8471d5da79adc3ef7bb534d317d69949a6c15000822b97cf78ba65ae790bafbd89fd4d7388d731618871772b831631ba7fbe213a7d233828890a8baf80122b39868a89188a677a57843f5b4c7b895ef2f6a8600f8da00b8d7672a6f5894c7b4788a22bf5863b8bcd7b36eeae7cbc7aa388c0c8e27cb9b1a97f18a43e7ea4155487f1c4797fae5a397d739757783cdaa27b49bdd0b0f72f2684eba23585974f5582e0bbcd7f89f90d7b1d5eef7f7c0f586bc4f47e797fd19e1aa42fcf7d7b08a1832ad13d7c08f1da80f612927d6ea7ea825ba91582df3d60b877428d883129448aa6a1a8846f91f9772b61cd819ba8488817d32b85f3e4827b464e3d1ca62b8d86045e467d770d3e781c5064851cc3268677d3407e6ad78d8027eb5c9da485877c3964d1773c6e7f80b9287481069b7c7d71e72d768671e38039e2f47c8737c1849a065e82acc70280fddad8898f09ac893398c088fd17828828f2fa8ff3a7b978bddbfd891bbcce7cd8a43685f421b5879893427abe2a5d7697bf85825123907933e4917512ad6c7efe17e689528444878994ab7dd2121686a3203218f9ccfbb539f89d83848054883afbea7bea82be837a784089d74359823632c07c7fc62489ba9b2b86aacbd77b6fef3787ddf98777f641ef79bf6031824e12818604e00ca3a529537affef3e821e12c889d10b2787e00a2c7a784ac0764444d9838b73ee81f4aa2a97f456c188aa1e7d789f49e37e23558979e30dd37d36d11778d02c98aa1cca728171e797839ed69f840b094e7c2efbb8805fd3ff8517a4d181051d8582ceb2c3800cfe9eae3e68e2814702ba7e21befd811b7f9383e9b1567e24925198062adda32dbbb07f51d0827d6122a880e0f14c850dfa87893da07f8314d4a184ff784e83bc277b7d83883109d35d4a8746c5a0792c44ad75dae83c7b9eb9a0825c7a1b7a5d52ce8506078986a9466684c7fcf1898c81f8824fb4347dd606fd81d8a42c8af1053179539681856a7dcf8dd362e41904e8497e9462097bc026c016948e1b8652962278811c51853e421278d9878688614d6086cd29d0821c21a97c8adcec7c6d87147671ce407bac0f55806881268f947260a23b13310b5edfd2a1c0c7f8940b8d7877b8c70b775a94d57fa43916770c1b2089645e357d593a168549cf977be181487c77cc1386e845a07535f2b68cc3f93d8f3201b17ac14abc8a8e117eb15ff74f93059ec07cd0aa487e5ed0d4894d0bfc8508db8380e04b67780134877ff9a5b77605c37786ff0130841ad252763105e8a7cdf2757929c75685b799a78a41b690a64996109aa444b7781e368c7c151c1476b3433378a7557c7dc6249381bb4f0d7e01e8c384f2d645887c0fc07c865f978c4b78dd89d64e48a8f454d57ddbf32d96a030748c39814a86ace04b8015be4b7eb8fde777fb7df485a3973f81e1881f89a6ba96887bfded79b98d907d9993f17ac0a40595b5684078cf95c1a99852d1ac70f49fa4fb34fe9073a422856b26b07974203a7613ea107f83cc6088d6e2587dc16a4c80414cba885367f67e9707a4890619d8850c673880f670ad8be94c86962f2c84a966d482915be34883e82c40784062b17e5ee96e76f09d1b8650b0cc7b4784d3877a25967d8c441d7df761db7f97a93080897025826283207be7378684332cfd7e206f437a63498e87e99f8375b4f75189bf8b8b7be32af98687c08479b7aef885f88c6b7d8f846f7bb3e9da7fe558957f0009dc8643a96c7e9b148879dbc08a8215ff388aaa42dd7f4a5ff0811ff1f5818e06e47ac8cd4a86f6eeee82aa30b87abdf8cb8432542b87761f5d86f3b5f486b23c8077ee9ac2777d4ca1751ab2a078171f08853d0527878355ec8757913e781726a784ea9b1c7e45db0980bfba027771e0358258eb2e89aaa7b47f5db73d8a47802a7e321e8879cd346383673d8e7ac4b55884a2772188b2a1717e6976c180ae9b567859579c761d69ee7556b9cb7c9bcc00847e071c82ac50d27a3433ac7948af2d881523bf894b91237b1a1e3c852750dc82ee23dc7840452f7e8cb0057b6d34a688a7f0a275f08be77df90aca76059f577c7397527c7d94e488f744fb83783fed85d5939b8708c94688cfb55b87f8686577fd51968287f6f77f337fbe86165543771acbf4779a06947deba203803e013b8a49172984a08d288449cf7d851a308c8172891783a59ab7898231ee7b1d704d848613c57ef62f6b81dcfd4f8026b9d276339c607d0b676a83f0d3d784e1acfc795372c67f94212d875410a28113cda37a1ad69d85a7d3d47ab6e8107627012f8823792085b2c5b689fe4230843eaee281838b5c7c92a70687ec03157bee18507a9a351d8019295b81ca605a7d1004fa819dab4179e63b4178def95b83a56493860e428676c7480081dc339c7f00feb180301bc287f90a0378d8c1e17a6993787ebc03b389350030881cb133894851ac754fe3c9757a3bce77bb5ac287ec6f8c823ae93f7c3e78068122c5217cfa0680878cf8e678ead59586af858e76f8fc64824caa7889d3a99581f55e177d90c1ee85430ef689c81cb975e0b45e7ef2e8f1820350b67a2d52c081925437839935b97f93ae69818a7e838367fc8384b5e10477daf33b87a7c180893f7f5b8100a4f081c6bf058280f88b85273e0b7f0f374584dcc70782d2891d7f00ab0b7f1e19ad813f50de75676c0f7db93a287f5e4e7583c117797af75a9e773e951688613b787bc217e47769ede279fc26547a405786859d4d487e9e54ac83ace08276357bf07c9d87be829271698a439ec77bef84018a124f4d88fbccb8778beaef890c879076ae2fe2885dff2a7aba41178a9b8b9689aacbe97de235658477559f819271118213ea9089aebfd081aead5a7ad987af758288d4891aef73819d8e8378a9c80579c0f0cc7805d523830a2a2c7b34aaa67d9c09937502cb4f8399110585a810cf7aa48c18812e3174836bd3ca81b4c8bf7ba55280851385bf7a23cc9c88e111248a3f7ff9836565b37b2886a389ad82808143d8d67b0fc02a7ded52378414f8ea85680a7c82021ef280d13ab77ecc26c478415e777595212b7637ea107fdf85e97da2d1d57877a85a8f054fd87817cc73785eacd181fd4d1e7ae8507c8608649675ab49ef753ed6a17d82594b8a75be6684e994417ed008b18982d7a68085346c75527dca7e4cfd827c925fb18a26fb9c7d83969e80d5c962781e2426808f9d187f1c7b6b81bd23737d36ad907c5ccd9b88c213bc7a2044cb7da30eb3848296f97a858edc7e66f5c1804dca5c97e631ac8383d3e9924d704e7be766177fc283e37e0940a081f5c3a577effb0f78d128dc7ef283a3849ff6ac768db7d7864141ba88f4fd0f85ae908783c86fdf7aea1d9976c06eac76d5395c7bff787578c2e8a180a0864e7b18d3047f8188748722b30178ddbcf278d68f468626d2a6818176b87618c81588a141c77d688c5799bc773b7706b86a762f8fd0795bd82a762766388564453c856c63237a9993867c28980d81e372fd82d659ef7c1d1de3817f0e7e7dd36cf77e48e5af80200a027c2b7ea4805127f67540e4ce77fd0900783fae4978ff664c801c57cb775701257d12f56581c313ff8387e42676cdc80a7e0541767dcfefb37c209eec7ab27d24874b3e52908b18f993d8534d932c7e948a6a2a6a81bce6f381587bb77d9feff67ebd766a7e14d49a88bf55a3791535c2888326787a68a97579943e53898613d986d8a4c189c1bdcc85633e9d80a826da754c1a851dfd44aef8d62ba41e5d0958f764995a18c37b007fb563ce7f82183f7a42d1b57d7f0edb788f604c89a6c53f83c6f508863d712182a796a8b87bfc419c5c4d928c461498b3c64e467df0f00577cbb3bd7cf9b0af84652f06779b180e7630af657d3e83c487f88c648321596d777838ed8534b14086f6483682494d52a54b994fb14cdf889674ed6fe45cd01677fac9c77cdae8f77eb63b028737324d82d2775d7bfb0d5d7ce1a574786c8be787446fbe7dd4cdba808b3d3e7f6f081176c736929aec6cdb931c365da2f60b657a63e42f894e61fd7ffa064277e70c1080eba22c7eaff558792e18c983a481be75246e828625a70d7787dd30804b1a267b30c8b7907cb36899915363b8a8da7c832c2e1c881eeaeb78400f5279d4a987769820c27d35d5827d56787d7d35e7d0838aee81787bf6f485b379f08946ba9e85f36d258811ad0cb5713fe3b036c48f803b9dee809a182a7ef8bbb47a816fa786238b207b02837b866815257d73f59f7c2c27247a456f3f8a7df9bd7a65d5ae7c446817839bde42b42288a1ae2704c77caf078d7b2ee94780d796e57625ec0d828d2e037ae850fb8070577a87aa8a7f88b9a6d080448c2a77e563cc89ce9c0e79f6a63779b5abe08996362dab3a1b5295f3a0c788b0816586d5b7c7830c990c78718d2d7ab1520c7945c9ee76279178835aadc77e936d187b67e3087a32c44e7ec7d1317758fd8d7c7db13a9d7462d78fddc781813ad7a88792a79383b4fea2758dc2c989eb612b78cda49f880c754177d7a15284f6e3418453794d7fe8624288e2fbbb792480f88789534188638656a9a84e11e2c2212b82fac52c83c91c0177bc320c81463c8a83a6d73f848fa8f3827ef5c586fee8e884bae36783cb26f87d28fd03897ee3f78490229887bbc6f09981c9e595298684870f9102839f9c44890022577c7403908286427e86c83960881696aa8591a06683b7b5f18627b56288c3c7a28517f01f898dbed87729f9d37fd8d3af9a36f8527b9f8ac97fa81b897950df3377dc74ff7d172686884b2c5e7ce34ea786a10be987fb8b28814606588577141781d0ceb5780080dc7b35b8089af50605a632ae6380b113b9841f448b7c53af8e82629c8e7b6d462c84b529f8853fa33e81f3541a7d5da56888347b008ad8f76f797a7175779db675760aa19a88e6fa9187b1590baab17eb086d71fb08185b2638ab04017856421707e37d805881cb15d88724ffa759038b588f69ba18984bddd7b47cc747938328d8319a72d7e1ae8ad862912c98eb6d158a54eb704887212bc76e02002850249b477f2f3da80020c547fa9222488c3b52c7c5b7edd815e06c4897fd88d7b30ecaf79f2faae8243c6a888761f9395281ded91edd4141ed0d5b978f28ecf8091ab527827aee97c6e424082b75bd187f7195d7c9a7df67ac230ce8419a0958948fe517f51c93877cbd7fe7fec03fd81652fc6882e1c87996f89b81c7afb5c7a5f31277e51b72685560c9a7f80e6cf7a86cbcc7cb64d1778f2c4f377c4eb0b83f3ae66890e01a978324d7d80597000884858f28894e06b6e7f37fc75b98dd887eae34880108d7b877db89d7722e2de869b79b580e99f9e822fd347852df4697514cd9d7fe032f18769db56862380f97f929572899eaeb77bb3670d85bded4318a4fda9827219bb87696b6f79f05cbd858a7e6d8901e84b76f7a2c97cda5fd17fbc7a1e8244d4ec8081234387b7494478ca6b05859607b68926aee289951d3d82e6b2a1769dab9f826582fa75eac33276de3abf77bf1f7a824bd2867eee2a257e93912887daa739834ffce37db83364850947b6875cd437897c1d8d7fa90cc4855ef0627bc0e5568409ac0d7fea82ce85b07afd7eff01eb78a8802189e84568772ccd0c80b2060e7fc271587d5343288876315283a33df2894dad79822b0e6e80b9b1dca037593b873416db7a3e4db47f87d20c89e8ce2aaf8d2b4d87b39d3f7908f86678bf43fe821016298197ea5188eadc098aeff09e7b3ee8858894b85f7d11b12c782ef4e396200a39180823cc7dd0a3477c6ad73278c7c6d4ee5bf2d17fcd6a4382b4c84583ab5b6581b8d90a7d0d46737e1dd5007aa0083076bdf83a7b62a7228138a59679ac983ba1759f3791f434af93ec47b7156889baedb06e6a8c964b139ff953427a3de10f7917afb4882cc93682cec40c7da128338a331abb76fc26618946cc1588e546a6877ff118a6a39c919a6e8d7598e435f3adadf59f9050ddb694d615" + } + ] + }, + "job_rerun_replays_its_hop": { + "kind": "job", + "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0001\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[{\"divisor\":1,\"price\":150000000,\"unit\":\"seconds\"}],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.encode\",\"prefer\":{\"quality\":null,\"sort\":null,\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", + "pricebook": "{\"currency\":\"EUR\",\"entries\":[{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\"},{\"capability\":\"wai.video.av1\",\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"region\":\"us-east\",\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\"},{\"capability\":\"wai.video.int_motion\",\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"region\":\"eu-central\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\"},{\"capability\":\"wai.audio.opus\",\"divisor\":1,\"endpoint\":\"opus.alpha\",\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":1000,\"provider\":\"prov-alpha\",\"quality\":900,\"region\":\"eu-west\",\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":50,\"unit\":\"seconds\"}],\"issued\":1792996400000000,\"kind\":\"wai-route-pricebook\"}", + "policy": "{\"exclude\":[\"prov-x\"],\"keys\":[\"price\",\"quality\",\"ttfb\",\"energy\"],\"kind\":\"wai-route-policy\",\"max_attempts\":3,\"name\":\"lowest-cost\",\"version\":3}", + "record": "{\"accepted\":1793172800000000,\"attempts\":[{\"billed\":true,\"end\":1793172804000000,\"endpoint\":\"im.alpha\",\"error\":null,\"hop\":1,\"not_billed\":null,\"outcome\":\"served\",\"receipt\":\"sha256:e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8\",\"start\":1793172800001000}],\"candidates\":[{\"capability\":\"wai.video.av1\",\"cost\":480000000,\"divisor\":1,\"endpoint\":\"av1.alpha\",\"energy\":10000000,\"energy_class\":\"ModelBased\",\"energy_uj\":2500000,\"ga\":true,\"needs_consent\":false,\"price\":120000000,\"provider\":\"prov-alpha\",\"quality\":820,\"rank\":2,\"region\":\"eu-west\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":900,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.av1\",\"cost\":800000000,\"divisor\":1,\"endpoint\":\"av1.beta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":200000000,\"provider\":\"prov-beta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit_price\",\"value\":200000000}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":400,\"unit\":\"seconds\",\"via_fallback\":true},{\"capability\":\"wai.video.int_motion\",\"cost\":1036800,\"divisor\":1000000,\"endpoint\":\"im.alpha\",\"energy\":31104000,\"energy_class\":\"OnChipCounter\",\"energy_uj\":900000,\"ga\":true,\"needs_consent\":false,\"price\":30000,\"provider\":\"prov-alpha\",\"quality\":950,\"rank\":1,\"region\":\"eu-central\",\"removed\":[],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":300,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":691200,\"divisor\":1000000,\"endpoint\":\"im.delta\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":false,\"needs_consent\":false,\"price\":20000,\"provider\":\"prov-delta\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"retention\",\"value\":null},{\"limit\":\"ga\",\"value\":false}],\"residency\":\"eu\",\"retention_days\":null,\"trains_on_inputs\":false,\"ttfb_ms\":250,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":null,\"divisor\":1,\"endpoint\":\"im.epsilon\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":5000,\"provider\":\"prov-epsilon\",\"quality\":900,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"unit\",\"value\":\"frames\"},{\"limit\":\"spend\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":200,\"unit\":\"frames\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":345600,\"divisor\":1000000,\"endpoint\":\"im.excluded\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":10000,\"provider\":\"prov-x\",\"quality\":990,\"rank\":null,\"region\":\"eu-west\",\"removed\":[{\"limit\":\"policy\",\"value\":\"prov-x\"}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":100,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":518400,\"divisor\":1000000,\"endpoint\":\"im.gamma\",\"energy\":null,\"energy_class\":null,\"energy_uj\":null,\"ga\":true,\"needs_consent\":false,\"price\":15000,\"provider\":\"prov-gamma\",\"quality\":null,\"rank\":null,\"region\":\"us-east\",\"removed\":[{\"limit\":\"region\",\"value\":\"us-east\"},{\"limit\":\"residency\",\"value\":null},{\"limit\":\"training\",\"value\":null},{\"limit\":\"retention\",\"value\":30}],\"residency\":null,\"retention_days\":30,\"trains_on_inputs\":null,\"ttfb_ms\":150,\"unit\":\"subpixels\",\"via_fallback\":false},{\"capability\":\"wai.video.int_motion\",\"cost\":1555200,\"divisor\":1000000,\"endpoint\":\"im.zeta\",\"energy\":24192000,\"energy_class\":\"Estimator\",\"energy_uj\":700000,\"ga\":true,\"needs_consent\":null,\"price\":45000,\"provider\":\"prov-zeta\",\"quality\":800,\"rank\":null,\"region\":\"eu-central\",\"removed\":[{\"limit\":\"consent\",\"value\":null}],\"residency\":\"eu\",\"retention_days\":0,\"trains_on_inputs\":false,\"ttfb_ms\":350,\"unit\":\"subpixels\",\"via_fallback\":false}],\"capability\":\"wai.video.int_motion\",\"deadline\":1793172830000000,\"decision\":{\"decided_by\":\"price\",\"runner_up\":\"av1.alpha\"},\"fallback\":\"wai.video.av1\",\"job\":\"job-0001\",\"kind\":\"wai-route-record\",\"operation\":\"media.encode\",\"order\":[\"im.alpha\",\"av1.alpha\"],\"policy\":{\"digest\":\"09d08acf6a76fa45d822f10a6806b21ad1c41172bcccfcd08548e59567c4c741\",\"name\":\"lowest-cost\",\"version\":3},\"pricebook\":{\"currency\":\"EUR\",\"digest\":\"af132fd169de93674a59d1735c840e539f9144f3a26dc18bee089efe57bcf108\",\"issued\":1792996400000000},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}],\"request\":\"84f9305c1a97b1fe8c4c865a604e9509743d39b6c5fe4daf715b0e4fc69c2139\",\"sig\":\"0f9f930ac48d5a9a63222408061ca286a44fb0714af2a4c935d7fcad3c641c482724796ed82c259ba9939eeede92119a4ce1a8c2fe01ac0a8ed9a827f11faa0b\",\"signer\":\"5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb\",\"version\":1}", + "receipts": [ + "{\"oer\":\"2.1\",\"receipt_id\":\"sha256:e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8\",\"kind\":\"job\",\"capability\":\"oer.job.media.encode\",\"intent\":\"replicate\",\"subject\":{\"inputs\":[\"sha256:0d69497c0597b96e23e625790c9c89527c1e1d50744c9acc9b901a0694060588\"],\"output\":\"blake3:8c05ad23f29dac4fa0cb00b9cb758eab315e7cbd6458cb04ad8eaaae24ae67d5\"},\"work\":{\"status\":1,\"outcome\":\"completed\",\"latency_s\":\"1.0\"},\"energy\":{\"joules_total\":\"31.104\",\"boundary\":\"the hop's host\",\"breakdown\":{\"compute_j\":\"31.104\",\"movement_j\":\"0\",\"idle_alloc_j\":\"0\",\"shared_alloc_j\":\"0\",\"upstream_j\":\"0\"},\"method\":\"hardware_meter\",\"temperature_k\":\"300\",\"class\":{\"label\":\"OnChipCounter\",\"relative_ppm\":20000,\"window_us\":4000000,\"filtering\":\"Unknown\"}},\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"measured\",\"input\":\"im.alpha\"}},\"floor\":{\"bits_committed\":0,\"bits_delivered\":\"0\",\"floor_j\":\"0\",\"decades_above_floor\":\"0\"},\"chain\":{\"prev_hash\":\"sha256:0000000000000000000000000000000000000000000000000000000000000000\",\"receipt_hash\":\"sha256:519736649ceb37a7bec8a0db6c87cb6e80fdb7e6a079fc7bd990aaddd7152a3b\",\"signer\":\"ed25519:route-corpus-hops\",\"signature\":\"80569744db45809f85f0d80de50d473419cbdfa6603e02a44f3c060be515e9d9579baee9eedd7c1162dcce2817d2b941a854b7930290edc77a51b2dbde1d1508\"},\"hash_alg\":\"sha-256\",\"sig_alg\":\"ed25519\"}" + ], + "keys": { + "ed25519:route-corpus-hops": "733470a6f6824eef91aa6b6fe4094e4e3b864ce6a368122223cf718fdc57fc57" + }, + "outputs": [ + { + "receipt": "{\"profile\":\"wai.video.int_motion/receipt\",\"codestream_hash\":\"277865b574029f700f4fac023de3ab06ae598ee6f8ed726444e91442a6bd59e9\",\"clip_hash\":\"3492566e8e2682e3b3bac083b4db02ef8eba9a7f15d9faf0e27feee0f6eb488c\",\"h\":48,\"w\":48,\"frames\":[\"e1d8e8f8b5c534be1421e5858ed9137808ddb849bdff2bf397beb32568d8eb69\",\"ae7214babf5bbd6ec279434740f3271be43d2392b8167813fa557b5c4426fa14\",\"b3ef86db27b82d303bc6b612cb224ad55259b4f2e2c0461c74dcce188cd64b99\",\"3906f91010bf1b9ca14f6e2ac9b499589ac02ff4465e1d9764a22443a8b29c22\",\"bedb1538c2071d705d2d3ab2df2c569ffed440278494649f9bf1ec881561c519\"],\"merkle_root\":\"0f3fe4aa201c275c10289c22f31e7211b555627b33a251bbe33d094abada6508\",\"work_pixel_ops\":34560,\"joules_micro\":0,\"uj_per_kilopixel_op\":null,\"energy_provenance\":null,\"parent_receipt_hash\":null,\"signer_pubkey\":\"4a29b9b42b4c7b8d947b226d503cfa1ee8b69a5cd3e4cb88b30db023d6d2bb8c\",\"signer_id\":\"did:example:sink/job-0001\",\"sig\":\"50bcbf089a0f15151ff0573d3fdb210636f66997ea2d23d08864ab2f1ed6b4529383206c88f30973b2314e8ceb2db80f888af5a201b494e0aba18507000db30c\",\"receipt_hash\":\"35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f\"}", + "codestream": "57495631010100000500000030000000300000000800000006000000d5ffffff590000000000000004000000090000000f000000160000001e00000028000000330000004100000051000000640000007a00000094000000b3000000d8000000030100003601000073010000bb0100001002000074020000ea020000750300001a040000dd040000c3050000d2060000130800008e0900004e0b00005f0d0000d00f0000b21200001a160000201a0000e11e00007e240000202b0000f6320000383c00002747000012540000556300005c750000a38a0000aa9c0000edab0000d8b80000c7c3000009cd0000dfd4000081db00001ee10000dfe50000e5e900004ded00002ff00000a0f20000b1f4000071f60000ecf700002df900003cfa000022fb0000e5fb00008afc000015fd00008bfd0000effd000044fe00008cfe0000c9fe0000fcfe000027ff00004cff00006bff000085ff00009bff0000aeff0000beff0000ccff0000d7ff0000e1ff0000e9ff0000f0ff0000f6ff0000fbff0000ffff00000000010000000000feff0100ffff01000000000000000000000000000200fdfffbfffbfffcfffbfffdfffbff00000000000000000300fdfffbfffbfffbfffbfffcfffbfffffffbff000000000100fffffdfffbfffbfffbfffbfffbff00000000000000000000000000000000fcfffdfffbfffcff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0400fbff0400fcfffbff0100000000000000feff02000500fbfffbfffbfffbfffbfffdfffbff00000000ffff01000500fcfffefffbfffbfffbfffbfffbff000000000000000000000000fdfffbfffcfffbfffbfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fdff0200fcff0300000000000000000000000000fbfffcfffbfffbfffcfffbfffffffdff00000000000000000500fbfffbfffbfffbfffbfffffffbff00000000000000000300fefffdfffbfffbfffbfffbfffbff000000000000000000000000fdfffdfffcfffbff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fbff0200fbff0400ffff010000000000000000000300fefffbfffbfffbfffcff0100feff00000000000000000100fffffffffbfffbfffbfffbfffbff00000000000000000000000001000000fdfffcfffbfffbff74420000280b35593c000000a214bea9451501b9ad0cbdbd9a309164812c7ece531911d14717c3dbbc1d27782217989f7218f20d121d7bd4ca1f3eb3ef1f71131429e2d1f626c3e7ab249c33af4fe2d2c042a6d2222fb9b3c460c33836571567e55a40c90777634123135bfb8e1357858f0dfd6cd3316844ae2fdb041518b067b9167f09231b1d6f1c16b7df311ea6450d1ea29eed1ecc320c2b747de0206b34c82a708a2525b67967473d53e643c54f6f2f42e6355aaafd6534aabbdf5fe1a866406fac26314f6ad812ef006e0dd2c39e124439f5306ba6e0396f65ff1627ef771e0071f21b088eae1efd0a7d22d53ada205042fa23135ba627dfdecc3c473a7353bf1ea7435671e53209bdc9581998ef568e94f260c2af006595d31a5d0301d30b0e96f22e27c43d162997a218be088f12dc9d0915dff4db17246c1a3c61066e3807ec553cb509743e78e21d250c19332ef30e3d2d4f94a0275d0ead4e59f7ff403f44c02ed1b145563b075b55c0ece2599ea8038a52c4e77287a40b3015595e178405f01716799f12508739158ce39537c6364439c2c61335c774823feb0e4a46484b29464146093e52ba6e2e3e5a0631121b992d5a46a848c8478e518c794638949ff05b4c57d23967cc4b57d3bb164147bebb2f4cd1452f40e1701817cec818f564ff13f4072317feda4f17504f253558977544d057484615df8028fbee312a5a55ed2c1c72d1257130d62be71dda51a0a97748680d2735181dc85721d3923f8d192f8794fcc96be998eb135e56840f812d952b26dc2f2f8537db1701de931a9e0b811c99606b1d7a72b81fe6796623b2e3442266abe726bfe71c26713b582970ff964812b3a7511b88474ad1a39d60fe77fb5b93faa43aa689e37f22cf656dcdde8d4a529b091869c19a199bf5af108ce21a33eb8ae43a8485f63a7c0ae7f3e9d6d3fbf35320f44e2572fb823070f38da5d5fb4cd37f3e167531443d7027341943de2d2e0dbe4918ce0f53b1e5a7368b2c3c6ff4ff9044663d54810e945168c3b1d019852e9412c0563e10ce0287359612c8365ce0fd34c5cdb9f290f0e017571aaef2f889ca179debd4f3065bae18380425f4d7c9dd18bb2cf626ea6faa24e3f4384b1da15a3d6df8b52cc90b935da2e4c23b9597355cee7f1945393e249b7b14ad729ff91432962e6218224895175df6f11b30546af34ef81f1840d4b4f24290fa19e90d41f3792fbd17b307eff2702189161f1552f429b1fc18154682320160642d6a8c304b9a74645336beb060762fd36780e26160b5380e4500bf316ab3b6594b76993031ace92b2efad9cb178983121b9d4845f4ba36071a9d404af3f9d4f2199f14cff37ed44717baad75f448628d19af3421f46a8b0f19184461f3ef7bc1178d00634bcf58624c6725ac619927f15545490f360e0fb877c6814c6b9ea2f952ccf3a3abad91da10ef63f3144a0f23340223ff35814709f4d3516c16e5bb75f3a3574719c25228f4b5a03e1636db84f49a58781762e821f35742a2181bc2f7f397ef311637054630ad43d250643a45395291b55b725ae5723bdd1b62eae7a445bd65319f19c63568c61a5b1206b34d3bed1dd63786de01f49a49affb2a9cebf3518ed9fb7f061bf402f2bdfb0bb9c0f344eaa4fb1bb58df3fb10a4fb6bcbb8f3bb45a0fbfbd11ef477204efb6ff5815087d668629a2a0c5d17e9236a698d0061d79fdb460b2f26a9c24f33529ee32911b3aa1515876dab37a91b8339f13feff22ac0af17a839caf36b8b36183cace0f27ca7e419927205f46713bd1740d4a5f49da36019a6baa1f4ceac2c19b530c1f2eaf4de19c6322751ef91df5b92e05f6533f2a760db246a7d1551e266a304d149e19ef5ab9b67c917279abc17e5eb69fbf214f2182014b4fb9ffe3e163705e1fba23dfe17e466a2fb4cd37c16dcc16ffbaaa6f61663923efb92173218345ca6fb512703175f6a95fb422b751962216358fc9aad68a047125962cd87430577849addf21174af2dc5499c4fcf8778b8293377e4ed3b12154cf3afbf46fb6ab6baf3dc41b0fb4cbe91f32c0b39fb2ef0f3f2262a42fbe21813f3cd5230fb476af8f3870f92fbefea62f47a7a92fb7234cdf297d11f5d8e12023c55bf666f4ef6005c8d58bc9549953d730699fd4ff8264fb23cf0ac1886051a1c4d05891af4edc2fbf792baf24163bdfbe896c3f32f65e3fb6023ebf2ca07d7fb66774bf4993ad1fbd99a01f30be2a6fbd34630f3275768fb7ef1a1f2f78972fbe65735554ebaa9390610c05a5315734087513d8c09862074f653884a096d36bad118d01b4762691789a9a4fb84456418eb0ca3fb4aa9b419eedf99fb865a481883a740fbccb302171e6b31fbdcc4a318d918a3fb2772ae179e15aefb5dcf1b17b3873ffb7673f4189ca65c5d080bdc43f1e1b278e9bbf0411aed639db279b76db39473596dd293164dfc0317fddbabfb866f2cf49fee5afb376ea4f3351975fb9baa96f394a45afbd65b6bf333ddaffb9de9fdf212c3d7fbeeead4f2262aa9fbf329a1f4fa10ccfbaee586f41f6791fbe442375aab92fb3ebf0cd287434e9d6e5b5143a22f6941660f17d858c6629c18cba73c167536ce1e8e78a81944d168f419b32118417e79f37ee3a4168ea062f3684ff8160f7514f3d3335e16d0c984f4e44f5716db178bf3f1e44f1765e6d5f39b589417ffd4263cb1b60b56568bf7410b9931975f374f710f5c7f4fa42325bcbf1b8b7648524f17d3a2bc1d17f181fb389846f3eaf47dfb8dede9f2ef7b22fb6cb646f4429fd8fb1ab5a5f4cd8e2ffb7dc74ef4164ac2fb955678f3aea1abfb511264f4825bd7fb2cdbf5f268318f5c44110177dd73146b7a24ef4d9557b1ac71c8ac855c5302606910468a110d2833d76a203d85cc1c16f25d02f36cc7671902a893f3c335ff186fdffbf23bdcd317eae2b3f3dfb9001a3c8153f3a8edd118ccf62bf491591a17bed596f344cc8916cbf73c423ef6c45d15bc03651b1912a83850df6979ba2a6292c2eebd5d91e87574332d1ec8640e210884defb523cdff2811f84fb900283f397e066fbb5636bf4b5af3afbe896e9f2ed4ea3fbaabb7cf42cfc51fb744940f4fc3dc7fb444010f3ebf46cfb40019ff4ba45685818c3743cc9991ca49c3d626e6cdb6d54ca28d67ab141e2599329871efca9c71b6444e22057e352f45dd8b917feaa52f42bfc1119743121f3694f9818db6c99f3d79c4618989917f426394b16467b0df3b1fb8f19521d21f48db4ca175a186e3dbffda754ced3533d92a3eea77ad2e668dd293056741d36bf0ff6d877943fff1c5c2be22145ecd620c546c1f3241b6b1627e58ef43e187e1951f342f42ab1b917ec3602f4f8a719192312a0f21e3ac51704a731f4981c4f168ba270f48c969c181fea6d40d92c977aaa69c464c1fe0b4fe4ac6e6dca35ef5f6aa19dc29fe940765b492821c377c822552bbe2132b22720a4a99cfbe66ccb18874f90fb5de48618e3bcc8fb7a9dfc174fca80fb033b2e19226885fb5cbaa018cf7481fb4bbe3b1690f45071cf3b545fc7dc4c460fb904a5f494e4710bb28c59b37201bc93c3b85cedf29ed26f8cac231756f41e282b6e208e484d416f57f1f2d56f3617c0a984f402e336196a0dacf401f23a182db681f4e431cd161e6be5f31d3a03177b0239f33755483d85c9d2624864d23f6bb7c5a69c7e2164d4f8ed6167d7fdbd05c2405ff28607d169ffc3238fcc5423c78c9346c614a93f473a2b17750683fbeed5a017f40664fb11227117579a43fbf768d91957ef44fb4994591639592ffb3898db867e356462c5292e4270ba30a3145c994dc63817c2b506ff790c88855f7484a1d36b3cb022b49dc01f39897e3db627e7453b7eaa47ee9c181a325d08f3aea14718447cc5f3ce9d6d187dabeef346352a168c4c9bf38ff00e189a54f0817d9e5867e164264acf9f89a0468af5865b1d0f5d44650780f7a37565a8f4c0ceb96346217425be3f42ddfb460186a443dc4f4f39e726fd2e11ac22fba65269162b0d79fbccdde017447c95fbeeead617a64c9a609f26b64105687099d3e8f96edd6e714832a030810d602862a462e0c908e95486467e4f657a255646fefc1b3d6612ce46f05c183a0aa2e63122e07a4fe77fc847dfdb81f4981cdd5a7e25095e2496ff7c0d982d61f1b45a40c98a5da72c8cd36f9bae985ea04c49826f6e0159848c46d189116c7425229a29dca5753180914230f7d87f2cd1e2294a23bd653c8f2c033180eb015dc4796a35ffb37e5d2a91a83e040b6f9551375c7318d897491c79c9bfd5a26b563d7258cbb89e8e7b06429a6fdbbb519019578d2cf852092d88bd0f2a78859a2f1ffe22519dd8cb507fba725b0e80ca5d8ed18c6087f6007e90e4ac6bd0224e504285c6ad5e2a4885c47c8e5608003a80cdac406c2de6e9d9c33b3d881826ea313c35a52f98773329023e2e2f644eed4c9fd91548d5819c5845a9c356a9e281411746cb75d8b9686bb07213513a6827af9e2d2582d3aaee5d0a1e1582463d496d83be81d6d0003b8586b2b42e814ecb2eb4b31b288dd08d4a6d71253f6f90872d7f2ee65a725a8f582404ef5c515ac37806644d66e187784bf25f74a43c7dd98747933f580caf12c97de8796af508f6ce38d753833fa7e3df673b922672b38b4a3750af52b2e56f46a293d2301edf675a87c40637fa24ff5de154a43f0bf38b907403487346618150454a15be8935547faf98945cf4502f7649b7fb6993be0d924ba7e87e5c44373d6a05774e1f29b0405671ec2d2912145352a9f45d41c46d625518ce670cea41617a3a074385e691abd1d9156a1d614d5fb54f097d82d80c614b6b92cfc8bbf18ecd7c70d5e7049d93d1e01d3208d0df28de97dd2f92b2384b83a3414fd99c8754c10b9e5dd1be2056f367ff898dec986d36884b48e0487aa87651128106a3085ec747b37f462bcf65f40a63d5ef3e7f7cceecdee0471af487ef9a024980f7d83f5964932f413bab5da896195fea92a65bb5e08a7a9bd68e57b2e1444310b9ef9f9b1b0772aea0435d832578761abacc584d3588cdec05e37241fdd2de96b2b67eb007b4e5f9d4ef2a8742af5235a45147c3cab7323fd344569e919373f12bd5554e858d3c3b21c295a1d79c4a4fb2a7b79f3ab4817fe0416134b576cce565e369d8770ed9c2924f8be0dfa9dd680fe77af006c4499b2cf442c3a8c83284915263e978a0343afd905502056046dc7ab6773c132468e7578f4cfde095720b8e785a720e4bc40228f788b0f27669314a5f931b46bd7903017f8e10890fe23b330b2f9651075346d3114f7bd0563a61aa2b540a0abd34d4772278bec85e703c9d214d3cb2b767c686f94e8433cfbe3fab6a77d952576510c5cc764230fd772df1fee062380a787e5e1da520a88c3e08b7c330d28d614ff959b861ecc7795fe87c22712ae536609e6bee407518e19d641c5b653da09e59b8800e89d17848545190aad2538d869bd717dbd8356a67798d9ec5e38da75c9caf09b6ab547b3941643edf2f02e64254d50e36362c41616048da6c425bf9318c40ad18669821964ec7cbd2b8714e8f5dc27626caff71a47bf64d476ce899f18c24556f7820202b9b3fc5f4e77ac5be8c1bd2e3492682645389b4b359578edf64a5a823571c07547829153e647579c44813fa75b8937cd5797249dd5803ee0386f9cab266917789d973c3c989dff5e0de1a65817d238899a57e9e48eb67c3d0321d4dee57cc778c5525bf8f542e90828a8b72085cd25067411b57e1a7d11a4f67da559757ad36fbb9f6f3c55cf6723acf5d58348dc055fce03fd929925ba3f6e40db6df8f0358c8a335945849649d783b52b0006124cf4042c3f16a854dd4a7672206614e215921735710d162219312c171da4b7649882772eb6ac18293790f8a6ad0eb8a98988789622911a626f368e825d45f92441e6949bc1b3e57a98a2136eda230713b4b6c6175aa129315646973336e21535ebbb8b8a549687da77a9c603751abd14723da67adc45dd8ecffb37995aabce4d743db9059e992aa435f87ebf2069785b20d1282e032f87ec90a5977b5b6eb7ce5e14e76816f6d8aa2a99d88a41cda754a8eed7955bdc677bda57b7c9308d8853619ea8795f972807be4487ad3c6ba8177c1897f6ea77077cd107d7b0c6eb9775068bd82b5994983546a8b789ca4fc7e30fe328a23afde89fc09b97d0dc4b878a796bc885f381176c0dedd7c3009868708763778074f1483b056e47b2d2e6f775e060d80613be47905e3c28197a2b2867f2087840da05978d324ca77d0d2c3838b448282d320027d0815b57a46e25386e546797aea5eba75c4748a77a68fe6769eb2d488d27ee978dcf669752152f679d5d4447e23d3767a076168784a958b79e1e6cb7e174abe7e0952b07a7e773b7e902dfe83891d937b603f948047799278c0e56181423661814ddcad7e50682c7bd2708e8791bd117bd3ad5d764ab9e9851e6116883d29db86051203825f38767ecf081c7edad64780ccc1007a419fa88156cbfb8877f0068a366cf17ab74967805410d1775c0ec084f283567a15ff0480a7bab185479db782a0e0027c86b9e58487498f89cbd316877cf25b801767548ac2714878a7a81c89caa1d885c0633589a0289b7edbef3e82b44ae88619e8d979c7e36585e1ac7d81e3dea681258e21841709f6844a668580588df77ba9ec97766b4321899d7c8f84f0b64b880840f485a1533d851767a57d4a544c83b06ffa7dc8b02f8747de5c7fbfcc1387f25f5a8a96d15289712fee8898a5408dfef27fb1fe337a96b4d9119e22eb87a7befdb497371c3793e2cc0aac6839ae8fdf2214934812e67a0fae55765e185b83fc9293793110d187dcce0e868708c67c2d76639bf921f880fcbef0956f67119f67b9559a35d5fb9b7c1ecc997a8cb6b78d5cf38c291b8a8b95baa28623c1f77ac0c1d18300ec467e9c6e94869bba197d07b41b8215ed717c33a3518a2a69078e6c91f6ad16430393da5506a55aa5d0037315fafc42ea2304936366d8c6a231a413a65288914a4878fdecb7772cc2ad83e9a63a83e6830079d593e17e3d6ab47b4116c192d8e6c68cfe19c9af3ec9c39005755d013a19f177bd9ea67a4d820f7a4e487c042afd998ae5808088048d1c7cc641628a98e6237ed84a2e7b4c74f67ceebe667d388172975a9ae8afa684c0785fc93092ba7ee176bb07db75b140997b504ba975669b2c865791fb8b03a2327b42ee2879af368c80edb7ac827a8a198220b7b9783bfb7779379079828ed03eb60c7aed938ef44a98ff16557824090280d15731762a53287703a3b77fe63bd287bed734819a8f2080aac48c8253cc6a75084b27795ed0db7599b7f18705651188868021a7a69f5eb33e38d97dc4fd1985411d5076c1516a76d8de15829ba8e8861fdc0f88aa783f7b9aac607cc4b52478d9b68f7d03c7a57974203779ea94e0764e1d8f9d8ac846a5eb023c84085245853201b782e10ac287e06f07774edcdb8465e7e67b4f201381f6363f8327e4cc7f49584576e49dec82aad62b849a18e57ddab9dfa1dfa972a6a2b7127f6d3b3278c7a2f881f0af98824535538250c2998900cf9b88288d2778e3a5a29607b80c8af1e1f989ebb4ae7dd81bfe750a478a76390a5b7b16d7b9993e2d42cf578d667a5a367b78e9f23b7b6d993680088c68756815f1891602298a214723abf3ed7f8f93785d8a60b62d7f9eb218890b14197c055eff793efab48717d3f3b1cef2697a0b0daf7db98d5c89fd22457b7ea64a823c2d0d7cbbea0f763d9900fbcd7c0993dd268a901da4f37cdd3adc772d0a257608a94f6af2ecfe8990c841a548ad0f0355bd0b81c8cd508356dde386276d8987e4c8a17eae24597cac2c9685aa9cc780e78af9a7128b35a7e9e43f76e95770804540f4893590636813988480ba2f139c9f0c55cf83ba19860744d489391892880cfe0388f03fa186e898e57bdd04e379cba8b8fab785aa7d307981941cf013891e2b397be66ac778effb7b751ce013894acb3e846196ee03de219d897ead1781e16f55890797767732717b7de9e02981710be977bbb463fcf62c2695d6343a977160de77eb897f7530e5f883ab1320711d583f8542c6327aa93f847c26e8ae8708649b7e8188db85b068b58886efc983bb205d897fa99c8468f85ef91cde28a52f53e29aed1c0d7f8d8119889b02c485d946ab664e23bf880482d580c720ae03112885d3da3ef5804946a079c54cd5818cf8d489cd3b628376f4f882b02727872cc97d22c71cbb9c815fe9843299d185b9f9268620db128aaa5432880e540f7cb833b28a1d5a6dd08ba32078d731a87a3e5420770bf03f8511f95e7a99760a8607a9747f5599b1fab4e9db81987a0e879b025b7c8d6f6978d1bc797d09ed717cc600de87c2131b8498b4da03fa05068478a4b287491ec17eaf7e0c897045e87ea6f48782d212087c4ae8261d0f6f8a88c0755084709f6979ab8ade7818d3fd752beacb841a37c8786bccc77c149311847b83808ad929b8893b3819847bd2c27b19348181d8928287ace0be81a2481c1b8b644980cf780f771cc794839d461e7a85872d8038e63b7e25abcd8744d4f87b76233aa0aa52ebd7dcf395882d352a8351529c88f9ed62882f1b3d8a2e67d87b6dfe6bf8af405a83b1c9a7781e89ca76b46e807b4dd55f83761f5e88a1414e8af230b1851248a69d78bd977f239dc87a30b68d7f59940889c67e1177fd92fd861dee88864cb1591be244838471e77587929508875f678889cfd22b885a6c6081052f387e616a7a95bd77bbb1b62c138a067367797a4de184bef4d981d4d4f285ee3ca07ddad63c7a8003257fe6a788793612258267cd397e8529538850e641762ba213796a18c57ed784b7a52fd508a5ff20d6869efabf75d4df5f8a0e37078a9a432585d194c478d9b3b2786337658a8ca57d878c9eac7a19cf377e01f38482fee8c97926b98085796ae89e949cc590dc73e87d162af77c02e8088786b96a808534d2869636ba7c3117c086a46f587ac74f0e7ad36cd0806677b183ef845e870b1448822f4aa087dbd2be7aca2314810d587d869815da803de1e7855f800b850d17f47e7a1e32f90724a69a3f96087f43ea127b6a182a7bfd348b830732597c4f97457bcdbd9182687a998096417287ac455787b3a8cc8246a11e03b6260486f3a3837effe43b89e505de9b95289f9334b3ab840b670f7df76c63884d12b084dc574579ebb4018104286881d85c12861f1510767af346872d88fb86b01c9176e2bb40dccaa58cfa8517f60409c52a9ecaebf394ed2204a1499d827e71164f818c8ce57bf993528a8dff547c5a128f78fdab6e8acf492e82caa1737939ff837903492c9e15b105837ccb6c96d364cea13718beab45cc63a4dbb267872ee9ba80433d0f89b982877b7aa0437c178bc57cc9db49812ee96d839b96ec7f3087a979a9fe4f8942f577a8b8aa46a5009723940ef30e8fdd242eaf54b30298a5b8d579ef96f8844b5bf77d5e8fd989c2dd558aa5f488833c105b7734aa5889366c7f787df91d806ecb87859e787d9079ff13a63b6b2a8b01851c8c0015a1a35783748ad81b147ec9a56b894303be88adf98775b82cea775b4f787ba9ec7675b3313d7e4d8de588bcf81d8969f4238d5e69be94a58fecaa4121839ae315a69706ddf68a3c56d97b031a4487a743597a04bca87a08df6284aa30628a2da15488948dab86f283667e2ded9e895a7ef0864a1ece89683dfe7946884da33af14bac9579888c54d36097cbe562899b6efe818e3cda7c6f139b800d6a0c842c6fd87c625ffc79621e5084fe4da9803ad79080c31a237694b56d762587fe7576d051879db28578da6af183a7ba867b838b1388eb4f7384144be37e35e7437bd647c37642b8047bac91fd8509359281e07abc84e38bcd7b2ef4177e16d7e4829677b57b6a3c1c86ffaeea7554a4c381e629cd76f4b5647af3c05781599421804f32568846ba047bf5f23485aa42797d24c8e1823a8f7d7ef5c3137cb9f9f17d9b79c5771320817ec486607559163f8aad94d487d96a8478604ae689faf40f884899338a71ca6f8052b3ca7934c7b279ef72b6833c2dcf87157d32833183f87e41530f850d5f2f7f6271647a08dfcc76e52240864fd8967f99ff6c81e8509d75436ca985506f117690e56b82115360820063ec842f79887b035b9e81b91d167da253ea76c2179082a27e977eeebe1482cc8030886afb707ce3c56a868b4f457870aa77776ea76c77fd1b65780504aa7812bf8d87f5700f7f4a42167a2d9aa485e8eba27a02a7f98750398e754c7f1c887ba3f17f01a0b27dcbba247c8da525856b67a37ac57b168437f122843eef50897e485d7de3cc4683d97ca77da1c35f7b1e65eb79f4b56a88bc1597889e305e7b62cb8975661d3d7e1b6dd27fe8bc4079c2f3808adaa7f8865c81887f9797c6852b613681a2003e7ca021df7d2683247de48b3d806d107e75b2c9a0758575197e3457fb84c129f88193ae7b80eb7e668391225c87e994eb86a62a357d2003e5822c5d6989eb3d3d89e4e5fb82447a8c7eab8af6799e71b775277fe87aca187d82215976893f7f4277bcbb3d7aeab82e82a4b01789650b0179d29442896852507cef31227c8015c47a1ebf568a159a7d7d797c5080c1995f7b5cdfcf7e7743fc81c01b817e6dfeb0770e1e5479b355ae7be5cf2d8905addf7b8d5d8e83f57b35834e903f77ed5de57da439b5794eca49779e6a8d7f03743b88c0da998279b2597da81b237ca9466478589160773815fa845326708352a80576e0dfed75c23b5d863773717622b332872989b17f3d4d2a786f7f6586858e6981eb61547e87ae438220d4b781ca47f88066367f77171f6a88bd47c281d086d77c69edcb7881ed067b7a423b84e9e73d85d3a99788c01bc985b61e8d76b1b04178483ff5807d0b4b7ddf077f82ea658b811eb49c8866adfa7e3dc41d861bf6b97b1da66c8463379688ba6c5778bf607b82eed76178484af37837732a8261abaf7cfe95a67f144ba6784c62e087cae92979238bb68159c3217f4d16e97d58695287eda5f579b737557e8a4804765d5d4888738cf9790689308957215e8069a5c6889500347e88bc947e6cb65577c719ef8680d45c7d6f8aef86feb97182e5059a7b7b08898a5ff7cc7ae2be8b81c89ebd89ad77c57532305b85370ef17ad5c9d68482fba7880ffdf18283140b8a546afd7f4809617e5e7dd8788e9a0c83ce42227ada17358838520b785fcca87d83ccb57e3cec797c81ed177d3797b3796f6d557c59827e879f99c49d4094599d8f2e11a25021b88741121578fcf0f187ab6d9d86fe9540800201cb80da5f0f8072dce8867182408610c3e687b517098405940177a3c62f87a11de7a140c6518b772ffe90d0ff517a6337bc7a4731e884f009e481c9ff5d7e10aaaf85640f837a9ddae585f677827570a3df88fd176c7fb821e581e3141a880da01185b9c74d91a8d0adb5a830b98a98d596854b503a7f71f93979f199b87b576d82856732b6802081e17513731f86aa71de7bab9c0b7b9504b77dd33dc588c3081d8711a6d69ce9d132b70f581690eeb4c387ef1f3f891488ff850c6ad17f65c33c83fb556d7a7593da766c6ea289b8f2d4773175507ff665ba7b1b1a07889b55d5872eb3a3ad2635c08f9e5cb1abf8cd4880b438bd8207fc1880c7852a825ff03b799761ba7d45ff6e83f3c032836bd33c88f9528b7842413a8acd5f41791241c6797e00768626b90a94288aa1a5666113b2936c9e8674c93378bd4b018435dcbc896f86f58183f0878944c2787e8bc6a3800e95f28030ab2f84eff0738796e3f086a3bb4a787ab2db8e8605e8029dba41fc0fec099202a3146465e9e48541ca737910aaee828218f280c17cd777ba82bb7f86543685b164ef7c2ba9438786dd3a8a7b507f0163f5d885402166773f8ac7876b9d607ce33c1c7e19b2be8724cf6d7c03e4fc8980c2947b38276678a01a957f5b20d979fbb3b980449eb677a3f51a7f85875e977409adb1d85985923d3d9285a373648968b7367a99c2bf777a4d5e8bec6933b49ebb36866d3b2a857b913e801439228606bf4879af072c7846471082c4eb50792a9bacd048f7eaaf50a4298c43f866892c81747939ed307b3117b0807c33b579b10ac6ab76fefd84ed4008863e54757ee60c6c84d8e57b7d6f137d7befe98b81fc80327e5272b490077466a3fa75cc7bd93f9b809600d4816a061682318eb88537eacc9f21a31596839c47862c81877795452d83768f2486d13aef84288d59805cf1fc7da8802b7d5c2bd79efe2c8a7ba4035a8adce0c37c9689f17f1addd17ffb0d3d76923ba99a5f20aa87138ca276bc035f79cd58d37a36caff7de1d482802d1158980da6d278e6c8b2b0243f45784809ef7ebfcca47fc88c2e7670912e824b6d88813c80618ac0073991fe161885080abf80bf4366782a53be7d00c88f7c081ca67d279ca488d6e2fc838529d8da6786987e49e16a8672369b85ea9bab7632300484fc094a834564e5821320058750819d787050477ac7c6fc85a97c4c77ce05b385d731aa81ec7a6b7b21116d9f402da080b61ed37b640f807b9557458322ba2980bf02c67f26cbf57f844a31f92599597dcabe038254c80a7c11f9247eda30877de65f277dacaa7c751e2fb2862d35b4d8ab39677e7c740f822d9a4b8286fa6d88fb38668752b38b8981ac3286cae9891c02f27487ab62bb7a1fbbd7830cdae57b0009c67d972b7b7f732b0b8a8c270a7926b9557e26b965778754407bdb421988008793830761d8810386eb891e616e7dcdfeb21daf446f7f4d284f88033e8284a97cc4898d76f6794403cf75b181cc8384b694886398b385b1ed06868003e785b80f6c789337238259b1cf8602d2ab82d17b4076c8df6b76b19eec7db223cb7af598fb8416b355867f0e5387afea5e8336d12c79d8f7827d10ceb685f72b9b76943ec675625f6c7b8ed07d7bf0e51289590b7789512ae7f942a87d193bc3b7860cfe8b756a23c6798e6bac77e4c1b776f9a53082692e80857c69bd0294a0e67cb08c0388f86fa6869b434e7c16188e8164bc08860b8beb8269e2dd7fec151e79bc9e1880227d888822e976806f499c7e152e227e8bf5fe7c00c82481e898fade846be38140e0277b84f7417c4f07417e06246179c3130784a5407a7d9868321b0570a17582be397afa231e825cc2eb795e065278485135838804da8666c69177ecf1107f5355087c41e7a28673c63484afc6d57e43856c7d52fb6b813af4d0844c1f6f76d44f8783c1e17f7db42da482acf24e771cfd3281b546ac7526d60b8748fe1f983c79bc753462cc825ed78578bda51c8887083f82eb0eeb84495f067e0fe4491cabd286868a4831815475fd818f38ea777403c98871e7ef7ea3c6678512d81d865d110f8c34698d7554afad851fcdd8810c87aa7b7576bd87b58e8d783051d57750323d8688983780655e137f06774080977ead773b79897760af1f7ab713d97c320134a58490f57fc391ec771c748778eeed3a83d8e5cd7b0c1b317650397e751e2d439514250f8869f46e7e32011f7b923bd47f1415e87cca2ae676abd23088e0933786f3370faf90fc5c7a11700077b044bb7d6b67398893c04976bb6ce7844caacb8e90150b8a47387d87a7267a8092e8f383250cb87f92304877e0684c79be2311806aa14e90e6c4b184e8d9447a36415e7e985d2b781cce728a5505d084eb7edd94a0c6317d564da07d912216873720188445ffa3887be4cc850b4ac98349c4fd77b0035ab57743148037b193853c104580ad82d77514159f7eb4c19d8ad4e6aaa8f7030ab738f7d0840fe4f07f92831f80181bbd80af198a8857912a874cc3538aa3a9b184886909a28f67ac8db04c29208044f37b744b1d85543f9976bb1265857df9789baf534c97fb418d816624f780bb2b107f3e951479f5987581800a257985c88e7e6c757b7600a4c1a0b45b9c9448f01495730fb57c8b73cf849c925379f7070f94c5f476aef6b56c92385d8c87d66b3c89f469477c88869387e26060883f981786026df37e2a19ef88f051149ed7d571ab211d0696fa4b4c925b06cdf90b12b2acea4cd994d5f38fa9096b1baa1006b2833d1705846fd93c7608d46e834a25df80a5acbf794cb5a77e37326d8a1b3e6a75d1efd3932d0e509fdb5a679bdeb55f839591fa9214bbeeb30790b58de9e509994ae8207b4929a68874c20377692e2b81b23c58827e0750779f499b816087c97dc582d977e65fd27dfad0327dae652276a0e4e380c964e3814df92e81d70d058694216f790b798d810e953b86987a5c76f1631e7a3199107cecbb598a1b5000879416c67a433d1e807fa9d0788b7e6b7d6b1f6b80f450b3784ad6cb7e7f3d7789b09e05783ff6838450b7a67c10fd09819ea75b84483429889117d079eb12ff7a64ce877e86dd3a7e44c2e576c4e4c78992dd2184b41b1083ad8de681a2659884516b99797ea68b8a7c3a96812140577d7050d186273ea2868625a48003f62486497c2d88cde11c82c797597a68273a86fda03f7a72d19a8025cf8e85c3e48a868b3d3c8216a1b67b863b8c7b9eb9ee81410b977d9bde3c7bcb1f817ad4cd4283a743b689f40f92893852d2824564128963b528848678487e4cd9178a6b7933817714a683b4a4d1794593ff83b695d47e01c4fa7facb56682ef67a086e7080681fb67627f9dcf917c8438338385bd647cffdd427886d68784084b84788f721284ccc1ae804a8a5f76b0731179ec61a875d319af7742add57f49588b781ff5878382a1be75ad9fc286be0da075eab1bf8001057f821ebf5179a17bce7f95c3768523d345893eef997f9803a383b5587d7ea65984813428377ccb4377750e1eb88273b48686eb199e7d47431b837e5a8c76a2000e8217f7a989b529a2871d00c0889cab9b8092fa3982b886b4760832407b0732107918d3578636b48d81ad9fce7f8b4f658a51ac688904d5e378be8fcd7758862e7960512d88e2602684f06ed37ad84ad7875f38518621ac3d84c5f94f83c105c17efc6e887f88864f8349f38381003f2e7a1c0f90805953987e2d11df7a75b7c276c87a1d7633d23b83f3c07b7f271a99754485fd89ae19697fcf49c475db428f75355e16875ac6f27f986fb77704451081b5ab4f8199c9fb85ddd5ff815bcdc98699d0da7e40aa8478d7a8217de64da08891b21a7b6a4e477f162aa184ef848f7963e4ae7f55b2747ee8aae78113973e85bc15dd7d10eb9576c3541b853c511277f7e38d8129ee7c8277de3d8425f39989f3ae567b08c297791d942b7743eacb864c9f9581eaee987a2bfc8e89b6255189a1a604884780c6894f322383d6c503779d874487740ab27a1987977ecd05c47d9e42d37e9582c97e93a3827a8b07197b60807e88ec27cb89c33ed2779cf7a38491b29d87e8ce317c7606967c1cbc427ff2a0a4895ca50c84099aba75434fd6808ec5db790bbae582d59a267b2193377c198e02882392d9786ce58185cd223a764444b782d5ed938493f6fa88497cbb88a547368407df8277524747800ac52479c3ef9379b7ef637d0a7d448ae505fb85d2000b81f89e868a14eded78354c89764d69d6883a67ea761f730d7c1660c57e4b910c7f4265d77adf24907994d9e97d9790c1794b25d48978b6ca805ba2e27bc9e65c825d47877a0075ac86e4f7ef885260a5894e07c382873e7f7a765976775ddb657c9d6a5c867224b68795a61e8a8b32bc854df9c77e2bc6047b471f6c88dafa7576b252ba86dfb46f7752befd880ec0ba89b9c3e67b955e41806d104688ebeaee817da6207ebb5ae182a6f4a0802140548aa224798a7d8296828dffb27c268397866c75ac893ee4f184c092557816be177d6cf78b8489c31a7a8b90237e0af46883a3bb458803d91f7f9653b476079058880c6ad07ed731e88555f31a8873442379ff13398a14f8d9820ffd8f882093ef7c55feb27f2c15658a7e072788a4931b7b7afaf276a3bfcb7fe65f8b848b0e817c89f27c89ae2b6f7653721d7e3a842777da587b7e47a8b17d85eceb7e2451a7855fde4e78d14ceb786f37007e717006847899d67a8015b87736eefa797b3712870022057a8176d47c810a5b88fc1b51832ae3317c6844e189980a4f86a13a727f5a5a8779c873bb79335b6c7f58e0be819283c283f594d77c459a187c4f3dca88dac04689497cdb7571e07b76cc02387cb3de3e7e1d82c389e2b3d77e79e1c187681c76786295c77a50157483bd35367f2dbec389cbba5088dae83a7827781a7e48f7e084e31fb586c9359a77f83d0b7e02cb9a89ada61b7f89b1e87a67d88c7e6707427b99938e781946a582d18214818af57b892ff7ab7ee8989c83f05c297f6f49ac7b60c87b87f337348746433583d576bc75c06e6582276dbb7d4045f27f54ec7b8095e77f8111b8f19346d17daba9fa6da11cf467aa3859288f3cc3728b9eed0699dce1c498a94081abcc74dc93adc02d80a611e69689d47e76d8e5ef81f8df167bee356686fa47db8079296890fe180ca68ea14a8fd585aea5e57284b721d15690735e838c7fa9fc90139b619ebd8bbb885e595787155da79923dc97853e25f6867c169a89cd4d7d8155b2fb7ef283a7b6a01fc59039f30d8be3f2fcaa038f8c914e98bba90e3c390475cfe121366e6c9c95282d8c24ddfb76fcd3e8a12ce52f8a9e9c2b7dd731f887e5e10678796af378db30477dd29b5e91d6efb598e5c645a6c33d249705e638963a36d927ed7b3f7b468fde7fe61793872174c38c3c56c8806533e4946850c47de95ea0881d24437ecf66a68911ca7183257cf48036e333b14fed0a94b7f8369e40c24ab15e273afbf4242f7bc014267d0d46218697689281f3c0707d42369a96dd92ef8486a7e285868a19877702ab7e74c2ba75911089820964c58cc1bb5a8d0d6168ab7fe96395f8046584913b5e84dcbc9d7d691c4f781880ac7fa4270798fc66e48569596e7919b2f9850b92ac7fbf720d7f5d9a7475b3b304b5ae023e9c438c2ca7b54f16ae68e4fd8838b7e27e9bfb787efa2ae5838a890d7e50f8278074ddea87b8a32d8a8360f575be9a587cd02c0c7fb07aaf7fe7789076ac2c098d48130bb71a90919c94084e8ca2e23e82e93a1c8744d405766af43176f3135e8af881de871356c17b23c142a4336d307fe8ebea7646c51f7bbff01d762d648178ba9bac76c395cd88e8eb12057ce8bfdfa887df81df2b0b8a88218882fc269a7918b6a085ab2ca1813feb8f860249749eda88c17d924dfb855f5cae8124b6bd8722e96f79f4a3c37677f0507ba3a6290509290add8ea2a189b00a0c83699ad087dfcdd688bd35d082444b5381864d367773a2068d65335879d21dee7c9cd324817c45bd793abe9a874f689495c77f489a9d579b87bc5d58813cc8ad7b4385387c0e66b7818d118a7d66129b8af10519864c0458a4fb9f2586dd3a327a2bd82c8087913a76c34221860ecb6b77001bd794f5078d84332c2a7b60e553865d064881db78b57dc3e43185337fb481f4b58376b372c08951e20a857ead35856815ff87070e4e8afdc4007de575328529774a79921e897d22a158fa6e65bb79c5579677223ce77887ef4a8381acdc861a660d851bf6ad8548a4ac16a756c877f57b6a7858b56a879a0d6186631a0281c6bf6a87194d5c8932e4969bfa8ccbf9895f23810a3cf07f65286c78f45bbc7524f7aa84178bd07e3f08cf83d9ab8fa3c8e08576fc02a08a8bcd767df2d64f85af6114827e7ee886805d0476e6a01e83358240fc52a107896e9cf08443329d7538f1f7777befdb8471d5da79adc3ef7bb534d317d69949a6c15000822b97cf78ba65ae790bafbd89fd4d7388d731618871772b831631ba7fbe213a7d233828890a8baf80122b39868a89188a677a57843f5b4c7b895ef2f6a8600f8da00b8d7672a6f5894c7b4788a22bf5863b8bcd7b36eeae7cbc7aa388c0c8e27cb9b1a97f18a43e7ea4155487f1c4797fae5a397d739757783cdaa27b49bdd0b0f72f2684eba23585974f5582e0bbcd7f89f90d7b1d5eef7f7c0f586bc4f47e797fd19e1aa42fcf7d7b08a1832ad13d7c08f1da80f612927d6ea7ea825ba91582df3d60b877428d883129448aa6a1a8846f91f9772b61cd819ba8488817d32b85f3e4827b464e3d1ca62b8d86045e467d770d3e781c5064851cc3268677d3407e6ad78d8027eb5c9da485877c3964d1773c6e7f80b9287481069b7c7d71e72d768671e38039e2f47c8737c1849a065e82acc70280fddad8898f09ac893398c088fd17828828f2fa8ff3a7b978bddbfd891bbcce7cd8a43685f421b5879893427abe2a5d7697bf85825123907933e4917512ad6c7efe17e689528444878994ab7dd2121686a3203218f9ccfbb539f89d83848054883afbea7bea82be837a784089d74359823632c07c7fc62489ba9b2b86aacbd77b6fef3787ddf98777f641ef79bf6031824e12818604e00ca3a529537affef3e821e12c889d10b2787e00a2c7a784ac0764444d9838b73ee81f4aa2a97f456c188aa1e7d789f49e37e23558979e30dd37d36d11778d02c98aa1cca728171e797839ed69f840b094e7c2efbb8805fd3ff8517a4d181051d8582ceb2c3800cfe9eae3e68e2814702ba7e21befd811b7f9383e9b1567e24925198062adda32dbbb07f51d0827d6122a880e0f14c850dfa87893da07f8314d4a184ff784e83bc277b7d83883109d35d4a8746c5a0792c44ad75dae83c7b9eb9a0825c7a1b7a5d52ce8506078986a9466684c7fcf1898c81f8824fb4347dd606fd81d8a42c8af1053179539681856a7dcf8dd362e41904e8497e9462097bc026c016948e1b8652962278811c51853e421278d9878688614d6086cd29d0821c21a97c8adcec7c6d87147671ce407bac0f55806881268f947260a23b13310b5edfd2a1c0c7f8940b8d7877b8c70b775a94d57fa43916770c1b2089645e357d593a168549cf977be181487c77cc1386e845a07535f2b68cc3f93d8f3201b17ac14abc8a8e117eb15ff74f93059ec07cd0aa487e5ed0d4894d0bfc8508db8380e04b67780134877ff9a5b77605c37786ff0130841ad252763105e8a7cdf2757929c75685b799a78a41b690a64996109aa444b7781e368c7c151c1476b3433378a7557c7dc6249381bb4f0d7e01e8c384f2d645887c0fc07c865f978c4b78dd89d64e48a8f454d57ddbf32d96a030748c39814a86ace04b8015be4b7eb8fde777fb7df485a3973f81e1881f89a6ba96887bfded79b98d907d9993f17ac0a40595b5684078cf95c1a99852d1ac70f49fa4fb34fe9073a422856b26b07974203a7613ea107f83cc6088d6e2587dc16a4c80414cba885367f67e9707a4890619d8850c673880f670ad8be94c86962f2c84a966d482915be34883e82c40784062b17e5ee96e76f09d1b8650b0cc7b4784d3877a25967d8c441d7df761db7f97a93080897025826283207be7378684332cfd7e206f437a63498e87e99f8375b4f75189bf8b8b7be32af98687c08479b7aef885f88c6b7d8f846f7bb3e9da7fe558957f0009dc8643a96c7e9b148879dbc08a8215ff388aaa42dd7f4a5ff0811ff1f5818e06e47ac8cd4a86f6eeee82aa30b87abdf8cb8432542b87761f5d86f3b5f486b23c8077ee9ac2777d4ca1751ab2a078171f08853d0527878355ec8757913e781726a784ea9b1c7e45db0980bfba027771e0358258eb2e89aaa7b47f5db73d8a47802a7e321e8879cd346383673d8e7ac4b55884a2772188b2a1717e6976c180ae9b567859579c761d69ee7556b9cb7c9bcc00847e071c82ac50d27a3433ac7948af2d881523bf894b91237b1a1e3c852750dc82ee23dc7840452f7e8cb0057b6d34a688a7f0a275f08be77df90aca76059f577c7397527c7d94e488f744fb83783fed85d5939b8708c94688cfb55b87f8686577fd51968287f6f77f337fbe86165543771acbf4779a06947deba203803e013b8a49172984a08d288449cf7d851a308c8172891783a59ab7898231ee7b1d704d848613c57ef62f6b81dcfd4f8026b9d276339c607d0b676a83f0d3d784e1acfc795372c67f94212d875410a28113cda37a1ad69d85a7d3d47ab6e8107627012f8823792085b2c5b689fe4230843eaee281838b5c7c92a70687ec03157bee18507a9a351d8019295b81ca605a7d1004fa819dab4179e63b4178def95b83a56493860e428676c7480081dc339c7f00feb180301bc287f90a0378d8c1e17a6993787ebc03b389350030881cb133894851ac754fe3c9757a3bce77bb5ac287ec6f8c823ae93f7c3e78068122c5217cfa0680878cf8e678ead59586af858e76f8fc64824caa7889d3a99581f55e177d90c1ee85430ef689c81cb975e0b45e7ef2e8f1820350b67a2d52c081925437839935b97f93ae69818a7e838367fc8384b5e10477daf33b87a7c180893f7f5b8100a4f081c6bf058280f88b85273e0b7f0f374584dcc70782d2891d7f00ab0b7f1e19ad813f50de75676c0f7db93a287f5e4e7583c117797af75a9e773e951688613b787bc217e47769ede279fc26547a405786859d4d487e9e54ac83ace08276357bf07c9d87be829271698a439ec77bef84018a124f4d88fbccb8778beaef890c879076ae2fe2885dff2a7aba41178a9b8b9689aacbe97de235658477559f819271118213ea9089aebfd081aead5a7ad987af758288d4891aef73819d8e8378a9c80579c0f0cc7805d523830a2a2c7b34aaa67d9c09937502cb4f8399110585a810cf7aa48c18812e3174836bd3ca81b4c8bf7ba55280851385bf7a23cc9c88e111248a3f7ff9836565b37b2886a389ad82808143d8d67b0fc02a7ded52378414f8ea85680a7c82021ef280d13ab77ecc26c478415e777595212b7637ea107fdf85e97da2d1d57877a85a8f054fd87817cc73785eacd181fd4d1e7ae8507c8608649675ab49ef753ed6a17d82594b8a75be6684e994417ed008b18982d7a68085346c75527dca7e4cfd827c925fb18a26fb9c7d83969e80d5c962781e2426808f9d187f1c7b6b81bd23737d36ad907c5ccd9b88c213bc7a2044cb7da30eb3848296f97a858edc7e66f5c1804dca5c97e631ac8383d3e9924d704e7be766177fc283e37e0940a081f5c3a577effb0f78d128dc7ef283a3849ff6ac768db7d7864141ba88f4fd0f85ae908783c86fdf7aea1d9976c06eac76d5395c7bff787578c2e8a180a0864e7b18d3047f8188748722b30178ddbcf278d68f468626d2a6818176b87618c81588a141c77d688c5799bc773b7706b86a762f8fd0795bd82a762766388564453c856c63237a9993867c28980d81e372fd82d659ef7c1d1de3817f0e7e7dd36cf77e48e5af80200a027c2b7ea4805127f67540e4ce77fd0900783fae4978ff664c801c57cb775701257d12f56581c313ff8387e42676cdc80a7e0541767dcfefb37c209eec7ab27d24874b3e52908b18f993d8534d932c7e948a6a2a6a81bce6f381587bb77d9feff67ebd766a7e14d49a88bf55a3791535c2888326787a68a97579943e53898613d986d8a4c189c1bdcc85633e9d80a826da754c1a851dfd44aef8d62ba41e5d0958f764995a18c37b007fb563ce7f82183f7a42d1b57d7f0edb788f604c89a6c53f83c6f508863d712182a796a8b87bfc419c5c4d928c461498b3c64e467df0f00577cbb3bd7cf9b0af84652f06779b180e7630af657d3e83c487f88c648321596d777838ed8534b14086f6483682494d52a54b994fb14cdf889674ed6fe45cd01677fac9c77cdae8f77eb63b028737324d82d2775d7bfb0d5d7ce1a574786c8be787446fbe7dd4cdba808b3d3e7f6f081176c736929aec6cdb931c365da2f60b657a63e42f894e61fd7ffa064277e70c1080eba22c7eaff558792e18c983a481be75246e828625a70d7787dd30804b1a267b30c8b7907cb36899915363b8a8da7c832c2e1c881eeaeb78400f5279d4a987769820c27d35d5827d56787d7d35e7d0838aee81787bf6f485b379f08946ba9e85f36d258811ad0cb5713fe3b036c48f803b9dee809a182a7ef8bbb47a816fa786238b207b02837b866815257d73f59f7c2c27247a456f3f8a7df9bd7a65d5ae7c446817839bde42b42288a1ae2704c77caf078d7b2ee94780d796e57625ec0d828d2e037ae850fb8070577a87aa8a7f88b9a6d080448c2a77e563cc89ce9c0e79f6a63779b5abe08996362dab3a1b5295f3a0c788b0816586d5b7c7830c990c78718d2d7ab1520c7945c9ee76279178835aadc77e936d187b67e3087a32c44e7ec7d1317758fd8d7c7db13a9d7462d78fddc781813ad7a88792a79383b4fea2758dc2c989eb612b78cda49f880c754177d7a15284f6e3418453794d7fe8624288e2fbbb792480f88789534188638656a9a84e11e2c2212b82fac52c83c91c0177bc320c81463c8a83a6d73f848fa8f3827ef5c586fee8e884bae36783cb26f87d28fd03897ee3f78490229887bbc6f09981c9e595298684870f9102839f9c44890022577c7403908286427e86c83960881696aa8591a06683b7b5f18627b56288c3c7a28517f01f898dbed87729f9d37fd8d3af9a36f8527b9f8ac97fa81b897950df3377dc74ff7d172686884b2c5e7ce34ea786a10be987fb8b28814606588577141781d0ceb5780080dc7b35b8089af50605a632ae6380b113b9841f448b7c53af8e82629c8e7b6d462c84b529f8853fa33e81f3541a7d5da56888347b008ad8f76f797a7175779db675760aa19a88e6fa9187b1590baab17eb086d71fb08185b2638ab04017856421707e37d805881cb15d88724ffa759038b588f69ba18984bddd7b47cc747938328d8319a72d7e1ae8ad862912c98eb6d158a54eb704887212bc76e02002850249b477f2f3da80020c547fa9222488c3b52c7c5b7edd815e06c4897fd88d7b30ecaf79f2faae8243c6a888761f9395281ded91edd4141ed0d5b978f28ecf8091ab527827aee97c6e424082b75bd187f7195d7c9a7df67ac230ce8419a0958948fe517f51c93877cbd7fe7fec03fd81652fc6882e1c87996f89b81c7afb5c7a5f31277e51b72685560c9a7f80e6cf7a86cbcc7cb64d1778f2c4f377c4eb0b83f3ae66890e01a978324d7d80597000884858f28894e06b6e7f37fc75b98dd887eae34880108d7b877db89d7722e2de869b79b580e99f9e822fd347852df4697514cd9d7fe032f18769db56862380f97f929572899eaeb77bb3670d85bded4318a4fda9827219bb87696b6f79f05cbd858a7e6d8901e84b76f7a2c97cda5fd17fbc7a1e8244d4ec8081234387b7494478ca6b05859607b68926aee289951d3d82e6b2a1769dab9f826582fa75eac33276de3abf77bf1f7a824bd2867eee2a257e93912887daa739834ffce37db83364850947b6875cd437897c1d8d7fa90cc4855ef0627bc0e5568409ac0d7fea82ce85b07afd7eff01eb78a8802189e84568772ccd0c80b2060e7fc271587d5343288876315283a33df2894dad79822b0e6e80b9b1dca037593b873416db7a3e4db47f87d20c89e8ce2aaf8d2b4d87b39d3f7908f86678bf43fe821016298197ea5188eadc098aeff09e7b3ee8858894b85f7d11b12c782ef4e396200a39180823cc7dd0a3477c6ad73278c7c6d4ee5bf2d17fcd6a4382b4c84583ab5b6581b8d90a7d0d46737e1dd5007aa0083076bdf83a7b62a7228138a59679ac983ba1759f3791f434af93ec47b7156889baedb06e6a8c964b139ff953427a3de10f7917afb4882cc93682cec40c7da128338a331abb76fc26618946cc1588e546a6877ff118a6a39c919a6e8d7598e435f3adadf59f9050ddb694d615" + } + ] + }, "job_routed_again": { "kind": "job", "request": "{\"capability\":\"wai.video.int_motion\",\"deadline_us\":30000000,\"fallback\":\"wai.video.av1\",\"job\":\"job-0001\",\"kind\":\"wai-route-request\",\"limits\":{\"consent_on_file\":false,\"ga_only\":true,\"max_price\":[],\"no_training\":true,\"regions\":[\"eu-central\",\"eu-west\"],\"residency\":[\"eu\"],\"spend_ceiling\":{\"amount\":2000000000,\"currency\":\"EUR\"},\"zero_retention\":true},\"operation\":\"media.encode\",\"prefer\":{\"quality\":null,\"sort\":\"energy\",\"ttfb_ms\":null},\"quantities\":[{\"quantity\":4,\"unit\":\"seconds\"},{\"quantity\":34560000,\"unit\":\"subpixels\"}]}", @@ -3919,9 +3991,9 @@ }, { "name": "invoice_version", - "about": "an invoice of version 3, which this revision does not define", + "about": "an invoice of version 4, which this revision does not define", "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", - "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "invoice": "574c493104c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", "previous": null, "sources": [ "lesson", @@ -5185,6 +5257,652 @@ "invoice" ] } + }, + { + "name": "jobs_priced_per_operation", + "about": "version 3: media jobs priced by capability and operation, beside a package: an int_motion encode and an int_motion decode at their own prices per job and per subpixel, a failover billed at the fallback's encode price, a job served past its deadline and one every allowed hop failed, billed nothing, and a job whose serving hop was unmetered", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552130000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f0f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b8710b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00800030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000090d003000000000090d00300000000000200000090fd70e078025bbfd27c4b21f6334361bd3a082007e84d0e28979f559f574913055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000010270000000000005901000000000000030000001afeda258a805026b9cefe37d00eeba15d680c72c01e3e9f377147bd05b8385b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de020502010000008096980000000000000000000504020000002058990200000000204e000000000000000000000100000000000000010000002e6739000000000061b0a2b47f000f52aecac7b434c42f5dc085a83e766d657fc83b0eef88c2920b684a686896610d875a91bf6c713c18fa93906b9c6424380b4bc5e7689820c805", + "previous": null, + "sources": [ + "package_base", + "job_int_motion", + "job_failover", + "job_late", + "job_unmetered", + "job_failed", + "job_decode" + ], + "outcome": { + "outcome": "accept", + "digest": "82b4a4c83d9657c1f0fbbb35ccc28b809738adb5889cc3ccf8e737475b1c0326", + "tariff": "2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec", + "currency": "EUR", + "start": 1790812800000000, + "end": 1793086400000000, + "records": 19, + "lines": [ + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "packages", + "quantity": 1, + "price": 500000, + "amount": 500000, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "entries", + "quantity": 10, + "price": 1000, + "amount": 10000, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "stored_bytes", + "quantity": 119779, + "price": 2000000, + "amount": 239, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 1, + "price": 2000000, + "amount": 2000000, + "cites": [ + [ + 14, + "3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a0" + ], + [ + 16, + "37f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c60" + ] + ], + "capability": "wai.video.av1", + "operation": "media.encode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 1, + "price": 250000, + "amount": 250000, + "cites": [ + [ + 14, + "a5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd45" + ], + [ + 16, + "a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b87" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.decode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "subpixels", + "quantity": 34560, + "price": 10000, + "amount": 345, + "cites": [ + [ + 14, + "a5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd45" + ], + [ + 15, + "901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f" + ], + [ + 16, + "a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b87" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.decode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 2, + "price": 500000, + "amount": 1000000, + "cites": [ + [ + 14, + "7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a1" + ], + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ], + [ + 16, + "f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d0" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "subpixels", + "quantity": 69120, + "price": 20000, + "amount": 1382, + "cites": [ + [ + 14, + "7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a1" + ], + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 15, + "35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f" + ], + [ + 15, + "97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e3" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ], + [ + 16, + "f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d0" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + } + ], + "energy": [ + { + "scope": "jobs", + "class": "ModelBased", + "figures": 1, + "joules": 10000000, + "max_ppm": 0 + }, + { + "scope": "jobs", + "class": "OnChipCounter", + "figures": 2, + "joules": 43604000, + "max_ppm": 20000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 1, + "ad_decisions": 0, + "jobs": 1 + }, + "coverage": { + "room_hops": "none", + "delivery_hops": "none", + "packaging": "partial", + "ad_decisions": "none", + "jobs": "partial" + }, + "amount": 3761966, + "reports": { + "ads": [], + "stitched": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "a_rerun_billed", + "about": "version 3: the next invoice of the chain bills job-0001 run again, a new route record and a new hop receipt, its output the receipt the first invoice holds", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec82b4a4c83d9657c1f0fbbb35ccc28b809738adb5889cc3ccf8e737475b1c03260070ec75cd5e06000040e2b52b5f0600455552030000000e1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c150f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f1038c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a0200055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04010000000000000020a107000000000020a107000000000002000000d4df07650fd333c1f21bf99556d5d9fb8dabd413522da5c01239eb0e24b38d3e055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb050087000000000000204e000000000000b30200000000000003000000171cd11bc981042d5f87215326f2efe765725a1058f8c69d7a058f00d25dfc1c01050401000000781dd70100000000204e00000000000000000000000000000000000000000000d3a3070000000000e082b0c142285853f6847864cfcb665d5862e81134cf6e04f0eb0bd91c7a27171a2eb5941a4714b459d3f682e338e537a25a1e85a53212aaf084b7c066570902", + "previous": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552130000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f0f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b8710b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00800030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000090d003000000000090d00300000000000200000090fd70e078025bbfd27c4b21f6334361bd3a082007e84d0e28979f559f574913055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000010270000000000005901000000000000030000001afeda258a805026b9cefe37d00eeba15d680c72c01e3e9f377147bd05b8385b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de020502010000008096980000000000000000000504020000002058990200000000204e000000000000000000000100000000000000010000002e6739000000000061b0a2b47f000f52aecac7b434c42f5dc085a83e766d657fc83b0eef88c2920b684a686896610d875a91bf6c713c18fa93906b9c6424380b4bc5e7689820c805", + "sources": [ + "job_rerun" + ], + "outcome": { + "outcome": "accept", + "digest": "27c46594977a0fc1a19afb2d2b0a6b8545684825a5e54544aae1c6c2a0907a94", + "tariff": "2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec", + "currency": "EUR", + "start": 1793086400000000, + "end": 1793491200000000, + "records": 3, + "lines": [ + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 1, + "price": 500000, + "amount": 500000, + "cites": [ + [ + 14, + "1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15" + ], + [ + 16, + "38c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "subpixels", + "quantity": 34560, + "price": 20000, + "amount": 691, + "cites": [ + [ + 14, + "1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15" + ], + [ + 15, + "35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f" + ], + [ + 16, + "38c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + } + ], + "energy": [ + { + "scope": "jobs", + "class": "OnChipCounter", + "figures": 1, + "joules": 30875000, + "max_ppm": 20000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 0, + "ad_decisions": 0, + "jobs": 0 + }, + "coverage": { + "room_hops": "none", + "delivery_hops": "none", + "packaging": "none", + "ad_decisions": "none", + "jobs": "whole" + }, + "amount": 500691, + "reports": { + "ads": [], + "stitched": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "one_output_under_two_hops", + "about": "version 3: job-0001 and its re-run in one invoice, two hops of one request: both billed, the output receipt they share cited once and its subpixels billed for each", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000040e2b52b5f0600455552050000000e1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c150ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f1038c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a1073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee2350200055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f00000000000400000093b315472706a2e381bc472a242cf8bdcc47d2d91668966b35e2ffa9a7c1eb7f055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000050000000ec245f06de45a7bf9d074809d774e7a0f77fbb38ca534dc06c4e2235e9111640105040200000078b9b10300000000204e00000000000000000000000000000000000000000000a6470f0000000000eff3d20712e01c804101d7d017941ee0598bab26b3fe3dcd4ec9324e04ac611ceab84305905c192b20fd1ce0e6e4e8dc0df30d4c2ebc136ddc601f7f50794409", + "previous": null, + "sources": [ + "job_int_motion", + "job_rerun" + ], + "outcome": { + "outcome": "accept", + "digest": "8b935f8e48652cdc0aef0df3f7d4fc19c22d966f611b1a875eef191a31402363", + "tariff": "2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec", + "currency": "EUR", + "start": 1790812800000000, + "end": 1793491200000000, + "records": 5, + "lines": [ + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 2, + "price": 500000, + "amount": 1000000, + "cites": [ + [ + 14, + "1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15" + ], + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 16, + "38c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "subpixels", + "quantity": 69120, + "price": 20000, + "amount": 1382, + "cites": [ + [ + 14, + "1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c15" + ], + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 15, + "35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f" + ], + [ + 16, + "38c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + } + ], + "energy": [ + { + "scope": "jobs", + "class": "OnChipCounter", + "figures": 2, + "joules": 61979000, + "max_ppm": 20000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 0, + "ad_decisions": 0, + "jobs": 0 + }, + "coverage": { + "room_hops": "none", + "delivery_hops": "none", + "packaging": "none", + "ad_decisions": "none", + "jobs": "whole" + }, + "amount": 1001382, + "reports": { + "ads": [], + "stitched": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "an_operation_the_tariff_does_not_price", + "about": "version 3: a tariff that prices int_motion's encode and not its decode: the decode bills nothing, its hop's energy stated", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000003000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fbd2be69e165d38cef1589a5a673f7b1b7aa3ce25b6c93f30be788193db76a742b2ecb4e410ff6f185b37823e8d1caf16149b66c1c394dee0b3538d134d81bb602a0b67855565daba9532644bddc421190e5a637828de8e83b6698b25274928c867f683e739fa4379e6f66c5df7bfca8beb819f54abdcc60988eb63f2355e7d202", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125cedad56bb49d83bd3ef135a966a4c98d54d3344fdb9c40774962a37400ee0e1c97f6000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000040e2b52b5f0600455552060000000ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f1073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee23510a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b870200055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000020a107000000000020a1070000000000020000009591096a43cca30f76ed87020cfe607b06f2703414fb7ac6263d98ac387bdd71055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb030087000000000000204e000000000000b30200000000000003000000676132191ea9254bc0d8aa266cb9da54e17011b9fa49e3b5c2b1a11a0360c890010504020000002058990200000000204e00000000000000000000000000000000000000000000d3a3070000000000b630dad7a4b2114edef009cfa236db763bcc178321a2677149cd9d611cf914f3a451d0e890bf84fea734679b1300f931d6968e99fa83dcdbc1cf99bfe7325f04", + "previous": null, + "sources": [ + "job_int_motion", + "job_decode" + ], + "outcome": { + "outcome": "accept", + "digest": "46225f3e21f3c9bd0b9dd602db63da07ca140bcec87115dee81b024ea57bfcb7", + "tariff": "d56bb49d83bd3ef135a966a4c98d54d3344fdb9c40774962a37400ee0e1c97f6", + "currency": "EUR", + "start": 1790812800000000, + "end": 1793491200000000, + "records": 6, + "lines": [ + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "jobs", + "quantity": 1, + "price": 500000, + "amount": 500000, + "cites": [ + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + }, + { + "source_kind": "job", + "source": "5a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb", + "unit": "subpixels", + "quantity": 34560, + "price": 20000, + "amount": 691, + "cites": [ + [ + 14, + "e38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda8" + ], + [ + 15, + "35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f" + ], + [ + 16, + "73a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235" + ] + ], + "capability": "wai.video.int_motion", + "operation": "media.encode" + } + ], + "energy": [ + { + "scope": "jobs", + "class": "OnChipCounter", + "figures": 2, + "joules": 43604000, + "max_ppm": 20000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 0, + "ad_decisions": 0, + "jobs": 0 + }, + "coverage": { + "room_hops": "none", + "delivery_hops": "none", + "packaging": "none", + "ad_decisions": "none", + "jobs": "whole" + }, + "amount": 500691, + "reports": { + "ads": [], + "stitched": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "a_hop_receipt_replayed", + "about": "version 3: the next invoice bills a new route record of job-0001 whose hop receipt the first invoice billed", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec82b4a4c83d9657c1f0fbbb35ccc28b809738adb5889cc3ccf8e737475b1c03260070ec75cd5e06000040e2b52b5f0600455552030000000ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f1096a402ff72a52846df547ee9a9b98fdd79801e5763ea139529426b6482dda8960200055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04010000000000000020a107000000000020a1070000000000020000005909e3546580c294e1b3f2d53094352b417796785dbb59bcf8609eee37a16422055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb050087000000000000204e000000000000b30200000000000003000000b093ef2d0846f4acb6646365e0890b2cecc138bfe924c2fac28c7e06d646ec0601050401000000009cda0100000000204e00000000000000000000000000000000000000000000d3a307000000000018322331d09afb85872d35b1e561e5f4b9a708589c131f8bc07df314799d6d04ac14af9768e00d6ef3cc64e632b7cd7998e1606f38648f167024dab497640706", + "previous": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552130000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f0f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b8710b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00800030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000090d003000000000090d00300000000000200000090fd70e078025bbfd27c4b21f6334361bd3a082007e84d0e28979f559f574913055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000010270000000000005901000000000000030000001afeda258a805026b9cefe37d00eeba15d680c72c01e3e9f377147bd05b8385b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de020502010000008096980000000000000000000504020000002058990200000000204e000000000000000000000100000000000000010000002e6739000000000061b0a2b47f000f52aecac7b434c42f5dc085a83e766d657fc83b0eef88c2920b684a686896610d875a91bf6c713c18fa93906b9c6424380b4bc5e7689820c805", + "sources": [ + "job_rerun_replays_its_hop" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_rebilled", + "at": [ + "source", + 0 + ] + } + }, + { + "name": "one_hop_under_two_lines", + "about": "version 3: a job whose fallback's hop receipt is another job's, billed on the int_motion encode lines and the av1 line", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000040e2b52b5f0600455552050000000e788f5f9baac2a827b270d23fe24ace09851cd9a5fdb3d69fc71eaa4fc6c130a10ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f1073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235107eb8cefc52097cb2f3fe9aae41d2c6e947845c9e81c6b66b300506baf1034e750300055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e000000000002000000fdc4d7acca594ddc33ab7b56ae4cfaec9412326efae561dbe2ac00a02662aeb2055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04010000000000000020a107000000000020a1070000000000020000009591096a43cca30f76ed87020cfe607b06f2703414fb7ac6263d98ac387bdd71055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb050087000000000000204e000000000000b30200000000000003000000676132191ea9254bc0d8aa266cb9da54e17011b9fa49e3b5c2b1a11a0360c890010504020000000038b50300000000204e000000000000000000000000000000000000000000005328260000000000026390c3cf7efb80e9b403f985c0b1fa02d89a29e8213aa3123f73d3a857f89675bde0e9c3f774d5c912b6a09a97744641cadb09ceae4e8c84e757c78a31ed05", + "previous": null, + "sources": [ + "job_int_motion", + "job_claims_another_hop" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_duplicate", + "at": [ + "source", + 1 + ] + } + }, + { + "name": "job_prices_unordered_by_operation", + "about": "a tariff of version 3 pricing int_motion's encode before its decode", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f000000000010270000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb650ec8ea682b420ba4a1ac34df16ee01eb52cf01871d683abd4da095a670547435bec9bf75b6102e3186d336f23a3f288908c20cac86827893a72aa03462e90991ad81d73f5362b14ba1a362b324f4a2e16e5ff0a16db1bc2f5ca360d5f8442c793dedd04baec5a7b2c64a91f04065d454ce482c6ee4802edaed9f4970705e03", + "invoice": "574c493103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552130000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f0f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b8710b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00800030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000090d003000000000090d00300000000000200000090fd70e078025bbfd27c4b21f6334361bd3a082007e84d0e28979f559f574913055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000010270000000000005901000000000000030000001afeda258a805026b9cefe37d00eeba15d680c72c01e3e9f377147bd05b8385b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de020502010000008096980000000000000000000504020000002058990200000000204e000000000000000000000100000000000000010000002e6739000000000061b0a2b47f000f52aecac7b434c42f5dc085a83e766d657fc83b0eef88c2920b684a686896610d875a91bf6c713c18fa93906b9c6424380b4bc5e7689820c805", + "previous": null, + "sources": [ + "package_base", + "job_int_motion", + "job_failover", + "job_late", + "job_unmetered", + "job_failed", + "job_decode" + ], + "outcome": { + "outcome": "refuse", + "reason": "tariff_invalid", + "at": [ + "tariff" + ] + } + }, + { + "name": "version_2_invoice_under_a_version_3_tariff", + "about": "an invoice of version 2 naming a tariff of version 3", + "tariff": "574c543103c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000005000d7761692e766964656f2e6176310c6d656469612e656e636f646501010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f646501010000000000000090d0030000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e6465636f64650240420f00000000001027000000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f646501010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0c6d656469612e656e636f64650240420f0000000000204e0000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb4280f4a16c5a0bcc82d1f221da4d7b89b874b3fc22afc6f4140acc9498731a0cb6740a24b61d42621dcd79df237078f42649c74ebfd4f1a10d823179e3e32b0ccf120bee5646f56a9dd230d970d1c5307fb19d3162f07ff704bccdde776fd3c211d5ae045178861cbb4039d485e3d3bd62ef40572290196e8f2393f9a9b04a00", + "invoice": "574c493102c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda2678e06d34cc5509a5e5abfb9d94d2a1b6b6f0023d75a7738012b4ec923d53ec000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552130000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea5b9d4dc04165f060ed7917f633c71330c3dbe3361c70e0e166f9daa93b0bd450ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f901b01c6beac88b5da555564ce862bdeaf15b019f6660a1b451ded44e7dfdb4f0f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810a0389c46dc4d3db89d35d137a0e7d6e60c815c006ccde867a8276b5290227b8710b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00800030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000090d003000000000090d00300000000000200000090fd70e078025bbfd27c4b21f6334361bd3a082007e84d0e28979f559f574913055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000010270000000000005901000000000000030000001afeda258a805026b9cefe37d00eeba15d680c72c01e3e9f377147bd05b8385b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb04020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb05000e010000000000204e0000000000006605000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de020502010000008096980000000000000000000504020000002058990200000000204e000000000000000000000100000000000000010000002e6739000000000045073a49faab9f62f2df91573f2494a4766a562d87497a6ec4db634ead50771e98971a88e7efa1b63f3c3533197af66ffc99df1e66afc333de7a2c35fe29fb0a", + "previous": null, + "sources": [ + "package_base", + "job_int_motion", + "job_failover", + "job_late", + "job_unmetered", + "job_failed", + "job_decode" + ], + "outcome": { + "outcome": "refuse", + "reason": "tariff_mismatch", + "at": [ + "invoice" + ] + } + }, + { + "name": "version_2_bills_an_output_once", + "about": "version 2 as before: the re-run of job-0001 in the next invoice of a version-2 chain, refused because its output receipt was billed", + "tariff": "574c543102c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125ceda00a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000003000d7761692e766964656f2e61763101010000000000000080841e0000000000147761692e766964656f2e696e745f6d6f74696f6e01010000000000000020a1070000000000147761692e766964656f2e696e745f6d6f74696f6e0240420f000000000030750000000000000200030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb937856a9d70719202fc68e5ea8135d8d8d124f30b7d70f0cda098a35b8a3fd28ed6c34d5ee250c7d0f2f62025271cbaeb20ca9d46642a6d992e5db62b482d503ac9355a4dd00d399b543fd1fb73ff4c6f69dcacdae90f59f87f6db6a5e907edb736e2c69b5bb4e1609cbcdaeb100fdb0909bbb2f220a49b54c6fda2d32be6b08", + "invoice": "574c493102c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125cedab4d12452bb22b7212775717eb298c95b73fb743996735f363d060e5d89e948e23b708131913b79252dff215d4a017f615e05a2db154bb3ae3726ae865e78daa10070ec75cd5e06000040e2b52b5f0600455552030000000e1ec11fa4f1090e796df073f2934a727b3d533cc95ba11464864f70b2574d3c150f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f1038c66b4b545baad309ff5875311c8de24d01155a91cd9d9d233254961bb3873a0200055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02010000000000000020a107000000000020a107000000000002000000d4df07650fd333c1f21bf99556d5d9fb8dabd413522da5c01239eb0e24b38d3e055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03008700000000000030750000000000000c0400000000000003000000171cd11bc981042d5f87215326f2efe765725a1058f8c69d7a058f00d25dfc1c01050401000000781dd70100000000204e000000000000000000000000000000000000000000002ca507000000000032fbe8ee0d43329fa7e03730f16b9f13cd82a904a6da51da20810f424071b3496435c4867886b3e26b8e1491a98f077eed5e2f4b9e985cd346349f67e3730509", + "previous": "574c493102c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6708ca98f0c81494c6a839414a36407fbe750023b8f4da697f31973ac8125cedab4d12452bb22b7212775717eb298c95b73fb743996735f363d060e5d89e948e2000000000000000000000000000000000000000000000000000000000000000000a0cd18bc5c06000070ec75cd5e0600455552100000000522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad7280e3c6645b9c0e6cdab3902504cd8ee66b23ff032603f8583ebdecc0ca3ad4b97a00e7aef4aa5530a9ddb95e3bb27ec1e5e15f327aebdcd05cdf6a5049c2eee8148a10e914fa2e20362c61d3db9acc53d584d5ec0a0170d0bb97aba6eab3a193819c8010ea94d8ad83c3917f13fad61c83527e4749bba77010d3787775bf774ff3d2ba6320eb1ab3a2c1ab7e0cc879e5695cea88e6eb7e966ff5f32d506e745fd09ca71a13e0ed7a82de010499e44964bc4c6cb34f71ba8ca69f6d22e122ae61244c790aa13ce0ee38c842892cf82c49224632e33ba5b9e22ae36d2df3bbc08f90791e148b0cda80f35db261a1721a9173a86729a0dd08a1b2da2f56a65daa5b431fbb3724908582f0f60da0cb4c5e501c877e3a26821781d67c87efe279bcb4d4c682b5e89ca4424e10f97d1ee8316e2e5bb2d7d354de0fffc095d3aace9f548ab676fe3ac5f79abe7e31037f13e61a897417a9086d273384c49e32c2c45845c34801399a87c3421350c601073a1b2e9ff637411e6ea87045baf39ce2112877e7e2caee4d898a46dfb1ee235109b0785fbaafed521c3b5fad6ac09bc91f69ccf8100b334e3b400790874a1855810b9a1856fc1c022ba41730d52baf96c751fef02a6f482f1b3202eb11b2cd5211210f2682da135ceb524c827b36ab9270e3d59911e34efa6e4b8a4b0e706134054d00600030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb01010000000000000080841e000000000080841e0000000000020000009a0d92808a957fb1033ec07fd7cb9e0d3e1501c457a79f57d8d7f5d90d8e50ca055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb02020000000000000020a107000000000040420f0000000000040000004d261c264d34b58c05cc1040b0a0d1bb3b31fcaf89e96200d623625f3660423b055a0d5a5922e427f6d315d59e962f0de9227f62c36cec2fb19839fa8eab8632fb03000e01000000000030750000000000001908000000000000060000003be0807baee6c8afddeb8b46ed2a85a3e14b7554462d2384eb29e9b9804860de02050201000000809698000000000000000000050401000000009cda0100000000204e00000000000000000000010000000000000001000000f89735000000000060102fa8f039f17af953d6d2ac3e11d7d9ffad121035ab0baeef77cae61f961a568d9d3bd6287e50fd4374f6fee1b855ad6dd5bcb500cff9f217f0925523c009", + "sources": [ + "job_rerun" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_rebilled", + "at": [ + "source", + 0 + ] + } } ] } diff --git a/wai/live-conformance/receipt_ledger_verify.py b/wai/live-conformance/receipt_ledger_verify.py index 1b9b36e5c..e5dcad6e2 100644 --- a/wai/live-conformance/receipt_ledger_verify.py +++ b/wai/live-conformance/receipt_ledger_verify.py @@ -10,12 +10,14 @@ settlement statement by settlement_verify.py (settlement §8), a package receipt ../pack-conformance/pack_verify.py (packaging §9), an ads room by live_ads_verify.py with ad_presentation_verify.py (live-ads §9, ad-presentation §9), and a stream stitched by others by stitched_presentation_verify.py (stitched-presentation §7), whose statement the ledger reports as -it is; and, under a tariff of version 2, a media job by route_record_verify.py (route-record §7, -its hop receipts by the OER/2 reference verifier) and its output receipts by +it is; and, under a tariff of version 2 or 3, a media job by route_record_verify.py (route-record +§7, its hop receipts by the OER/2 reference verifier) and its output receipts by _int_motion_receipt.py (integer-payloads §15). Everything else is here: -- the tariff (§4): reading, rules, both signatures, its scope; -- the invoice (§6): reading, rules, signature, chain; +- the tariff (§4): reading, rules, both signatures, its scope, its job prices per capability + (version 2) or per capability and operation (version 3); +- the invoice (§6): reading, rules, signature, chain, and what a chain bills once (§6.9): in + version 3 a job's work, its route record and hop receipts, and never its output's receipt; - what each source contributes (§5.2), its placement in time (§5.3), and the checks across sources (§7.2 step 6); - every line, citation, energy row and amount recomputed (§6.2 to §6.6), compared in §7.2 step @@ -77,7 +79,9 @@ KIND_NAMES = {ROOM: "room", DELIVERY: "delivery", PACKAGE: "package", ADS: "ads" STITCHED = "stitched" # §5.1: a source kind billed as ads SCOPES = {1: "room_hops", 2: "delivery_hops", 3: "packaging", 4: "ad_decisions", 5: "jobs"} # §6.6 JOB_UNITS = ["jobs", "subpixels"] # §4.5 +JOB_VERSIONS = (2, 3) # §4.5, §4.6: the versions that price jobs CAPABILITY = re.compile(r"\Awai\.[a-z0-9_.]{0,124}\Z") +OPERATION = re.compile(r"\A[a-z][a-z0-9]*\.[a-z][a-z0-9]*\Z") # §4.6, route-record §2 LABELS = {2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"} TAGS = {v: k for k, v in LABELS.items()} CLASS_NAMES = ["live", "fetch", "fill", "ancillary"] @@ -130,7 +134,7 @@ def head(b, magic): return "malformed" if len(b) < 5: return "truncated" - if b[4] not in (1, 2): + if b[4] not in (1, 2, 3): return "unknown_version" return None @@ -161,11 +165,12 @@ def read_tariff(b): t["package"] = [r.u(8) for _ in range(3)] t["ads"] = [r.u(8) for _ in range(2)] t["version"] = b[4] - t["job"] = [] - if t["version"] == 2: # §4.5 + t["job"] = [] # (capability, operation, unit, divisor, price) + if t["version"] in JOB_VERSIONS: # §4.5, §4.6 for _ in range(r.u(2)): cap = r.take(r.u(1)) - t["job"].append((cap, r.u(1), r.u(8), r.u(8))) + op = r.take(r.u(1)) if t["version"] == 3 else b"" + t["job"].append((cap, op, r.u(1), r.u(8), r.u(8))) t["scope"] = [(r.u(1), r.take(32)) for _ in range(r.u(2))] body_end = r.p seller_sig, buyer_sig = r.take(64), r.take(64) @@ -175,15 +180,16 @@ def read_tariff(b): raise Refuse("malformed", ["tariff"]) def job_price(j): try: - cap = j[0].decode("utf-8") + cap, op = j[0].decode("utf-8"), j[1].decode("utf-8") except UnicodeDecodeError: return False - return CAPABILITY.match(cap) is not None and 1 <= j[1] <= len(JOB_UNITS) and j[2] >= 1 - top = 5 if t["version"] == 2 else 4 + named = OPERATION.match(op) is not None and len(j[1]) <= 128 if t["version"] == 3 else op == "" + return CAPABILITY.match(cap) is not None and named and 1 <= j[2] <= len(JOB_UNITS) and j[3] >= 1 + top = 5 if t["version"] in JOB_VERSIONS else 4 rules = (t["start"] < t["end"] and capitals(t["currency"]) and t["seller"] != ZERO and t["buyer"] != ZERO and t["seller"] != t["buyer"] and t["scope"] and ascending(t["scope"]) and all(1 <= k <= top and i != ZERO for k, i in t["scope"]) - and len(t["job"]) <= 255 and ascending([(j[0], j[1]) for j in t["job"]]) and all(job_price(j) for j in t["job"])) + and len(t["job"]) <= 255 and ascending([(j[0], j[1], j[2]) for j in t["job"]]) and all(job_price(j) for j in t["job"])) if not rules and "tariff-rules" not in SKIP: raise Refuse("tariff_invalid", ["tariff"]) body = b[:body_end] @@ -199,12 +205,12 @@ def read_tariff(b): def price(t, kind, unit): if kind == JOB: - return t["job"][unit - 1][3] + return t["job"][unit - 1][4] return {ROOM: t["room"], DELIVERY: t["delivery"], PACKAGE: t["package"], ADS: t["ads"]}[kind][unit - 1] def divisor(t, kind, unit): - return t["job"][unit - 1][2] if kind == JOB else UNITS[kind][unit - 1][1] + return t["job"][unit - 1][3] if kind == JOB else UNITS[kind][unit - 1][1] # ---------------------------------------------------------------- §6 the invoice @@ -224,7 +230,7 @@ def read_invoice(b, at): "amount": r.u(8), "cited": r.u(4), "cites": r.take(32)}) i["energy"] = [(r.u(1), r.u(1), r.u(4), r.u(8), r.u(4)) for _ in range(r.u(1))] i["version"] = b[4] - v2 = i["version"] == 2 + v2 = i["version"] in JOB_VERSIONS i["unmetered"] = [r.u(4) for _ in range(5 if v2 else 4)] + ([] if v2 else [0]) i["amount"] = r.u(8) sig = r.take(64) @@ -472,7 +478,7 @@ def job_source(s): raise Refuse("output_invalid", ["outputs", i]) outputs.append((IMR.receipt_hash(r), r["work"])) return {"kind": JOB, "digest": bytes.fromhex(RRV.digest(rec)), "router": bytes.fromhex(rec["signer"]), "job": rec["job"], - "accepted": rec["accepted"], "hops": [oer_identity(a["receipt"]) for a in rec["attempts"]], "billed": billed, + "operation": rec["operation"].encode("utf-8"), "accepted": rec["accepted"], "hops": [oer_identity(a["receipt"]) for a in rec["attempts"]], "billed": billed, "figure": fig, "outputs": outputs} @@ -481,8 +487,8 @@ _SOURCES = {} def verify_source(s, signers, version=1): """§5.1: the source verified by its own extension, a stitched source with `signers`, a job - source under a tariff of version 2, or Refuse with that extension's place.""" - key = json.dumps([s, sorted(k.hex() for k in signers), version, sorted(SKIP & {"job-figure", "output"})], sort_keys=True) + source under a tariff of version 2 or 3, or Refuse with that extension's place.""" + key = json.dumps([s, sorted(k.hex() for k in signers), version in JOB_VERSIONS, sorted(SKIP & {"job-figure", "output"})], sort_keys=True) if key not in _SOURCES: try: _SOURCES[key] = read_source(s, signers, version) @@ -499,7 +505,7 @@ def read_source(s, signers, version): raise Refuse("malformed", []) if s.get("kind") == "stitched": return stitched_source(s, signers) - if s.get("kind") == "job" and version == 2: + if s.get("kind") == "job" and version in JOB_VERSIONS: return job_source(s) readers = {"room": room_source, "delivery": delivery_source, "package": package_source, "ads": ads_source} if s.get("kind") not in readers: @@ -583,8 +589,8 @@ def overlapping(a, b): return a[0] < b[1] and b[0] < a[1] -def bills_again(f, g): - """§5.3: whether f bills what g, an earlier source, billed.""" +def bills_again(f, g, version): + """§5.3: whether f bills what g, an earlier source, billed, under a tariff of `version`.""" if f["kind"] != g["kind"]: return False if f["kind"] == ROOM: @@ -595,25 +601,32 @@ def bills_again(f, g): if f["kind"] == ADS: return f["room"] == g["room"] and any((x["cue"], x["session"]) == (y["cue"], y["session"]) for x in f["receipts"] for y in g["receipts"]) - if f["kind"] == JOB: - return f["router"] == g["router"] and f["job"] == g["job"] + if f["kind"] == JOB: # version 2; in version 3 two records of one job are two runs of it + return version == 2 and f["router"] == g["router"] and f["job"] == g["job"] return False -def check_across(facts, previous): +def billed_once(version, kind): + """§6.9: whether a chain bills a record of `kind` once. In version 3 a job's output receipt is + the evidence of what it delivered, which a re-run delivers again; its work, the route record and + the hop receipts, is billed once.""" + return not (kind == VIDEO_RECEIPT and (version == 3 or "output-once" in SKIP)) + + +def check_across(facts, previous, version=1): """§7.2 step 6: (the index, the reason) of the first source refused, or None.""" - billed = set(previous["records"]) if previous else set() + billed = {r for r in (previous["records"] if previous else []) if billed_once(version, r[0])} seen, jobs = set(), set() for k, f in enumerate(facts): hs = heads(f) if "duplicate" not in SKIP and (any(h in seen for h in hs) or len(set(hs)) != len(hs)): return k, "record_duplicate" - if f["kind"] == JOB: # §5.2: one hop, one output, one job - own = contributed(f) - set(hs) + if f["kind"] == JOB: # §5.2: one hop, one job; in version 2, one output too + own = {r for r in contributed(f) - set(hs) if billed_once(version, r[0])} if "job-duplicate" not in SKIP and own & jobs: return k, "record_duplicate" jobs |= own - if "overlap" not in SKIP and any(bills_again(f, g) for g in facts[:k]): + if "overlap" not in SKIP and any(bills_again(f, g, version) for g in facts[:k]): return k, "record_overlap" if "rebilled" not in SKIP and contributed(f) & billed: return k, "record_rebilled" @@ -672,8 +685,8 @@ def compute(t, facts): if f["billed"] is None: continue cap, hop = f["billed"] - for k, (pcap, unit, _, _) in enumerate(t["job"]): - if pcap != cap: + for k, (pcap, pop, unit, _, _) in enumerate(t["job"]): + if pcap != cap or (t["version"] == 3 and pop != f["operation"] and "operation" not in SKIP): continue cites = [(ROUTE_RECORD, f["digest"]), (OER_RECEIPT, hop)] if unit == 1: @@ -909,7 +922,7 @@ def verify(doc): if bad: raise Refuse(bad, ["source", k]) facts.append(f) - across = check_across(facts, prev) # step 6 + across = check_across(facts, prev, t["version"]) # step 6 if across: raise Refuse(across[1], ["source", across[0]]) c = compute(t, facts) # step 7 @@ -926,17 +939,19 @@ def verify(doc): def line_json(x, t): """§7.3: a line, a job line naming its job price's unit and capability.""" out = {"source_kind": KIND_NAMES[x["kind"]], "source": x["source"].hex(), - "unit": JOB_UNITS[t["job"][x["unit"] - 1][1] - 1] if x["kind"] == JOB else UNITS[x["kind"]][x["unit"] - 1][0], + "unit": JOB_UNITS[t["job"][x["unit"] - 1][2] - 1] if x["kind"] == JOB else UNITS[x["kind"]][x["unit"] - 1][0], "quantity": x["quantity"], "price": x["price"], "amount": x["amount"], "cites": [[k, i.hex()] for k, i in x["cited_records"]]} if x["kind"] == JOB: out["capability"] = t["job"][x["unit"] - 1][0].decode("utf-8") + if t["version"] == 3: + out["operation"] = t["job"][x["unit"] - 1][1].decode("utf-8") return out def accepted(inv, facts, c, t): unmetered, coverage = {}, {} - for s in range(1, 6 if inv["version"] == 2 else 5): + for s in range(1, 6 if inv["version"] in JOB_VERSIONS else 5): n = inv["unmetered"][s - 1] unmetered[SCOPES[s]] = n coverage[SCOPES[s]] = "partial" if n > 0 else "whole" if any(r[0] == s for r in inv["energy"]) else "none" @@ -1030,6 +1045,31 @@ def mutations(doc): k = next(i for i, v in enumerate(doc["vectors"]) if v["name"] == "jobs_routed_and_served") m["vectors"][k]["outcome"]["coverage"]["jobs"] = "whole" out.append(("jobs_routed_and_served: its jobs' coverage stated whole", m)) + names = {v["name"]: i for i, v in enumerate(doc["vectors"])} + if "jobs_priced_per_operation" in names: # version 3 + k = names["jobs_priced_per_operation"] + lines = doc["vectors"][k]["outcome"]["lines"] + dec = next(i for i, x in enumerate(lines) if x.get("operation") == "media.decode" and x["unit"] == "subpixels") + enc = next(i for i, x in enumerate(lines) if x.get("operation") == "media.encode" and x["unit"] == "subpixels") + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["lines"][dec]["price"] = lines[enc]["price"] + out.append(("jobs_priced_per_operation: the decode priced as the encode", m)) + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["lines"][dec]["operation"] = "media.encode" + out.append(("jobs_priced_per_operation: the decode line named an encode", m)) + k = names["one_output_under_two_hops"] + lines = doc["vectors"][k]["outcome"]["lines"] + sp = next(i for i, x in enumerate(lines) if x["unit"] == "subpixels") + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["lines"][sp]["quantity"] //= 2 + out.append(("one_output_under_two_hops: the shared output's subpixels billed once", m)) + k = names["a_rerun_billed"] + m = copy.deepcopy(doc) + m["vectors"][k]["previous"] = None + out.append(("a_rerun_billed: the invoice before it not given", m)) + m = copy.deepcopy(doc) + m["vectors"][k]["sources"] = ["job_rerun_replays_its_hop"] + out.append(("a_rerun_billed: its re-run replaced by one replaying the billed hop", m)) for k, v in enumerate(doc["vectors"]): if v["outcome"]["outcome"] == "refuse": m = copy.deepcopy(doc) @@ -1041,7 +1081,7 @@ def mutations(doc): CHECKS = ["signatures", "buyer-signature", "tariff-rules", "invoice-rules", "row-of-no-class", "binding", "outside-tariff", "previous-given", "previous-overlap", "scope", "conflict", "period", "duplicate", "overlap", "rebilled", "quantity", - "summed", "citations", "mismatch", "job-figure", "output", "job-duplicate", "private-kind"] + "summed", "citations", "mismatch", "job-figure", "output", "job-duplicate", "private-kind", "operation", "output-once"] def main(corpus_dir): diff --git a/wai/wai-ledger/src/ledger.rs b/wai/wai-ledger/src/ledger.rs index 851f970cc..a8dda700e 100644 --- a/wai/wai-ledger/src/ledger.rs +++ b/wai/wai-ledger/src/ledger.rs @@ -57,7 +57,8 @@ pub struct Chain { pub sources: Vec, /// The chain, in order: each invoice's bytes and its verification document. pub invoices: Vec<(Invoice, Vec, Value)>, - /// The records of every invoice of the chain (§6.9). + /// The records of every invoice of the chain (§6.9); of them, a source may not bring again + /// those its tariff's version bills once ([`rl::billed_once`]). pub billed: BTreeSet<(u8, [u8; 32])>, } @@ -155,8 +156,10 @@ impl Ledger { rl::check_source(&c.tariff, c.tariff.start, c.tariff.end, &facts).map_err(|r| refuse(r.code(), json!(["source"])))?; let mut all: Vec = c.sources.iter().map(|h| h.facts.clone()).collect(); all.push(facts.clone()); - rl::check_across(&all, None).map_err(|(_, r)| refuse(r.code(), json!(["source"])))?; - if facts.records().iter().any(|r| c.billed.contains(r)) { + rl::check_across(c.tariff.version, &all, None).map_err(|(_, r)| refuse(r.code(), json!(["source"])))?; + // A record the chain billed is billed never again; under a tariff of version 3, a job's + // output receipt is cited again by a re-run, whose hop and route record are new (§6.9). + if facts.records().iter().any(|r| rl::billed_once(c.tariff.version, r.0) && c.billed.contains(r)) { return Err(refuse(Reason::RecordRebilled.code(), json!(["source"]))); } let n = c.sources.len() as u64; diff --git a/wai/wai-ledger/src/tests.rs b/wai/wai-ledger/src/tests.rs index 7a24d0b31..aca1d387e 100644 --- a/wai/wai-ledger/src/tests.rs +++ b/wai/wai-ledger/src/tests.rs @@ -1,7 +1,8 @@ //! The service's ledger against the committed corpus (`live-conformance/ledger/`): given the //! corpus's tariff and sources in the corpus's order, it issues the corpus's invoices byte for //! byte, with the outcomes the corpus states; it recovers its chain from its directory after a -//! restart; it bills each source once; and one process holds its directory. +//! restart; it bills each source once, and under a tariff of version 3 a job's work once; and +//! one process holds its directory. use std::path::PathBuf; @@ -107,6 +108,32 @@ fn a_source_waits_for_the_invoice_of_its_period() { let _ = std::fs::remove_dir_all(&dir); } +/// Under a tariff of version 3 the ledger bills a job's work once (receipt-ledger §6.9): it issues +/// the corpus's chain of version 3 byte for byte, a re-run of a job it billed, whose output +/// receipt it billed already, is held and billed in the next invoice; a hop receipt it billed, +/// brought again under a new route record, is refused. +#[test] +fn a_rerun_is_billed_and_a_replayed_hop_is_not() { + let doc = corpus(); + let (k, r) = (vector(&doc, "jobs_priced_per_operation"), vector(&doc, "a_rerun_billed")); + let dir = scratch("rerun"); + let t = tariff_of(k); + let mut l = Ledger::open(&dir, seller()).unwrap(); + l.add_tariff(&unhex(k["tariff"].as_str().unwrap()).unwrap()).unwrap(); + add_sources(&mut l, &doc, k); + let (start, end) = (k["outcome"]["start"].as_u64().unwrap(), k["outcome"]["end"].as_u64().unwrap()); + let i = l.invoice(&t, start, end).unwrap(); + assert_eq!(Value::String(crate::ledger::hex(&i.invoice)), k["invoice"], "the corpus's first invoice of version 3, byte for byte"); + // The service checks a source against every one it holds, billed or not: a hop receipt held + // already is a duplicate before it is a rebilling. + assert_eq!(reason(l.add_source(&t, doc["sources"]["job_rerun_replays_its_hop"].clone()).unwrap_err()), "record_duplicate", "a billed hop receipt"); + add_sources(&mut l, &doc, r); + let j = l.invoice(&t, end, r["outcome"]["end"].as_u64().unwrap()).unwrap(); + assert_eq!(Value::String(crate::ledger::hex(&j.invoice)), r["invoice"], "the re-run, billed in the next invoice"); + assert_eq!(j.outcome, r["outcome"]); + let _ = std::fs::remove_dir_all(&dir); +} + #[test] fn one_process_holds_the_directory_and_tariffs_are_its_sellers() { let doc = corpus(); diff --git a/wai/wai-rs/src/receipt_ledger.rs b/wai/wai-rs/src/receipt_ledger.rs index 67959228e..3e2bc2a54 100644 --- a/wai/wai-rs/src/receipt_ledger.rs +++ b/wai/wai-rs/src/receipt_ledger.rs @@ -15,9 +15,12 @@ //! //! What this module implements, by section of the extension: //! -//! - §4: the tariff ([`Tariff`]), its two signatures and its scope; +//! - §4: the tariff ([`Tariff`]), its two signatures and its scope, and its job prices: per +//! serving capability in version 2, per serving capability and operation in version 3; //! - §5: the sources ([`verify_source`]) and what each contributes ([`Facts`]); -//! - §6: the invoice ([`Invoice`]), its lines, citations, energy and chain ([`compute`]); +//! - §6: the invoice ([`Invoice`]), its lines, citations, energy and chain ([`compute`]), and +//! what a chain bills once ([`billed_once`]): in version 3, a job's work, never its output's +//! receipt; //! - §7: verification ([`verify`]); §8: the reports. //! //! The corpus `live-conformance/ledger/` is written by [`corpus::generate`] and checked by @@ -51,6 +54,9 @@ pub const INVOICE_MAGIC: [u8; 4] = *b"WLI1"; pub const VERSION: u8 = 1; /// The version of a tariff that prices jobs, and of its invoices (§4.5). pub const VERSION_2: u8 = 2; +/// The version of a tariff that prices jobs per operation and bills a job's work, and of its +/// invoices (§4.6, §6.9). +pub const VERSION_3: u8 = 3; pub const TARIFF_DOMAIN: &[u8] = b"wai:ledger-tariff\x01"; pub const ACCEPT_DOMAIN: &[u8] = b"wai:ledger-accept\x01"; pub const INVOICE_DOMAIN: &[u8] = b"wai:ledger-invoice\x01"; @@ -310,7 +316,7 @@ impl<'a> Rd<'a> { } } -/// The first steps of reading (§4.3, §6.8): the magic and the version, 1 or 2. +/// The first steps of reading (§4.3, §6.8): the magic and the version, 1, 2 or 3. fn read_head(b: &[u8], magic: &[u8; 4]) -> Result { if b.len() < 4 { return Err(TRUNCATED); @@ -320,15 +326,24 @@ fn read_head(b: &[u8], magic: &[u8; 4]) -> Result { } match b.get(4) { None => Err(TRUNCATED), - Some(&v) if v == VERSION || v == VERSION_2 => Ok(v), + Some(&v) if v == VERSION || v == VERSION_2 || v == VERSION_3 => Ok(v), Some(_) => Err(UNKNOWN_VERSION), } } -/// A job price (§4.5): a capability, a unit (1 `jobs`, 2 `subpixels`), a divisor and a price. +/// Whether a tariff or an invoice of `version` prices jobs (§4.5, §4.6): versions 2 and 3. +pub fn prices_jobs(version: u8) -> bool { + version == VERSION_2 || version == VERSION_3 +} + +/// A job price (§4.5, §4.6): a capability, in version 3 an operation, a unit (1 `jobs`, 2 +/// `subpixels`), a divisor and a price. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct JobPrice { pub capability: String, + /// Version 3: the catalogue operation it prices (§4.6). Empty in version 2, whose job + /// prices name none. + pub operation: String, pub unit: u8, pub divisor: u64, pub price: u64, @@ -341,6 +356,13 @@ fn job_capability(c: &str) -> bool { (1..=128).contains(&c.len()) && c.starts_with("wai.") && c.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'.') } +/// A job price's operation (§4.6): route-record §2's form, two words joined by `.`, each a +/// lowercase ASCII letter and then lowercase letters and digits, of at most 128 bytes. +fn job_operation(o: &str) -> bool { + let word = |w: &str| !w.is_empty() && w.as_bytes()[0].is_ascii_lowercase() && w.bytes().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()); + o.len() <= 128 && matches!(o.split_once('.'), Some((n, v)) if word(n) && word(v)) +} + fn ascending(v: &[T]) -> bool { v.windows(2).all(|w| w[0] < w[1]) } @@ -352,7 +374,8 @@ fn ascending(v: &[T]) -> bool { /// A tariff (§4.1). #[derive(Clone, Debug, PartialEq, Eq)] pub struct Tariff { - /// 1, or 2 for a tariff that prices jobs (§4.5). + /// 1; 2 for a tariff that prices jobs (§4.5); 3 for one that prices them per operation and + /// bills their work (§4.6, §6.9). pub version: u8, pub seller: [u8; 32], pub buyer: [u8; 32], @@ -367,7 +390,8 @@ pub struct Tariff { pub package: [u64; 3], /// Per presented impression, per billable impression. pub ads: [u64; 2], - /// Version 2: the job prices, in strictly ascending order of (capability, unit) (§4.5). + /// Versions 2 and 3: the job prices, in strictly ascending order of (capability, unit) in + /// version 2 (§4.5) and of (capability, operation, unit) in version 3 (§4.6). pub job: Vec, /// (kind, id), strictly ascending. pub scope: Vec<(u8, [u8; 32])>, @@ -392,11 +416,15 @@ impl Tariff { for p in self.delivery.iter().chain(&self.package).chain(&self.ads) { o.extend_from_slice(&p.to_le_bytes()); } - if self.version == VERSION_2 { + if prices_jobs(self.version) { o.extend_from_slice(&u16::try_from(self.job.len()).expect("at most 65535 job prices").to_le_bytes()); for j in &self.job { o.push(u8::try_from(j.capability.len()).expect("a capability of at most 255 bytes")); o.extend_from_slice(j.capability.as_bytes()); + if self.version == VERSION_3 { + o.push(u8::try_from(j.operation.len()).expect("an operation of at most 255 bytes")); + o.extend_from_slice(j.operation.as_bytes()); + } o.push(j.unit); o.extend_from_slice(&j.divisor.to_le_bytes()); o.extend_from_slice(&j.price.to_le_bytes()); @@ -454,13 +482,21 @@ impl Tariff { } let ads = [r.u64()?, r.u64()?]; let mut job = Vec::new(); - if version == VERSION_2 { + if prices_jobs(version) { let n = r.u16()? as usize; + // Bytes that are not text break §4.5's and §4.6's forms, a rule (§4.3): read as + // empty, which no form admits, and refused `tariff_invalid` with the rules. + let text = |b: &[u8]| String::from_utf8(b.to_vec()).unwrap_or_default(); for _ in 0..n { let len = r.u8()? as usize; - let cap = r.take(len)?; - let capability = String::from_utf8(cap.to_vec()).map_err(|_| MALFORMED)?; - job.push(JobPrice { capability, unit: r.u8()?, divisor: r.u64()?, price: r.u64()? }); + let capability = text(r.take(len)?); + let operation = if version == VERSION_3 { + let len = r.u8()? as usize; + text(r.take(len)?) + } else { + String::new() + }; + job.push(JobPrice { capability, operation, unit: r.u8()?, divisor: r.u64()?, price: r.u64()? }); } } let n = r.u16()? as usize; @@ -482,10 +518,13 @@ impl Tariff { && t.seller != t.buyer && !t.scope.is_empty() && ascending(&t.scope) - && t.scope.iter().all(|(k, id)| (1..=if t.version == VERSION_2 { 5 } else { 4 }).contains(k) && *id != [0; 32]) + && t.scope.iter().all(|(k, id)| (1..=if prices_jobs(t.version) { 5 } else { 4 }).contains(k) && *id != [0; 32]) && t.job.len() <= 255 - && t.job.windows(2).all(|w| (w[0].capability.as_bytes(), w[0].unit) < (w[1].capability.as_bytes(), w[1].unit)) - && t.job.iter().all(|j| job_capability(&j.capability) && (1..=JOB_UNITS.len() as u8).contains(&j.unit) && j.divisor >= 1); + && t.job.windows(2).all(|w| (w[0].capability.as_bytes(), w[0].operation.as_bytes(), w[0].unit) < (w[1].capability.as_bytes(), w[1].operation.as_bytes(), w[1].unit)) + && t.job.iter().all(|j| { + let operation = if t.version == VERSION_3 { job_operation(&j.operation) } else { j.operation.is_empty() }; + job_capability(&j.capability) && operation && (1..=JOB_UNITS.len() as u8).contains(&j.unit) && j.divisor >= 1 + }); if !rules { return Err((Reason::TariffInvalid.code(), at())); } @@ -554,7 +593,7 @@ pub struct Row { /// An invoice (§6.1). #[derive(Clone, Debug, PartialEq, Eq)] pub struct Invoice { - /// Its tariff's version, 1 or 2. + /// Its tariff's version, 1, 2 or 3. pub version: u8, pub seller: [u8; 32], pub buyer: [u8; 32], @@ -566,7 +605,7 @@ pub struct Invoice { pub records: Vec<(u8, [u8; 32])>, pub lines: Vec, pub energy: Vec, - /// Scopes 1 to 4, and in version 2 scope 5; a version-1 invoice states no fifth. + /// Scopes 1 to 4, and in versions 2 and 3 scope 5; a version-1 invoice states no fifth. pub unmetered: [u32; 5], pub amount: u64, pub sig: [u8; 64], @@ -608,7 +647,7 @@ impl Invoice { o.extend_from_slice(&e.joules.to_le_bytes()); o.extend_from_slice(&e.max_ppm.to_le_bytes()); } - let scopes = if self.version == VERSION_2 { 5 } else { 4 }; + let scopes = if prices_jobs(self.version) { 5 } else { 4 }; for u in &self.unmetered[..scopes] { o.extend_from_slice(&u.to_le_bytes()); } @@ -636,7 +675,7 @@ impl Invoice { /// Read an invoice (§6.8): its rules, then its signature. pub fn parse(b: &[u8]) -> Result { let version = read_head(b, &INVOICE_MAGIC)?; - let v2 = version == VERSION_2; + let v2 = prices_jobs(version); let mut r = Rd { b, p: 5 }; let (seller, buyer, tariff, prev) = (r.arr()?, r.arr()?, r.arr()?, r.arr()?); let (start, end) = (r.u64()?, r.u64()?); @@ -847,7 +886,7 @@ pub struct StitchedFacts { pub statement: Value, } -/// A verified job source (§5.1, version 2). +/// A verified job source (§5.1, versions 2 and 3). #[derive(Clone, Debug)] pub struct JobFacts { /// The route record's digest, and its signer: the router. @@ -855,6 +894,8 @@ pub struct JobFacts { pub router: [u8; 32], /// The job's id, which the router names it by. pub job: String, + /// The route record's operation, which a tariff of version 3 prices by (§4.6). + pub operation: String, pub accepted: u64, /// Each attempt's OER/2 receipt, by the 32 bytes its id names, in attempt order. pub hops: Vec<[u8; 32]>, @@ -1339,6 +1380,7 @@ fn job_source(o: &Map) -> Result { record: rr::digest(&record), router: unhex(record["signer"].as_str().expect("a key")).expect("a key").try_into().expect("32 bytes"), job: record["job"].as_str().expect("a token").to_string(), + operation: record["operation"].as_str().expect("an operation").to_string(), accepted: record["accepted"].as_u64().expect("a time"), hops, billed, @@ -1347,11 +1389,11 @@ fn job_source(o: &Map) -> Result { })) } -/// §5.1: verify one source by its extension, a stitched source with `signers` (§4.4), a job -/// source only under a tariff of version 2. A refusal's place is the extension's own, relative -/// to the source. +/// §5.1: verify one source by its extension, a stitched source with `signers` (§4.4); a job +/// source is read here whatever the tariff, and only a tariff of version 2 or 3 bills it. A +/// refusal's place is the extension's own, relative to the source. pub fn verify_source(v: &Value, signers: &[[u8; 32]]) -> Result { - verify_source_of(v, signers, VERSION_2) + verify_source_of(v, signers, VERSION_3) } fn verify_source_of(v: &Value, signers: &[[u8; 32]], version: u8) -> Result { @@ -1362,7 +1404,7 @@ fn verify_source_of(v: &Value, signers: &[[u8; 32]], version: u8) -> Result package_source(o), Some("ads") => ads_source(o), Some("stitched") => stitched_source(o, signers), - Some("job") if version == VERSION_2 => job_source(o), + Some("job") if prices_jobs(version) => job_source(o), _ => Err(bad_source()), } } @@ -1436,25 +1478,36 @@ fn overlaps(a: (u64, u64), b: (u64, u64)) -> bool { a.0 < b.1 && b.0 < a.1 } -/// Whether source `f` bills again what `g`, an earlier source, billed (§5.3). -fn overlap(f: &Facts, g: &Facts) -> bool { +/// Whether source `f` bills again what `g`, an earlier source, billed, under a tariff of +/// `version` (§5.3). +fn overlap(version: u8, f: &Facts, g: &Facts) -> bool { match (f, g) { (Facts::Room(a), Facts::Room(b)) => a.room == b.room && overlaps((a.start, a.end), (b.start, b.end)), (Facts::Delivery(a), Facts::Delivery(b)) => { a.statement.party == b.statement.party && overlaps((a.statement.start, a.statement.end), (b.statement.start, b.statement.end)) } (Facts::Ads(a), Facts::Ads(b)) => a.room == b.room && a.receipts.iter().any(|x| b.receipts.iter().any(|y| x.cue == y.cue && x.session == y.session)), - // Two records of one router and one job route one job twice (§5.3). - (Facts::Job(a), Facts::Job(b)) => a.router == b.router && a.job == b.job, + // In version 2, two records of one router and one job route one job twice. In version 3 + // they are two runs of it, each billed from its own hop (§5.3). + (Facts::Job(a), Facts::Job(b)) => version == VERSION_2 && a.router == b.router && a.job == b.job, _ => false, } } -/// §7.2 step 6: the index of the source refused and the reason, or none. -pub fn check_across(facts: &[Facts], previous: Option<&Invoice>) -> Result<(), (usize, Reason)> { +/// Whether a chain under a tariff of `version` bills a record of `kind` once (§6.9): every +/// record but, in version 3, a job's output receipt. A job's work, its route record and its +/// hops' OER/2 receipts, is new on every run and billed once; its output receipt is the +/// evidence of what it delivered, which a deterministic re-run delivers again. +pub fn billed_once(version: u8, kind: u8) -> bool { + !(version == VERSION_3 && kind == record_kind::VIDEO_RECEIPT) +} + +/// §7.2 step 6 under a tariff of `version`: the index of the source refused and the reason, or +/// none. +pub fn check_across(version: u8, facts: &[Facts], previous: Option<&Invoice>) -> Result<(), (usize, Reason)> { let billed: BTreeSet<(u8, [u8; 32])> = previous.map(|p| p.records.iter().copied().collect()).unwrap_or_default(); let mut heads: BTreeSet<(u8, [u8; 32])> = BTreeSet::new(); - // One hop serves one job, and one output is one job's (§5.2). + // One hop serves one job; in version 2, one output is one job's too (§5.2). let mut job_records: BTreeSet<(u8, [u8; 32])> = BTreeSet::new(); for (k, f) in facts.iter().enumerate() { let hs = f.heads(); @@ -1462,16 +1515,16 @@ pub fn check_across(facts: &[Facts], previous: Option<&Invoice>) -> Result<(), ( return Err((k, Reason::RecordDuplicate)); } if let Facts::Job(_) = f { - let own: BTreeSet<(u8, [u8; 32])> = f.records().into_iter().filter(|(kind, _)| *kind != record_kind::ROUTE_RECORD).collect(); + let own: BTreeSet<(u8, [u8; 32])> = f.records().into_iter().filter(|(kind, _)| *kind != record_kind::ROUTE_RECORD && billed_once(version, *kind)).collect(); if own.iter().any(|r| job_records.contains(r)) { return Err((k, Reason::RecordDuplicate)); } job_records.extend(own); } - if facts[..k].iter().any(|g| overlap(f, g)) { + if facts[..k].iter().any(|g| overlap(version, f, g)) { return Err((k, Reason::RecordOverlap)); } - if f.records().iter().any(|r| billed.contains(r)) { + if f.records().iter().any(|r| billed_once(version, r.0) && billed.contains(r)) { return Err((k, Reason::RecordRebilled)); } heads.extend(hs); @@ -1566,10 +1619,13 @@ pub fn compute(t: &Tariff, facts: &[Facts]) -> Result { add(source_kind::ADS, r.decider, unit, counted.len() as u128, &cites); } } - // A job is billed at the prices of its serving capability, from its billed hop (§6.3). + // A job is billed at the prices of its serving capability, in version 3 of its + // operation too, from its billed hop (§6.3). An output it shares with another job is + // cited once and counted for each (§6.5). Facts::Job(j) => { let Some((cap, hop)) = &j.billed else { continue }; - for (k, p) in t.job.iter().enumerate().filter(|(_, p)| p.capability == *cap) { + let priced = |p: &JobPrice| p.capability == *cap && (t.version != VERSION_3 || p.operation == j.operation); + for (k, p) in t.job.iter().enumerate().filter(|(_, p)| priced(p)) { let unit = u8::try_from(k + 1).expect("at most 255 job prices"); let mut cites = vec![(record_kind::ROUTE_RECORD, j.record), (record_kind::OER_RECEIPT, *hop)]; let n = if p.unit == 1 { @@ -1919,7 +1975,7 @@ pub fn verify_sources(t: &Tariff, start: u64, end: u64, sources: &[Value], previ check_source(t, start, end, &f).map_err(|r| (r.code(), json!(["source", k])))?; facts.push(f); } - check_across(&facts, previous).map_err(|(k, r)| (r.code(), json!(["source", k])))?; + check_across(t.version, &facts, previous).map_err(|(k, r)| (r.code(), json!(["source", k])))?; Ok(facts) } @@ -1988,18 +2044,22 @@ pub fn accepted_json(a: &Accepted) -> Value { "unit": unit_name(l.source_kind, l.unit).unwrap_or(""), "quantity": l.quantity, "price": l.price, "amount": l.amount, "cites": cited.iter().map(|(k, id)| json!([k, hex(id)])).collect::>(), }); - // A job line names its unit and capability by its job price (§7.3). + // A job line names its unit and capability by its job price, and in version 3 its + // operation (§7.3). if l.source_kind == source_kind::JOB { let p = &a.tariff.job[l.unit as usize - 1]; v["unit"] = json!(JOB_UNITS[p.unit as usize - 1]); v["capability"] = json!(p.capability); + if a.tariff.version == VERSION_3 { + v["operation"] = json!(p.operation); + } } v }) .collect(); let energy: Vec = i.energy.iter().map(|r| json!({"scope": scope::name(r.scope), "class": class_label(r.tag), "figures": r.figures, "joules": r.joules, "max_ppm": r.max_ppm})).collect(); let (mut unmetered, mut coverage) = (Map::new(), Map::new()); - for s in 1..=if i.version == VERSION_2 { 5u8 } else { 4 } { + for s in 1..=if prices_jobs(i.version) { 5u8 } else { 4 } { let n = i.unmetered[s as usize - 1]; let has_row = i.energy.iter().any(|r| r.scope == s); unmetered.insert(scope::name(s).into(), n.into()); @@ -2062,14 +2122,89 @@ mod tests { let mut t = tariff(); t.version = VERSION_2; t.job = vec![ - JobPrice { capability: "wai.video.av1".into(), unit: 1, divisor: 1, price: 7 }, - JobPrice { capability: "wai.video.int_motion".into(), unit: 2, divisor: 1_000_000, price: 9 }, + JobPrice { capability: "wai.video.av1".into(), operation: String::new(), unit: 1, divisor: 1, price: 7 }, + JobPrice { capability: "wai.video.int_motion".into(), operation: String::new(), unit: 2, divisor: 1_000_000, price: 9 }, ]; t.scope.push((source_kind::JOB, [7; 32])); t.scope.sort(); t.sign(&key(1), &key(2)) } + fn tariff_v3() -> Tariff { + let mut t = tariff_v2(); + t.version = VERSION_3; + let p = |op: &str, unit, divisor, price| JobPrice { capability: "wai.video.int_motion".into(), operation: op.into(), unit, divisor, price }; + t.job = vec![p("media.decode", 2, 1_000_000, 10_000), p("media.encode", 1, 1, 500_000), p("media.encode", 2, 1_000_000, 20_000)]; + t.sign(&key(1), &key(2)) + } + + /// A tariff of version 3 reads back with each job price's operation, and every altered byte + /// is refused; its rules order the prices by (capability, operation, unit) and hold each + /// operation to route-record §2's form (§4.3, §4.6). A version-2 price states no operation, + /// and its bytes are as they were. + #[test] + fn a_tariff_of_version_3_prices_each_operation() { + let t = tariff_v3(); + let b = t.to_bytes(); + assert_eq!(b[4], 3); + assert_eq!(Tariff::parse(&b).unwrap(), t); + for at in 0..b.len() { + let mut m = b.clone(); + m[at] ^= 0x01; + assert!(Tariff::parse(&m).is_err(), "byte {at} altered and accepted"); + } + let refused = |f: &dyn Fn(&mut Tariff)| { + let mut x = tariff_v3(); + f(&mut x); + Tariff::parse(&x.sign(&key(1), &key(2)).to_bytes()).map(|_| ()).map_err(|e| e.0) + }; + assert_eq!(refused(&|x| x.job.swap(0, 1)), Err("tariff_invalid"), "an encode price before a decode price"); + assert_eq!(refused(&|x| x.job[2].operation = "media.decode".into()), Err("tariff_invalid"), "out of order, and a decode priced twice"); + assert_eq!(refused(&|x| x.job[0].operation = String::new()), Err("tariff_invalid"), "no operation"); + for bad in ["media", "media.encode.x", "Media.encode", "media.2x", "media.", ".encode", "media-x.encode"] { + assert_eq!(refused(&|x| x.job[0].operation = bad.into()), Err("tariff_invalid"), "{bad}"); + } + let long = format!("media.{}", "e".repeat(122)); + assert_eq!(long.len(), 128); + assert_eq!(refused(&|x| x.job[0].operation = long.clone()), Ok(()), "128 bytes"); + assert_eq!(refused(&|x| x.job[0].operation = format!("{long}e")), Err("tariff_invalid"), "129 bytes"); + // One price per operation: the same capability and unit under two operations. + assert_eq!(refused(&|x| x.job.retain(|p| p.unit == 2)), Ok(())); + // A version-2 price is the four fields it always was: one capability, then the unit. + let v2 = tariff_v2(); + let b2 = v2.to_bytes(); + let caps: usize = v2.job.iter().map(|p| 1 + p.capability.len() + 17).sum(); + assert_eq!(b2.len(), 4 + 1 + 64 + 16 + 3 + 52 + 32 + 24 + 16 + 2 + caps + 2 + 33 * v2.scope.len() + 128); + assert!(Tariff::parse(&b2).unwrap().job.iter().all(|p| p.operation.is_empty())); + // A version-2 tariff writes no operation, whatever its prices hold. + let mut named = tariff_v2(); + named.job[0].operation = "media.encode".into(); + assert_eq!(named.body(), v2.body(), "no operation written in version 2"); + // A capability or an operation that is not text breaks its form: a rule, not the layout. + let mut not_text = tariff_v3(); + not_text.job[0].operation = "media.\u{e9}ncode".into(); + let mut nb = not_text.sign(&key(1), &key(2)).to_bytes(); + let at = nb.windows(2).position(|w| w == [0xc3, 0xa9]).expect("the two bytes of é"); + nb[at] = 0xff; + assert_eq!(Tariff::parse(&nb).unwrap_err().0, "tariff_invalid"); + // A tariff of version 4 is not defined. + let mut v4 = b.clone(); + v4[4] = 4; + assert_eq!(Tariff::parse(&v4).unwrap_err().0, "unknown_version"); + } + + /// What a chain bills once (§6.9): in version 3, every record but an output receipt; in + /// versions 1 and 2, every record. + #[test] + fn a_chain_bills_a_jobs_work_once_and_cites_its_output_again() { + for k in [record_kind::SESSION, record_kind::PACKAGE, record_kind::OER_RECEIPT, record_kind::ROUTE_RECORD] { + assert!(billed_once(VERSION, k) && billed_once(VERSION_2, k) && billed_once(VERSION_3, k), "kind {k}"); + } + assert!(billed_once(VERSION_2, record_kind::VIDEO_RECEIPT), "version 2 bills an output once"); + assert!(!billed_once(VERSION_3, record_kind::VIDEO_RECEIPT), "version 3 bills the work, and cites the output"); + assert!(prices_jobs(VERSION_2) && prices_jobs(VERSION_3) && !prices_jobs(VERSION) && !prices_jobs(4)); + } + /// A tariff of version 2 reads back with its job prices, and every altered byte is refused; /// its rules refuse prices out of order, a unit outside the two, a divisor of 0, a capability /// outside WAI's form, and a router in a tariff of version 1 (§4.3, §4.5). @@ -2134,6 +2269,15 @@ mod tests { assert_eq!(with(k, &i), Err("private_kind"), "kind {k} in version 2"); assert_eq!(with(k, &v1), Err("private_kind"), "kind {k} in version 1"); } + // Version 3 is laid out as version 2, and lists the same kinds. + let t3 = tariff_v3(); + let i3 = invoice_of(&t3, [0; 32], 10, 20, &c).sign(&key(1)); + assert_eq!(i3.to_bytes().len(), i.to_bytes().len()); + assert_eq!(Invoice::parse(&i3.to_bytes()).unwrap(), i3); + for k in [record_kind::OER_RECEIPT, record_kind::VIDEO_RECEIPT, record_kind::ROUTE_RECORD] { + assert_eq!(with(k, &i3), Ok(()), "kind {k} in version 3"); + } + assert_eq!(with(record_kind::RUN, &i3), Err("invoice_invalid")); } #[test] @@ -2194,8 +2338,8 @@ mod tests { assert_eq!(Invoice::parse(&unit.sign(&key(1)).to_bytes()).unwrap_err(), "invoice_invalid"); assert_eq!(Invoice::parse(&[b.clone(), vec![0]].concat()).unwrap_err(), "malformed"); let mut v = b.clone(); - v[4] = 3; - assert_eq!(Invoice::parse(&v).unwrap_err(), "unknown_version", "version 3 is not defined (2 is, §6.1)"); + v[4] = 4; + assert_eq!(Invoice::parse(&v).unwrap_err(), "unknown_version", "version 4 is not defined (2 and 3 are, §6.1)"); let mut s = b; let n = s.len(); s[n - 1] ^= 1; diff --git a/wai/wai-rs/src/receipt_ledger/corpus.rs b/wai/wai-rs/src/receipt_ledger/corpus.rs index 71c00c158..05b1f116d 100644 --- a/wai/wai-rs/src/receipt_ledger/corpus.rs +++ b/wai/wai-rs/src/receipt_ledger/corpus.rs @@ -6,8 +6,11 @@ //! `live-conformance/ads/` and `live-conformance/presentation/`, and packages from //! `pack-conformance/packs/`. The statements of two delivering parties, with the //! acknowledgements their viewers signed and their per-hop records, are written here from -//! fixed seeds, as are the tariffs and the invoices. [`generate`] reads those files from a -//! `wai/` directory; [`verify`] checks every vector against the outcome it states. +//! fixed seeds, as are the tariffs and the invoices. Media jobs come from +//! `live-conformance/routes/`, and those the version-3 vectors add (a decode, a re-run, a re-run +//! replaying a hop receipt, a job claiming another's) are routed here under that corpus's own +//! seeds. [`generate`] reads those files from a `wai/` directory; [`verify`] checks every vector +//! against the outcome it states. use std::collections::BTreeMap; use std::path::Path; @@ -17,6 +20,7 @@ use serde_json::{json, Map, Value}; use super::*; use crate::energy_class::{CounterFiltering, Uncertainty}; +use crate::route_record::corpus as rr; use crate::jwp_hash::content_hash; use crate::live_metering::HopRecord; use crate::settlement::{class, item_list, Entry, Statement}; @@ -402,6 +406,47 @@ pub fn generate(wai: &Path) -> Result { // Another record of job-0001, served by another endpoint: its own hop receipt, one job. pool.insert("job_routed_again", job_of(&routes, "an_unknown_energy_ranks_last", vec![])?); pool.insert("job_unmetered_claims_another_output", job_of(&routes, "served_by_an_unmetered_hop", out("job-0001"))?); + // Version 3: jobs routed here by the route corpus's router, its hops' receipts sealed under + // its hop key (route-record §6). A decode of int_motion; job-0001 run again two days later, + // a new hop, new energy and a new receipt, its output the same receipt as before; that re-run + // replaying job-0001's own hop receipt instead; and another job whose fallback's hop claims + // that receipt too. + let accepted = rr::ACCEPTED_AT; + let first_hop: Value = { + let d = &named(&routes["vectors"], "served_first_try")?["document"]; + serde_json::from_str(d["receipts"][0].as_str().ok_or("served_first_try's hop receipt")?).map_err(|e| e.to_string())? + }; + let served = |endpoint: &'static str, receipt: Value| rr::Hop { endpoint, error: None, start: 1_000, end: 4_000_000, receipt }; + let routed = |d: Value, outputs: Vec| -> Value { + json!({ + "kind": "job", "request": d["request"], "pricebook": d["pricebook"], "policy": d["policy"], "record": d["record"], + "receipts": d["receipts"], "keys": d["keys"], "outputs": outputs, + }) + }; + pool.insert( + "job_decode", + routed(rr::job_document("job-0007", "media.decode", accepted, &[served("im.alpha", rr::hop_receipt("media.decode", "job-0007", "im.alpha", Some("12.5")))]), out("job-0007")), + ); + pool.insert( + "job_rerun", + routed(rr::job_document("job-0001", "media.encode", accepted + 2 * DAY_US, &[served("im.alpha", rr::hop_receipt("media.encode", "job-0001", "im.alpha", Some("30.875")))]), out("job-0001")), + ); + pool.insert("job_rerun_replays_its_hop", routed(rr::job_document("job-0001", "media.encode", accepted + 2 * DAY_US, &[served("im.alpha", first_hop.clone())]), out("job-0001"))); + pool.insert( + "job_claims_another_hop", + routed( + rr::job_document( + "job-0008", + "media.encode", + accepted, + &[ + rr::Hop { endpoint: "im.alpha", error: Some("server_error"), start: 1_000, end: 2_000_000, receipt: rr::hop_receipt("media.encode", "job-0008", "im.alpha", None) }, + rr::Hop { endpoint: "av1.alpha", error: None, start: 2_000_100, end: 9_000_000, receipt: first_hop }, + ], + ), + vec![], + ), + ); // The rooms' prices are the lesson receipts'; the tariff's scope is every party of the // accepted invoices. @@ -494,7 +539,7 @@ pub fn generate(wai: &Path) -> Result { Vector { name: "tariff_period_empty", about: "a tariff whose period is empty, signed by both", tariff: tariff_period.to_bytes(), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, // The invoice. Vector { name: "invoice_trailing_byte", about: "an invoice with a byte after its signature", tariff: tb.clone(), invoice: [ab.clone(), vec![0]].concat(), previous: None, sources: day_a.to_vec() }, - Vector { name: "invoice_version", about: "an invoice of version 3, which this revision does not define", tariff: tb.clone(), invoice: { let mut b = ab.clone(); b[4] = 3; b }, previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_version", about: "an invoice of version 4, which this revision does not define", tariff: tb.clone(), invoice: { let mut b = ab.clone(); b[4] = 4; b }, previous: None, sources: day_a.to_vec() }, Vector { name: "invoice_signature", about: "an invoice whose signature does not verify", tariff: tb.clone(), invoice: flip(ab.clone(), ab.len() - 1), previous: None, sources: day_a.to_vec() }, Vector { name: "invoice_lines_unordered", about: "an invoice whose first two lines are swapped", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| i.lines.swap(0, 1)).to_bytes(), previous: None, sources: day_a.to_vec() }, Vector { name: "invoice_row_of_no_class", about: "an energy row of tag 0: joules of no one class", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| i.energy.insert(0, Row { scope: 1, tag: 0, figures: 1, joules: 1, max_ppm: 0 })).to_bytes(), previous: None, sources: day_a.to_vec() }, @@ -541,9 +586,9 @@ pub fn generate(wai: &Path) -> Result { // Version 2: a tariff that prices jobs (§4.5), and its invoices. let router: [u8; 32] = routes["keys"]["router"].as_str().and_then(unhex).and_then(|b| b.try_into().ok()).ok_or("the route corpus's router")?; let job_prices = vec![ - JobPrice { capability: "wai.video.av1".into(), unit: 1, divisor: 1, price: 2_000_000 }, - JobPrice { capability: "wai.video.int_motion".into(), unit: 1, divisor: 1, price: 500_000 }, - JobPrice { capability: "wai.video.int_motion".into(), unit: 2, divisor: 1_000_000, price: 30_000 }, + JobPrice { capability: "wai.video.av1".into(), operation: String::new(), unit: 1, divisor: 1, price: 2_000_000 }, + JobPrice { capability: "wai.video.int_motion".into(), operation: String::new(), unit: 1, divisor: 1, price: 500_000 }, + JobPrice { capability: "wai.video.int_motion".into(), operation: String::new(), unit: 2, divisor: 1_000_000, price: 30_000 }, ]; let v2 = |scope: Vec<(u8, [u8; 32])>, job: Vec| -> Tariff { let mut t2 = tariff_of("buyer-j", room_prices, scope); @@ -588,6 +633,56 @@ pub fn generate(wai: &Path) -> Result { Vector { name: "job_energy_row_altered", about: "the jobs' counter row one microjoule above its hop's figure", tariff: tjb.clone(), invoice: sealed(&t_j, [0; 32], MONTH.0, MONTH.1, &refs(&day_j), |i| { if let Some(r) = i.energy.iter_mut().find(|r| r.scope == scope::JOBS && r.tag == 4) { r.joules += 1; } }).to_bytes(), previous: None, sources: day_j.to_vec() }, ]); + // Version 3: job prices per operation (§4.6), and a chain that bills a job's work once and + // cites its output's receipt again (§6.9). Encode and decode of int_motion are priced apart: + // a decode reconstructs, an encode searches for motion and then writes the codestream. + if routed_at != accepted { + return Err(format!("the route corpus accepts its jobs at {routed_at}, its seeds at {accepted}")); + } + const IM: &str = "wai.video.int_motion"; + let jp = |capability: &str, operation: &str, unit: u8, divisor: u64, price: u64| JobPrice { capability: capability.into(), operation: operation.into(), unit, divisor, price }; + let job_prices_3 = vec![ + jp("wai.video.av1", "media.encode", 1, 1, 2_000_000), + jp(IM, "media.decode", 1, 1, 250_000), + jp(IM, "media.decode", 2, 1_000_000, 10_000), + jp(IM, "media.encode", 1, 1, 500_000), + jp(IM, "media.encode", 2, 1_000_000, 20_000), + ]; + let v3 = |job: Vec| -> Tariff { + let mut t3 = tariff_of("buyer-j", room_prices, vec![package_scope, (source_kind::JOB, router)]); + t3.version = VERSION_3; + t3.job = job; + t3.sign(&seller, &key("buyer-j")) + }; + let t_k = v3(job_prices_3.clone()); + let tkb = t_k.to_bytes(); + // The first invoice of the chain ends a day after the jobs were accepted; the re-run is + // accepted a day later, in the next. + let mid = accepted + DAY_US; + let day_k = ["package_base", "job_int_motion", "job_failover", "job_late", "job_unmetered", "job_failed", "job_decode"]; + let inv_k = make_invoice(&seller, &t_k, None, MONTH.0, mid, &src(&day_k)).map_err(|e| format!("invoice k: {e:?}"))?; + let kb = inv_k.to_bytes(); + let inv_k_rerun = make_invoice(&seller, &t_k, Some(&inv_k), mid, MONTH.1, &src(&["job_rerun"])).map_err(|e| format!("invoice k, the re-run: {e:?}"))?; + let inv_k_both = make_invoice(&seller, &t_k, None, MONTH.0, MONTH.1, &src(&["job_int_motion", "job_rerun"])).map_err(|e| format!("invoice k, both runs: {e:?}"))?; + let t_k_encode = v3(job_prices_3.iter().filter(|p| p.operation == "media.encode").cloned().collect()); + let inv_k_encode = make_invoice(&seller, &t_k_encode, None, MONTH.0, MONTH.1, &src(&["job_int_motion", "job_decode"])).map_err(|e| format!("invoice k, encode priced: {e:?}"))?; + let mut decode_after_encode = job_prices_3.clone(); + decode_after_encode[1..].rotate_left(2); + let t_k_unordered = v3(decode_after_encode); + // Version 2 bills an output once: its chain's first half-month, then the re-run. + let inv_j_first = make_invoice(&seller, &t_j, None, MONTH.0, mid, &src(&day_j)).map_err(|e| format!("invoice j, first: {e:?}"))?; + vectors.extend([ + Vector { name: "jobs_priced_per_operation", about: "version 3: media jobs priced by capability and operation, beside a package: an int_motion encode and an int_motion decode at their own prices per job and per subpixel, a failover billed at the fallback's encode price, a job served past its deadline and one every allowed hop failed, billed nothing, and a job whose serving hop was unmetered", tariff: tkb.clone(), invoice: kb.clone(), previous: None, sources: day_k.to_vec() }, + Vector { name: "a_rerun_billed", about: "version 3: the next invoice of the chain bills job-0001 run again, a new route record and a new hop receipt, its output the receipt the first invoice holds", tariff: tkb.clone(), invoice: inv_k_rerun.to_bytes(), previous: Some(kb.clone()), sources: vec!["job_rerun"] }, + Vector { name: "one_output_under_two_hops", about: "version 3: job-0001 and its re-run in one invoice, two hops of one request: both billed, the output receipt they share cited once and its subpixels billed for each", tariff: tkb.clone(), invoice: inv_k_both.to_bytes(), previous: None, sources: vec!["job_int_motion", "job_rerun"] }, + Vector { name: "an_operation_the_tariff_does_not_price", about: "version 3: a tariff that prices int_motion's encode and not its decode: the decode bills nothing, its hop's energy stated", tariff: t_k_encode.to_bytes(), invoice: inv_k_encode.to_bytes(), previous: None, sources: vec!["job_int_motion", "job_decode"] }, + Vector { name: "a_hop_receipt_replayed", about: "version 3: the next invoice bills a new route record of job-0001 whose hop receipt the first invoice billed", tariff: tkb.clone(), invoice: sealed(&t_k, inv_k.digest(), mid, MONTH.1, &refs(&["job_rerun_replays_its_hop"]), |_| {}).to_bytes(), previous: Some(kb.clone()), sources: vec!["job_rerun_replays_its_hop"] }, + Vector { name: "one_hop_under_two_lines", about: "version 3: a job whose fallback's hop receipt is another job's, billed on the int_motion encode lines and the av1 line", tariff: tkb.clone(), invoice: sealed(&t_k, [0; 32], MONTH.0, MONTH.1, &refs(&["job_int_motion", "job_claims_another_hop"]), |_| {}).to_bytes(), previous: None, sources: vec!["job_int_motion", "job_claims_another_hop"] }, + Vector { name: "job_prices_unordered_by_operation", about: "a tariff of version 3 pricing int_motion's encode before its decode", tariff: t_k_unordered.to_bytes(), invoice: kb.clone(), previous: None, sources: day_k.to_vec() }, + Vector { name: "version_2_invoice_under_a_version_3_tariff", about: "an invoice of version 2 naming a tariff of version 3", tariff: tkb.clone(), invoice: sealed(&t_k, [0; 32], MONTH.0, mid, &refs(&day_k), |i| i.version = VERSION_2).to_bytes(), previous: None, sources: day_k.to_vec() }, + Vector { name: "version_2_bills_an_output_once", about: "version 2 as before: the re-run of job-0001 in the next invoice of a version-2 chain, refused because its output receipt was billed", tariff: tjb.clone(), invoice: sealed(&t_j, inv_j_first.digest(), mid, MONTH.1, &refs(&["job_rerun"]), |_| {}).to_bytes(), previous: Some(inv_j_first.to_bytes()), sources: vec!["job_rerun"] }, + ]); + let mut sources = Map::new(); for (n, v) in &pool { sources.insert((*n).into(), v.clone()); diff --git a/wai/wai-rs/src/route_record/corpus.rs b/wai/wai-rs/src/route_record/corpus.rs index 799874823..6a7968720 100644 --- a/wai/wai-rs/src/route_record/corpus.rs +++ b/wai/wai-rs/src/route_record/corpus.rs @@ -557,6 +557,61 @@ fn vectors() -> Vec { v } +// ───────────────────────────── jobs routed for the receipt ledger ───────────────────────────── + +/// When this corpus's router accepts its jobs. +#[cfg(feature = "receipt_ledger")] +pub(crate) const ACCEPTED_AT: u64 = ACCEPTED; + +/// One hop of a job routed for another corpus (receipt-ledger §11): its endpoint, its error +/// (`None` when it served), its start and end after acceptance, and its OER/2 receipt. +#[cfg(feature = "receipt_ledger")] +pub(crate) struct Hop { + pub endpoint: &'static str, + pub error: Option<&'static str>, + pub start: u64, + pub end: u64, + pub receipt: Value, +} + +/// The OER/2 receipt of a hop of `job` at `endpoint` for `operation`, sealed under this corpus's +/// hop key and bound to its job as this corpus binds its own: served, its figure a counter +/// reading of `joules`, or failed (`None`), unmetered. +#[cfg(feature = "receipt_ledger")] +pub(crate) fn hop_receipt(operation: &str, job: &str, endpoint: &str, joules: Option<&str>) -> Value { + let mut r = match joules { + Some(j) => receipt(operation, endpoint, 1, Some((j, counter(), "measured"))), + None => receipt(operation, endpoint, -1, None), + }; + r["subject"]["inputs"] = json!([sha(&format!("{job}/{endpoint}"))]); + reseal_receipt(&mut r); + r +} + +/// The verification document (§7.1) of this corpus's request under `job` and `operation`, +/// routed by its router under its price book and policy, accepted at `accepted`, with +/// `attempts` in their order and their receipts given as they are. +#[cfg(feature = "receipt_ledger")] +pub(crate) fn job_document(job: &str, operation: &str, accepted: u64, attempts: &[Hop]) -> Value { + let mut request = request(); + request["job"] = json!(job); + request["operation"] = json!(operation); + let r = Request::read(&canonical(&request)).expect("the request reads"); + let b = PriceBook::read(&canonical(&pricebook())).expect("the price book reads"); + let p = Policy::read(&canonical(&policy(3))).expect("the policy reads"); + let atts: Vec = + attempts.iter().map(|h| Attempt { endpoint: h.endpoint.to_string(), error: h.error, start: accepted + h.start, end: accepted + h.end, receipt: id(&h.receipt) }).collect(); + let record = seal(&router(), &r, &b, &p, accepted, &atts); + json!({ + "request": canonical(&request), + "pricebook": canonical(&pricebook()), + "policy": canonical(&policy(3)), + "record": canonical(&record), + "receipts": attempts.iter().map(|h| text(&h.receipt)).collect::>(), + "keys": {HOP_SIGNER: hex(&hops().verifying_key().to_bytes())}, + }) +} + /// The corpus, as `vectors.json` holds it. pub fn generate() -> Result { let mut out = Vec::new(); diff --git a/wai/wai-rs/tests/receipt_ledger.rs b/wai/wai-rs/tests/receipt_ledger.rs index 1ca24d351..2ec7fed68 100644 --- a/wai/wai-rs/tests/receipt_ledger.rs +++ b/wai/wai-rs/tests/receipt_ledger.rs @@ -115,6 +115,71 @@ fn the_corpus_covers_every_reason_and_case() { assert_eq!(rows, ["ModelBased", "Estimator", "OnChipCounter"], "one row per class, never one across them"); } +/// Version 3 (§4.6, §6.9): one capability's encode and decode at their own prices; a re-run billed +/// in the next invoice of its chain, the output receipt the first invoice holds cited again; one +/// output under two hops of one invoice, cited once and billed for each; an operation the tariff +/// does not price billing nothing; a replayed hop receipt and a hop two jobs claim refused; and +/// version 2 billing an output once, as it did. +#[test] +fn version_3_prices_each_operation_and_bills_the_work() { + use serde_json::{json, Value}; + let doc: Value = serde_json::from_str(&committed()).unwrap(); + let vectors = doc["vectors"].as_array().unwrap(); + let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); + let line = |o: &Value, cap: &str, op: &str, unit: &str| -> Value { + o["lines"] + .as_array() + .unwrap() + .iter() + .find(|l| l["source_kind"] == "job" && l["capability"] == cap && l["operation"] == op && l["unit"] == unit) + .cloned() + .unwrap_or_else(|| panic!("no {cap} {op} {unit} line")) + }; + let cites = |l: &Value, kind: u64| -> Vec { l["cites"].as_array().unwrap().iter().filter(|c| c[0] == kind).map(|c| c[1].as_str().unwrap().to_string()).collect() }; + const IM: &str = "wai.video.int_motion"; + + let k = get("jobs_priced_per_operation"); + assert_eq!(k["outcome"], "accept"); + let (enc, dec) = (line(&k, IM, "media.encode", "subpixels"), line(&k, IM, "media.decode", "subpixels")); + assert_eq!((enc["price"].as_u64(), dec["price"].as_u64()), (Some(20_000), Some(10_000)), "an encode and a decode at their own prices"); + assert_eq!(enc["quantity"].as_u64().unwrap(), 2 * dec["quantity"].as_u64().unwrap(), "two encodes and one decode of one clip"); + assert_eq!((line(&k, IM, "media.encode", "jobs")["quantity"].as_u64(), line(&k, IM, "media.decode", "jobs")["quantity"].as_u64()), (Some(2), Some(1))); + assert_eq!(line(&k, "wai.video.av1", "media.encode", "jobs")["quantity"], 1, "the failover, at the fallback's encode price"); + + let r = get("a_rerun_billed"); + assert_eq!(r["outcome"], "accept"); + let rerun = line(&r, IM, "media.encode", "subpixels"); + let output = cites(&rerun, 15); + assert_eq!(output.len(), 1); + assert!(cites(&enc, 15).contains(&output[0]), "the re-run's output receipt is one the first invoice billed with job-0001"); + assert!(cites(&rerun, 14).iter().all(|h| !cites(&enc, 14).contains(h)), "the re-run's hop receipt is new"); + + let both = get("one_output_under_two_hops"); + assert_eq!(both["outcome"], "accept"); + let b = line(&both, IM, "media.encode", "subpixels"); + assert_eq!((cites(&b, 14).len(), cites(&b, 15), cites(&b, 16).len()), (2, output.clone(), 2), "two hops and two records, one output cited once"); + assert_eq!(b["quantity"].as_u64().unwrap(), 2 * rerun["quantity"].as_u64().unwrap(), "its subpixels billed for each hop"); + assert_eq!(line(&both, IM, "media.encode", "jobs")["quantity"], 2); + + let n = get("an_operation_the_tariff_does_not_price"); + assert_eq!(n["outcome"], "accept"); + assert!(n["lines"].as_array().unwrap().iter().all(|l| l["operation"] == "media.encode"), "the decode bills nothing"); + assert_eq!(n["energy"].as_array().unwrap().iter().filter(|e| e["scope"] == "jobs").map(|e| e["figures"].as_u64().unwrap()).sum::(), 2, "both hops' energy stated"); + + for (name, reason, at) in [ + ("a_hop_receipt_replayed", "record_rebilled", json!(["source", 0])), + ("one_hop_under_two_lines", "record_duplicate", json!(["source", 1])), + ("job_prices_unordered_by_operation", "tariff_invalid", json!(["tariff"])), + ("version_2_invoice_under_a_version_3_tariff", "tariff_mismatch", json!(["invoice"])), + ("version_2_bills_an_output_once", "record_rebilled", json!(["source", 0])), + ("output_claimed_by_two_jobs", "record_duplicate", json!(["source", 1])), + ("job_routed_twice", "record_overlap", json!(["source", 6])), + ] { + let o = get(name); + assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{name}"); + } +} + /// §10 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. #[test] fn the_extension_lists_the_reasons() {