Skip to main content

WAI Extension: Audience Decisions and Territories

Mirrored from the canonical text at commit c5e7133e ().

Status: Draft. Registers two records, each a route-record document signed by one Ed25519 key. wai.video.audience: a decider’s signed audience decision (wai-audience-decision, §4): whether the sessions in one region may be shown a programme over a span, under the blackouts in force for it. Each blackout is named by the id a session class carries (blackouts, http-delivery Appendix H), and states the regions it covers in rung-price §2’s region codes and the programme shown in its place, if any. A session’s location is aggregated in the session, before it leaves the session, to the one listed region that holds it, to outside, or to unresolved (§4.3). A decision carries that and nothing else of where a session is, and is refused when it carries more (audience_location_not_aggregated) or a region its blackouts do not list (audience_region_not_in_set). Which blackouts apply, the verdict and the programme shown in place follow from the blackouts and the region, and a verifier recomputes them; a session class of the programme carries the blackouts that apply as its own (§4.7). wai.delivery.territory: a publisher’s signed territory statement (wai-territory, §5): the regions its content may be shown in, everywhere, only in some, or everywhere but some. A publisher’s latest statement stands alone, and one that lapses revives none. A verifier reports, beside the placement of a server key (cache-authority §4.4), whether the receivers’ region is within the publisher’s territory, and the placement is the same with the statement as without it: a statement names no server, origin or role, and one that does is refused (territory_scopes_serving). No new transport and no new cryptography. The reference implementation is the audience_decision module of wai-rs (feature audience_decision); the corpus is live-conformance/audience/, and its second implementation is live-conformance/audience_decision_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

A Media document of SCTE 224 whose policies are applied by time, over audiences stated in region codes and with the blackout action, imports to the blackouts in force over each span of its programme, and spans are written back as such a document (§10). What a document states that those members do not carry exactly is refused with its own reason, never passed over. A decision or a sink predicate is then held to the document it was made from.

Texts read for this revision: WAI’s own; and, for §10, the XML schema of ANSI/SCTE 224 2021 (scte_224_20211011.xsd, target namespace http://www.scte.org/schemas/224) and the practice Blackouts and Airing Substitution (BP-EPG-BLK v1.0 draft, 1 October 2026), each as §10.1 credits it. The text of ANSI/SCTE 224 2021 itself could not be read: its publisher’s site refused the request. §10 therefore binds only what the schema and that practice fix, and §10.1 lists what it leaves unread. ISO 3166 was not read: this text binds only the structure of a code (rung-price §2).

1. Scope

In scope: the audience decision, its form, digest and signature, how a session’s location is aggregated before it leaves the session, what follows from a decision’s blackouts and region, and a decision beside a session class (§4); the territory statement, its form, digest and signature, the statement that stands at an instant, a region against a territory, and the report beside a placement (§5); and a bearer with no return path (§6).

Also in scope: the Media documents of SCTE 224 that import to a programme’s blackouts, the document written from them, and a decision or a predicate held to a document (§10).

Out of scope: a transport; how a session learns where it is; the parts of SCTE 224 §10.1 lists as unread, and how a decider obtains a document; what a rights holder and a decider agree, which their own contract states; the session class and its receipt (http-delivery §8.6 and Appendix H), which this extension reads and does not change; who may serve what a node holds, which cache-authority §4.3 gives and this extension leaves as it is; the form in which a predicate travels on a bearer with no return path, which sink-predicate registers; and payment.

Relationship to the core spec: wai.video.audience and wai.delivery.territory are records (SPEC §5): signed records a WAI tool emits and verifies. They name no payload format and are not dispatchable. They travel beside the envelopes and playlists they are about and are checked where those are used: a decision where a session is served, a territory statement where a placement is reported.

2. Terms

3. Why these two records

  1. A blackout is contracted per region, and decided per region. A rights holder restricts a programme in regions, and a session is shown it or not by where it is. A decision states the region it decided for, the blackouts in force and what follows, signed, so a party that holds it checks what follows without asking anyone.
  2. A decision needs a region, not a location. Whether a blackout applies is fixed by which of its listed codes holds the session. So a session aggregates its own location to that code before anything leaves it, and a decision carries the code and no more (§4.3). Every session that aggregates to one region gets one decision, as every viewer of a session class gets one playlist (http-delivery §8.6).
  3. One region scheme. Blackouts, a decision’s region and a territory are written in rung-price §2’s codes and held by its holds, which a lineup’s countries, a price list’s footprint and a cache’s population already use.
  4. Where content may be shown is not who may serve it. A node’s operator places a node (cache-authority §4.3), and nothing a publisher signs changes that. A publisher’s territory is reported beside a placement, as a key log’s confirmation of a claim is reported beside its verdict (cache-authority §4.4), and never in it.

4. The audience decision

4.1 Members

A decision is a document of exactly these members:

memberform
kind"wai-audience-decision"
version1
decidera key: 64 lowercase hexadecimal digits
programmea token: the programme
starta time: from when the decision holds
enda time: from when it holds nothing
blackouts0 to 256 blackouts (§4.2), in strictly ascending order of their ids’ bytes: every blackout in force for the programme over the span
evidencean object of region and resolved (§4.3)
applied0 to 256 tokens, strictly ascending by their bytes: the ids of the blackouts that apply (§4.6)
verdict"show", "withhold" or "alternate" (§4.6)
alternatea token, the programme shown in place, when verdict is "alternate"; null otherwise
siga signature (§4.4)

A decision holds for one span. A decider given blackouts that begin or end inside a programme signs one decision for each span between those instants.

4.2 A blackout

memberform
ida token: the blackout’s id
regions1 to 8,192 region codes, strictly ascending by their bytes, with no subdivision code whose country it also lists: where the programme is not shown
alternatea token, the programme shown in its place there, or null for none

The regions of one blackout have one encoding, as a footprint does (rung-price §4.5 rule 3). Two blackouts may list the same codes, or one a country and another a subdivision of it.

4.3 The evidence, aggregated in the session

A session’s location never leaves the session. What leaves it is the location’s aggregation against the listed codes of the blackouts in force, written as evidence:

memberform
regiona region code, or null
resolvedtrue or false; false only with a region of null

The aggregation of a location s against listed codes L is the first of these that holds:

  1. s is none: unresolved, {"region": null, "resolved": false};
  2. L lists s: {"region": s, "resolved": true};
  3. s is a subdivision code and L lists its country: {"region": the country, "resolved": true};
  4. s is a country code and L lists a subdivision of it: unresolved, since s does not say which of its subdivisions holds the session;
  5. otherwise: outside, {"region": null, "resolved": true}.

So the region, when there is one, is the most specific listed code that holds the location, and a blackout’s regions hold that region exactly when they hold the location (rung-price §2 Holds): the aggregation decides what the location would decide, and says no more of it. A session that holds a finer location keeps it. One that holds none, or too coarse a one, is unresolved, and every blackout in force applies to it (§4.6).

The evidence holds nothing else: no coordinates, postal code, address, network address or session’s identifier, and no region finer than the one the aggregation gives. A decision whose evidence holds any other member, or a region its listed codes hold without listing it (one finer than the aggregation gives), is refused audience_location_not_aggregated; one whose region its listed codes neither list nor hold is refused audience_region_not_in_set (§4.5).

4.4 Digest and signature

The digest of a decision is SHA-256 over the canonical form of the decision without sig, written as 64 lowercase hexadecimal digits. The signature is Ed25519 by decider over wai:audience-decision (ASCII) and 0x01, followed by that same canonical form: route-record §5.3’s construction under its own domain, as rung-price §4.4 is. It is verified strictly, as SPEC §7 Signatures verifies every signature: a key that is not the canonical encoding of a point of large order fails, as does a signature whose scalar is not below the group order.

A signature over any other bytes fails: over the canonical form without the domain, under another record’s domain, or over the text as it was written when that is not its canonical form. The canonical form is what is signed, so a decision reads the same whatever the order of its members.

4.5 Reading and rules

A reader is given a text. It refuses it audience_decision_malformed when the text holds more than 1,048,576 bytes, is not a document, or breaks one of rules 1 to 4. A decision that reads is then refused for rule 5, 6 or 7, with that rule’s own reason, and then for its signature (audience_decision_signature_invalid, §4.4). The rules, in order:

  1. Its members are exactly §4.1’s, and each blackout’s exactly §4.2’s, each of its form; evidence is an object that holds region and resolved, each of its form. version is 1.
  2. start is below end.
  3. The blackouts are in strictly ascending order of their ids, each blackout’s regions are as §4.2 states, and applied is strictly ascending.
  4. resolved is false only with a region of null, and alternate is a token exactly when verdict is "alternate".
  5. evidence holds no member but region and resolved, and the listed codes do not hold a region they do not list (audience_location_not_aggregated).
  6. A region that is not null is a listed code (audience_region_not_in_set).
  7. applied, verdict and alternate are what §4.6 gives (audience_decision_inconsistent).

Rule 5 comes before rule 6: a region the listed codes hold without listing it is a finer location passed on, and a region they neither list nor hold is not a region of the blackouts at all.

4.6 What follows from the blackouts and the region

A decision is the same for every session whose location aggregates to its region: a decider MAY sign it once and give it to each of them.

4.7 A decision and a session class

A session class (http-delivery Appendix H, class) of a programme carries the blackouts that apply to its sessions as its blackouts. Given a decision’s text and a class, a verifier, in order:

  1. reads the decision (§4.5), and refuses with its reason when it does not read;
  2. reads the class: an object of exactly content_id, entitlement, geo, ad_pod_id (each a string), blackouts (an array of strings) and window (an integer from 0 to 2⁵³ − 1), and refuses it audience_class_malformed otherwise;
  3. requires the class’s content_id to be the decision’s programme;
  4. requires the class’s blackouts, distinct and in ascending order of their bytes as its hash orders them (http-delivery Appendix H), to be applied;
  5. requires the class’s geo to be the region’s country when the decision has a region, and not a listed code when the decision is outside, since a listed country would hold every session in it. An unresolved decision requires nothing of geo.

A class that fails step 3, 4 or 5 is refused audience_class_mismatch, naming the first member that differs: content_id, blackouts or geo. One that passes is consistent with the decision, and the verifier states its session_class_hash, as Appendix H computes it, by which a playlist receipt of that class names it.

5. The territory statement

5.1 Members

A territory statement is a document of exactly these members:

memberform
kind"wai-territory"
version1
publishera key: the publisher
issueda time: from when it states the territory
expiresa time: from when it states nothing
show"everywhere", "only" or "except": shown everywhere, only in regions, or everywhere but in regions
regions0 to 8,192 region codes, strictly ascending by their bytes, with no subdivision code whose country it also lists; none exactly when show is "everywhere"
siga signature (§5.2)

show and regions are a lineup’s geo and countries (channel-lineup §6.2), for all of a publisher’s content and in region codes.

5.2 Digest and signature

The digest is SHA-256 over the canonical form of the statement without sig. The signature is Ed25519 by publisher over wai:territory (ASCII) and 0x01, followed by that same canonical form, verified strictly as a decision’s is (§4.4); a signature over any other bytes fails.

5.3 Reading and rules

A statement names no server. Who may serve what a node holds is given by the node’s operator’s origin (cache-authority §4.3), and a territory statement changes none of it. A statement that holds a member by which it would name who serves — servers or nodes (keys), origins (key logs), roles or may_sign (authorities, as a key log writes them) — is refused territory_scopes_serving, before any other rule of form: one reader would take such a member as scoping the servers it places, and another would pass over it.

A reader is given a text. It refuses it territory_malformed when the text holds more than 1,048,576 bytes or is not a document; territory_scopes_serving as above; territory_malformed when it breaks one of rules 1 to 3; and then territory_signature_invalid for its signature (§5.2). The rules, in order:

  1. Its members are exactly §5.1’s, each of its form. version is 1.
  2. issued is below expires.
  3. regions are as §5.1 states, and empty exactly when show is "everywhere".

5.4 The standing statement

Given texts, a publisher and an instant t (a time), the publisher’s statements are the texts that read (§5.3) and whose publisher is that key; a text that does not read is passed over. Of the publisher’s statements whose issued is at or before t, the first of these that holds:

  1. there are none: no statement stands (territory_not_stated);
  2. two that differ (by their digests) share the latest issued: none stands (territory_contradicted), and no earlier one revives;
  3. the latest’s expires is at or before t: none stands (territory_lapsed), and no earlier one revives;
  4. otherwise the latest stands.

Two texts of one body under two signatures are one statement, with one digest. A publisher’s latest statement replaces every earlier one, as a seller’s latest price list does (rung-price §5).

5.5 A region against a territory

Of a standing statement and the region a relying party states for the receivers (a region code, or none when it states none), the territory is:

It is written within statement=<digest>, outside statement=<digest> or undetermined statement=<digest>, and with no standing statement unstated reason=<code>, the code of §5.4. An unstated territory is reported as unstated, never as within.

5.6 Beside a placement

Given a server key, an instant at_ms and the heads its relying party names, as cache-authority §4.4 is, and a publisher, the receivers’ region and territory statements, a verifier reports two lines: the placement, computed by cache-authority §4.4 from the key, the instant and the heads alone, and the territory (§5.5) of the publisher’s statement standing at at_ms × 1000.

The placement is the same whatever statements are given and whatever territory is reported beside it: an outside territory refuses no key, and a within territory confirms none. cache-authority §4.3 and §4.4 are unchanged. What a relying party does with a placement beside an outside territory is its own: a payer that pays only for service within a publisher’s territory says so in its own terms.

6. No return path

On a bearer with no return path (unidirectional-delivery §2), every sink receives every byte and no decision is made away from it. The blackouts in force are then a sink-evaluated predicate of the programme and the sink’s location: the sink aggregates its location against their listed codes (§4.3) and presents what §4.6 gives, with the programme shown in place when it holds it. It causes no request in doing so, and its location and its verdict leave the device nowhere (unidirectional-delivery §2). Two sinks holding the same blackouts and the same location reach the same verdict. There is then no decision to sign: a decision of §4 records one made away from the sink. The blackouts travel to the sink in a predicate of sink-predicate, whose blackouts are §4.2’s and whose verdict is §4.6’s at the sink’s aggregated location (sink-predicate §4.2).

7. Reasons

reasonraised at
audience_decision_malformed§4.5 rules 1 to 4, §4.7 step 1
audience_location_not_aggregated§4.3, §4.5 rule 5
audience_region_not_in_set§4.3, §4.5 rule 6
audience_decision_inconsistent§4.5 rule 7
audience_decision_signature_invalid§4.4, §4.5
audience_class_malformed§4.7 step 2
audience_class_mismatch§4.7 steps 3 to 5
territory_malformed§5.3
territory_scopes_serving§5.3
territory_signature_invalid§5.2, §5.3
territory_not_stated§5.4 step 1
territory_contradicted§5.4 step 2
territory_lapsed§5.4 step 3

8. Conformance

live-conformance/audience/ holds the corpus, written from fixed seeds by wai_audience_decision_vectors gen: vectors.json, which holds every key and each case’s inputs and outcome. Every key is Ed25519 from a seed of one byte repeated 32 times and attests nothing, and every region is an ISO 3166-1 user-assigned code or a subdivision of one, which names no place. The cases:

A conforming implementation gives every case its outcome. wai_audience_decision_vectors verify and the test tests/audience_decision.rs check that the file regenerates byte for byte, that wai-rs gives every case its outcome, and that §7 lists exactly the codes the reference reports. live-conformance/audience_decision_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it reads documents and their canonical form with the route record corpus’s second implementation, region codes with the rung price corpus’s, placements with the cache authority corpus’s, Ed25519 with WAI’s strict rule (claim-conformance/_ed25519.py) and BLAKE3 with claim-conformance/_blake3.py. It requires every case’s outcome and every reason of §7; that a decision aggregated from a location decides what the location decides; that no placement changes when its statements are removed; and that no outcome depends on the order of a document’s members or of the statements given. It then mutates copies of the corpus and requires every copy to fail, and drops each of its own checks in turn and requires the corpus to catch the weakened verifier.

9. Security and privacy considerations

10. SCTE 224 documents

A rights holder that states its blackouts in SCTE 224 (Event Scheduling and Notification Interface) sends Media documents: a service, the points on it at which policies are applied and removed, and for each policy the audiences it applies to and what is done for them. This section says which of those documents state a programme’s blackouts exactly, what each imports to, and how the same blackouts are written back. An import is a pure function of the document’s bytes.

10.1 What was read, and what was not

10.2 The document

A document is at most 1,048,576 bytes of UTF-8 with no byte order mark, read over the XML subset of stitched-presentation §3.5 with namespaces added: an attribute xmlns or xmlns:prefix declares a namespace for its element and that element’s content; an element’s name is its namespace and its local name, an unprefixed element taking the default namespace; an attribute’s is its prefix’s namespace and its local name, an unprefixed attribute having no namespace; the prefix xml is bound, and any other prefix must be declared. A document outside the subset, one with a prefix that is not declared, or an element with two attributes of one name is refused scte224_malformed.

In this section an element named without a prefix is in the namespace http://www.scte.org/schemas/224, xlink:href is the attribute href of http://www.w3.org/1999/xlink, and audience: and action: are the practice’s two namespaces. WAI defines two elements of its own, in the place the schema gives to properties of another namespace:

elementnamespacecontent
Regiontag:transaction.science,2026:wai/audiencean audience property: one region code (rung-price §2)
Alternatetag:transaction.science,2026:wai/actionan action property: a token, the programme shown in place

The root is Media, and its id is the programme: a token (scte224_id_not_token otherwise; scte224_malformed when the root is another element or has no id).

Wherever an element may stand, these state no policy and are passed over with their content: the elements AltID, Metadata and Ext, the attributes description and lastUpdated, namespace declarations, a media point’s id and its expectedDuration. Any other attribute of an element this section reads, beside the id, xlink:href, match, matchTime and duration it reads where it says so, is refused scte224_not_carried: its meaning is one §10.1 lists as unread.

A Policy, a ViewingPolicy or an Audience with an id and no xlink:href defines that id, wherever it stands in the document; an id defined twice by elements of one name is scte224_malformed. One with an xlink:href stands for the element of its name that defines that value, compared byte for byte: it has no id, no content and no other attribute (scte224_malformed), and a value the document does not define is refused scte224_reference_unresolved. Nothing is fetched.

10.3 Times

A media point’s matchTime is an xs:dateTime that names an instant: a date from 1970, T, a time of day with at most six digits of a fraction, and a zone, Z or an offset. A duration is an xs:duration of days, hours, minutes and seconds alone, the seconds with at most six digits of a fraction, above zero. Each is read to the microsecond, exactly. A time with no zone, a fraction finer than a microsecond, a duration with years or months, and a media point with no matchTime are refused scte224_not_carried: a document that states them states no instant a decision’s start and end carry.

10.4 Audiences, viewing policies and policies

An audience names a set of region codes:

A viewing policy states one blackout (§4.2). Its id is the blackout’s id, a token (scte224_id_not_token when it is not, or when there is none). It holds exactly one Audience (scte224_malformed otherwise), whose set is the blackout’s regions, written in the one encoding §4.2 requires: ascending, and without a subdivision whose country the set also holds, which changes no place the set names. It holds exactly one action:Content whose text is urn:scte:224:action:blackout, and at most one Alternate, the blackout’s alternate (scte224_id_not_token when it is not a token). Any other action, another value of action:Content, a second action:Content or Alternate, or no blackout action is refused scte224_action_unmappable; an element of the schema’s own namespace other than the Audience is scte224_malformed.

A policy states the blackouts of its viewing policies, and may hold none; any other child of it is scte224_malformed, as is an Apply or a Remove that does not hold exactly one Policy.

10.5 What a document imports to

Each child of Media that is not passed over is a MediaPoint (scte224_malformed otherwise). The media points take effect in the order of their instants, those of one instant in document order, and in each its Remove and Apply children in document order:

A policy still in force with no end once every media point has taken effect is refused scte224_span_open: a decision holds for a span that ends (§4.1).

The instants of a document are the instants at which a policy comes into force or ends. Each two consecutive instants bound a span, and the span’s blackouts are those of every policy in force throughout it, one for each id, in ascending order of their ids; a span between two policies is a span under no blackout. A span of more than 256 blackouts, a document of more than 4,096 media points and a set of more than 8,192 regions are refused scte224_not_carried.

A document imports to its programme and its spans in order. Each span is the programme, start, end and blackouts of a decision (§4.1), and of a sink predicate (sink-predicate §3.1), over that span.

10.6 Writing a document

Spans of one programme, in order, none beginning before the last one ends, each of §4.2’s form and each id stating one blackout throughout, are written as one document: the XML declaration, a Media whose id is the programme, and for each span a MediaPoint whose matchTime is the span’s start in UTC and whose one Apply has the span’s length as its duration and a Policy with no id, which holds no viewing policy for a span under no blackout. A blackout is written in full, a ViewingPolicy of an Audience of ANY with one Region for each code, the blackout action and its Alternate if any, in the first span that holds it, and by reference after that. The document imports to the spans it was written from when each begins where the last ended; a gap between two of them imports as a span under no blackout. The reference writes one element a line, indented one space a level, times as YYYY-MM-DDThh:mm:ss with a fraction only when there is one and Z, and durations in seconds as PTnS; the corpus’s exported case holds those bytes. An importer reads any spelling of §10.2’s subset.

10.7 A decision and a predicate against a document

A verifier that holds a document and a decision (§4), or a sink predicate, first imports the document, with its refusal; then reads the decision (§4.5) or the predicate (sink-predicate §3.5), with its own refusal; and then requires the document’s programme to be the record’s, and one of its spans to have the record’s start and end and exactly its blackouts, member for member. A record that fails this is refused scte224_decision_differs.

What a decision states beside its span and its blackouts, the evidence, the blackouts applied, the verdict and the programme shown in place, follows from them (§4.6) and is checked by §4.5. So a verifier that holds the document and the region a session aggregated to recomputes the whole decision, and a sink that holds a predicate of the document reaches the verdict a decision made away from it would state (§6).

reasonraised at
scte224_malformed§10.2, §10.4, §10.5
scte224_not_carried§10.2, §10.3, §10.5
scte224_reference_unresolved§10.2
scte224_id_not_token§10.2, §10.4
scte224_audience_unmappable§10.4
scte224_action_unmappable§10.4
scte224_span_open§10.5
scte224_decision_differs§10.7

10.8 Conformance and considerations

live-conformance/audience-scte224/ holds vectors.json, written by tests/audience_scte224.rs: documents with the spans each imports to or the one reason it is refused, among them a document for each rule above, one edit from a document that imports; the document §10.6 writes; and decisions and predicates held to a document, each with the verdict a session or a sink at its location reaches. The reference is the audience_decision::scte224 module of wai-rs, and live-conformance/audience_scte224_verify.py is the second implementation, written from this section and sharing no code with it.