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]>
38 KiB
WAI Extension: Opportunity Report
Status: Draft. Registers the record
wai.delivery.opportunity: the ad opportunities a linear channel's own signed records prove, per content owner and per period, and the check of a platform's reported figures against them. An opportunity report,WOR1, states for one party of a revenue-share contract and one period the ad breaks that aired in the programmes of the splits it shares in: each break from the cues of a room the contract names (live-ads §4.3, the SCTE 35 sections they carry read for their slots) or from an ad entry of the channel's signed program-as-run (wai.video.channel), placed at its start on a window that aired it and attributed to the programme on air; and, per split and class, the breaks, their seconds and their slots, with the records each line came from. A reported statement is what a platform says it counted: a documented JSON or CSV form of impressions and revenue for the period, per day or per programme, which this text reads and never treats as evidence. A statement check,WCK1, sets the report beside the reported figures: per line, the opportunities, the reported impressions, the implied fill and the revenue per slot, the lines where a reported figure has no break behind it or lies far from the period's own rate, and, where the operator's signed revenue-share statement of the same party and period is given, the signed impressions and revenue beside the reported ones. Integer arithmetic throughout. No new transport, no payment, and no reading of any platform's own report format. The reference implementation is theopportunity_reportmodule ofwai-rs(featureopportunity_report); the corpus islive-conformance/opportunity/, and its second implementation islive-conformance/opportunity_report_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision: the WAI texts this one cites; ANSI/SCTE 35-1 2023r2 through the fields cue-injection §5 writes (its §9.6 to §9.9 and §10.2 and §10.3, as that text read them); ISO/IEC 13818-1 Annex A (the CRC); RFC 4180 (the CSV form of §9.2); RFC 8259; RFC 8032; FIPS 180-4.
Why a text of its own, beside revenue-share: a report rests on records the statement does not read (a room's cues and the SCTE 35 sections they carry), needs no invoice, and is useful to an owner whose platform signs no statement; and revenue-share's records keep their bytes. Why the name: the record states the opportunities a channel's records prove, and names both of its parts, the report and the check, under one string.
1. Scope
In scope: the evidence a report rests on, the contract, the runs and the rooms (§3); breaks, from a room's cues and from the program-as-run, their start, duration and slots (§4); the subset of an SCTE 35 section this text reads, and its agreement with the cue that carries it (§5); attribution, from a break's start to a split (§6); the report, its lines and citations (§7) and its verification (§8); the reported statement, in JSON and in CSV (§9); the check, its lines, figures and flags, and the signed statement beside it (§10), and its verification (§11); and the operations (§12).
Out of scope: a transport; a parser of any platform's own report format, which turns that format into §9's; paying an owner, which revenue-share leaves out too; an audience count, which no record of this text states (§10.3); and the records this text reads, which their own extensions define, verify and leave as they are. No existing record's signed bytes change.
Relationship to the core spec: wai.delivery.opportunity 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
- Contract, operator, owner, party, split, run, window, entry,
programme, carrier, ppm, time, period: as revenue-share §2 and §3 define
them. Time is microseconds since 1970-01-01T00:00:00Z; a period is
[start, end). - Room, cue, break, open, closed, avail: as live-ads §2 and §4.3.
- Issuer: the Ed25519 key (RFC 8032) that signs a report or a check: the owner itself, the operator, or an auditor either chooses. A report's figures are recomputed from the records, so an issuer's signature binds the issuer to what it states, and adds nothing a verifier trusts.
- Section: an SCTE 35-1
splice_info_section()a cue of form 1 carries (live-events §4.3). - Slots: the ads one break holds (§4.4).
- Day: a UTC calendar date, written
YYYY-MM-DD; its span is from its 00:00:00Z (included) to the next date's (excluded). - u64, u32, u16, u8: little-endian unsigned integers in this text's records; the bit fields of a section are big-endian, as SCTE 35 writes them. A digest is SHA-256 (FIPS 180-4); zero is 32 zero bytes. A text field is a u16 length and that many bytes of UTF-8.
3. The evidence
3.1 The contract
The contract is a revenue-share contract (revenue-share §3), in force, and verified as revenue-share §8.2 steps 1 and 2 verify one, with the other contracts given for the overlap check. It names the channel, the key that signs its runs, its carriers, its owners, its splits and the map from each asset to a split, and the rule for an entry that is not a programme.
3.2 The runs
Each run is a wai.video.channel receipt in its JSON carriage (revenue-share §4.1), verified as
revenue-share §8.2 step 4 verifies one: it reads, it verifies, it is of the contract's channel and
signer, and its window shares no instant with the window of a run before it.
3.3 The rooms
Each room is given as a live-ads verifier is given one (live-ads §9.1), without receipts: its
descriptor, grants, key_log (the key-log head, or null), chains and policy. It is
verified as live-ads §9.2 steps 1 to 3 verify one: the room (live-rooms §9), the policy, and the
schedule its accepted cues make. A room with a gap has no schedule: a schedule read from part of
the cues is not the room's, and the room is refused delivery_gap. The room's id is a carrier of
kind 1 of the contract, or the room is refused room_foreign; a room whose id is that of a room
given before it is refused room_repeated.
So the cues are the room's own: each signed by a principal the room's grants permit cue, in its
chain, and held to live-ads' schedule rules. A cue an injector signed (cue-injection)
is such a cue; its injection record rides in the same chain, and this text does not translate it
again (cue-injection §8 does).
4. Breaks
4.1 Cued breaks
Each break of a room's schedule (live-ads §4.3) is a cued break:
- its start is the room descriptor's
t0plus thetof the cue that opens it, in microseconds since 1970; a start that does not fit in a u64 lies outside every period; - its duration, in milliseconds, is a closed break's duration (live-ads §4.3: an out's, from its in, or a break's of type 4), and an open break's avail;
- its opening cue is the cue whose digest names it (live-ads §4.4), and its closing cue the in that closed it, when one did;
- its section is the opening cue's, when the cue is of form 1 (§5).
4.2 Scheduled breaks
The runs are taken in ascending order of window_start_ms, and in each the entries of its aired
list in ascending order. An entry of kind ad is a scheduled break, once: an entry whose leaf
(revenue-share §4.2) is the leaf of an entry taken in an earlier run is the same break and is
passed over. Its start is the entry's start_ms × 1000, its duration the entry's duration_ms,
and its run and entry those it was taken in. An entry that airs in two windows is one break.
A scheduled break is cued when the start of a cued break of any room given lies within its span, from its start (included) to its start plus its duration × 1000 (excluded). A cued scheduled break is counted by its cued break, and is not a break of its own.
4.3 Classes, and the period
A break is in a report when its start lies within the report's period; a break outside it is another period's, and is passed over. Every break of a report is of one class:
| class | name | the break |
|---|---|---|
| 1 | cued | a cued break (§4.1) |
| 2 | scheduled | a scheduled break no cued break counts (§4.2) |
A class is never summed into another: each has its own lines (§7.2).
4.4 Slots
A break's slots, and where they come from:
- a cued break whose section states slots (§5.2): those;
- another cued break: its room policy's
slots(live-ads §3.1), the most ads one break holds; - a scheduled break: none. Its record states no slots, and it is unslotted.
A section that states slots is the content's own signal of the break's placements, and is taken over the policy, which states the most a decision fills.
5. The section a cue carries
5.1 Reading
A section is read as SCTE 35-1 Table 5 lays it out, every field in its order and its width, and
refused cue_section_invalid at the first of these that holds:
- it is shorter than 20 bytes, or its
CRC_32, the last four bytes, is not the MPEG-2 CRC (ISO/IEC 13818-1 Annex A: polynomial 0x04C11DB7, register preset to all ones, no reflection, no final inversion) over every byte before them; - its
protocol_versionis not 0; - its
encrypted_packetis 1: the section is read no further, and states nothing (§5.2); - its command, the
splice_command_lengthbytes from byte 14 (aftersplice_command_type, byte 13), runs past the CRC; asplice_command_lengthof 0xFFF states no length, and the command's length is then what step 5 reads of it, or, for a command step 5 does not read, the section states nothing past its CRC check; - the command does not read as its table lays it out, filling exactly its length:
splice_insert()(type 0x05):splice_event_id,splice_event_cancel_indicatorand, when it is 0, the flagsout_of_network_indicator,program_splice_flag,duration_flagandsplice_immediate_flag, thensplice_time()when the program flag is 1 and the immediate flag 0, orcomponent_countand each component with itssplice_time()when the immediate flag is 0, when the program flag is 0; thenbreak_duration()(auto_return,duration) when the duration flag is 1; thenunique_program_id,avail_numandavails_expected;time_signal()(type 0x06): onesplice_time();splice_null()(type 0x00): nothing; asplice_time()is one byte when itstime_specified_flagis 0, and five, withpts_time, when it is 1. Another command is not read; descriptor_loop_lengthruns past the CRC, or the loop's last descriptor does not end where the CRC begins; a descriptor (a tag, a length, that many bytes) runs past the loop;- a descriptor of tag 0x02 whose first four bytes are
CUEIdoes not read as asegmentation_descriptor()(SCTE 35-1 Table 20) fills its length:segmentation_event_id,segmentation_event_cancel_indicator, and, when that is 0,program_segmentation_flag,segmentation_duration_flag,delivery_not_restricted_flagand the restriction bits, the components when the program flag is 0 (six bytes each),segmentation_duration(40 bits) when flagged,segmentation_upid_type,segmentation_upid_lengthand the UPID,segmentation_type_id,segment_numandsegments_expected, then, for the types 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x44 and 0x46, the two bytessub_segment_numandsub_segments_expectedwhen two bytes remain. Any other count of bytes remaining refuses it.
A section is read only as far as this. Another descriptor is passed over whole.
5.2 What a section states
- Its id: a
splice_insert()'ssplice_event_id; atime_signal()'s opening descriptor'ssegmentation_event_id. The opening descriptor is the first segmentation descriptor of the section not cancelled whosesegmentation_type_idmakes an out (cue-injection §6.2): 0x22, 0x30, 0x32, 0x34 or 0x36. - Its duration, in 90 kHz ticks: a
splice_insert()'sbreak_duration()duration, when its duration flag is 1; atime_signal()'s opening descriptor'ssegmentation_duration, when flagged. - Its slots: the
sub_segments_expectedof its first segmentation descriptor not cancelled whosesegmentation_type_idis a placement opportunity start, 0x34 or 0x36, when that descriptor carries the sub-segment fields andsub_segments_expectedis above 0.
A section states nothing more. An encrypted section, a command this text does not read, and a
time_signal() with no opening descriptor state no id and no duration; a form 2 message and a cue
of form 0 have no section.
5.3 The section and the cue's summary
A producer MUST NOT write a summary its message contradicts (live-events §4.3). An opening
cue's section is refused cue_section_mismatch when:
- it is a
splice_insert()whose cancel indicator is 1, or whoseout_of_network_indicatoris 0: it states a cancellation or a return, and the cue opens a break; - it states an id that is not the cue's
cue_id; - it states a duration d ticks and ⌊d / 90⌋ is not the cue's
duration_ms.
6. Attribution
A break is attributed as revenue-share §6 attributes an impression, by its start:
- a cued break: its run is the run whose window holds its start, and its entry the entry of
that run's schedule whose span holds it. One with no run, or a run and no entry, aired in no
window the verifier holds, and is refused
outside_window; - a scheduled break: its run and entry are those of §4.2.
The entry gives the asset: a programme's own asset_id, or, for an entry that is not a
programme, the asset the contract's rule gives (revenue-share §6.2), or none. A break with an asset
is attributed to the asset's split; an asset the contract does not map is refused
asset_unmapped. A break with no asset is attributed to the contract's between split.
7. The opportunity report
7.1 Layout
| field | size | meaning |
|---|---|---|
magic |
4 | WOR1 (ASCII) |
version |
1 | 1 in this revision |
issuer |
32 | the key that signs the report |
contract |
32 | the contract's digest (revenue-share §3.3) |
party |
32 | the party it is for: the operator or an owner |
start, end |
8 each | u64: the period it states |
rooms |
2 + 32 × m | a u16 count, then each room's id, in strictly ascending order |
runs |
2 + 32 × r | a u16 count, then each run's identity (revenue-share §4.2), in strictly ascending order |
lines |
2 + 75 × l | a u16 count, then each line (§7.2), in strictly ascending order of (split, class) |
breaks |
8 | u64: the sum of the lines' breaks |
break_ms |
8 | u64: the sum of the lines' break_ms |
slots |
8 | u64: the sum of the lines' slots |
sig |
64 | §7.4 |
7.2 Lines
A line is 75 bytes:
| field | size | meaning |
|---|---|---|
split |
2 | u16: the split's index |
class |
1 | 1 cued, 2 scheduled (§4.3) |
breaks |
8 | u64: the breaks of the class attributed to the split |
break_ms |
8 | u64: the sum of their durations, in milliseconds |
slots |
8 | u64: the sum of their slots |
unslotted |
8 | u64: those of them with no slots (§4.4) |
ppm |
4 | u32: the party's share of the split |
cited |
4 | u32: the records the line cites (§7.3) |
cites |
32 | their citation digest (§7.3) |
There is one line for each split of which the party holds a share (revenue-share §3.2) and each class with at least one break attributed to that split, and none other. A report so states every opportunity of every split the party shares in, and the share by which it shares it.
7.3 Citations
Each line cites the records its breaks came from: for a cued break, its opening cue and its
closing cue (kind 12, each by its event digest, live-events §3.2), its room's policy when its slots
are the policy's (kind 13, the policy's digest, live-ads §3.2), and its run (kind 10, its identity);
for a scheduled break, its run (kind 10). cited is their number, each record counted once, and
cites is SHA-256 over the bytes wai:opportunity-cites (ASCII) and 0x01, followed by each cited
record's kind (u8) and identity (32), in ascending order of (kind, identity). Kind 10 is
revenue-share §7.4's; 12 and 13 are this text's. Every kind a citation digest names, these
among them, is registered in receipt-ledger §5.2.
7.4 Digest and signature
The digest of a report is SHA-256 over its bytes before sig. Its signature is Ed25519 by
issuer over the bytes wai:opportunity-report (ASCII) and 0x01, followed by the bytes the digest
covers, verified strictly (SPEC §7 Signatures): a key that fails step 1 or 2 is
bad_key, and a signature that fails a later step is bad_signature.
7.5 Reading and rules
A reader reads exactly the bytes it is given as one report, in the order of §7.1, and refuses at
the first of these that holds: fewer than 4 bytes, truncated; a magic other than WOR1,
malformed; no fifth byte, truncated; a version other than 1, unknown_version; the bytes end
before a field the layout requires, truncated; bytes after sig, malformed. It then refuses
as opportunity_report_invalid a report that breaks any of these:
startis belowend;issuerandpartyare not zero;rooms,runsandlinesare each in the strictly ascending order §7.1 gives it;- each line's
classis 1 or 2, itsbreaksandcitedabove 0, itsunslottedat most itsbreaks, and itsppm1 to 10^6.
8. Verifying a report
8.1 Inputs
A verifier is given the contract, the other contracts it holds of the operator or the
channel, the report, the runs and the rooms. Its verification document holds them as
one JSON object (RFC 8259) with exactly the members contract and report (hexadecimal),
contracts (a list of hexadecimal strings), runs (a list of runs in their carriage,
revenue-share §4.1) and rooms (a list of rooms, each an object with exactly the members
descriptor and policy (hexadecimal), grants (a list of hexadecimal strings), key_log (a
string or null) and chains (a list of lists of hexadecimal strings, each a principal's events
in order)). 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, at
contract, as revenue-share §8.2 step 1. - The other contracts, each at
contractsand its index, as revenue-share §8.2 step 2 (contract_overlapamong its reasons). - The report, at
report: it reads (§7.5:truncated,malformed,unknown_version,opportunity_report_invalid); itscontractis the contract's digest and itspartythe operator or an owner of the contract: otherwisecontract_mismatch; its period lies within the contract's: otherwiseoutside_contract; its signature verifies under its issuer (§7.4):bad_key,bad_signature. - The runs, each at
runand its index, as revenue-share §8.2 step 4 (malformed,run_invalid,run_foreign,run_overlap). - The rooms, each at
roomand its index: it reads (malformed); it verifies (§3.3), a refusal being live-ads' reason with live-ads' place after the index, and a gapdelivery_gap; its id is a carrier (room_foreign) and not that of a room before it (room_repeated). - The cued breaks in the period (§4.3), the rooms in order and each room's breaks in its
schedule's order, each at
room, the room's index,breakand the break's index in its room's schedule: its section, when it has one, reads (§5.1:cue_section_invalid) and agrees with its opening cue (§5.3:cue_section_mismatch); its span shares an instant with the span of a cued break in the period of a room before it:break_overlap(two rooms that carry the channel at once would count one break twice); it is placed and attributed (§6:outside_window,asset_unmapped). - The scheduled breaks in the period, in the order of §4.2, each at
run, the run's index in the document,entryand the entry's index: attributed (§6:asset_unmapped). - The figures, at
report, recomputed as §7.2 and §7.3 compute them for the report's party, and compared in this order:- a line whose split and class give the party no break, or that states more breaks than they
give:
line_unsourced; - a line whose
citedorcitesis not the records it came from:citation_mismatch; - any other difference (the rooms, the runs, a line missing, a figure of a line, a total):
opportunity_report_mismatch.
- a line whose split and class give the party no break, or that states more breaks 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 report's digest; the contract's digest; the issuer; the party and whether it is
the operator or an owner; the period; the rooms and the runs; each break in the period, in the
order of steps 6 and 7, with its class, its room and its index there (a cued break) or its run
and entry (a scheduled break), its opening cue's digest or
null, its start, its duration, whether it is open, its slots and where they came from (section,policyornull), what its section states (nullwithout one), its run, entry and asset, its split, and the records it cites; each line, with its cited records; and the totals.
A break of a split the party holds no share of is stated with the others, and counted in no line of the party's: the outcome shows each party the whole channel's breaks in the period, and its report states those it shares in.
9. The reported statement
A reported statement is what a platform reports it counted for a channel and a period. It is an input, never a signed WAI record and never evidence: a check (§10) names it by the digest of its bytes, and states it beside what the records prove. A platform's own report is turned into one of these two forms outside this text.
9.1 JSON
One JSON object (RFC 8259) with exactly these members, and optionally days and programmes:
| member | value |
|---|---|
channel |
a string: the channel's id, 1 to 1024 bytes of UTF-8 |
start, end |
integers: the period, in microseconds since 1970 |
currency |
a string of three capitals A–Z (ISO 4217) |
basis |
gross, for revenue before the contract's split, or share, for the party's part |
impressions |
an integer: the impressions reported for the period |
revenue |
an integer: the revenue reported, an amount (receipt-ledger §2: millionths of a minor unit) |
days |
a list of objects, each with exactly day (a day, §2), impressions and revenue |
programmes |
a list of objects, each with exactly asset (an asset_id, 1 to 1024 bytes of UTF-8), impressions and revenue |
An integer is a JSON integer from 0 to 2^64 − 1. An empty list is the same as its member left out.
9.2 CSV
A text of RFC 4180 records, each ended by CRLF or LF (the last line break optional), each of
exactly four fields. A field is unquoted, holding no comma, double quote, CR or LF, or quoted: a
double quote, any bytes with each double quote doubled, and a double quote. The first record is
scope,key,impressions,revenue. Then, in this order:
channel,start,end,currencyandbasis, each once, its value inkeyand the last two fields empty;period, once,keyempty, and the period'simpressionsandrevenue;- each
day, its day inkey, then eachprogramme, its asset inkey, each with itsimpressionsandrevenue.
An integer is 0, or a digit 1 to 9 followed by digits, at most 2^64 − 1. The text read is the
JSON form with the same values.
9.3 Rules
A reported statement is refused reported_invalid when its text is not one of these forms, or:
- its
startis not below itsend; - a day is not a date of the Gregorian calendar from 1970 to 9999, or its span does not lie within the period, or the days are not in strictly ascending order;
- the programmes are not in strictly ascending order of
asset's bytes.
It is refused reported_foreign when its channel is not the contract's, its currency not the
contract's, or its period not the report's.
10. The statement check
10.1 Layout
| field | size | meaning |
|---|---|---|
magic |
4 | WCK1 (ASCII) |
version |
1 | 1 in this revision |
issuer |
32 | the key that signs the check |
report |
32 | the report's digest (§7.4) |
reported |
32 | SHA-256 over the reported statement's bytes, as given |
form |
1 | 1 JSON (§9.1), 2 CSV (§9.2) |
statement |
32 | the digest of the signed revenue-share statement set beside it (revenue-share §7.5), or zero |
floor_ppm, ceiling_ppm |
4 each | u32: the bounds of §10.3, in millionths of the period's rate |
lines |
2 + … | a u16 count, then each line (§10.2), in strictly ascending order of (scope, key's bytes) |
sig |
64 | §10.5 |
10.2 Lines
| field | size | meaning |
|---|---|---|
scope |
1 | 1 the period, 2 a day, 3 a programme |
key |
text | empty for the period; the day (YYYY-MM-DD); the programme's asset_id |
breaks, break_ms, slots, unslotted |
8 each | u64: the opportunities of the scope (§10.3) |
impressions, revenue |
8 each | u64: the reported figures of the scope |
fill_ppm |
8 | u64: the implied fill (§10.3) |
revenue_per_slot |
8 | u64: an amount (§10.3) |
signed |
1 | 1 when signed figures follow, 0 otherwise |
signed_impressions, signed_revenue |
8 each | only when signed is 1: §10.4 |
flags |
1 | §10.3 and §10.4 |
There is one line of the period, and one line for each day and each programme the reported statement lists, and none other.
10.3 Figures and flags
The opportunities of a line are the report's breaks in the period that are counted in a line
of the report's (§7.2), restricted to its scope: every one for the period; those whose start lies
within the day's span for a day; those whose asset (§6) is the programme's for a programme. A break
with no asset is in no programme's line. breaks, break_ms, slots and unslotted are their
count and sums, as §7.2 sums them.
fill_ppmis ⌊impressions× 10^6 /slots⌋ andrevenue_per_slot⌊revenue/slots⌋, computed in integers wide enough not to overflow, a quotient above 2^64 − 1 held as 2^64 − 1; both are 0 whenslotsis 0. The implied fill is impressions per slot: a break's slots are filled once per viewer, so it is the audience times the share of slots filled, and no record of this text states an audience.- The rate is the period line's
fill_ppm.
A line's flags are these bits:
| bit | name | set when |
|---|---|---|
| 0 | unproved |
impressions is above 0 and breaks is 0: a figure no break the records prove stands behind |
| 1 | above |
not the period's line, its slots and the period's above 0, and fill_ppm × 10^6 > rate × ceiling_ppm |
| 2 | below |
not the period's line, its slots and the period's above 0, and fill_ppm × 10^6 < rate × floor_ppm |
| 3 | impressions_above_signed |
signed is 1 and impressions > signed_impressions |
| 4 | impressions_below_signed |
signed is 1 and impressions < signed_impressions |
| 5 | revenue_above_signed |
signed is 1 and revenue > signed_revenue |
| 6 | revenue_below_signed |
signed is 1 and revenue < signed_revenue |
Every other bit is 0. above and below are relative to the period's own rate, at bounds the
issuer chooses and the check states: a day or programme far from the rest of the period. A
reported statement low in every line is low against nothing but the signed statement (§10.4).
10.4 The signed statement beside it
Where the operator's signed revenue-share statement of the report's contract, party and period is
given, verified as revenue-share §8 verifies it, the period's line is signed, and states:
signed_impressions: the sum of the statement's lines'impressions;signed_revenue: for the basisgross, the sum of its lines'revenue; forshare, itstotal, the party's part.
So the three stand side by side: the opportunities the cues and runs prove, the impressions and
revenue the invoices' signed records prove, and the figures the platform reported. A day's or a
programme's line is never signed: the statement's lines are per split, and the party's part is
rounded once per line (revenue-share §7.3).
10.5 Digest and signature
The digest of a check is SHA-256 over its bytes before sig. Its signature is Ed25519 by
issuer over the bytes wai:statement-check (ASCII) and 0x01, followed by the bytes the digest
covers, verified strictly (SPEC §7 Signatures).
10.6 Reading and rules
A reader reads exactly the bytes it is given as one check, as §7.5 reads a report with WCK1 for
WOR1 (truncated, malformed, unknown_version), and refuses as statement_check_invalid one
that breaks any of these:
issueris not zero;formis 1 or 2;floor_ppmis at most 10^6 andceiling_ppmat least 10^6;- its first line is of scope 1 with an empty key, and no other line is of scope 1; the lines are in
the strictly ascending order §10.1 gives them; each
scopeis 1, 2 or 3; a day's key is a day (§9.3) and a programme's 1 to 1024 bytes of UTF-8; signedis 0 or 1, and 1 only on the period's line; no bit offlagsabove bit 6 is set.
11. Verifying a check
11.1 Inputs
A check's verification document is one JSON object with exactly the members report (a report's
verification document, §8.1), reported (an object with exactly form, json or csv, and
text, the reported statement's text), statement (a revenue-share verification document,
revenue-share §8.1, or null) and check (hexadecimal). A document that is not such an object is
refused malformed. The reported statement's bytes are text in UTF-8.
11.2 Steps
It runs these steps and stops at the first refusal:
- The report, at
reportand the place §8.2 gives: every reason of §8. - The reported statement, at
reported: it reads in its form and keeps §9.3's rules (reported_invalid), and is of the contract's channel and currency and the report's period (reported_foreign). - The signed statement, when given, at
statementand the place revenue-share §8.2 gives: every reason of revenue-share §8; it is of the report's contract, party and period, or it is refusedstatement_foreign. - The check, at
check: it reads (§10.6:truncated,malformed,unknown_version,statement_check_invalid); its signature verifies under its issuer (§10.5:bad_key,bad_signature); and itsreport,reported,formandstatementare those given and its lines those §10.2 to §10.4 compute at its own bounds: otherwisestatement_check_mismatch.
11.3 Outcome
- refuse: the reason and where.
- accept: the check's digest; the issuer; the report's digest, its party and period; the
reported statement's digest, form and basis; the signed statement's digest or
null; the bounds; and each line with its figures and the names of its flags.
12. 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 |
|---|---|---|
opportunity.create |
wai opportunity create <inputs.json> --party <key> --start µs --end µs --key <file> [--out F] |
the report of a party and a period, computed from a verification document without its report (§8.1), signed by the issuer key, written with its document |
opportunity.verify |
wai opportunity verify <document.json> |
a report's (§8.1) or a check's (§11.1) verification document: its outcome; exit 1 with the reason on a refusal |
statement.check |
wai statement check <report.json> <reported> [--statement <document.json>] [--floor ppm] [--ceiling ppm] --key <file> [--out F] |
the check of a reported statement (JSON, or CSV for a file named .csv) against a report's verification document and, when given, a signed statement's, at the bounds given (500000 and 2000000 when not), signed by the issuer key, written with its document |
13. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
room_foreign |
§3.3, §8.2 step 5 |
room_repeated |
§3.3, §8.2 step 5 |
cue_section_invalid |
§5.1, §8.2 step 6 |
cue_section_mismatch |
§5.3, §8.2 step 6 |
opportunity_report_invalid |
§7.5, §8.2 step 3 |
opportunity_report_mismatch |
§8.2 step 8 |
reported_invalid |
§9.3, §11.2 step 2 |
reported_foreign |
§9.3, §11.2 step 2 |
statement_foreign |
§11.2 step 3 |
statement_check_invalid |
§10.6, §11.2 step 4 |
statement_check_mismatch |
§11.2 step 4 |
A verifier of this extension also raises, with the meaning this text gives them: truncated,
malformed and unknown_version for a report or a check that does not read, and malformed for a
document or a room that does not read; bad_key and bad_signature for their signatures;
contract_mismatch and outside_contract (revenue-share §11) for a report of another contract or
past its period; delivery_gap (live-events §6.4) for a room with a gap; break_overlap (live-ads
§4.3) for the breaks of two rooms at once; outside_window and asset_unmapped (revenue-share §11)
for a break no window places and an asset the contract does not map; line_unsourced and
citation_mismatch for a line, as revenue-share raises them; and, at a contract, a run, a room or
a signed statement, every code revenue-share and live-ads raise there.
14. Conformance
live-conformance/opportunity/ holds the corpus, in one file,
vectors.json, written by wai_opportunity_vectors gen from fixed seeds. The contracts, runs,
rooms, ledger documents and statements are held once, by name, and each vector is a report's or a
check's verification document naming them, with the outcome §8.3 or §11.3 gives it:
- Accepted: a channel's day under a contract of two owners: two windows airing each owner's
programmes, a room whose producer cues breaks inside them, with SCTE 35 sections of every kind
§5 reads (a
splice_insert()with a break duration, atime_signal()with a placement opportunity stating its slots, one whose type states none, asplice_insert()carrying a segmentation descriptor) and cues of form 0, a break inside an ad entry, and an ad entry no cue covers; the reports of both owners and of the operator, and one made from the runs alone; and checks of reported statements in JSON and in CSV, with and without the operator's signed statement of the day, one under-reporting an owner's programme, one reporting impressions in a programme with no break. - Refused: each reason of §13 at least once, each vector a control of one defect; each code §13 reuses; and a refusal at a contract, a run, a room and a signed statement, at its place.
wai_opportunity_vectors verify and the test tests/opportunity_report.rs check that the file
regenerates byte for byte, that wai-rs gives every vector its outcome, that every reason of §13
refuses a vector, and that §13 lists exactly the reference's codes; the test also holds the worked
example, a channel's day of two owners whose reported statement under-reports one owner's breaks,
with the check that shows it, computed and then recomputed from the public records alone.
live-conformance/opportunity_report_verify.py is the second implementation, written from this
text: it shares no code with wai-rs, reads every report, check, section and reported statement
itself, verifies contracts, runs and signed statements with revenue_share_verify.py and rooms with
live_ads_verify.py, recomputes every break, attribution, line, citation and flag, 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.
15. Security and privacy considerations
- What a report proves. A break in a report is one the room's own cues opened, signed by a principal the room's grants permit to cue, or an ad entry the channel signer signed as aired, placed in a window the channel signer signed. Nothing of it rests on the platform's figures or on the issuer's word: anyone holding the records computes the same report.
- What it does not. Opportunities are per break, not per viewer: a report says what was on air
to be sold, not how many saw it. The fill of a check is therefore impressions per slot, and its
aboveandbelowflags are relative to the period's own rate. Only a signed statement states signed impressions; where none is given, a reported statement uniformly low or high is not flagged by the report alone. - What the operator still controls. A room the contract does not name, or a channel carried
without cues or runs, proves no opportunity; the contract's carriers and its channel signer are
what the owners agreed to. Two rooms carrying the channel at once are refused rather than
counted twice (
break_overlap), and so are a cue whose message its summary contradicts and a break no window places. - The reported statement. It is labelled and kept apart: a check names it by the digest of its bytes, and no figure of it changes a report. A platform that disputes a check recomputes it from the same records and its own reported bytes.
- What a document reveals. A report's document holds the room's chains, whose events name the principals of the room and its other events (chat among them, live-events §4.4); a check's holds a signed statement's, with the ledger's sources (revenue-share §13). Each travels between the owner, the operator, the platform and an auditor they choose.