Kinds 10 to 13 were already revenue-share §7.4's run and invoice and opportunity-report §7.3's cue event and room policy; the job source's OER/2 receipt, int_motion decode receipt and route record took 10 to 12 and so meant two things. They are now 14, 15 and 16. §5.2's table registers every kind a WAI text names a record by, 10 to 13 pointing to the texts that define them, unchanged, and those texts say so. Kinds 128 to 255 are reserved for private and provisional use: a WAI record never states one, and an invoice that lists one is refused private_kind, in either version. A version-2 invoice lists kinds 1 to 9 and 14 to 16, never another text's. Three controls; the corpus and both implementations follow. Co-Authored-By: Claude Opus 5.5 <[email protected]>
605 lines
37 KiB
Markdown
605 lines
37 KiB
Markdown
# WAI Extension: Revenue Share
|
||
|
||
> Status: Draft. Registers the record `wai.delivery.revenue_share`: what each content owner of a
|
||
> linear channel is owed from the ads sold in the channel's breaks, as a statement anyone holding
|
||
> the public records recomputes. An operator and every owner it names sign a **share contract**,
|
||
> `WRC1`: its period, its channel and the key that signs the channel's program-as-run, the
|
||
> carriers whose ad evidence belongs to the channel, the map from each asset to a **split** of
|
||
> integer millionths among owners and the operator, and the rule for a break that falls between
|
||
> programmes. The operator signs a **statement**, `WRS1`, for one party and one period: the
|
||
> invoices of [receipt-ledger](receipt-ledger.md) it rests on, the program-as-run windows of
|
||
> `wai.video.channel` it places impressions in, and one line per split and class, each with the
|
||
> impressions attributed, the revenue the invoices' tariffs priced them at, the party's share of
|
||
> it divided by the largest-remainder rule, and the records the line came from. A verifier given
|
||
> the contract, the windows and the ledgers' own verification documents recomputes every line and
|
||
> refuses a statement that does not recompute. No new transport, no payment and no currency
|
||
> conversion: a statement is the recomputable figures a payment to an owner rests on. The
|
||
> reference implementation is the `revenue_share` module of `wai-rs` (feature `revenue_share`);
|
||
> the corpus is [`live-conformance/share/`](../live-conformance/share/), and its second
|
||
> implementation is `live-conformance/revenue_share_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: the WAI texts this one cites; ISO 4217 (the alphabetic currency
|
||
> code); RFC 3339 §5.6, for the date-time form of §5.4; RFC 8032; RFC 8259; FIPS 180-4.
|
||
>
|
||
> Why the name: the extension states how revenue is shared, and the record names both of its
|
||
> parts, the contract and the statement, under one string, as `wai.delivery.ledger` names its
|
||
> tariff and its invoice.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the share contract, its splits, its signatures and when two contracts overlap (§3); the
|
||
program-as-run as a verifier is given it, the JSON carriage of a `wai.video.channel` receipt, whose
|
||
signed bytes this text writes out and does not change (§4); the evidence, invoices of the receipt
|
||
ledger and the impressions their sources state, with their instants (§5); attribution, from an
|
||
impression's instant to an asset and its split (§6); the statement, its lines, rounding and
|
||
citations (§7); verification (§8); how a statement composes with the ledger (§9); and the
|
||
operations (§10).
|
||
|
||
Out of scope: a transport; paying an owner, an owner's account and any contact with a party
|
||
outside the records; invoicing a buyer, which the receipt ledger does; converting a currency; how
|
||
an operator chooses an owner's share; placing in time a presentation whose marker states no time
|
||
this text reads (§5.4), which is kept in a class of its own; and the records themselves, which
|
||
their own extensions define, verify and leave as they are. The channel receipt keeps its signed
|
||
bytes: §4 writes them out for a verifier that is not `wai-rs`.
|
||
|
||
Relationship to the core spec: `wai.delivery.revenue_share` is a **record** ([SPEC](../SPEC.md)
|
||
§5): a signed record a WAI tool emits and verifies. It names no payload format and is not
|
||
dispatchable.
|
||
|
||
## 2. Terms
|
||
|
||
- **Operator**, **owner**: Ed25519 keys (RFC 8032). The operator carries the channel, sells its
|
||
breaks and signs contracts and statements; an owner holds programmes the channel airs. A
|
||
**party** of a contract is its operator or one of its owners.
|
||
- **Time**: microseconds since 1970-01-01T00:00:00Z, the unit of receipt-ledger §2. A **period**
|
||
is a span of time from its `start` (included) to its `end` (excluded). A channel receipt states
|
||
milliseconds since the same instant; an instant of *m* milliseconds is *m* × 1000 microseconds,
|
||
computed without overflow.
|
||
- **Run**: one `wai.video.channel` receipt, a signed program-as-run window (§4). Its **window** is
|
||
`[window_start_ms, window_end_ms)`, and an **entry** is one slot of its schedule.
|
||
- **Programme**: an entry of kind `program`. An entry of kind `ad` or `filler` is not a programme.
|
||
- **Carrier**: what delivers the channel's ads: a room of [live-ads](live-ads.md), named by its
|
||
room id, whose break-form receipts the ledger bills; or a stitcher of
|
||
[stitched-presentation](stitched-presentation.md), named by the key that signs its pod-input
|
||
records.
|
||
- **Ledger document**: a verification document of receipt-ledger §7.1, for one invoice.
|
||
- **Impression**: one ad presented or billed, as the ledger counts it (§5.2).
|
||
- **Minor unit**, **amount**: as receipt-ledger §2: an amount is an integer of millionths of a
|
||
currency's minor unit.
|
||
- **ppm**: an integer share in millionths, of 10^6.
|
||
- *u64*, *u32*, *u16*, *u8*: little-endian unsigned integers of that many bits in the records of
|
||
§3 and §7; the channel receipt's signed bytes (§4.2) are big-endian, as `wai.video.channel`
|
||
writes them. A **digest** is SHA-256 (FIPS 180-4), 32 bytes; **zero** of a key or digest field
|
||
is 32 zero bytes. A *text* field is a u16 length and that many bytes of UTF-8.
|
||
|
||
## 3. The share contract
|
||
|
||
### 3.1 Layout
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `magic` | 4 | `WRC1` (ASCII) |
|
||
| `version` | 1 | 1 in this revision |
|
||
| `operator` | 32 | the operator's key, which signs the contract and every statement under it |
|
||
| `start`, `end` | 8 each | u64: the period the contract is in force |
|
||
| `currency` | 3 | three capitals `A`–`Z` (ISO 4217): the currency of every invoice it shares |
|
||
| `channel` | 2 + *n* | a text field of 1 to 1024 bytes: the `channel_id` of the channel's runs |
|
||
| `channel_signer` | 32 | the key that signs the channel's runs |
|
||
| `rule` | 1 | for an impression in an entry that is not a programme (§6.2): 1 **preceding**, 2 **following**, 3 **between** |
|
||
| `between` | 2 | u16: the index of the **between split** (§6.2, §6.3) |
|
||
| `carriers` | 2 + 33 × *c* | a u16 count, then each carrier: `kind` (u8: 1 a room, 2 a stitcher) and `id` (32: the room id, or the stitcher's key), in strictly ascending order of (`kind`, `id`) |
|
||
| `owners` | 1 + 32 × *o* | a u8 count, then each owner's key, in strictly ascending order |
|
||
| `splits` | 2 + … | a u16 count, then each split (§3.2) |
|
||
| `assets` | 4 + … | a u32 count, then each asset: `asset` (a text field of 1 to 1024 bytes, an `asset_id` of the channel's schedule) and `split` (u16, the index of its split), in strictly ascending order of `asset`'s bytes |
|
||
| `operator_sig` | 64 | §3.3 |
|
||
| `countersigs` | 64 × *o* | each owner's countersignature, in the order of `owners` (§3.3) |
|
||
|
||
### 3.2 Splits
|
||
|
||
A split divides revenue among parties:
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `operator_ppm` | 4 | u32: the operator's share, 0 to 10^6 |
|
||
| `m` | 1 | u8: the owners it names |
|
||
| `shares` | 5 × *m* | each: `owner` (u8, the owner's index in `owners`) and `ppm` (u32, its share), in strictly ascending order of `owner` |
|
||
|
||
The **parties** of a split, in their **order**, are its owners in the order it lists them, then
|
||
the operator when `operator_ppm` is above 0. A party **holds a share** of a split when it is one of
|
||
its parties. The shares of a split sum to 10^6 exactly: a split is the whole of the revenue it is
|
||
given, and no part of it is left unassigned.
|
||
|
||
### 3.3 Digest and signatures
|
||
|
||
The **body** of a contract is its bytes before `operator_sig`, and its **digest** is SHA-256 over
|
||
the body. `operator_sig` is Ed25519 by `operator` over the bytes `wai:share-contract` (ASCII) and
|
||
0x01, followed by the body. Each owner's countersignature is Ed25519 by that owner over the bytes
|
||
`wai:share-accept` (ASCII) and 0x01, followed by the body; 64 zero bytes in its place state that
|
||
the owner has not countersigned. An operator offers a contract with every countersignature zero,
|
||
and each owner fills its own. Every signature is verified strictly, as [SPEC](../SPEC.md) §7
|
||
*Signatures* requires: a key that fails step 1 or 2 is `bad_key`, and a signature that fails a
|
||
later step is `bad_signature`.
|
||
|
||
A contract is **in force** only when every owner it names has countersigned it: the splits and
|
||
the asset map bind each owner only by its own signature, and a contract some owner did not sign
|
||
shares nothing.
|
||
|
||
### 3.4 Reading and rules
|
||
|
||
A reader reads exactly the bytes it is given as one contract, in the order of §3.1, and refuses at
|
||
the first of these that holds: fewer than 4 bytes, `truncated`; a magic other than `WRC1`,
|
||
`malformed`; no fifth byte, `truncated`; a version other than 1, `unknown_version`; the bytes end
|
||
before a field the layout requires, `truncated`; bytes after the last countersignature,
|
||
`malformed`. It then refuses as `contract_invalid` a contract that breaks any of these:
|
||
|
||
- `start` is below `end`; `currency` is three capitals `A`–`Z`;
|
||
- `operator` and `channel_signer` are not zero, and `operator` is not an owner;
|
||
- `channel` and every `asset` are UTF-8 of 1 to 1024 bytes;
|
||
- `rule` is 1, 2 or 3, and `between` is the index of a split;
|
||
- `carriers` has at least one entry, each `kind` 1 or 2 and each `id` not zero, in strictly
|
||
ascending order;
|
||
- `owners` has at least one key, none zero, in strictly ascending order;
|
||
- `splits` has at least one split; in each, the owners are in strictly ascending order of index,
|
||
each index below the count of `owners`, and each `ppm` above 0; and every owner holds a share of
|
||
at least one split;
|
||
- `assets` has at least one entry, in strictly ascending order, each `split` the index of a split.
|
||
|
||
It refuses as `split_not_whole` a contract with a split whose `operator_ppm` and `ppm`s do not sum
|
||
to 10^6. Then it verifies `operator_sig` (`bad_key`, `bad_signature`), then each countersignature
|
||
in turn: one of 64 zero bytes is `not_countersigned`, and one that does not verify is `bad_key` or
|
||
`bad_signature`. A refusal of a signature names the operator, or the owner and its index.
|
||
|
||
### 3.5 Contracts that overlap
|
||
|
||
Two contracts **overlap** when their periods share an instant and they name the same `channel`, or
|
||
a carrier in common. An asset aired under two contracts at once would have two owners, and a
|
||
statement under either would share revenue the other also shares; a verifier holding both refuses
|
||
a statement under either (§8.2 step 2, `contract_overlap`). An operator **MUST NOT** sign two
|
||
contracts that overlap; one that changes a contract's terms signs a new contract whose period
|
||
begins where the old one's ends.
|
||
|
||
## 4. The program-as-run
|
||
|
||
### 4.1 Its JSON carriage
|
||
|
||
A run is carried as one JSON object (RFC 8259) with exactly these members; a byte field is
|
||
lowercase hexadecimal of its exact length, and an integer a JSON integer from 0 to 2^64 − 1:
|
||
|
||
| member | value |
|
||
|---|---|
|
||
| `channel_id` | a string: the channel |
|
||
| `schedule_root` | 32 bytes: §4.2 |
|
||
| `schedule` | an array of entries, each `{"asset_id", "start_ms", "duration_ms", "kind"}`: `asset_id` a string, `kind` one of `program`, `ad` and `filler` |
|
||
| `window_start_ms`, `window_end_ms` | the window |
|
||
| `aired` | an array of integers: the indices of the entries on air in the window |
|
||
| `work_schedule_ops` | the entries' count, as the receipt states it |
|
||
| `joules_micro` | the assembly's energy, or 0 unmetered |
|
||
| `energy_provenance` | its class's label (energy-measurement §2.7: `ModelBased`, `Estimator`, `OnChipCounter`, `CalibratedInstrument`), or `null` unmetered |
|
||
| `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | only for a class that carries them, as live-ads §8.2 writes them |
|
||
| `grant` | `{"grant_hash", "capability", "joule_ceiling_micro", "funds_ceiling"}`, `funds_ceiling` `null` for none |
|
||
| `parent_receipt_hash` | 32 bytes: the run before it in the channel's chain, or `null` |
|
||
| `signer_pubkey` | 32 bytes: the channel signer's key |
|
||
| `signer_id` | a string |
|
||
| `sig` | 64 bytes: §4.2 |
|
||
|
||
A run **reads** when it is such an object and each entry's `start_ms` + `duration_ms` is at most
|
||
2^64 − 1. The legacy class label `HwShunt` (energy-measurement §2.6) does not read in this
|
||
carriage.
|
||
|
||
### 4.2 Its signed bytes and identity
|
||
|
||
These are the bytes `wai.video.channel` signs, written out; BLAKE3 throughout, integers
|
||
big-endian:
|
||
|
||
- an entry's **leaf**: BLAKE3 over `wai:video-channel-entry` (ASCII) and 0x01, `asset_id`'s length
|
||
(u32) and bytes, `start_ms` (u64), `duration_ms` (u64) and its kind's tag (u8: 1 `program`, 2
|
||
`ad`, 3 `filler`);
|
||
- the **schedule root**: the Merkle root over the leaves in schedule order, [jwp-receipts](jwp-receipts.md)
|
||
§2.2's tree, each leaf taken as a content hash (its first leaf form) and the final one duplicated
|
||
on an odd level; all zero for an empty schedule;
|
||
- the **signing payload**: `wai:video-channel-receipt` (ASCII) and 0x01; `channel_id`'s length
|
||
(u32) and bytes; `schedule_root`; `window_start_ms`, `window_end_ms`, `work_schedule_ops` and
|
||
`joules_micro` (u64 each); the class in its signed encoding (energy-measurement §2.7), the tag
|
||
byte 0 for none; the grant reference as live-ads §8.4 writes it (`grant_hash`, `capability`'s
|
||
length (u32) and bytes, `joule_ceiling_micro` (u64), then 1 and `funds_ceiling` (u64), or 0);
|
||
1 and `parent_receipt_hash`, or 0; the count of `aired` (u64) and each index (u64); the count of
|
||
`schedule` (u64) and each entry's leaf; and `signer_pubkey`, then `signer_id`'s length (u32) and
|
||
bytes;
|
||
- `sig`: Ed25519 by `signer_pubkey` over the signing payload, verified strictly;
|
||
- the run's **identity**, its receipt hash: BLAKE3 over `wai:video-channel-receipt-id` (ASCII) and
|
||
0x01, the signing payload and `sig`.
|
||
|
||
### 4.3 When a run verifies
|
||
|
||
A run verifies when:
|
||
|
||
1. `schedule_root` is the schedule root of its entries, and `work_schedule_ops` their count;
|
||
2. `window_start_ms` is below `window_end_ms`;
|
||
3. the schedule is continuous: each entry starts where the one before it ends;
|
||
4. `aired` is exactly the indices, ascending, of the entries that touch the window: those whose
|
||
`start_ms` is below `window_end_ms` and whose `start_ms` + `duration_ms` is above
|
||
`window_start_ms`;
|
||
5. the figure and its class cohere as live-ads §8.5 rule 5 states it; and
|
||
6. its signature verifies.
|
||
|
||
So the program-as-run is the schedule the channel signer signed, and what aired in a window is
|
||
recomputed from it rather than read from a log.
|
||
|
||
## 5. The evidence
|
||
|
||
### 5.1 Invoices
|
||
|
||
A statement rests on **invoices** of the receipt ledger, each given as its ledger document
|
||
(receipt-ledger §7.1): the tariff, the invoice, the invoice before it in its chain, and the
|
||
sources. A ledger document is **accepted** when the receipt ledger's verification (§7.2 there)
|
||
accepts it. Its tariff states the prices, its invoice the period and the records, and its sources
|
||
the impressions. A statement prices nothing itself: an impression's revenue is the price the
|
||
invoice billed it at.
|
||
|
||
Each invoice of a statement is one the operator sold as the seller of its tariff, in the
|
||
contract's currency; the buyer is whoever bought the channel's breaks under that tariff. A channel
|
||
whose breaks several buyers bought rests on several invoices, one chain per tariff.
|
||
|
||
### 5.2 Impressions and their classes
|
||
|
||
An accepted ledger document contributes, from its sources in order:
|
||
|
||
- for an `ads` source whose `room` is a carrier of kind 1: each receipt in the source's order, and
|
||
each of its impressions in slot order that the ledger counts (receipt-ledger §6.3 and §8.1):
|
||
- under a policy that requires presentation claims, an impression that counts, its own claim
|
||
`presented` (ad-presentation §9.2): class **presented**, its records the receipt (kind 6) and
|
||
its own claim (kind 7);
|
||
- under a policy that does not, a billable impression (quartile at least 1): class **beacons**,
|
||
its record the receipt (kind 6);
|
||
- for a `stitched` source: each record the source accepts whose `signer` is a carrier of kind 2,
|
||
each of its ads in slot order, and each presentation of the ad (stitched-presentation §7.4, a
|
||
presented own claim) in the order its claims are given: class **presented** when its instant is
|
||
stated (§5.4) and **unplaced** otherwise, its records the pod-input record (kind 8) and the
|
||
claim (kind 9).
|
||
|
||
Record kinds are receipt-ledger §5.2's. Every other source, and every impression of a carrier the
|
||
contract does not name, contributes nothing: an invoice may bill a buyer for several channels, and
|
||
a statement shares the impressions of its own.
|
||
|
||
An impression's **price** is its invoice's tariff's `ads` price (receipt-ledger §4.1): per
|
||
presented impression for the classes presented and unplaced, the unit `presented_impressions`
|
||
bills; per billable impression for the class beacons, the unit `billable_impressions` bills. So an
|
||
impression is priced at the unit the invoice billed it under, and the classes keep apart what the
|
||
players' own claims show presented and what rests on a decider's beacons alone. A class is never
|
||
summed into another: each has its own lines (§7.2).
|
||
|
||
### 5.3 Its instant
|
||
|
||
An impression's **instant** places it on the channel's timeline:
|
||
|
||
- an impression of an `ads` source: its room's `t0` plus the `t` of the break its receipt names
|
||
(live-ads §4.1, placed as receipt-ledger §5.3 places it). Every impression of a break shares the
|
||
break's instant: a break is attributed whole;
|
||
- a presentation of a stitched record: the instant its marker states (§5.4). A presentation whose
|
||
marker states none has no instant: it is **unplaced**.
|
||
|
||
### 5.4 The instant a marker states
|
||
|
||
A marker of carriage 1 or 2 (stitched-presentation §3.1) carries the `START-DATE` of its date
|
||
range, the second long field of its body. It **states** an instant when it is exactly this form, in
|
||
ASCII, of RFC 3339 §5.6's `date-time` with its letters in capitals:
|
||
|
||
```
|
||
YYYY "-" MM "-" DD "T" hh ":" mm ":" ss [ "." 1 to 9 digits ] ( "Z" / ( "+" / "-" ) hh ":" mm )
|
||
```
|
||
|
||
with `YYYY` a year of four digits, `MM` 01 to 12, `DD` 01 to the days of that month in the
|
||
Gregorian calendar, `hh` 00 to 23, `mm` 00 to 59 and `ss` 00 to 59 (an offset's `hh` and `mm` the
|
||
same), and the instant it names, in microseconds, at or after 1970-01-01T00:00:00Z. The instant is
|
||
the date and time less the offset, its fraction truncated to the microsecond. A marker of carriage
|
||
3 carries no date (§3.4 there): its presentations are unplaced, as are those of a `START-DATE` that
|
||
does not state an instant.
|
||
|
||
## 6. Attribution
|
||
|
||
### 6.1 The run of an instant
|
||
|
||
The runs a verifier is given are of the contract's channel and signer and do not overlap (§8.2
|
||
step 4). The **run** of an instant is the run whose window holds it; the **entry** of the instant
|
||
is the entry of that run's schedule whose span holds it. An impression with an instant and no run,
|
||
or a run and no entry (an instant past its schedule's end), aired in no window the verifier holds:
|
||
it is refused `outside_window`.
|
||
|
||
### 6.2 The asset and its split
|
||
|
||
The entry of an impression's instant gives its **asset**:
|
||
|
||
- a programme gives its own `asset_id`: a break inside a programme is that programme's;
|
||
- an entry that is not a programme gives, by the contract's `rule`: for **preceding**, the
|
||
`asset_id` of the last programme before it in the run's schedule; for **following**, that of the
|
||
first programme after it; when there is no such programme, or for **between**, no asset.
|
||
|
||
An impression with an asset is attributed to the asset's split in the contract's `assets`; an asset
|
||
the contract does not map is refused `asset_unmapped`. One with no asset is attributed to the
|
||
between split. The rule applies to the run's own signed schedule, so anyone holding the run finds
|
||
the same programme.
|
||
|
||
### 6.3 Unplaced impressions
|
||
|
||
An unplaced impression is attributed to the between split, in the class unplaced. It is revenue
|
||
the channel earned in some break that no record places in a programme, and the contract's between
|
||
split is how its parties agreed to share such revenue. It is never counted in the class presented:
|
||
a statement states it apart, and an owner sees how much of its revenue no record placed.
|
||
|
||
## 7. The statement
|
||
|
||
### 7.1 Layout
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `magic` | 4 | `WRS1` (ASCII) |
|
||
| `version` | 1 | 1 in this revision |
|
||
| `contract` | 32 | the contract's digest (§3.3) |
|
||
| `party` | 32 | the party it is for: the operator or an owner |
|
||
| `start`, `end` | 8 each | u64: the period it states |
|
||
| `currency` | 3 | the contract's |
|
||
| `invoices` | 2 + 32 × *i* | a u16 count, then each invoice's digest (receipt-ledger §6.7), in strictly ascending order |
|
||
| `runs` | 2 + 32 × *r* | a u16 count, then each run's identity (§4.2), in strictly ascending order |
|
||
| `lines` | 2 + 67 × *l* | a u16 count, then each line (§7.2), in strictly ascending order of (`split`, `class`) |
|
||
| `total` | 8 | u64: the sum of the lines' amounts |
|
||
| `sig` | 64 | §7.5 |
|
||
|
||
### 7.2 Lines
|
||
|
||
A line is 67 bytes:
|
||
|
||
| field | size | meaning |
|
||
|---|---|---|
|
||
| `split` | 2 | u16: the split's index |
|
||
| `class` | 1 | 1 presented, 2 beacons, 3 unplaced (§5.2) |
|
||
| `impressions` | 8 | u64: the impressions of the class attributed to the split |
|
||
| `revenue` | 8 | u64: the sum of their prices |
|
||
| `ppm` | 4 | u32: the party's share of the split |
|
||
| `amount` | 8 | u64: the party's part of the revenue (§7.3) |
|
||
| `cited` | 4 | u32: the records the line cites (§7.4) |
|
||
| `cites` | 32 | their citation digest (§7.4) |
|
||
|
||
There is one line for each split of which the party holds a share and each class with at least one
|
||
impression attributed to that split, and none other. A party's statement so states the whole
|
||
revenue of every split it shares in, and its part of each.
|
||
|
||
### 7.3 Revenue, shares and rounding
|
||
|
||
A line's revenue is divided among the parties of its split by the **largest-remainder rule**, so
|
||
that the parts are integers and sum to the revenue exactly:
|
||
|
||
1. For each party *i* of the split, in its order (§3.2), its **quota** is `revenue` × `ppm`*ᵢ*,
|
||
computed in integers wide enough not to overflow; its **base** is ⌊quota / 10^6⌋, and its
|
||
**remainder** is quota − base × 10^6.
|
||
2. The **leftover** is `revenue` less the sum of the bases. It is less than the count of parties.
|
||
3. The parties with the largest remainders, as many as the leftover, each receive one more than
|
||
their base; between equal remainders, the earlier party in the order receives it first.
|
||
4. A party's **part** is its base, plus one when step 3 gives it one.
|
||
|
||
The parts of a split's parties sum to its revenue, whoever's statement states them: the operator's
|
||
statement and each owner's state the same revenue for a split and class, and their parts sum to
|
||
it. Rounding is applied once per split and class over the statement's period, never per
|
||
impression, so it does not accumulate.
|
||
|
||
### 7.4 Citations
|
||
|
||
Each line cites the records it came from: for each impression attributed to its split in its class,
|
||
the impression's records (§5.2), the run that placed it (kind 10, its identity) when it has an
|
||
instant, and the invoice that billed it (kind 11, its digest). `cited` is their number, each record
|
||
counted once, and `cites` is SHA-256 over the bytes `wai:share-cites` (ASCII) and 0x01, followed by
|
||
each cited record's `kind` (u8) and identity (32), in ascending order of (`kind`, identity). A line
|
||
that cites no record has no source (`line_unsourced`, §8.2 step 7). An owner shows one line to an
|
||
auditor with the records it cites, the contract and the runs. Kinds 10 and 11, like every kind a
|
||
citation digest names, are registered in [receipt-ledger](receipt-ledger.md) §5.2.
|
||
|
||
### 7.5 Digest and signature
|
||
|
||
The **digest** of a statement is SHA-256 over its bytes before `sig`. Its signature is Ed25519 by
|
||
the contract's `operator` over the bytes `wai:share-statement` (ASCII) and 0x01, followed by the
|
||
bytes the digest covers, verified strictly (SPEC §7 *Signatures*).
|
||
|
||
### 7.6 Reading and rules
|
||
|
||
A reader reads exactly the bytes it is given as one statement, as §3.4 reads a contract with `WRS1`
|
||
for `WRC1` (`truncated`, `malformed`, `unknown_version`), and refuses as
|
||
`share_statement_invalid` one that breaks any of these:
|
||
|
||
- `start` is below `end`; `currency` is three capitals `A`–`Z`; `party` is not zero;
|
||
- `invoices`, `runs` and `lines` are each in the strictly ascending order §7.1 gives it;
|
||
- each line's `class` is 1, 2 or 3, its `impressions` and `cited` above 0, its `ppm` 1 to 10^6, and
|
||
its `amount` at most its `revenue`.
|
||
|
||
Its signature is checked against the contract's operator (§8.2 step 3).
|
||
|
||
## 8. Verification
|
||
|
||
### 8.1 Inputs
|
||
|
||
A verifier is given the **contract**; the **other contracts** it holds of the operator or of the
|
||
channel, for the overlap check; the **statement**; the **runs**; and the **ledger documents**. Its
|
||
verification document holds them as one JSON object with exactly the members `contract` and
|
||
`statement` (hexadecimal), `contracts` (a list of hexadecimal strings), `runs` (a list of runs in
|
||
their carriage, §4.1) and `ledgers` (a list of ledger documents, receipt-ledger §7.1). A document
|
||
that is not such an object is refused `malformed`.
|
||
|
||
### 8.2 Steps
|
||
|
||
It runs these steps and stops at the first refusal, which it reports with the reason and where:
|
||
|
||
1. **The contract** (§3.4), at `contract`; a signature's refusal at `contract` and `operator`, or
|
||
`contract`, `owner` and the owner's index: `truncated`, `malformed`, `unknown_version`,
|
||
`contract_invalid`, `split_not_whole`, `bad_key`, `bad_signature`, `not_countersigned`.
|
||
2. **The other contracts**, each at `contracts` and its index, a signature's place after it: each
|
||
is read and verified as step 1, with those reasons; one whose digest is the contract's is the
|
||
contract and is passed over; one that overlaps the contract (§3.5) is `contract_overlap`.
|
||
3. **The statement**, at `statement`: it reads (§7.6: `truncated`, `malformed`, `unknown_version`,
|
||
`share_statement_invalid`); its `contract` is the contract's digest, its `party` the operator
|
||
or an owner of the contract, and its `currency` the contract's: otherwise `contract_mismatch`;
|
||
its period lies within the contract's: otherwise `outside_contract`; its signature verifies
|
||
under the operator (§7.5): `bad_key`, `bad_signature`.
|
||
4. **The runs**, each at `run` and its index: it reads (§4.1: `malformed`); it verifies (§4.3:
|
||
`run_invalid`); its `channel_id` is the contract's `channel` and its `signer_pubkey` the
|
||
`channel_signer`: otherwise `run_foreign`; its window shares an instant with the window of a
|
||
run before it: `run_overlap`.
|
||
5. **The ledger documents**, each at `ledger` and its index: it is verified as receipt-ledger §7.2
|
||
verifies one, a refusal being that extension's reason with its place after the index; its
|
||
invoice's `seller` is the operator and its `currency` the contract's: otherwise
|
||
`invoice_foreign`; its period lies within the statement's: otherwise `outside_period`; and no
|
||
record of kinds 6 to 9 in its invoice's `records` is in the `records` of an invoice before it,
|
||
and no ad receipt of its sources names the room, the cue and the session an ad receipt of a
|
||
document before it names: otherwise `impression_duplicate`. A ledger refuses a record billed
|
||
twice within one invoice and one chain (receipt-ledger §5.2, §6.9); this step refuses one billed
|
||
by two invoices of a statement, under two tariffs among them.
|
||
6. **The impressions** (§5.2), in the order the documents give them, each at `ledger`, its index,
|
||
`source` and the source's index: an impression with an instant (§5.3) whose instant does not lie
|
||
within the statement's period is `outside_period`; one that aired in no window (§6.1) is
|
||
`outside_window`; one whose asset the contract does not map (§6.2) is `asset_unmapped`.
|
||
7. **The figures**, at `statement`, recomputed from the impressions as §7.2 to §7.4 compute them for
|
||
the statement's party, and compared in this order:
|
||
1. a line whose split and class give the party no impression, or that states more impressions
|
||
than they give: `line_unsourced`, a line no signed record covers;
|
||
2. a line whose `cited` or `cites` is not the records it came from: `citation_mismatch`;
|
||
3. any other difference (the invoices, the runs, a line missing, fewer impressions, a revenue, a
|
||
`ppm`, an amount, the `total`): `share_statement_mismatch`.
|
||
|
||
A figure that cannot be held in its field is a difference.
|
||
|
||
### 8.3 Outcome
|
||
|
||
- **refuse**: the reason and where.
|
||
- **accept**: the statement's digest; the contract's digest; the party and whether it is the
|
||
operator or an owner; the currency and the period; the invoices and the runs; each impression of
|
||
the contract's carriers, in the order of step 6, with the ledger document and source it came
|
||
from, its records, its class and price, its instant, run, entry and asset (each `null` when it has
|
||
none) and its split; each line with its split, class name, impressions, revenue, the party's
|
||
`ppm` and amount, its cited records, and the **division**, every party of its split with its
|
||
`ppm` and part; and the total.
|
||
|
||
The division is not signed: anyone holding the records computes it, and it shows that the parts of
|
||
a split sum to its revenue.
|
||
|
||
## 9. The statement and the receipt ledger
|
||
|
||
A statement composes with the ledger and duplicates none of it:
|
||
|
||
- **Its sources are the ledger's.** The impressions a statement shares are those of the `ads` and
|
||
`stitched` sources of invoices the ledger accepted, verified as the ledger verifies them: by
|
||
live-ads with ad-presentation, and by stitched-presentation. A statement recomputes no decision,
|
||
no claim and no presentation; it reads the ledger's counts and verdicts.
|
||
- **Its prices are the tariff's.** An impression's revenue is its invoice's price for the unit that
|
||
billed it, so an owner's revenue is a part of what the buyer was invoiced, never a figure the
|
||
operator states.
|
||
- **Its records are the invoice's.** Each impression is named by the records the invoice holds,
|
||
and the statement cites the invoice itself. A statement adds the two things the ledger does not
|
||
hold: the instant of each impression on the channel's program-as-run, and the split the contract
|
||
gives the programme that aired at that instant.
|
||
- **An operator that bills a channel's breaks** puts the sources of the channel's carriers into the
|
||
ledger as the ledger receives any source (receipt-ledger §5.1, §9), under a tariff whose scope
|
||
names the carriers' deciders and stitchers (receipt-ledger §4.4). The verification document the
|
||
ledger keeps with each invoice (receipt-ledger §9: `GET /v1/invoices/{digest}`) is the ledger
|
||
document a statement is given, unchanged.
|
||
|
||
Paying an owner its part, and an owner invoicing the operator for it, are not sources the ledger
|
||
defines, and this revision defines none.
|
||
|
||
## 10. Operations
|
||
|
||
`wai` runs the operations of this extension, under the agent rules of the operation catalogue (the
|
||
`--json` envelope, the exit codes, `--dry-run` on every operation that writes, `--yes` before a file
|
||
is replaced). A refusal by this text has its reason as the operation's code.
|
||
|
||
| operation | command line | does |
|
||
|---|---|---|
|
||
| `contract.create` | `wai contract create <terms.json> --key <file> [--out F]` | the operator's contract from its terms, signed by the operator with every countersignature zero (§3.3) |
|
||
| `contract.update` | `wai contract accept <contract> --key <file> [--out F]` | an owner's countersignature added, after the contract's rules and the operator's signature are checked |
|
||
| `statement.create` | `wai statement create <inputs.json> --party <key> --start µs --end µs --key <file> [--out F]` | the statement of a party and a period, computed from a verification document without its statement (§8.1), signed by the operator, written with its document |
|
||
| `statement.verify` | `wai statement verify <document.json>` | a verification document's outcome (§8.3); exit 1 with the reason on a refusal |
|
||
|
||
## 11. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `contract_invalid` | §3.4, §8.2 steps 1 and 2 |
|
||
| `split_not_whole` | §3.2, §3.4, §8.2 steps 1 and 2 |
|
||
| `not_countersigned` | §3.3, §8.2 steps 1 and 2 |
|
||
| `contract_overlap` | §3.5, §8.2 step 2 |
|
||
| `share_statement_invalid` | §7.6, §8.2 step 3 |
|
||
| `contract_mismatch` | §8.2 step 3 |
|
||
| `outside_contract` | §8.2 step 3 |
|
||
| `run_invalid` | §4.3, §8.2 step 4 |
|
||
| `run_foreign` | §8.2 step 4 |
|
||
| `run_overlap` | §6.1, §8.2 step 4 |
|
||
| `invoice_foreign` | §5.1, §8.2 step 5 |
|
||
| `impression_duplicate` | §8.2 step 5 |
|
||
| `outside_window` | §6.1, §8.2 step 6 |
|
||
| `asset_unmapped` | §6.2, §8.2 step 6 |
|
||
| `share_statement_mismatch` | §8.2 step 7 |
|
||
|
||
A verifier of this extension also raises, with the meaning this text gives them: `truncated`,
|
||
`malformed` and `unknown_version` for a contract or a statement that does not read, and `malformed`
|
||
for a document or a run that does not read; `bad_key` and `bad_signature` for their signatures;
|
||
`outside_period` (receipt-ledger §10) for an invoice or an impression outside the statement's
|
||
period; `line_unsourced` (fleet §10) for a line no impression gives; `citation_mismatch`
|
||
(receipt-ledger §10) for a line's citations; and, at a ledger document, every code the receipt
|
||
ledger raises (receipt-ledger §10 and the codes it lists).
|
||
|
||
## 12. Conformance
|
||
|
||
[`live-conformance/share/`](../live-conformance/share/) holds the corpus, in one file,
|
||
`vectors.json`, written by `wai_revenue_share_vectors gen` from fixed seeds and from the sources of
|
||
the ledger corpus (`live-conformance/ledger/`): a room's break-form receipts under a policy that
|
||
requires claims and under one that does not, and three streams stitched by others, one of whose
|
||
markers are DASH events. The ledger documents, the runs and the contracts are held once, by name,
|
||
and each vector is a verification document (§8.1) naming them, with the outcome §8.3 gives it:
|
||
|
||
- **Accepted**: a channel's month, its two windows airing two owners' programmes with mid-roll
|
||
breaks and an ad slot between programmes, its impressions from two invoices of one chain, one of
|
||
the room's breaks and one of the stitched streams; the statement of each owner and of the
|
||
operator under the rule preceding, so that the three statements' parts of each split sum to its
|
||
revenue; and an owner's statement under the rules following and between.
|
||
- **Refused**: each reason of §11 at least once, each vector a control of one defect; each code §11
|
||
reuses; and a ledger document refused by the receipt ledger, at its place.
|
||
|
||
`wai_revenue_share_vectors verify` and the test `tests/revenue_share.rs` check that the file
|
||
regenerates byte for byte, that `wai-rs` gives every vector its outcome, that every reason of §11
|
||
refuses a vector, and that §11 lists exactly the reference's codes; the test also holds the worked
|
||
example, three statements of one month computed and then recomputed from the public records alone,
|
||
and the module's unit tests the rounding rule's ties. `live-conformance/revenue_share_verify.py` is
|
||
the second implementation, written from this text: it shares no code with `wai-rs`, verifies
|
||
signatures with WAI's strict rule written from RFC 8032 (`claim-conformance/_ed25519.py`) and BLAKE3
|
||
with `claim-conformance/_blake3.py`, verifies each ledger document with that corpus's own second
|
||
implementation (`receipt_ledger_verify.py`), reads every contract, run, marker and statement itself,
|
||
recomputes every attribution, line, division and citation, gives every vector the same outcome,
|
||
requires every mutated copy of the corpus to fail, and drops each of its own checks in turn and
|
||
requires the corpus to catch the weakened verifier.
|
||
|
||
## 13. Security and privacy considerations
|
||
|
||
- **Who signs what.** Each owner signs the splits and the asset map it is bound by; the channel
|
||
signer signs what aired; the decider, the stitcher and each player sign the impressions; the
|
||
buyer signs the prices; the operator signs the statement. An operator cannot move an
|
||
impression to another programme, price it at another price or round a part against an owner: the
|
||
verifier recomputes every line from records the operator did not sign alone.
|
||
- **What the operator still controls.** The records show what was invoiced from the contract's
|
||
carriers. An operator that sells a break without invoicing it, or carries the channel through a
|
||
room or a stitcher the contract does not name, keeps that revenue out of every statement; the
|
||
contract's carriers are what the owners agreed to, and an owner that suspects more carriers asks
|
||
for them by name.
|
||
- **The between split.** Revenue in an entry that is not a programme, and every unplaced
|
||
presentation, goes to the between split. An owner that agrees a between split in which it holds
|
||
no share agrees to receive nothing from such breaks, and a statement states their revenue apart
|
||
so that the owner sees it.
|
||
- **Double counting.** One invoice and one chain bill a record once (the ledger's rules); two
|
||
invoices of one statement may not bill one impression (`impression_duplicate`); two contracts
|
||
may not share one channel or carrier at once (`contract_overlap`); and two runs may not place one
|
||
instant (`run_overlap`). A verifier holding fewer contracts than the operator signed cannot see
|
||
an overlap with the ones it lacks.
|
||
- **What a statement reveals.** A statement names the invoices and runs it rests on, and a line
|
||
states its split's whole revenue: the co-owners of a split, and the operator, see the same
|
||
figures. A verification document holds the ledgers' sources, whose receipts name sessions and
|
||
whose claims name session keys made for one session alone (ad-presentation §3,
|
||
stitched-presentation §5.2). It travels between the operator, the owner and an auditor they
|
||
choose.
|