open-standards/wai/extensions/opportunity-report.md
Transaction Science ba9c439cf9 receipt-ledger §5.2: one registry of record kinds; the job kinds are 14 to 16
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]>
2026-10-30 00:51:06 -04:00

38 KiB
Raw Permalink Blame History

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 the opportunity_report module of wai-rs (feature opportunity_report); the corpus is live-conformance/opportunity/, and its second implementation is live-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 t0 plus the t of 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:

  1. a cued break whose section states slots (§5.2): those;
  2. another cued break: its room policy's slots (live-ads §3.1), the most ads one break holds;
  3. 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:

  1. 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;
  2. its protocol_version is not 0;
  3. its encrypted_packet is 1: the section is read no further, and states nothing (§5.2);
  4. its command, the splice_command_length bytes from byte 14 (after splice_command_type, byte 13), runs past the CRC; a splice_command_length of 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;
  5. 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_indicator and, when it is 0, the flags out_of_network_indicator, program_splice_flag, duration_flag and splice_immediate_flag, then splice_time() when the program flag is 1 and the immediate flag 0, or component_count and each component with its splice_time() when the immediate flag is 0, when the program flag is 0; then break_duration() (auto_return, duration) when the duration flag is 1; then unique_program_id, avail_num and avails_expected; time_signal() (type 0x06): one splice_time(); splice_null() (type 0x00): nothing; a splice_time() is one byte when its time_specified_flag is 0, and five, with pts_time, when it is 1. Another command is not read;
  6. descriptor_loop_length runs 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;
  7. a descriptor of tag 0x02 whose first four bytes are CUEI does not read as a segmentation_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_flag and 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_length and the UPID, segmentation_type_id, segment_num and segments_expected, then, for the types 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x44 and 0x46, the two bytes sub_segment_num and sub_segments_expected when 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()'s splice_event_id; a time_signal()'s opening descriptor's segmentation_event_id. The opening descriptor is the first segmentation descriptor of the section not cancelled whose segmentation_type_id makes an out (cue-injection §6.2): 0x22, 0x30, 0x32, 0x34 or 0x36.
  • Its duration, in 90 kHz ticks: a splice_insert()'s break_duration() duration, when its duration flag is 1; a time_signal()'s opening descriptor's segmentation_duration, when flagged.
  • Its slots: the sub_segments_expected of its first segmentation descriptor not cancelled whose segmentation_type_id is a placement opportunity start, 0x34 or 0x36, when that descriptor carries the sub-segment fields and sub_segments_expected is 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 whose out_of_network_indicator is 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:

  • start is below end; issuer and party are not zero;
  • rooms, runs and lines are each in the strictly ascending order §7.1 gives it;
  • each line's class is 1 or 2, its breaks and cited above 0, its unslotted at most its breaks, and its ppm 1 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:

  1. The contract, at contract, as revenue-share §8.2 step 1.
  2. The other contracts, each at contracts and its index, as revenue-share §8.2 step 2 (contract_overlap among its reasons).
  3. The report, at report: it reads (§7.5: truncated, malformed, unknown_version, opportunity_report_invalid); its contract is the contract's digest and its party the operator or an owner of the contract: otherwise contract_mismatch; its period lies within the contract's: otherwise outside_contract; its signature verifies under its issuer (§7.4): bad_key, bad_signature.
  4. The runs, each at run and its index, as revenue-share §8.2 step 4 (malformed, run_invalid, run_foreign, run_overlap).
  5. The rooms, each at room and its index: it reads (malformed); it verifies (§3.3), a refusal being live-ads' reason with live-ads' place after the index, and a gap delivery_gap; its id is a carrier (room_foreign) and not that of a room before it (room_repeated).
  6. 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, break and 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).
  7. The scheduled breaks in the period, in the order of §4.2, each at run, the run's index in the document, entry and the entry's index: attributed (§6: asset_unmapped).
  8. The figures, at report, recomputed as §7.2 and §7.3 compute them for the report's party, and compared in this order:
    1. a line whose split and class give the party no break, or that states more breaks than they give: line_unsourced;
    2. a line whose cited or cites is not the records it came from: citation_mismatch;
    3. any other difference (the rooms, the runs, a line missing, a figure of a line, a total): opportunity_report_mismatch.

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, policy or null), what its section states (null without 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, currency and basis, each once, its value in key and the last two fields empty;
  • period, once, key empty, and the period's impressions and revenue;
  • each day, its day in key, then each programme, its asset in key, each with its impressions and revenue.

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 start is not below its end;
  • 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_ppm is ⌊impressions × 10^6 / slots⌋ and revenue_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 when slots is 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 basis gross, the sum of its lines' revenue; for share, its total, 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:

  • issuer is not zero; form is 1 or 2; floor_ppm is at most 10^6 and ceiling_ppm at 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 scope is 1, 2 or 3; a day's key is a day (§9.3) and a programme's 1 to 1024 bytes of UTF-8;
  • signed is 0 or 1, and 1 only on the period's line; no bit of flags above 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:

  1. The report, at report and the place §8.2 gives: every reason of §8.
  2. 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).
  3. The signed statement, when given, at statement and 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 refused statement_foreign.
  4. 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 its report, reported, form and statement are those given and its lines those §10.2 to §10.4 compute at its own bounds: otherwise statement_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, a time_signal() with a placement opportunity stating its slots, one whose type states none, a splice_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 above and below flags 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.