WAI Extension: Revenue Share
Mirrored from the canonical text at commit 260cbd09 ().
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 it rests on, the program-as-run windows ofwai.video.channelit 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 therevenue_sharemodule ofwai-rs(featurerevenue_share); the corpus islive-conformance/share/, and its second implementation islive-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.ledgernames 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
§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 itsend(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.channelreceipt, 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 kindadorfilleris not a programme. - Carrier: what delivers the channel’s ads: a room of live-ads, named by its room id, whose break-form receipts the ledger bills; or a stitcher of stitched-presentation, 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.channelwrites 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 §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:
startis belowend;currencyis three capitalsA–Z;operatorandchannel_signerare not zero, andoperatoris not an owner;channeland everyassetare UTF-8 of 1 to 1024 bytes;ruleis 1, 2 or 3, andbetweenis the index of a split;carriershas at least one entry, eachkind1 or 2 and eachidnot zero, in strictly ascending order;ownershas at least one key, none zero, in strictly ascending order;splitshas at least one split; in each, the owners are in strictly ascending order of index, each index below the count ofowners, and eachppmabove 0; and every owner holds a share of at least one split;assetshas at least one entry, in strictly ascending order, eachsplitthe index of a split.
It refuses as split_not_whole a contract with a split whose operator_ppm and ppms 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: 1program, 2ad, 3filler); - the schedule root: the Merkle root over the leaves in schedule order, jwp-receipts §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_opsandjoules_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 andfunds_ceiling(u64), or 0); 1 andparent_receipt_hash, or 0; the count ofaired(u64) and each index (u64); the count ofschedule(u64) and each entry’s leaf; andsigner_pubkey, thensigner_id’s length (u32) and bytes; sig: Ed25519 bysigner_pubkeyover 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 andsig.
4.3 When a run verifies
A run verifies when:
schedule_rootis the schedule root of its entries, andwork_schedule_opstheir count;window_start_msis belowwindow_end_ms;- the schedule is continuous: each entry starts where the one before it ends;
airedis exactly the indices, ascending, of the entries that touch the window: those whosestart_msis belowwindow_end_msand whosestart_ms+duration_msis abovewindow_start_ms;- the figure and its class cohere as live-ads §8.5 rule 5 states it; and
- 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
adssource whoseroomis 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);
- under a policy that requires presentation claims, an impression that counts, its own claim
- for a
stitchedsource: each record the source accepts whosesigneris 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
adssource: its room’st0plus thetof 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, theasset_idof 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:
- 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. - The leftover is
revenueless the sum of the bases. It is less than the count of parties. - 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.
- 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 §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:
startis belowend;currencyis three capitalsA–Z;partyis not zero;invoices,runsandlinesare each in the strictly ascending order §7.1 gives it;- each line’s
classis 1, 2 or 3, itsimpressionsandcitedabove 0, itsppm1 to 10^6, and itsamountat most itsrevenue.
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:
- The contract (§3.4), at
contract; a signature’s refusal atcontractandoperator, orcontract,ownerand the owner’s index:truncated,malformed,unknown_version,contract_invalid,split_not_whole,bad_key,bad_signature,not_countersigned. - The other contracts, each at
contractsand 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) iscontract_overlap. - The statement, at
statement: it reads (§7.6:truncated,malformed,unknown_version,share_statement_invalid); itscontractis the contract’s digest, itspartythe operator or an owner of the contract, and itscurrencythe contract’s: otherwisecontract_mismatch; its period lies within the contract’s: otherwiseoutside_contract; its signature verifies under the operator (§7.5):bad_key,bad_signature. - The runs, each at
runand its index: it reads (§4.1:malformed); it verifies (§4.3:run_invalid); itschannel_idis the contract’schanneland itssigner_pubkeythechannel_signer: otherwiserun_foreign; its window shares an instant with the window of a run before it:run_overlap. - The ledger documents, each at
ledgerand 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’sselleris the operator and itscurrencythe contract’s: otherwiseinvoice_foreign; its period lies within the statement’s: otherwiseoutside_period; and no record of kinds 6 to 9 in its invoice’srecordsis in therecordsof 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: otherwiseimpression_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. - The impressions (§5.2), in the order the documents give them, each at
ledger, its index,sourceand the source’s index: an impression with an instant (§5.3) whose instant does not lie within the statement’s period isoutside_period; one that aired in no window (§6.1) isoutside_window; one whose asset the contract does not map (§6.2) isasset_unmapped. - 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:- 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; - a line whose
citedorcitesis not the records it came from:citation_mismatch; - any other difference (the invoices, the runs, a line missing, fewer impressions, a revenue, a
ppm, an amount, thetotal):share_statement_mismatch.
- a line whose split and class give the party no impression, or that states more impressions
than they give:
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
nullwhen it has none) and its split; each line with its split, class name, impressions, revenue, the party’sppmand amount, its cited records, and the division, every party of its split with itsppmand 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
adsandstitchedsources 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/ 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.