All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 12s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 22s
wai-app and Create / wai-app · does this change touch it (push) Successful in 16s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 17s
wai-sink-swift native sink package / does this change touch it (push) Successful in 17s
WAI Studio / does this change touch it (push) Successful in 15s
wai-sink-android native sink library / does this change touch it (push) Successful in 20s
wai-ledger service / does this change touch it (push) Successful in 1m2s
wai-quantum for JavaScript / does this change touch it (push) Successful in 1m6s
wai-fleet service / does this change touch it (push) Successful in 1m8s
WAI operation catalogue / does this change touch it (push) Successful in 1m8s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m13s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m8s
wai-key-proxy / does this change touch it (push) Successful in 1m8s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m9s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 9s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 8s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 39s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m8s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 2m38s
wai-ledger service / service · linux (push) Successful in 2m13s
joule-code conformance gate / joule-moq-relay, every target (push) Successful in 6m28s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m47s
wai-key-proxy / proxy · linux (push) Successful in 1m22s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 3m10s
wai-audit collector and player integration / collector · linux (push) Successful in 2m18s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 4m54s
wai-fleet service / service · linux (push) Successful in 3m50s
joule-code conformance gate / joule-moq-ingest, every target (push) Successful in 7m51s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 5m52s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 9m49s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 11m34s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m7s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 11m25s
WAI Studio / Studio in WebKit · linux (push) Successful in 13m57s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 11m7s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 28m12s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 1m46s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 13m21s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 9m10s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 10m1s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 15m25s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 18m35s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 49s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 7m51s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 15m54s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m24s
wai-ledger service / service · macos (push) Successful in 1m7s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 53s
wai-fleet service / service · macos (push) Successful in 45s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m53s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m58s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m10s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m59s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 7m31s
Three mappings, each with a reference module, a corpus and a second implementation written from its text. None changes a byte of a decision, a cue, a policy, a receipt or a claim, and nothing is wired into a relay, a room service or a player. - SCTE 224 import (audience-decision §10, new; audience_decision::scte224). A Media document 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; spans are written back as a document that imports to them; a decision or a sink predicate is held to its document, so a verifier recomputes the decision. What does not carry exactly refuses the document with its own reason: an audience of postal codes or one that excludes, another action, a signal-matched media point, an eligibility window, a priority, an open-ended policy, an unresolved reference. Read: the schema of ANSI/SCTE 224 2021 (scte_224_20211011.xsd) and a public encoding practice (BP-EPG-BLK v1.0 draft); the standard's own text was refused to an unattended fetch and was not read, and §10.1 lists what that leaves unbound (decision D10). - Ad creative signalling (draft-riedl-moq-ad-creative-signaling-01; live-ads §13, new; live_ads::creative). A session's fill of a break as the draft's record on an MSF event timeline, signed by the decider as a detached JWS over the record's RFC 8785 form; records read as untrusted input and held to the room's schedule and receipts. The draft's own vectors are in the corpus and its signed record verifies with its test key. The catalog member wai_ads (moq-streaming-format §4.7) is registered and held by catalog_check.py --strict, with ten planted faults. - Content stitching (draft-englishm-moq-content-stitching-00; live-ads §14, new; live_ads::stitch). The composed track is a function of the programme's listing, the schedule and the receipts, changing ingredient at Group boundaries alone, and a verifier recomputes it; a break is stitched at a break candidate of a package's marks where marks are given. The stitcher is the edge of ad-presentation §14.3 and writes the session origin (WSO1); the player writes the device binding (WDB1); the invalid-traffic rules decide from those records in the corpus (decision D11). Planted faults, each refused with its own reason by the reference and by the second implementations: a policy with an unmappable audience (scte224_audience_unmappable); a creative signal for a break that does not exist (creative_break_unknown); 31 more documents, 15 more room cases, 13 stitch cases and 23 record cases. Corpora: wai/live-conformance/audience-scte224/ (audience_scte224_verify.py) and wai/live-conformance/ad-signalling/ (ad_signalling_verify.py); wai conform --independent runs both. Each second implementation prints the checks of its own that the corpus holds no case for (39 and 14). Co-Authored-By: Claude <[email protected]>
536 lines
34 KiB
Markdown
536 lines
34 KiB
Markdown
# WAI Extension: Sink-Evaluated Predicates
|
||
|
||
> Status: Draft. Registers the record `wai.delivery.sink_predicate`: a source's signed
|
||
> **predicate** (`wai-sink-predicate`, §3), the form in which a condition a sink evaluates travels
|
||
> beside a programme, on any bearer and on one with no return path
|
||
> ([unidirectional-delivery](unidirectional-delivery.md) §2). A predicate carries the programme's
|
||
> blackouts ([audience-decision](audience-decision.md) §4.2) and its ad slots, each a list of
|
||
> candidate ads in the order the source prefers them, with the regions each is shown in and its
|
||
> frequency caps. A sink evaluates it from its own state alone (§4): its location and its record
|
||
> of its own exposures. It gives the programme's verdict as audience-decision §6 has it, and fills
|
||
> each slot itself, taking in order the candidates whose regions hold its location and whose caps
|
||
> its exposures have not reached. Nothing about the device leaves it. A predicate whose evaluation
|
||
> would need a report leaving the device is refused (`predicate_needs_report`), and a sink's log
|
||
> that presents a candidate whose cap was reached is refused (`predicate_cap_breached`, §5). No new
|
||
> transport and no new cryptography. The reference implementation is the `sink_predicate` module
|
||
> of `wai-rs` (feature `sink_predicate`); the corpus is
|
||
> [`live-conformance/sink-predicate/`](../live-conformance/sink-predicate/), and its second
|
||
> implementation is `live-conformance/sink_predicate_verify.py`.
|
||
>
|
||
> This revision adds **highlight reels** (§9) and registers the record `wai.delivery.sink_reel`: a
|
||
> source's signed reel predicate (`wai-sink-reel`) over the clips of a programme's marks
|
||
> (§8.11 of [packaging](packaging.md)), each reel a list of candidate clips with the tags a viewer must
|
||
> follow to want each. A sink assembles each reel from its own location, exposures and interests,
|
||
> the tags its viewer follows, held on the device; a sink's log of a reel that names a clip with no
|
||
> record in the marks is refused (`reel_clip_unknown`). The reference is the `reel` module (feature
|
||
> `sink_reel`); the corpus is [`live-conformance/sink-reel/`](../live-conformance/sink-reel/), and its
|
||
> second implementation `live-conformance/sink_reel_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: WAI's own.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the predicate, its form, digest and signature, and the members by which it would send
|
||
something away from the device (§3); the sink's state, the programme's verdict, a candidate's
|
||
regions and caps, a slot's fill and the evaluation (§4); and a sink's log of a slot, checked (§5).
|
||
|
||
Out of scope: a transport, and how a predicate and the creatives it names reach a sink; how a sink
|
||
learns where it is; which candidates a source offers, and in which order, which is the source's
|
||
own; what a slot's filler is; and counting presentations, which
|
||
[impression-aggregates](impression-aggregates.md) does apart from any predicate. A predicate's
|
||
evaluation sends nothing, and nothing here requires a sink to report what it presented.
|
||
|
||
Relationship to the core spec: `wai.delivery.sink_predicate` is a **record** ([SPEC](../SPEC.md)
|
||
§5): a signed record a WAI tool emits and verifies. It names no payload format and is not
|
||
dispatchable. It travels beside the programme it is about and is evaluated where the programme is
|
||
presented: on the sink.
|
||
|
||
Relationship to [unidirectional-delivery](unidirectional-delivery.md): §2 there states what a
|
||
predicate owes: it is a total function of the object and the sink's declared state class, and it
|
||
cannot cause a request. This extension is the registered form of such a predicate: the predicate
|
||
is the object, `state` declares its state classes (§3.1), and §4 is the function.
|
||
|
||
Relationship to [audience-decision](audience-decision.md): a predicate's blackouts are that
|
||
extension's (§4.2 there), and the programme's verdict is what its §4.3 and §4.6 give at the sink's
|
||
location, as its §6 has a sink on a bearer with no return path decide it. A decision of its §4
|
||
records a verdict reached away from the sink; a predicate travels to a sink that reaches it.
|
||
|
||
Relationship to [live-ads](live-ads.md) and [stitched-presentation](stitched-presentation.md):
|
||
their decisions are made away from the sink, by a decider that receives a request. A predicate's
|
||
slots are filled where no request is made: on a sink of a linear channel whose breaks reach every
|
||
sink alike.
|
||
|
||
## 2. Terms
|
||
|
||
- **Document**, **canonical form**, **time**, **token**, **key** and **signature**: as
|
||
[route-record](route-record.md) §2. A time is in microseconds.
|
||
- **Region code**, its **country**, and **holds**: [rung-price](rung-price.md) §2.
|
||
- **Blackout**, **listed codes** and a session's **location**: audience-decision §2 and §4.2. Here
|
||
the session is the sink's.
|
||
- **Source**: an Ed25519 key that signs predicates: the party whose programme and slots they are.
|
||
- **Sink**: the device that presents the programme.
|
||
- **Slot**: a span of the programme, at an instant, that ads fill.
|
||
- **Candidate**: an ad a slot may be filled with.
|
||
- **Creative digest**: [ad-presentation](ad-presentation.md) §4.
|
||
- **Exposure**: one presentation by the sink of a candidate with a cap, kept on the device as the
|
||
cap's counter and the instant: one exposure for each cap the candidate has.
|
||
|
||
## 3. The predicate
|
||
|
||
### 3.1 Members
|
||
|
||
A predicate is a document of exactly these members:
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `kind` | `"wai-sink-predicate"` |
|
||
| `version` | `1` |
|
||
| `source` | a key: 64 lowercase hexadecimal digits |
|
||
| `programme` | a token: the programme, as a session class names it (`content_id`, [http-delivery](http-delivery.md) Appendix H) |
|
||
| `start` | a time: from when the predicate holds |
|
||
| `end` | a time: from when it holds nothing |
|
||
| `state` | the state classes it reads (§4.1), strictly ascending by their bytes: `"exposures"` exactly when a candidate has a cap, and `"location"` exactly when it has a blackout or a candidate shown other than everywhere |
|
||
| `blackouts` | 0 to 256 blackouts (audience-decision §4.2), in strictly ascending order of their ids' bytes |
|
||
| `slots` | 0 to 64 slots (§3.2), in strictly ascending order of `at`, their `id`s distinct |
|
||
| `sig` | a signature (§3.4) |
|
||
|
||
### 3.2 A slot
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `id` | a token: the slot |
|
||
| `at` | a time, at or after `start` and before `end`: when the slot begins |
|
||
| `avail_ms` | an integer from 1 to 3,600,000: the slot's length, in milliseconds |
|
||
| `candidates` | 1 to 64 candidates (§3.3), in the order the source prefers them, their `ad`s distinct |
|
||
| `filler` | a token, what the sink shows in the part of the slot its fill leaves, or `null` for what the stream itself carries there |
|
||
|
||
### 3.3 A candidate and its caps
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `ad` | a token: the ad |
|
||
| `creative` | its creative digest: 64 lowercase hexadecimal digits |
|
||
| `duration_ms` | an integer from 1 to its slot's `avail_ms` |
|
||
| `show` | `"everywhere"`, `"only"` or `"except"`: shown everywhere, only in `regions`, or everywhere but in `regions` |
|
||
| `regions` | 0 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"` |
|
||
| `caps` | 0 to 8 caps, in strictly ascending order of their counters' bytes |
|
||
|
||
`show` and `regions` are a territory statement's (audience-decision §5.1), for one ad. A **cap** is
|
||
an object of exactly these members:
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `counter` | a token: what the cap counts, which candidates that share it count together |
|
||
| `limit` | an integer from 1 to 1,000: the most exposures of the counter in a window |
|
||
| `window` | an integer from 1 to 2⁵³ − 1: the window's length, in microseconds |
|
||
|
||
### 3.4 Digest and signature
|
||
|
||
The **digest** of a predicate is SHA-256 over the canonical form of the predicate without `sig`,
|
||
written as 64 lowercase hexadecimal digits. The **signature** is Ed25519 by `source` over
|
||
`wai:sink-predicate` (ASCII) and 0x01, followed by that same canonical form: route-record §5.3's
|
||
construction under its own domain, as audience-decision §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.
|
||
|
||
### 3.5 Reading and rules
|
||
|
||
**A predicate sends nothing.** Its evaluation reads only state the device holds (§4.1). A predicate
|
||
that holds, at any depth, a member by which its evaluation would send something away from the
|
||
device — `report`, `beacon`, `callback` or `uri` (somewhere to send to), or `household` or
|
||
`account` (a count kept across devices, which no device holds unless the others report to it) — is
|
||
refused `predicate_needs_report`, before any other rule of form: one sink would take such a member
|
||
as asking it to send, and another would pass over it.
|
||
|
||
A reader is given a text. It refuses it `predicate_malformed` when the text holds more than
|
||
1,048,576 bytes or is not a document; `predicate_needs_report` as above; `predicate_malformed` when
|
||
it breaks one of rules 1 to 4; and then `predicate_signature_invalid` for its signature (§3.4). The
|
||
rules, in order:
|
||
|
||
1. Its members are exactly §3.1's, each blackout's audience-decision §4.2's, each slot's §3.2's,
|
||
each candidate's and each cap's §3.3's, each of its form. `version` is 1.
|
||
2. `start` is below `end`; each slot's `at` is at or after `start` and below `end`; the slots are
|
||
in strictly ascending order of `at`, and their `id`s are distinct.
|
||
3. The blackouts are in strictly ascending order of their ids, and each blackout's `regions` are as
|
||
audience-decision §4.2 states; in each slot, the `ad`s are distinct and each `duration_ms` is at
|
||
most the slot's `avail_ms`; each candidate's `regions` are as §3.3 states, empty exactly when
|
||
`show` is `"everywhere"`; and its caps are in strictly ascending order of their counters.
|
||
4. `state` is exactly the classes §3.1 gives.
|
||
|
||
## 4. Evaluation, on the sink
|
||
|
||
### 4.1 The sink's state
|
||
|
||
A sink's **state** is its own, held on the device:
|
||
|
||
- its **location**: a region code, at the finest it holds, or none (audience-decision §2);
|
||
- its **exposures**: each a counter (a token) and an instant (a time), at most 65,536; or **not
|
||
kept**, for a sink that keeps none;
|
||
- its **sources**: the keys whose predicates it evaluates, provisioned on the device out of band,
|
||
as a trust anchor is (unidirectional-delivery §3).
|
||
|
||
The location is the state class `location` and the exposures the class `exposures`
|
||
(unidirectional-delivery §2). A predicate reads no class its `state` does not name.
|
||
|
||
### 4.2 The programme
|
||
|
||
The sink aggregates its location against the listed codes of the predicate's blackouts
|
||
(audience-decision §4.3), and the programme's **verdict**, the blackouts applied and the programme
|
||
shown in place are what audience-decision §4.6 gives at that aggregation: `show`, `withhold` or
|
||
`alternate`. Unresolved is not outside: with no location, every blackout applies. A predicate
|
||
whose blackouts were imported from an SCTE 224 document is held to that document as a decision is
|
||
(audience-decision §10.7).
|
||
|
||
### 4.3 A candidate's regions
|
||
|
||
A candidate is **shown** at the sink's location when:
|
||
|
||
- its `show` is `"everywhere"`; or
|
||
- its `show` is `"only"`, the sink holds a location, and the candidate's `regions` hold it; or
|
||
- its `show` is `"except"`, the sink holds a location, the candidate's `regions` do not hold it,
|
||
and the location is not a country of which the `regions` list a subdivision.
|
||
|
||
So a sink with no location is shown only a candidate shown everywhere, and one whose location is a
|
||
country of which a candidate lists a subdivision is shown it neither under `"only"` nor under
|
||
`"except"`: it does not say which of its subdivisions holds it, as audience-decision §5.5 calls such
|
||
a territory undetermined.
|
||
|
||
### 4.4 A candidate's caps
|
||
|
||
A cap is **reached** at an instant *t* when the sink's exposures of its counter at instants *e*
|
||
with *e* ≤ *t* < *e* + `window` number at least its `limit`. An exposure at an instant after *t*
|
||
is not counted. A candidate **admits** at *t* when it has no cap, or when the sink keeps its
|
||
exposures and none of its caps is reached at *t*. A sink that keeps no exposures cannot honour a
|
||
cap, and a candidate with one never admits there.
|
||
|
||
### 4.5 A slot's fill
|
||
|
||
A slot is filled at its `at`. With the avail not yet taken starting at `avail_ms`, the candidates
|
||
are taken in their order. A candidate is **taken** when it is shown at the location (§4.3), it
|
||
admits at `at` (§4.4), counting with the sink's exposures those that the candidates taken before it
|
||
in the slot added, and its `duration_ms` is at most the avail not yet taken. A taken candidate adds
|
||
one exposure at `at` for each of its caps' counters, and its `duration_ms` is taken from the avail.
|
||
|
||
The slot's **fill** is the taken candidates' `ad`s, in order, and its **rest** is the avail not
|
||
taken, in milliseconds, in which the slot's `filler` is shown.
|
||
|
||
### 4.6 The evaluation
|
||
|
||
A sink's **evaluation** of a predicate's text, in order:
|
||
|
||
1. reads it (§3.5), and refuses with its reason when it does not read;
|
||
2. requires its `source` to be among the sink's sources: otherwise `predicate_source_unknown`;
|
||
3. gives the programme's verdict (§4.2);
|
||
4. when the verdict is `show`, fills each slot (§4.5) in order of `at`, each from the sink's
|
||
exposures and those the earlier slots' fills added; under any other verdict it fills no slot,
|
||
and the sink shows what audience-decision §6 says.
|
||
|
||
It gives the verdict, the blackouts applied and the programme shown in place, and for each slot
|
||
filled its `id`, its fill and its rest.
|
||
|
||
**What it reads and what it sends.** The evaluation is a function of the predicate and the sink's
|
||
state alone. It **MUST NOT** cause a request, and the location, the exposures, the verdict and the
|
||
fills leave the device nowhere (unidirectional-delivery §2). Two sinks holding the same state reach
|
||
the same evaluation. A sink presents each slot's fill and keeps the exposures it adds; one that does
|
||
not hold a taken candidate's creative whole shows the slot's filler in its place for its
|
||
`duration_ms`, and keeps no exposure for it.
|
||
|
||
## 5. A sink's log, checked
|
||
|
||
A sink's **log** of a slot is the `ad`s it presented in it, in order. A log stays on the device,
|
||
as the exposures do. It is read where the device itself is examined: by the sink, holding itself
|
||
to its predicate before it presents each ad, and by a harness running a sink under test
|
||
([conformance-results](conformance-results.md)). A sink **MUST NOT** present a candidate whose cap
|
||
is reached or one not shown at its location, and its log never holds one.
|
||
|
||
A checker is given a predicate's text, a sink's state as it was before the slot, a slot's `id` and
|
||
a log. In order:
|
||
|
||
1. It evaluates the predicate's programme (§4.6 steps 1 to 3), refusing with its reason, and
|
||
fills the slot of that `id` alone (§4.5), from the exposures given, when the verdict is `show`;
|
||
a predicate with no slot of that `id` is refused `predicate_fill_mismatch`.
|
||
2. For each `ad` of the log, in order, it requires:
|
||
- the `ad` to be a candidate's of the slot, not earlier in the log: otherwise
|
||
`predicate_fill_mismatch`;
|
||
- the candidate to be shown at the location (§4.3): otherwise `predicate_not_targeted`;
|
||
- the candidate to admit at the slot's `at` (§4.4), counting with the exposures given those
|
||
that the ads before it in the log added: otherwise `predicate_cap_breached`.
|
||
3. It requires the log to be the slot's fill, under the programme's verdict (no fill unless it is
|
||
`show`): otherwise `predicate_fill_mismatch`.
|
||
|
||
A log that passes **conforms**.
|
||
|
||
## 6. Reasons
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `predicate_malformed` | §3.5 |
|
||
| `predicate_needs_report` | §3.5 |
|
||
| `predicate_signature_invalid` | §3.4, §3.5 |
|
||
| `predicate_source_unknown` | §4.6 step 2, §5 step 1 |
|
||
| `predicate_not_targeted` | §5 step 2 |
|
||
| `predicate_cap_breached` | §5 step 2 |
|
||
| `predicate_fill_mismatch` | §5 steps 1 to 3 |
|
||
|
||
## 7. Conformance
|
||
|
||
[`live-conformance/sink-predicate/`](../live-conformance/sink-predicate/) holds the corpus, written
|
||
from fixed seeds by `wai_sink_predicate_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:
|
||
|
||
- **predicates** (§3.4, §3.5): one with blackouts and capped, targeted slots; one reading no state;
|
||
one reading each class alone; one at the bounds of its counts; one written in another order than
|
||
its canonical form; each of the six members that would send something, at the top and nested in
|
||
a slot, a candidate and a cap; `state` short of, beyond and out of the order of what the
|
||
predicate reads; a control of each other rule; and signatures over the wrong bytes (without the
|
||
domain, under the audience decision's domain, over the text as written, over a body edited after
|
||
signing), by another key, flipped and under a key of small order;
|
||
- **evaluations** (§4): each verdict, with no slot filled when it is not `show`; candidates shown
|
||
and passed over by their regions, with a location in a listed subdivision, in a country of which
|
||
only subdivisions are listed, outside, and with none; caps reached by the sink's exposures, by
|
||
an earlier candidate of the same slot and by an earlier slot; exposures at the edges of a
|
||
window and after the slot; a sink that keeps no exposures; an avail used up; and a source the
|
||
sink does not hold;
|
||
- **logs** (§5): logs that conform, among them an empty one; a log presenting a candidate whose cap
|
||
the sink's exposures reached, one presenting two candidates of one counter where its limit is one,
|
||
and one presenting a capped candidate on a sink that keeps no exposures, each
|
||
`predicate_cap_breached`; a log presenting a candidate not shown at the location
|
||
(`predicate_not_targeted`); and logs that pass over a candidate the fill takes, present an ad
|
||
that is not a candidate, present the fill out of order, overrun the avail, fill a withheld
|
||
programme, or name a slot the predicate does not have (`predicate_fill_mismatch`).
|
||
|
||
A conforming implementation gives every case its outcome. `wai_sink_predicate_vectors verify` and
|
||
the test `tests/sink_predicate.rs` check that the file regenerates byte for byte, that `wai-rs`
|
||
gives every case its outcome, and that §6 lists exactly the codes the reference reports.
|
||
`live-conformance/sink_predicate_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, blackouts, their
|
||
aggregation and what follows from them with the audience decision corpus's, and Ed25519 with WAI's
|
||
strict rule (`claim-conformance/_ed25519.py`). It requires every case's outcome and every reason of
|
||
§6; that no evaluation depends on the order of the sink's exposures, on exposures of a counter no
|
||
cap names, or on the order of a document's members; and that each slot's fill, given as a log
|
||
with the state the sink held before that slot, conforms. 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.
|
||
|
||
## 8. Security and privacy considerations
|
||
|
||
- **Nothing leaves the device.** A sink evaluates a predicate from its own location and exposures
|
||
(§4), and its evaluation causes no request. The source learns nothing of where a sink is, what it
|
||
was shown or what it presented. A predicate that names somewhere to send, or a count kept across
|
||
devices, is refused before it is read (§3.5).
|
||
- **A cap is the device's own.** A cap counts the presentations of one device, kept on it. A count
|
||
across a household's or an account's devices would need each to report to the others, and a
|
||
predicate that names one is refused (§3.5).
|
||
- **What a predicate proves.** That the source stated these blackouts and these slots for the
|
||
programme over the span. It does not prove that a rights holder issued the blackouts, as
|
||
audience-decision §9 says of a decision.
|
||
- **Determinism, not enforcement.** Two sinks with the same state reach the same evaluation, and a
|
||
sink's log is checked against it (§5). A sink can state its location or its exposures falsely,
|
||
and nothing here prevents that (unidirectional-delivery §2).
|
||
- **Unresolved is not outside.** A sink with no location has every blackout applied, and is shown
|
||
only candidates shown everywhere (§4.2, §4.3). A sink that keeps no exposures is shown no capped
|
||
candidate (§4.4).
|
||
- **Bounded reading.** Texts hold at most 1,048,576 bytes, documents nest at most 64 deep, and every
|
||
integer is checked against its bound before any arithmetic.
|
||
|
||
## 9. Highlight reels
|
||
|
||
A **reel predicate** (`wai-sink-reel`) is a source's signed statement, travelling beside a
|
||
programme, of the highlight reels a sink may assemble from the **clips** of the programme's marks
|
||
(§8.11 of [packaging](packaging.md)): the runs of whole pieces each moment of the packaging policy
|
||
covers, each with an id and tags. Each reel is a length and a list of candidate clips in the order
|
||
the source prefers them, each with the tags a viewer must follow to want it, the regions it is shown
|
||
in and its caps. A sink assembles each reel itself, from its own state: its location and its
|
||
exposures (§4.1), and its **interests**, the tags its viewer follows, which the device holds and the
|
||
source never learns. Two viewers of one programme are shown two reels, and nothing about either
|
||
leaves the device. This section registers the record `wai.delivery.sink_reel` ([SPEC](../SPEC.md)
|
||
§5): a signed record a WAI tool emits and verifies, which names no payload format and is not
|
||
dispatchable. The reference is the `reel` module of `wai-rs`'s `sink_predicate` (feature
|
||
`sink_reel`); the corpus is [`live-conformance/sink-reel/`](../live-conformance/sink-reel/), and
|
||
its second implementation `live-conformance/sink_reel_verify.py`.
|
||
|
||
### 9.1 The reel predicate
|
||
|
||
A reel predicate is a document of exactly these members:
|
||
|
||
| member | form |
|
||
|---|---|
|
||
| `kind` | `"wai-sink-reel"` |
|
||
| `version` | `1` |
|
||
| `source` | a key |
|
||
| `programme` | a token: the programme |
|
||
| `marks` | the digest of the programme's marks (§8.11.3 of packaging): 64 lowercase hexadecimal digits |
|
||
| `start` | a time: from when it holds |
|
||
| `end` | a time: from when it holds nothing |
|
||
| `state` | the state classes it reads, strictly ascending by their bytes: `"exposures"` exactly when a candidate has a cap, `"interests"` exactly when a candidate wants a tag, and `"location"` exactly when a candidate is shown other than everywhere |
|
||
| `reels` | 1 to 16 reels, in strictly ascending order of their ids' bytes |
|
||
| `sig` | a signature |
|
||
|
||
A **reel** is an object of exactly the members `id`, a token; `max_ms`, an integer from 1 to
|
||
3,600,000, the longest the reel runs, in milliseconds; and `candidates`, 1 to 256 candidates in the
|
||
order the source prefers them, their `clip`s distinct.
|
||
|
||
A **candidate** is an object of exactly the members `clip`, a token, the id of a clip of the marks;
|
||
`duration_ms`, an integer from 1 to its reel's `max_ms`, the clip's length; `want`, 0 to 16
|
||
tokens strictly ascending by their bytes, the tags a viewer must follow one of to want the clip
|
||
(none: every viewer wants it); and `show`, `regions` and `caps`, as §3.3's.
|
||
|
||
Its digest and signature are §3.4's under its own domain: SHA-256 over the canonical form without
|
||
`sig`, and Ed25519 by `source` over `wai:sink-reel` (ASCII) and 0x01, followed by that canonical
|
||
form, verified strictly. A reader is given a text and refuses it as §3.5 refuses a predicate:
|
||
`predicate_malformed` when the text holds more than 1,048,576 bytes or is not a document;
|
||
`predicate_needs_report` when it holds, at any depth, a member §3.5 names (`report`, `beacon`,
|
||
`callback`, `uri`, `household`, `account`); `predicate_malformed` when its members, each reel's and
|
||
each candidate's are not exactly those above, each of its form, `version` is not 1, `start` is not
|
||
below `end`, the reels are not in order of their ids, a reel's clips are not distinct, or `state` is
|
||
not exactly the classes above; and then `predicate_signature_invalid` for its signature.
|
||
|
||
### 9.2 The sink's state
|
||
|
||
A sink's state for a reel predicate is §4.1's (its location, its exposures and its sources) and its
|
||
**interests**: the tags its viewer follows, 0 to 256 tokens held on the device, or **not kept**,
|
||
for a sink that keeps none. The interests are the state class `interests`
|
||
([unidirectional-delivery](unidirectional-delivery.md) §2). A sink also reads its own clock: `now`,
|
||
the instant at which it assembles a reel.
|
||
|
||
### 9.3 Assembly, on the sink
|
||
|
||
A candidate is **wanted** when its `want` is empty, or when the sink keeps interests and one of the
|
||
candidate's `want` is among them. A sink that keeps no interests wants only a candidate that wants no
|
||
tag.
|
||
|
||
A reel is assembled at `now`. With the reel not yet taken starting at `max_ms`, the candidates are
|
||
taken in their order. A candidate is **taken** when it is wanted, it is shown at the location
|
||
(§4.3), it admits at `now` (§4.4), counting with the sink's exposures those that the candidates taken
|
||
before it added, and its `duration_ms` is at most what is left of the reel. A taken candidate adds
|
||
one exposure at `now` for each of its caps' counters, and its `duration_ms` is taken from the reel.
|
||
The reel's **clips** are the taken candidates' clips in the programme's order: by their `start_ms`
|
||
in the marks, and at one `start_ms` in the marks' order of clips. They **take** the sum of their
|
||
durations, and **leave** `max_ms` less that.
|
||
|
||
A sink's **evaluation** of a reel predicate's text at `now`, given the programme's marks as a text,
|
||
in order:
|
||
|
||
1. reads it (§9.1), and refuses with its reason when it does not read;
|
||
2. requires its `source` to be among the sink's sources: otherwise `predicate_source_unknown`;
|
||
3. requires the marks' digest to be the predicate's `marks`, and the marks to read (§8.11.3 of
|
||
packaging): otherwise `reel_marks_mismatch`;
|
||
4. requires each candidate's `clip`, in order of the reels and of their candidates, to be the id of
|
||
a clip of the marks (`reel_clip_unknown`) whose `end_ms` less its `start_ms` is the candidate's
|
||
`duration_ms` and whose tags hold every tag the candidate wants (`reel_clip_mismatch`);
|
||
5. when `now` is at or after `start` and before `end`, assembles each reel in order of the reels,
|
||
each from the sink's exposures and those the earlier reels added; otherwise it assembles none.
|
||
|
||
It gives, for each reel assembled, its `id`, its clips in the order presented, what they take and
|
||
what they leave. The sink presents each clip as the run of pieces its record names (`first_piece`
|
||
and `pieces`, §8.11.3 of packaging), of every component of the package, and keeps the exposures the
|
||
reel adds.
|
||
|
||
**What it reads and what it sends.** The evaluation is a function of the predicate, the marks, and
|
||
the sink's state and clock alone. It **MUST NOT** cause a request, and the interests, the location,
|
||
the exposures and the reels leave the device nowhere (unidirectional-delivery §2). Two sinks holding
|
||
the same state at the same instant assemble the same reels.
|
||
|
||
### 9.4 A sink's log of a reel, checked
|
||
|
||
A sink's **log** of a reel is the clips it presented in it, in order; it stays on the device, as
|
||
§5's does. A checker is given a reel predicate's text, the programme's marks, a sink's state as it
|
||
was before the reel, `now`, a reel's `id` and a log. In order:
|
||
|
||
1. It runs §9.3's steps 1 to 4, refusing with their reason; a predicate with no reel of that `id`
|
||
is refused `predicate_fill_mismatch`. It assembles that reel alone (§9.3), from the exposures
|
||
given, when `now` is in the predicate's span; otherwise the reel is empty.
|
||
2. For each clip of the log, in order, it requires:
|
||
- the clip to be the id of a clip of the marks: otherwise `reel_clip_unknown`;
|
||
- the clip to be a candidate's of the reel, not earlier in the log: otherwise
|
||
`predicate_fill_mismatch`;
|
||
- the candidate to be wanted (§9.3): otherwise `reel_not_wanted`;
|
||
- the candidate to be shown at the location (§4.3): otherwise `predicate_not_targeted`;
|
||
- the candidate to admit at `now` (§4.4), counting with the exposures given those the clips
|
||
before it in the log added: otherwise `predicate_cap_breached`.
|
||
3. It requires the log to be the reel's clips, in the order presented: otherwise
|
||
`predicate_fill_mismatch`.
|
||
|
||
A log that passes **conforms**.
|
||
|
||
### 9.5 Reasons
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `predicate_malformed` | §9.1 |
|
||
| `predicate_needs_report` | §9.1 |
|
||
| `predicate_signature_invalid` | §9.1 |
|
||
| `predicate_source_unknown` | §9.3 step 2, §9.4 step 1 |
|
||
| `reel_marks_mismatch` | §9.3 step 3, §9.4 step 1 |
|
||
| `reel_clip_unknown` | §9.3 step 4, §9.4 steps 1 and 2 |
|
||
| `reel_clip_mismatch` | §9.3 step 4, §9.4 step 1 |
|
||
| `reel_not_wanted` | §9.4 step 2 |
|
||
| `predicate_not_targeted` | §9.4 step 2 |
|
||
| `predicate_cap_breached` | §9.4 step 2 |
|
||
| `predicate_fill_mismatch` | §9.4 steps 1 to 3 |
|
||
|
||
### 9.6 Conformance
|
||
|
||
[`live-conformance/sink-reel/`](../live-conformance/sink-reel/) holds the corpus, written from fixed
|
||
values by `wai_sink_reel_vectors gen`: `vectors.json`, which holds every key and each case's inputs
|
||
and outcome. The programme's marks are derived by `wai-rs`'s `pack_marks` from a stage index the
|
||
generator writes: eight pieces and six moments, four of them tagged with a team and two with none.
|
||
Its keys and regions are as §7's. The cases:
|
||
|
||
- **reel predicates** (§9.1): two reels, capped, wanting tags and targeted; one reading no state;
|
||
one reading each class alone; one at the bounds of its counts; one written in another order than
|
||
its canonical form; each of the six members that would send something, at the top and nested in a
|
||
reel, a candidate and a cap; `state` short of, beyond and out of the order of what it reads; a
|
||
control of each other rule; and signatures over the wrong bytes (without the domain, under the
|
||
sink predicate's domain, over the text as written, over a body edited after signing), by another
|
||
key, flipped and under a key of small order;
|
||
- **evaluations** (§9.3): viewers following one team, the other, both, none, a tag no clip carries,
|
||
and keeping no interests; clips shown and passed over by their regions, at a location and with
|
||
none; caps reached by the sink's exposures and by an earlier reel's clip, exposures at the edge of
|
||
a window and after the instant, and a sink that keeps no exposures; instants before the span and at
|
||
its end; and a source the sink does not hold, other marks, marks that do not read, a clip with no
|
||
record, a clip of another length and a tag its record does not carry;
|
||
- **logs** (§9.4): logs that conform, among them empty ones; a log naming a clip with no record in
|
||
the marks (`reel_clip_unknown`); logs presenting a clip the sink's interests do not want, with
|
||
interests kept and without (`reel_not_wanted`); a clip not shown at the location; clips whose cap
|
||
the sink's exposures reached or that a sink keeping no exposures presents; and logs in the
|
||
source's order rather than the programme's, passing over a clip the reel takes, repeating one,
|
||
presented outside the span, presenting a clip of the marks that is not the reel's, or naming a
|
||
reel the predicate does not have.
|
||
|
||
A conforming implementation gives every case its outcome. `wai_sink_reel_vectors verify` and the
|
||
test `tests/sink_reel.rs` check that the file regenerates byte for byte, that `wai-rs` gives every
|
||
case its outcome, and that §9.5 lists exactly the codes the reference reports.
|
||
`live-conformance/sink_reel_verify.py` is the second implementation, written from this text: it
|
||
shares no code with `wai-rs`; it reads documents, regions, territories, caps and signatures with
|
||
the sink predicate corpus's second implementation, and the marks with the marks corpus's
|
||
(`pack-conformance/marks_verify.py`). It requires every case's outcome and every reason of §9.5;
|
||
that no assembly depends on the order of the sink's exposures or interests or of a document's
|
||
members; and that each reel it assembles, given as a log, conforms. 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.7 Security and privacy considerations
|
||
|
||
- **Nothing about the viewer leaves the device.** A sink's interests, location and exposures, and
|
||
the reels it assembles, stay on it. Every sink receives the same predicate and the same marks, and
|
||
the source learns nothing of what a viewer follows, where it is or what it was shown.
|
||
- **A tag is the packager's, not the viewer's.** A candidate wants only tags its clip's record
|
||
carries (§9.3 step 4), and the record's tags are the packaging policy's moments', so a predicate
|
||
cannot reach a viewer by a tag no clip states. The interests are matched on the device.
|
||
- **What a reel predicate proves.** That the source stated these reels over these marks for the
|
||
programme over the span. The marks bind the clips to the lead's stage index by its digest, whose
|
||
entries name the objects by content hash (packaging §6), so the clips a sink presents are the
|
||
package's bytes.
|
||
- **Determinism, not enforcement.** Two sinks with the same state at the same instant assemble the
|
||
same reels, and a sink's log is checked against them (§9.4). A sink can state its interests
|
||
falsely, and nothing here prevents that, as §8 says of a location.
|
||
- **Bounded reading.** As §8: texts hold at most 1,048,576 bytes, and every count is checked against
|
||
its bound before it is read.
|