diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml
index 1cc19a454..de429a1be 100644
--- a/.github/workflows/wai-conformance.yml
+++ b/.github/workflows/wai-conformance.yml
@@ -102,6 +102,10 @@ on:
# e2ee_verify.py reads this extension's §13 table, and the registry's wire identifiers
# test reads its sections.
- 'wai/extensions/confidentiality.md'
+ # tests/live_ads.rs requires this extension's §10 to list the codes the reference
+ # reports, live_ads_verify.py reads that table, and the registry's wire identifiers test
+ # reads its sections.
+ - 'wai/extensions/live-ads.md'
- 'wai/wai-web/**'
# The audio provenance check below reads the corpora above with this tool.
- 'wai/tools/wai_audio_provenance_check.py'
@@ -771,8 +775,8 @@ jobs:
# live-recording corpus), timed_text_verify.py (the timed-text corpus),
# live_metering_verify.py (the live-metering corpus), energy_steering_verify.py (the
# energy-steering corpus), and live_moderation_verify.py (the live-moderation corpus), and
- # e2ee_verify.py (the end-to-end encrypted rooms corpus), and live_watermark_verify.py (the
- # forensic-watermark corpus).
+ # e2ee_verify.py (the end-to-end encrypted rooms corpus), live_watermark_verify.py (the
+ # forensic-watermark corpus) and live_ads_verify.py (the live-ads corpus).
# Each exits non-zero on any failure or any difference, and the step fails with it.
# One platform: what they check is the reading of the text, not the architecture.
independent-verifiers:
@@ -1064,6 +1068,29 @@ jobs:
(cd wai-rs && cargo test --no-default-features --features live_moderation --lib live_moderation::)
(cd wai-rs && cargo test --no-default-features --features live_moderation --test live_moderation)
python3 -I live-conformance/live_moderation_verify.py live-conformance/moderation
+ # wai.live.ads (extensions/live-ads.md): the reference module's unit tests (policies and
+ # annotations round-trip and their rules refuse, a break's identity, tags, events and
+ # asset lists written exactly), the break form of wai.video.ssai's receipt (its JSON
+ # carriage, each of its rules, its strict signature, the stitched form's legacy pin
+ # unchanged), and tests/live_ads.rs (every file of the committed corpus, vectors.json and
+ # each room's HLS tags, regenerates byte for byte from its fixed seeds and no other file
+ # is there, wai-rs gives every room its stated outcome, and §10 lists exactly the
+ # reference's codes, each of which the corpus gives); then the corpus's second
+ # implementation, live_ads_verify.py, written from the extension's text and sharing no
+ # code with wai-rs, which verifies each room with live_rooms_verify.py, writes every tag,
+ # event and asset list byte for byte, verifies every receipt with WAI's strict Ed25519
+ # rule and BLAKE3 from claim-conformance, reads the extension's reason table, and
+ # requires every mutated copy of the corpus to fail; standard library only, run isolated
+ # (-I).
+ - name: live ads · wai-rs and live_ads_verify.py
+ working-directory: wai
+ shell: bash
+ run: |
+ set -euo pipefail
+ (cd wai-rs && cargo test --no-default-features --features live_ads --lib live_ads::)
+ (cd wai-rs && cargo test --no-default-features --features live_ads --lib wai_video_ssai::)
+ (cd wai-rs && cargo test --no-default-features --features live_ads --test live_ads)
+ python3 -I live-conformance/live_ads_verify.py live-conformance/ads
# wai.live.e2ee (extensions/confidentiality.md): the corpus's second implementation,
# e2ee_verify.py, written from the extension's text and the documents it reads and
# sharing no code with the reference (joule-moq's secure_objects and joule-moq-mls,
diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md
index 2e9d37386..abfdf52f4 100644
--- a/wai/CHANGELOG.md
+++ b/wai/CHANGELOG.md
@@ -6,6 +6,59 @@ wire format may change and such changes are recorded here.
## v0.4.0 — current
+### `wai.live.ads`: a room's cues as server-guided ad breaks, and the break form of `wai.video.ssai`
+
+- **The extension** ([live-ads](extensions/live-ads.md)) registers the record `wai.live.ads`.
+ The room authority signs an **ad policy**, `WAP1`: the grant the ad decider runs under, whose
+ funds ceiling is the room's ad budget; the slots of a break; the avail of a break opened with
+ no planned duration; a preload window; skip and jump restrictions, a skip control and
+ snapping; and the bases of the asset-list and creative URIs.
+- **Breaks from cues.** A room's breaks are its signed `cue` events of `wai.live.events`,
+ unchanged: an out and its in, or a break of known duration; a chapter opens nothing. A
+ post-roll is a break its author annotates with a `WAB1` `meta` event right after it, and ends
+ the program. A verifier refuses an out or a break inside another break, an in with no open
+ out of its `cue_id` and author, a break of no duration, an out with no planned duration when
+ the policy has no open avail, an in whose stated duration is not the one that passed, an
+ annotation that does not read or that follows anything but a break, and any cue after the
+ post-roll; the room refuses a cue by a principal whose grant does not permit `cue`.
+- **Shared manifests.** Each break is one Interstitial `EXT-X-DATERANGE`
+ (`draft-pantos-hls-rfc8216bis-22` Appendix D) with `CUE`, its duration or planned duration,
+ `X-ASSET-LIST`, `X-RESUME-OFFSET`, `X-PLAYOUT-LIMIT`, `X-SNAP`, `X-RESTRICT` and
+ `X-SKIP-CONTROL-OFFSET`, a closing tag at its in, and a Preload date range (Appendix F); and
+ one DASH alternative-presentation event of ISO/IEC 23009-1:2026, a replacement for a mid-roll
+ and an insertion for a post-roll, read from the sixth edition's schema and its examples, with
+ the attributes whose meaning that revision could not state left unwritten. Every value is a
+ function of the signed cue, its annotation, the policy and `t0`, and the `ID` is derived from
+ the cue's digest, so every edge writes the same bytes.
+- **Asset lists.** The one URI a break names is where each viewer is decided: an HLS asset list
+ or a DASH list MPD, written exactly, each creative's URI naming it by its hash, filled by
+ `wai.video.ssai`'s decision over the viewer's pod; a viewer who joins a break running is
+ filled from what remains of it. An empty fill is `{"ASSETS":[]}` in HLS and no list MPD in
+ DASH.
+- **`wai.video.ssai` gains form 2, the break form.** A receipt names the room, the break by its
+ cue's digest, the session, the viewer's offset, the asset list's SHA-256 and format, the
+ avail, the slots and the funds the decision ran with, beside the stitched form's pod hash,
+ impressions, Merkle root, billable spend and energy figure. It is carried as JSON, signs
+ under its own domain (`wai:video-ssai-break-receipt`) and shares the identity domain, so the
+ figure profile `wai:video-ssai-receipt-id` names either form. Every stitched receipt keeps
+ its bytes, and its legacy pin is unchanged.
+- **Verification end to end**: the room, the policy, the schedule, the renderings, then each
+ receipt: its own rules, the decider's key and grant reference, the break, the decider grant's
+ window and revocation, the pod hash, the decision recomputed, the asset list's digest, one
+ receipt per session and break, and the spend of every receipt together within the decider
+ grant's ceiling (prices in millionths of a minor unit of its currency).
+- **Twenty refusal reasons**, from `ad_policy_malformed` to `funds_exceeded`.
+- **Reference and corpus.** `wai-rs` feature `live_ads` (`src/live_ads.rs`), and
+ `SsaiBreakReceipt` in `src/wai_video_ssai.rs`. `live-conformance/ads/`, written from fixed
+ seeds by `wai_live_ads_vectors`: a live break with a viewer at its start and one who joins it
+ running, a mid-roll under restrictions with an empty fill, a program closed by a post-roll,
+ chapters with a planned out, a gap, and one refused room per reason; each accepted room's
+ HLS tags a file of its own. Checked by `tests/live_ads.rs` and by
+ `live-conformance/live_ads_verify.py`, a second implementation that shares no code with
+ `wai-rs`, verifies each room with `live_rooms_verify.py`, writes every tag, event and asset
+ list itself, and requires every mutated copy of the corpus to fail. The registry lists the
+ record and the magics `WAP1` and `WAB1`.
+
### `wai.live.watermark`: forensic watermark sessions, matched and disclosed one at a time
- **The extension** ([forensic-watermark](extensions/forensic-watermark.md)) registers the
diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md
index 8ea53010a..5e28e271c 100644
--- a/wai/REGISTRY.md
+++ b/wai/REGISTRY.md
@@ -41,8 +41,8 @@ Every registered string has one kind.
`wai.asset.provenance`, the quantum-operations receipts, the delivery records
`wai.video.manifest` and its siblings; the live records `wai.live.events`,
`wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`,
- `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee` and
- `wai.live.watermark`; timed text, `wai.text.timed`).
+ `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee`,
+ `wai.live.watermark` and `wai.live.ads`; timed text, `wai.text.timed`).
It names no payload format.
A derivation or record string is not a payload capability, so it is not
@@ -196,6 +196,7 @@ and a role are fixed once registered.
| `wai.image.jpegai` | payload | registered | image | standalone | royalty-free | neural-entropy-exact | — | §5 Neural capabilities |
| `wai.image.jxl` | payload | registered | image | standalone | royalty-free | standard-tolerance | recommended | §5 Recommended modern set |
| `wai.image.png` | payload | registered | image | standalone | royalty-free | standard-defined | floor | §5 Mandatory floor |
+| `wai.live.ads` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.live.compose` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.live.e2ee` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
| `wai.live.events` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
@@ -301,12 +302,14 @@ and a role are fixed once registered.
| `GRANT_CHALLENGE` | setup-option | proposed | live-rooms §10.5.2 |
| `GRANT_PROOF` | setup-option | proposed | live-rooms §10.5.2 |
| `SECURE_OBJECT_KEY_ID` | catalog | defined | confidentiality §4.3 |
+| `WAB1` | magic | defined | live-ads §4.2 |
| `WAI-Room-Challenge` | http-header | proposed | live-rooms §10.5.3 |
| `WAI-Room-Proof` | http-header | proposed | live-rooms §10.5.3 |
| `WAI1` | magic | defined | SPEC §2 |
| `WAI2` | magic | defined | SPEC §7.1 |
| `WAI_INNER_DIGEST` | catalog | defined | confidentiality §4.4 |
| `WAI_RECEIPT` | catalog | defined | moq-streaming-format §5 |
+| `WAP1` | magic | defined | live-ads §3.1 |
| `WCD1` | magic | defined | live-compose §3.1 |
| `WCL1` | magic | defined | live-recording §5.1 |
| `WCN1` | magic | defined | capture-binding §8.1 |
diff --git a/wai/SPEC.md b/wai/SPEC.md
index 3f7c9b209..5ee4fd520 100644
--- a/wai/SPEC.md
+++ b/wai/SPEC.md
@@ -593,9 +593,9 @@ kind:
delivery records `wai.video.manifest`, `wai.video.ssai`, `wai.video.keys`,
`wai.video.package` and `wai.video.channel`; and the live records `wai.live.events`,
`wai.live.room`, `wai.capture.take`, `wai.live.compose`, `wai.live.recording`,
- `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee` and
- `wai.live.watermark`, and
- timed text, `wai.text.timed`), which names no payload format.
+ `wai.live.metering`, `wai.live.steering`, `wai.live.moderation`, `wai.live.e2ee`,
+ `wai.live.watermark` and `wai.live.ads`, and timed text, `wai.text.timed`), which names no
+ payload format.
Only payload, companion and refinement capabilities have a media class, a
fidelity tier, a replicate class and a role. A derivation or record string is not a payload
@@ -719,7 +719,7 @@ registration and makes no conforming envelope non-conforming (§8).
| `wai.video.int_motion` | DETERMINISTIC INTEGER inter-frame — I-frame + integer block motion-compensated P-frames + integer-quantized residual over rANS, no float/ONNX; motion is whole-pel or **half-pel** (`mv_precision`), the latter predicting at the half-grid by integer bilinear interpolation (`(a+b+1)>>1`, `(a+b+c+d+2)>>2`) so real footage — which pans a non-integer number of pixels per frame — costs fewer residual bits while staying byte-exact | byte-identical frame-by-frame on every machine (*frame-state-equivalence*, a per-frame portable hash); the temporal extension of the integer contract, where a float inter-frame decode desyncs down the GOP | payload `WIV1`, keyframe 0; `int_video.rs` (the encode tools write a JSON form a converter reads). Payload: [integer-payloads](extensions/integer-payloads.md) §3; decoded through the integer operator set's stage operators, [integer-operators](extensions/integer-operators.md) §13.5 (`rans64_decode`, `mc_predict`, `residual_step`), with no graph |
| `wai.video.int_hyper` | NEURAL-KEYFRAME inter-frame — the I-frame is the LEARNED integer codec `int_hyper` decoded byte-exact, the P-frames are integer block-MC residuals on top | a learned keyframe you can put in a receipt + integer inter-frames; whole decode integer, byte-identical everywhere | payload `WIV1`, keyframe 1 (a nested `WIH1`); `int_video::decode_pframes_typed` ∘ `int_hyper_synth::decode_conditional_checked`. Payload: [integer-payloads](extensions/integer-payloads.md) §3, its keyframe §4; the keyframe's networks decoded through their lowering, [integer-operators](extensions/integer-operators.md) §13.2 (`whm1/1`), and the P-frames through the operator set's stage operators, §13.5 |
| `wai.video.manifest` | ATTESTED PER-SESSION MANIFEST — the OTT delivery layer, not a codec: a personalised HLS manifest (ads stitched at `EXT-X-DISCONTINUITY`, blackouts + geo enforced) rendered as a **pure function of the session *class*** `(content, entitlement, geo, blackout set, ad pod, window)` rather than the viewer, and sealed in an Ed25519 receipt that binds the JCP grant (capability, joule ceiling, `funds_ceiling` = ad budget), the composition, the stitched ads and the policy applied. A receipt over a WAI playlist MAY carry `render` and `extras`, the rendering parameters and per-segment lines that playlist adds, both bound through `manifest_hash` and through a render digest the signature covers ([http-delivery](extensions/http-delivery.md) §8.6) | **manifest-equivalence**: `manifest_hash == hash(render(segments))` and `session_class_hash` are recomputable by anyone holding the bytes, so the ads, blackouts and compose-work stop being dashboard claims; class-determinism means one generation serves every viewer of the class, and `uj_per_viewer` is the amortised energy that a 1:1-unique generator cannot report below its per-viewer cost. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_manifest.rs` |
-| `wai.video.ssai` | ATTESTED SERVER-SIDE AD INSERTION — a **deterministic** ad decision (greedy by price within the avail duration and the funds remaining) plus a per-impression Ed25519 receipt binding the candidate pod, the manifest it belongs to, the served `creative_hash`es and the VAST quartile each reached | **spend-equivalence**: `budget_spent_micros` is recomputed from the *billable* impressions and rejected if it exceeds the grant's `funds_ceiling` — an overspent receipt fails verification; every creative and quartile is checkable, so a buyer and seller reconcile impressions from the same bytes instead of two logs that only a dashboard joins. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_ssai.rs` |
+| `wai.video.ssai` | ATTESTED SERVER-SIDE AD INSERTION — a **deterministic** ad decision (greedy by price within the avail duration and the funds remaining) plus a per-impression Ed25519 receipt binding the candidate pod, the manifest it belongs to, the served `creative_hash`es and the VAST quartile each reached. The receipt has two forms: form 1, stitched, names an avail of an attested manifest; form 2, break ([live-ads](extensions/live-ads.md) §8), names a break of a room's schedule, the viewer's session and offset into it, and the asset list served, signed under its own domain so that every form-1 receipt keeps its bytes | **spend-equivalence**: `budget_spent_micros` is recomputed from the *billable* impressions and rejected if it exceeds the grant's `funds_ceiling` — an overspent receipt fails verification; every creative and quartile is checkable, so a buyer and seller reconcile impressions from the same bytes instead of two logs that only a dashboard joins. In form 2 the decision is recomputed from the bound pod, avail, slots and funds, and the spend of every receipt of a room together is held to the decider grant's ceiling. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_ssai.rs` |
| `wai.video.keys` | ATTESTED CONTENT-KEY RELEASE — the DRM licensing decision as a record: every key release, and every refusal, is an Ed25519 receipt binding the KID, the DRM system, the entitlement demanded and the window enforced, with a BLAKE3 `key_commit` proving *which* key was released without carrying it. It is one of Lane A's records (§6.5): it covers the release decision, never a frame the key opens | **decision-equivalence**: `verify()` re-runs the release policy over the receipt's own `(grant capability, entitlement, issued, expiry)` and rejects any misreported outcome, and enforces granted-iff-committed — so "keys went only to entitled, unexpired sessions" is checkable against the signed log, not asserted. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_keys.rs` |
| `wai.video.package` | DETERMINISTIC PACKAGING + ATTESTED SEGMENTMAP — a source sliced into content-hashed segments on GOP boundaries; the ordered `(index → content_hash)` map is a signed Merkle commitment, and identical content (ads, bumpers, replays, simulcast overlap) dedups to one stored/encoded copy | **map-equivalence + verifiable dedup**: `segment_map_root` lets a `wai.video.manifest` reference be checked against the package that produced it, and `verify()` recomputes `unique_segments`/`stored_bytes` from the segments so the storage/encode saving cannot be overstated; `uj_per_unique_segment` is the encode-once energy. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable. Version 2 ([packaging](extensions/packaging.md) §9) binds the policy digest and one stage index per component, names each object by its jwp-receipts §2.1 content hash, and covers every media class a packager packs; the string keeps `video` for history | `wai_video_package.rs`, `package.rs` |
| `wai.video.channel` | VIRTUAL LINEAR / FAST ASSEMBLY — a continuous schedule of assets over wall-clock time, sliced into attested playback windows; windows chain via `parent_receipt_hash`, so a channel's history is a hash chain of program-as-run records | **schedule-continuity + recomputable program-as-run**: `verify()` rejects a schedule with a gap or overlap (no dead air, no double-booking) and recomputes `aired` from the schedule and the window, so what actually aired is checkable by a licensor, advertiser or regulator against the signed chain rather than an operator-held play-out log. Only the measurement — `joules_micro`, its acquisition class and the class's evidence — is measured-attested: signed, and not re-measurable | `wai_video_channel.rs` |
@@ -1044,6 +1044,29 @@ record and the grant without learning other sessions. A room that is end-to-end
no edge that can mark it, and a record for one is refused:
[`extensions/forensic-watermark.md`](extensions/forensic-watermark.md).
+And for the **ads** of a room — `wai.live.ads`: server-guided ad insertion that keeps the
+manifests shared. The room authority signs an ad policy, `WAP1`, naming the grant its ad
+decider runs under, whose funds ceiling is the room's ad budget, and how breaks are presented
+(slots, an avail for a break opened with no planned duration, a preload window, skip and jump
+restrictions, a skip control, snapping, and the base URIs of asset lists and creatives). A
+room's breaks are its signed `cue` events of `wai.live.events`, unchanged: an out and its in,
+or a break of known duration, and a post-roll that a `WAB1` annotation in the cue author's
+chain marks; a verifier refuses overlapping breaks, an in with no out, a break of no duration,
+an in that misstates its duration and any cue after the post-roll, and the room refuses a cue
+by a principal whose grant does not permit one. Each break maps, as a pure function of the
+signed records, onto one interstitial date range of HLS (`draft-pantos-hls-rfc8216bis-22`
+Appendix D, its closing tag at the in, and a preload range of Appendix F) and one
+alternative-presentation event of DASH (ISO/IEC 23009-1:2026, replace for a mid-roll and
+insert for a post-roll), with an `ID` derived from the opening cue's digest, so every edge
+writes the same bytes. Each viewer's decision is made behind the one URI a break names: the
+asset list (an HLS asset list, or a DASH list MPD importing each ad's MPD), filled by
+`wai.video.ssai`'s deterministic decision over the viewer's pod and what remains of the break
+for a viewer who joins it running, and sealed in that capability's receipt of form 2, which
+binds the break, the session, the decision's inputs and the asset list served. A verifier
+recomputes the fill and the asset list, refuses a second receipt of one session for one break,
+and holds the spend of every receipt together to the decider grant's ceiling:
+[`extensions/live-ads.md`](extensions/live-ads.md).
+
### Payloads of the WAI-native integer codecs
This revision pins a payload format to each WAI-native integer codec whose row named
diff --git a/wai/extensions/live-ads.md b/wai/extensions/live-ads.md
new file mode 100644
index 000000000..b3adf5d2c
--- /dev/null
+++ b/wai/extensions/live-ads.md
@@ -0,0 +1,715 @@
+# WAI Extension: Live Ads
+
+> Status: Draft. Registers the record `wai.live.ads`: server-guided ad insertion for rooms.
+> The room authority signs an **ad policy**, `WAP1`, naming the grant its ad decider runs
+> under and how the room's breaks are presented. The breaks themselves are the room's signed
+> `cue` events ([live-events](live-events.md) §4.3), an out and its in, or a break of known
+> duration, with a post-roll marked by a **break annotation**, `WAB1`, in the cue author's
+> chain. Each break maps, deterministically, onto one interstitial date range of HLS and one
+> alternative-presentation event of DASH, so every edge writes the same playlist and MPD bytes
+> and the manifests stay shared and cacheable. Per-viewer decisioning happens behind the one
+> URI each break names: the **asset list** a player fetches when it reaches the break, filled
+> by `wai.video.ssai`'s deterministic decision. Each viewer's impressions are sealed in a
+> `wai.video.ssai` receipt of its **break form**, which names the break, the decision's
+> inputs and the asset list served, and a verifier recomputes the decision, the asset list and
+> the spend against the decider grant's funds ceiling. No new transport. The reference
+> implementation is the `live_ads` module of `wai-rs` (feature `live_ads`) with the break form
+> in `wai_video_ssai`; the corpus is [`live-conformance/ads/`](../live-conformance/ads/), and
+> its second implementation is `live-conformance/live_ads_verify.py`.
+> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
+>
+> Texts read for this revision: `draft-pantos-hls-rfc8216bis-22` (1 May 2026), its §4.4.5.1
+> (`EXT-X-DATERANGE`), §6.2.1, §6.2.4, Appendix D (Interstitials) and Appendix F (Preloading
+> HLS Date Range Resources). Its Appendix H is a glossary and defines no date-range schedule,
+> so nothing here maps onto it. ISO/IEC 23009-1:2026, the sixth edition, through the XML
+> schema that accompanies it (its types `EventType`, `AlternativeMPDEventType`,
+> `AlternativeMPDReplaceEventType`, `ImportedMpdType` and `PresentationType`) and the schema's
+> example MPDs G23, G29-1 and G29-2, at the schema's revision of 2026-06-13; the clause text of
+> its §5.16 was not available to this revision (§6.1). A section number of one of these texts
+> is that revision's.
+
+## 1. Scope
+
+In scope: the ad policy, its signature and rules (§3); the room's breaks, derived from its
+`cue` events and break annotations, with their identity and refusals (§4); their HLS
+interstitial date ranges (§5) and their DASH alternative-presentation events (§6); the asset
+list, the decision that fills it, and a viewer joining a break already running (§7); the break
+form of the `wai.video.ssai` receipt (§8); and verification end to end, from the room's events
+to the spend (§9).
+
+Out of scope: a transport; how a decider assembles each viewer's candidate pod (targeting);
+how beacons reach it; the SCTE-35 messages a cue may carry, which WAI carries verbatim and
+never reads (live-events §4.3); the stitched form of `wai.video.ssai`, which is unchanged
+(§8.1); and a pre-roll, which a room does not have: a viewer who joins a running break is
+given what remains of it (§7.4), and one who joins between breaks starts with the program.
+
+Relationship to the core spec: `wai.live.ads` 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.
+
+Relationship to [live-events](live-events.md): this extension changes nothing in it. A break
+is opened and closed by `cue` events as they are; the break annotation rides in the `meta`
+kind (§4.2 there), bound to its cue by the chain, as [timed-text](timed-text.md)'s
+annotation is bound to its caption. Live-events' own date range of each cue (§8.3 there) is
+written beside the interstitial date range of this extension, under another `ID` and another
+`CLASS`.
+
+Relationship to [live-rooms](live-rooms.md): a cue's author is held to its grant: a principal
+whose grant in effect does not permit `cue` is refused by the room verifier,
+`kind_not_permitted`, before this extension reads its cue (live-rooms §9.2 step 4). The
+decider runs under a room grant, whose funds ceiling bounds the room's ad spend (§3.4), and
+names it in its receipts as live-rooms §4.5 has every receipt name a grant.
+
+Why a record of its own, beside `wai.video.ssai`: the policy, the breaks and their renderings
+are verified against a room and its events track, which the stitched form never reads; the
+impressions are `wai.video.ssai`'s, in a second form of its receipt (§8), so that one decision
+rule and one spend rule serve stitched and server-guided insertion alike, and every receipt of
+the stitched form keeps its bytes.
+
+## 2. Terms
+
+- **Room**, **room clock**, **principal**, **event**, **chain**, **grant**, **room key**,
+ **merged order**: as in live-events §2 and live-rooms §2 and §9.2.
+- **Cue**: a `cue` event (live-events §4.3). Its **type** is out (1), in (2), chapter (3) or
+ break (4).
+- **Break**: a span of the room's program that ads replace, opened by a cue of type out or
+ break (§4.3). A break is **open** from an out until its in, and **closed** otherwise.
+- **Post-roll**: a break played after the program ends (§4.2).
+- **Avail**: the duration, in milliseconds, a break's decision fills (§4.3).
+- **Decider**: the principal that decides each viewer's fill and signs the receipts, under
+ the grant the policy names (§3.4).
+- **Pod**: the candidate ads a decision runs over for one viewer and one break, each an
+ `ad_id`, a `creative_hash`, a `duration_ms` and a `price_micros` (§7.1).
+- **Fill**: the ads a decision selects, in slot order.
+- **Asset list**: the response to the request a player makes for a break: an HLS asset list
+ (§7.2) or a DASH list MPD (§7.3).
+- **Session**: one viewer's playback session, named in a receipt by 16 bytes the decider
+ assigns (§8.2).
+
+Integers in the records of §3 and §4 are little-endian, and a digest there is SHA-256 (FIPS
+180-4), 32 bytes. Integers of the receipt's signed bytes (§8) are big-endian and its hashes
+BLAKE3, as the stitched form's are. Ed25519 is RFC 8032's, verified strictly ([SPEC](../SPEC.md)
+§7 *Signatures*): a key that fails step 1 or 2 is `bad_key`, and a signature that fails a
+later step is `bad_signature`. A *long* field is a u16 length and that many bytes.
+
+## 3. The ad policy
+
+### 3.1 Layout
+
+| offset | size | field | meaning |
+|---|---|---|---|
+| 0 | 4 | `magic` | `WAP1` (ASCII) |
+| 4 | 1 | `version` | 1 in this revision |
+| 5 | 32 | `room` | the room id (live-rooms §3.2) |
+| 37 | 32 | `decider_grant` | the digest of the room grant the decider runs under (§3.4) |
+| 69 | 1 | `slots` | the most ads one break holds, 1 to 16 |
+| 70 | 4 | `open_avail_ms` | u32: the avail of a break opened with no planned duration (§4.3); 0: such a break is refused |
+| 74 | 4 | `preload_ms` | u32: how long before a break a player may fetch its asset list (§5.3, §6.2); 0: no preload |
+| 78 | 1 | `restrict` | bit 0 **skip**: no seeking forward in a break; bit 1 **jump**: no seeking past a break unplayed |
+| 79 | 2 | `skip_after_s` | u16: seconds of a break played before a viewer may skip the rest; 65535: no skip control |
+| 81 | 1 | `snap` | bit 0 **out**: leave the program at the segment boundary nearest the break's start; bit 1 **in**: return at the one nearest its end |
+| 82 | long | `asset_base` | the base of every asset-list URI (§7) |
+| … | long | `creative_base` | the base of every creative's URI (§7) |
+| … | 64 | `sig` | §3.2 |
+
+A **base** is `https://` followed by 1 to 492 bytes, each of `!` to `~` (0x21–0x7E) other
+than `"`, `#`, `&`, `'`, `<`, `>`, `?` and `\`, the last of them `/`. A base so holds no
+character that HLS's quoted strings, JSON strings or XML attribute values would have to escape,
+and a URI built on it (§7) is the base followed by a name of the break or of the creative.
+
+### 3.2 Digest and signature
+
+The **digest** of a policy is SHA-256 over its record from `magic` through `creative_base`.
+The **signature** is Ed25519 by the room descriptor's `authority` over the 20 bytes
+`wai:live-ads-policy` (ASCII) and 0x01, followed by the bytes the digest covers.
+
+### 3.3 Reading and rules
+
+A reader reads exactly the bytes it is given as one policy, in the order of §3.1, and refuses
+at the first of these that holds:
+
+1. Fewer than 4 bytes: `truncated`. A magic other than `WAP1`: `malformed`. No byte after the
+ magic: `truncated`. A version other than 1: `unknown_version`.
+2. The bytes end before a field the layout requires: `truncated`.
+3. Bytes after `sig`: `malformed`.
+
+It then refuses as `ad_policy_malformed` a policy whose `slots` is not 1 to 16, whose
+`restrict` or `snap` sets a bit above bit 1, or whose `asset_base` or `creative_base` is not a
+base (§3.1).
+
+### 3.4 The decider
+
+The decider is the `principal` of the grant whose digest is `decider_grant`. That grant is one
+of the room's grants, which the room verifier checks (live-rooms §4.3); it need not be used in
+a `join`, since a decider sends no event. Its `funds_ceiling` and `currency` are the room's ad
+budget: every price and every spend of §7 and §8 is in millionths of a minor unit of
+`currency`, as [live-metering](live-metering.md)'s tariff prices are, so a spend of *s* is
+within a ceiling of *c* minor units when *s* ≤ *c* × 10^6. A `funds_ceiling` of 2^64 − 1
+states no ceiling. The decider's receipts name the grant as live-rooms §4.5 has a receipt name
+one (§9.3).
+
+A grant is good for a break when it is good at the break's `t` (live-rooms §4.3), and it is
+not revoked through the authority's key log (live-rooms §5).
+
+## 4. Breaks
+
+### 4.1 Cues
+
+A cue (live-events §4.3) carries a `type`, a `cue_id`, a `duration_ms` and, optionally, an
+SCTE-35 message this extension never reads. Its `t` is the instant of the splice on the room
+clock: a publisher **MAY** send a cue before that instant, and a cue anchored to a media object
+(live-events §3.1) splices at that object. Every cue the room accepted, of any principal
+permitted `cue`, the room keys among them, is read here, in the merged order (live-rooms §9.2),
+each once.
+
+### 4.2 The break annotation
+
+A break is mid-roll unless its author annotates it as a post-roll. The **annotation** is the
+payload of a `meta` event whose first four bytes are `WAB1`, by the same principal, at the
+cue's `seq` + 1 and at the cue's `t`:
+
+| offset | size | field | meaning |
+|---|---|---|---|
+| 0 | 4 | `magic` | `WAB1` (ASCII) |
+| 4 | 1 | `version` | 1 in this revision |
+| 5 | 32 | `cue` | the digest of the cue it annotates |
+| 37 | 1 | `placement` | 1 mid-roll, 2 post-roll |
+
+An annotation is refused `annotation_malformed` when it is not 38 bytes, its version is not 1,
+or its `placement` is not 1 or 2. It is refused `annotation_misplaced` unless the event before
+it in its author's chain, at `seq` − 1, is a cue of type break, at the same `t`, whose digest
+is `cue`.
+
+A **post-roll** is the break of an annotation of placement 2. It ends the program: after it,
+the room has no break and no cue (§4.3, `cue_after_close`), and a room that closes ends with
+it. A player plays it after the program has ended (§5.1, §6.2).
+
+### 4.3 The schedule
+
+The **schedule** is the room's breaks and chapters, in the order of the cues that open them.
+Over the cues and annotations in the merged order, holding the open break (at most one) and
+the end of the last closed break, a verifier refuses, at the record:
+
+- any cue after a post-roll's annotation: `cue_after_close`;
+- **out** (1): when a break is open, or `t` is before the end of the last closed break,
+ `break_overlap`. Otherwise it opens a break: its **planned** duration is `duration_ms`, and
+ its avail is `duration_ms`, or the policy's `open_avail_ms` when `duration_ms` is 0; with
+ both 0, `break_duration_unknown`;
+- **in** (2): unless the open break was opened by an out of this `cue_id` and of the in's
+ author, `cue_in_unmatched`. The break's **duration** is ⌊(`t` − the out's `t`) / 1000⌋
+ milliseconds: when it is 0, `break_zero_duration`; when the in's `duration_ms` is not 0 and
+ is not the duration, `cue_duration_mismatch`. The break is then closed, and its end is the
+ in's `t`;
+- **chapter** (3): a chapter of the schedule, its `t` and its `duration_ms` (0 when not known).
+ A chapter opens and closes nothing, and may fall inside a break;
+- **break** (4): when `duration_ms` is 0, `break_zero_duration`; when a break is open, or `t`
+ is before the end of the last closed break, `break_overlap`. Otherwise it is a closed break
+ whose duration and avail are `duration_ms`, ending at `t` + `duration_ms` × 1000;
+- an **annotation** (§4.2): read and placed, or refused; of placement 2, its break is the
+ post-roll.
+
+A break open at the end is stated open: the room may still be live. A cue of the room that
+does not read is the room's refusal, and an event with a gap before it is a delivery gap the
+room states (§9.2 step 1).
+
+### 4.4 A break's identity
+
+A break is named by the digest of the cue that opens it. From that digest:
+
+- its HLS **ID** is `wai-ad-` followed by the digest's 64 lowercase hexadecimal digits;
+- its DASH **event id** is the digest's first eight bytes read as a big-endian u64, of which
+ the low 53 bits are kept, so that a player that holds an id as a double holds it exactly. A
+ receiver identifies a break by its digest; the id lets a player tell one event from another
+ in an EventStream, where a repeat of 53 bits among a room's breaks is possible but not
+ expected.
+
+Every edge holding the room's events derives the same identity, and no other event of the room
+has it: live-events' own date range of the cue has the digest alone as its `ID`.
+
+## 5. HLS: interstitial date ranges
+
+### 5.1 The interstitial tag
+
+Each break is one Interstitial `EXT-X-DATERANGE` tag (`draft-pantos-hls-rfc8216bis-22`
+§4.4.5.1, Appendix D.2), written into every Media Playlist of the room when its cue is known.
+Its attributes are written in this order, separated by commas with no spaces, each only where
+its row says:
+
+| attribute | value |
+|---|---|
+| `ID` | quoted: the break's ID (§4.4) |
+| `CLASS` | quoted: `com.apple.hls.interstitial`, the class Appendix D.2 defines |
+| `START-DATE` | quoted: `t0` + `t` of the opening cue, as `YYYY-MM-DDTHH:MM:SS.mmmZ` in UTC, the milliseconds floored (live-events §8.3) |
+| `CUE` | quoted: `ONCE` for a mid-roll, `POST,ONCE` for a post-roll (§4.4.5.1) |
+| `DURATION` | a break of type 4: its duration as decimal seconds with three digits after the point (`30.000`) |
+| `PLANNED-DURATION` | an out with a planned duration: that duration, as `DURATION` |
+| `X-ASSET-LIST` | quoted: the break's HLS asset-list URI (§7.2) |
+| `X-RESUME-OFFSET` | a mid-roll of type 4: its duration, as `DURATION` |
+| `X-PLAYOUT-LIMIT` | a break of type 4: its duration, as `DURATION` |
+| `X-SNAP` | when `snap` is not 0: quoted, `OUT`, `IN` or `OUT,IN` by its bits |
+| `X-RESTRICT` | when `restrict` is not 0: quoted, `SKIP`, `JUMP` or `SKIP,JUMP` by its bits |
+| `X-SKIP-CONTROL-OFFSET` | when `skip_after_s` is not 65535: `skip_after_s` in decimal (Appendix D.3) |
+
+- `ONCE` keeps a viewer who seeks back over a break in a window of the room from being shown
+ it again; `POST` plays the post-roll once the program has played to its end, which for a
+ room is its playlist's `EXT-X-ENDLIST`.
+- `X-RESUME-OFFSET` equal to the break's duration returns the viewer to the program where the
+ break ends on the room's timeline, whatever the fill's length, so the program a viewer
+ returns to is the program after the in. A post-roll has nothing to resume, and no
+ `X-RESUME-OFFSET`.
+- An out whose end is not yet known has no `DURATION`, `X-RESUME-OFFSET` or
+ `X-PLAYOUT-LIMIT`: the closing tag adds them (§5.2).
+
+### 5.2 The closing tag
+
+When an out's in closes it, the break gains a second tag with the same `ID`, as §6.2.1 has a
+server signal the actual end of a date range with a planned duration, and as §4.4.5.1 lets a
+later tag add attributes to a date range:
+
+| attribute | value |
+|---|---|
+| `ID` | quoted: the break's ID |
+| `CLASS` | quoted: as §5.1 |
+| `DURATION` | the break's duration (§4.3), as §5.1 writes durations |
+| `X-RESUME-OFFSET` | the same value |
+| `X-PLAYOUT-LIMIT` | the same value |
+
+No attribute of the first tag changes: the closing tag adds three it did not have, and repeats
+`CLASS` with its value. A player inside the break when the closing tag arrives ends the break
+at the playout limit, and returns to the program where the in placed its end.
+
+### 5.3 The preload tag
+
+When the policy's `preload_ms` is not 0, each break also has a Preload `EXT-X-DATERANGE` tag
+(Appendix F, Appendix D.2), written just before its interstitial tag:
+
+| attribute | value |
+|---|---|
+| `ID` | quoted: the break's ID followed by `-preload` |
+| `CLASS` | quoted: `com.apple.hls.preload`, the class Appendix F defines |
+| `START-DATE` | quoted: the interstitial's `START-DATE` less `preload_ms`, in its form |
+| `DURATION` | `preload_ms`, as §5.1 writes durations |
+| `X-URI` | quoted: the break's HLS asset-list URI (§7.2) |
+| `X-TARGET-ID` | quoted: the break's ID |
+| `X-TARGET-CLASS` | quoted: the interstitial class |
+
+A player picks a random instant in the preload range and fetches the asset list there
+(Appendix D.2), so the requests of a room's audience for one break are spread over
+`preload_ms` rather than arriving together at its start.
+
+### 5.4 What stays shared
+
+Every attribute of §5.1 to §5.3 is a function of the signed cue, its annotation, the signed
+policy and the room's `t0`, and of nothing a viewer supplies. Every Media Playlist of the room
+therefore carries the same date ranges with the same attributes, as §6.2.4 requires, and every
+edge that holds the room's events writes the same bytes, so playlists stay cacheable. The one
+URI a break names is where each viewer's decision is made (§7), and the request for it carries
+what makes it a viewer's: the `_HLS_primary_id` and `_HLS_start_offset` query parameters
+Appendix D.4 has a player add.
+
+A playlist that carries these tags carries `EXT-X-PROGRAM-DATE-TIME` on the same UTC timeline
+as `t0` (§4.4.5.1). The corpus states the tags of each room in the order a packager writes
+them: a break's preload and interstitial tags at its cue, its closing tag at its in.
+
+## 6. DASH: alternative presentation events
+
+### 6.1 What this revision read
+
+ISO/IEC 23009-1:2026 defines alternative presentation events in its §5.16, whose text this
+revision could not read. It read the sixth edition's MPD schema and its example MPDs. From
+them it takes: the event schemes `urn:mpeg:dash:event:alternativeMPD:replace:2025` and
+`urn:mpeg:dash:event:alternativeMPD:insert:2025` (example G23 and G29-1 use the first); an
+`Event`'s `presentationTime`, `duration` and `id`; its child `ReplacePresentation` or
+`InsertPresentation`, with the attributes `uri` (required), `earliestResolutionTimeOffset`,
+`maxDuration`, `executeOnce`, `skipAfter` and, for a replacement, `returnOffset`, with their
+types; and the **list MPD**, an MPD of `type` `list` whose Periods each import one MPD
+(`ImportedMPD`). The values below are written from those types and examples. Where the
+schema has an attribute whose meaning this revision could not state, it does not write it:
+`noJump`, `serviceDescriptionId`, `startWithOffset` and `clip`, whose defaults stand. A later
+revision checks every value here against §5.16.
+
+### 6.2 The event
+
+A mid-roll is an `Event` of the replace scheme; a post-roll is one of the insert scheme, since
+after the program there is nothing to replace. With `timescale` 1000 on its EventStream:
+
+| attribute | value |
+|---|---|
+| `presentationTime` | `pt0` + ⌊`t` / 1000⌋, `t` the opening cue's, where `pt0` is the presentation time of `t0` in milliseconds on the Period's timeline (live-events §8.4) |
+| `duration` | when the break's duration is known (a break of type 4, or an out its in has closed): that duration in milliseconds |
+| `id` | the break's event id (§4.4) |
+
+and its one child, `ReplacePresentation` for a mid-roll and `InsertPresentation` for a
+post-roll:
+
+| attribute | value |
+|---|---|
+| `uri` | the break's DASH asset-list URI (§7.3) |
+| `earliestResolutionTimeOffset` | when `preload_ms` is not 0: `preload_ms` |
+| `maxDuration` | when the duration is known: the duration in milliseconds |
+| `executeOnce` | `true`, as HLS's `ONCE` |
+| `skipAfter` | `PT`, then `skip_after_s` in decimal, then `S`; with no skip control, the avail in whole seconds rounded up (`PT30S` for 30 000 ms), so that no viewer skips before the avail has played: the schema's examples read a missing `skipAfter` as a break a viewer may skip at once |
+| `returnOffset` | a `ReplacePresentation` whose duration is known: the duration in milliseconds, as HLS's `X-RESUME-OFFSET` |
+
+Attributes are written in the order of the two tables, each as `name="value"`, the element as
+``, with no whitespace other than one space before
+each attribute. An out's event is written first without `duration`, `maxDuration` and
+`returnOffset`, and the MPD that follows its in writes the same event with them: an MPD update
+gives the event its end as HLS's closing tag does (§5.2). A planned duration has no DASH
+attribute.
+
+### 6.3 The event streams
+
+The Period that holds the room's program carries, after every break of the schedule is
+written:
+
+1. when the schedule has a mid-roll, ``,
+ the mid-rolls' events in schedule order, and ``;
+2. when it has a post-roll, the same with the insert scheme and the post-roll's event.
+
+Every value is a function of the signed records, the policy and `pt0`, so the MPD stays one
+for every viewer, as the playlists do (§5.4).
+
+## 7. The asset list
+
+### 7.1 The decision
+
+A player that reaches a break fetches its asset list. The decider fills it for that viewer with
+the decision of `wai.video.ssai` (SPEC §5), unchanged:
+
+- its inputs are the viewer's pod, the policy's `slots`, an **avail** and the **funds
+ remaining**: the break's avail (§4.3) less the viewer's start offset (§7.4), or 0 when the
+ offset reaches it; and the funds the decider states it had left of the decider grant's
+ ceiling, at most that ceiling (§3.4);
+- the candidates are taken in order of `price_micros` descending, ties by `ad_id` ascending
+ (as bytes); each is taken when fewer than `slots` are taken, its `duration_ms` is at most the
+ avail not yet taken and its `price_micros` at most the funds not yet taken; the fill is the
+ taken candidates in that order, slot 0 first.
+
+The fill is a pure function of those inputs, so anyone holding them recomputes it, and the
+receipt binds each of them (§8).
+
+### 7.2 HLS asset list
+
+The break's **HLS asset-list URI** is `asset_base`, the break's ID, `.json?_HLS_interstitial_id=`
+and the ID again: the query parameter Appendix D.4 has a packager add. The response is the
+JSON object Appendix D.2 defines, written exactly:
+
+```
+{"ASSETS":[{"URI":".m3u8","DURATION":},…]}
+```
+
+one Asset-Description for each ad of the fill in slot order, `` its 64
+lowercase hexadecimal digits and `` its `duration_ms` as decimal seconds with three digits
+after the point, with no whitespace and no byte after the closing brace. An empty fill is
+`{"ASSETS":[]}`, which has a player resume the program without playing an ad (Appendix D.5).
+The asset list carries no `SKIP-CONTROL`: the tag's attributes stand.
+
+A creative's URI names it by its hash, so the asset list a viewer is served binds the creative
+bytes the receipt names.
+
+### 7.3 DASH list MPD
+
+The break's **DASH asset-list URI** is `asset_base`, the break's ID and `.mpd`. The response is
+a list MPD, written exactly as the XML declaration ``, a
+line feed, and:
+
+```
+.mpd…
+```
+
+one Period for each ad of the fill, its `id` the slot in decimal and `` as §7.2, with no
+whitespace other than that shown and no byte after ``. The profile and `minBufferTime`
+are those of the schema's list-MPD example (G29-2). An MPD has at least one Period, so an empty
+fill has no list MPD: the decider answers the request with no body, and the receipt binds the
+empty asset list, zero bytes (§8.2).
+
+### 7.4 Joining a break
+
+A viewer who reaches a break after its start, by joining the room inside it, has its player
+add the offset into the break at which it starts (`_HLS_start_offset`, Appendix D.4). The
+decider fills the remaining avail only (§7.1), and the receipt states the offset
+(`start_offset_ms`). A viewer who joins between breaks is given no break: a room has no
+pre-roll. The asset list a joining viewer is served is a fill like any other, and every check
+of §9.3 applies to it.
+
+## 8. The break form of the `wai.video.ssai` receipt
+
+### 8.1 Forms
+
+A `wai.video.ssai` receipt has two forms:
+
+- **form 1, stitched**: an avail of an attested manifest, named by `avail_id` and
+ `manifest_hash`, as SPEC §5 registers it. This revision changes nothing in it: its signed
+ bytes, its identity and every receipt of it stay as they were;
+- **form 2, break**: a break of a room's schedule and the asset list one viewer was served for
+ it, defined here.
+
+The two forms sign under different domains (§8.4), so no signature of one is a signature of
+the other, and they share the impression, the pod hash, the Merkle root, the billable spend and
+the identity domain, so a figure profile of energy-measurement §6.1,
+`wai:video-ssai-receipt-id`, names either.
+
+### 8.2 Fields and their JSON carriage
+
+A form-2 receipt is carried as one JSON object (RFC 8259) with exactly these members; a byte
+field is lowercase hexadecimal of its exact length, and an integer a JSON integer from 0 to
+2^64 − 1:
+
+| member | value |
+|---|---|
+| `form` | 2 |
+| `room` | 32 bytes: the room id |
+| `cue` | 32 bytes: the break's digest (§4.4) |
+| `session` | 16 bytes: the session (§2), one value for every break of one playback session, and no other's |
+| `start_offset_ms` | the viewer's offset into the break (§7.4); 0 for a viewer present at its start |
+| `format` | from 0 to 255: 1, the HLS asset list (§7.2); 2, the DASH list MPD (§7.3) |
+| `asset_list_sha256` | 32 bytes: SHA-256 of the asset list served, its exact bytes |
+| `avail_ms` | the break's avail (§4.3) |
+| `slots` | the policy's `slots` |
+| `funds_remaining_micros` | the funds the decision ran with (§7.1) |
+| `pod_hash` | 32 bytes: §8.3 |
+| `impressions` | an array: each filled slot, `{"slot", "ad_id", "creative_hash", "duration_ms", "price_micros", "quartiles_reached"}`, in slot order; `ad_id` a string, `creative_hash` 32 bytes, `quartiles_reached` 0 to 255 |
+| `merkle_root` | 32 bytes: §8.3 |
+| `budget_spent_micros` | the billable spend (§8.3) |
+| `work_decide_ops` | the candidates the decision considered |
+| `joules_micro` | the decision's energy, or 0 unmetered |
+| `energy_provenance` | its class's label (energy-measurement §2.7: `ModelBased`, `Estimator`, `OnChipCounter`, `CalibratedInstrument`), or `null` unmetered |
+| `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | only for a class that carries them, as energy-measurement §2.7 names and writes them: `{"relative_ppm", "window_us"}`, `relative_ppm` from 0 to 2^32 − 1; `Unknown`, `Off` or `On`; a string |
+| `grant` | `{"grant_hash", "capability", "joule_ceiling_micro", "funds_ceiling"}`: the grant reference (live-rooms §4.5), `funds_ceiling` `null` for none |
+| `signer_pubkey` | 32 bytes: the decider's Ed25519 key |
+| `signer_id` | a string: the decider's `did:key` or origin-bound id |
+| `sig` | 64 bytes: §8.4 |
+
+`quartiles_reached` is the stitched form's: 0 never rendered, 1 started (the billable
+impression), 2 to 5 a quarter, a half, three quarters and all of it. A receipt that does not
+read so, or that carries the legacy class label `HwShunt` (energy-measurement §2.6), is
+refused `receipt_malformed`.
+
+### 8.3 Hashes
+
+BLAKE3, with integers big-endian, as the stitched form computes them:
+
+- the **pod hash**: over `wai:video-ssai-pod` 0x01, the candidates' count (u64), and for each
+ candidate in the pod's order, `ad_id`'s length (u32) and bytes, `creative_hash`,
+ `duration_ms` (u64) and `price_micros` (u64);
+- an impression's **leaf**: over `wai:video-ssai-imp` 0x01, `slot` (u64), `ad_id`'s length
+ (u32) and bytes, `creative_hash`, `duration_ms` (u64), `price_micros` (u64) and
+ `quartiles_reached` (u8);
+- the **Merkle root** over the leaves in slot order: [jwp-receipts](jwp-receipts.md) §2.2's
+ tree, each leaf taken as a content hash (its first leaf form) and the final one duplicated
+ on an odd level; all zero for no impression;
+- the **billable spend**: the sum of `price_micros` over the impressions whose
+ `quartiles_reached` is at least 1.
+
+### 8.4 Signing payload and identity
+
+The **signing payload** is the concatenation of:
+
+1. `wai:video-ssai-break-receipt` (ASCII) and 0x01;
+2. `room`, `cue`, `session`, `start_offset_ms` (u64), `format` (u8), `asset_list_sha256`,
+ `avail_ms` (u64), `slots` (u64), `funds_remaining_micros` (u64);
+3. `pod_hash`, `merkle_root`, `budget_spent_micros` (u64), `work_decide_ops` (u64),
+ `joules_micro` (u64), then the class in its signed encoding (energy-measurement §2.7), the
+ tag byte 0 for none;
+4. the grant reference: `grant_hash`, the length (u32) and bytes of `capability`,
+ `joule_ceiling_micro` (u64), then 1 and `funds_ceiling` (u64), or 0 for none;
+5. the impressions' count (u64), then each one's leaf;
+6. `signer_pubkey`, then the length (u32) and bytes of `signer_id`.
+
+`sig` is Ed25519 by `signer_pubkey` over the signing payload. The receipt's **identity**, its
+receipt hash, is BLAKE3 over `wai:video-ssai-receipt-id` 0x01, the signing payload and `sig`:
+the stitched form's identity domain, over a payload whose first bytes no stitched payload has.
+
+### 8.5 Its own rules
+
+A form-2 receipt holds, on its own:
+
+1. `format` is 1 or 2;
+2. its signature verifies (`bad_key`, `bad_signature`);
+3. `merkle_root` is the root of its impressions, `budget_spent_micros` their billable spend,
+ and when `grant`'s `funds_ceiling` is not `null`, the spend is within it (§3.4);
+4. the impressions' slots are 0, 1, 2, … in order, each `quartiles_reached` is at most 5, and
+ `work_decide_ops` is at least their count;
+5. the figure and its class cohere as energy-measurement §2 has them: `joules_micro` 0 with no
+ class, or above 0 with a class, a hardware class's declared uncertainty with
+ `relative_ppm` and `window_us` above 0 and at the resolution floor (`joules_micro` ×
+ `relative_ppm` at least 288 676), and a calibrated instrument's reference holding a
+ character that is not Unicode White_Space.
+
+A rule of 1, 3, 4 or 5 that fails is `receipt_invalid`. These rules are what a buyer holding
+one receipt checks alone; §9.3 adds what the room, the policy and the pod let a verifier check.
+
+## 9. Verification
+
+### 9.1 Inputs
+
+An ads verifier is given what a room verifier is given (live-rooms §9.1: a descriptor, grants,
+a key-log head or none, and the chains), and:
+
+- **policy**: the room's ad policy;
+- **pt0**: the presentation time of `t0`, in milliseconds on the Period's timeline (§6.2);
+- **receipts**: form-2 receipts in JSON (§8.2), in a list, each with the **pod** its decision
+ ran over, the candidates in the order the decider gave them.
+
+### 9.2 Steps
+
+It runs these steps and stops at the first refusal, which it reports with the reason and
+where:
+
+1. **The room** (live-rooms §9): a refusal is the ads verifier's, at `room` with the room
+ verifier's own place. A gap is the outcome **gap**: a schedule read from part of the cues
+ is not the room's, so the verifier states the gaps and nothing more.
+2. **The policy**, at `policy`: it reads (§3.3: `truncated`, `malformed`, `unknown_version`,
+ `ad_policy_malformed`); its `room` is the room id (`wrong_room`); its signature verifies
+ under the descriptor's `authority` (`bad_key`, `bad_signature`); `decider_grant` is the
+ digest of a grant the room verifier holds (`decider_grant_unknown`); and that grant is not
+ revoked through the key log the room verifier read (live-rooms §5, `grant_revoked`).
+3. **The schedule** (§4.2, §4.3), over the events the room accepted in the merged order, each
+ once: the refusal, at the record's chain and its index in that chain.
+4. **The renderings** (§5, §6): the tags, the events and the asset-list URIs of every break.
+ No refusal.
+5. **The receipts**, in the list's order, each by §9.3: the refusal, at `receipt` and its
+ index.
+6. **The outcome** (§9.4).
+
+### 9.3 Each receipt
+
+For receipt *k*, with its pod, in this order:
+
+1. It reads as §8.2 has it: `receipt_malformed`.
+2. Its `room` is the room id: `wrong_room`.
+3. Its own rules (§8.5): `bad_key`, `bad_signature` or `receipt_invalid`.
+4. `signer_pubkey` is the decider (§3.4): `receipt_not_decider`.
+5. `grant` is the decider grant's reference: `grant_hash` its digest, `capability`
+ `wai.live.room`, `joule_ceiling_micro` its `joules_ceiling`, and `funds_ceiling` its
+ `funds_ceiling`, or `null` when that is 2^64 − 1 (live-rooms §4.5):
+ `receipt_grant_mismatch`.
+6. `cue` names a break of the schedule: `receipt_break_unknown`.
+7. The decider grant is good at the break's `t` (§3.4): `outside_grant_window`.
+8. `pod_hash` is the pod hash (§8.3) of the pod given: `pod_mismatch`.
+9. `avail_ms` is the break's avail, `slots` the policy's, and the impressions, less
+ `quartiles_reached`, are the fill §7.1 gives for the pod, the slots, the avail less
+ `start_offset_ms` and `funds_remaining_micros`: `decision_mismatch`.
+10. `asset_list_sha256` is SHA-256 of the asset list of the fill in the receipt's `format`
+ (§7.2, §7.3): `asset_list_mismatch`.
+11. No receipt before it in the list names this `cue` and this `session`: `receipt_duplicate`.
+ A viewer is filled once for a break.
+12. **Spend-equivalence.** `funds_remaining_micros` is within the decider grant's ceiling, and
+ so is the billable spend of this receipt and every receipt before it, together (§3.4):
+ otherwise `funds_exceeded`.
+
+Step 12 holds the room's ad spend to one ceiling across every viewer and break, in the order the
+receipts are given. A decision's stated funds are the decider's statement; that the spend of
+every receipt together stays within the ceiling is checked, whatever was stated.
+
+### 9.4 Outcomes and what a verification states
+
+- **refuse**: the reason and where (§9.2).
+- **gap**: the room has a gap: its gaps, under live-events' code `delivery_gap`.
+- **accept**: no refusal and no gap.
+
+With accept, the verifier states:
+
+- each break, in schedule order: its digest, ID and event id, its author, `cue_id`, `t` and
+ type (out or break), its placement, its planned duration and avail, and, once closed by an
+ in, the in's digest, its end and its duration; or that it is open;
+- each chapter: its digest, `t` and `duration_ms`;
+- the HLS tags, in the order of §5.4; each break's HLS and DASH asset-list URIs; the DASH event
+ streams (§6.3), and each out's event as it was written before its in;
+- each receipt: its identity (§8.4), the asset list it binds, written by the verifier, and its
+ billable spend; and the spend of every receipt together.
+
+## 10. Reasons
+
+The codes this extension adds, each with where it is raised:
+
+| reason | raised at |
+|---|---|
+| `ad_policy_malformed` | §3.3, §9.2 step 2 |
+| `decider_grant_unknown` | §9.2 step 2 |
+| `annotation_malformed` | §4.2 |
+| `annotation_misplaced` | §4.2 |
+| `cue_after_close` | §4.2, §4.3 |
+| `break_overlap` | §4.3 |
+| `break_duration_unknown` | §4.3 |
+| `break_zero_duration` | §4.3 |
+| `cue_in_unmatched` | §4.3 |
+| `cue_duration_mismatch` | §4.3 |
+| `receipt_malformed` | §8.2, §9.3 step 1 |
+| `receipt_invalid` | §8.5, §9.3 step 3 |
+| `receipt_not_decider` | §9.3 step 4 |
+| `receipt_grant_mismatch` | §9.3 step 5 |
+| `receipt_break_unknown` | §9.3 step 6 |
+| `pod_mismatch` | §9.3 step 8 |
+| `decision_mismatch` | §9.3 step 9 |
+| `asset_list_mismatch` | §9.3 step 10 |
+| `receipt_duplicate` | §9.3 step 11 |
+| `funds_exceeded` | §9.3 step 12 |
+
+An ads verifier also raises every code a room verifier raises (live-rooms §11 and the
+live-events codes it lists), at the room: `kind_not_permitted` among them for a cue or an
+annotation by a principal whose grant does not permit its kind; `truncated`, `malformed` and
+`unknown_version` for a policy that does not read; `wrong_room`, `bad_key` and `bad_signature`
+for a policy or a receipt of another room or whose signature fails; `grant_revoked` and
+`outside_grant_window` for the decider grant (§9.2 step 2, §9.3 step 7); and `delivery_gap` for
+a gap, which is not a refusal.
+
+## 11. Conformance
+
+[`live-conformance/ads/`](../live-conformance/ads/) holds the corpus, written from fixed seeds
+by `wai_live_ads_vectors gen`: `vectors.json`, and each accepted room's HLS tags as a file of
+its own, `NAME.m3u8`, one tag a line in the order of §5.4. It has:
+
+- **Rooms**, each a room (descriptor, grants, key-log head or none, chains), its policy, `pt0`
+ and its receipts with their pods, and the outcome §9.4 gives them with everything it states:
+ - a live break, an out with no planned duration and its in, a viewer present at its start
+ filled from an HLS asset list, and a viewer who joins it running, filled from a DASH list
+ MPD with what remains of it;
+ - a mid-roll of known duration under a policy that restricts skipping and jumping, snaps
+ both ways and offers a skip control, with a viewer whose pod nothing fits;
+ - a program with chapters, a mid-roll, and a post-roll at its close;
+ - chapters, and an out with a planned duration closed by its in;
+ - one refused room for each reason of §10, each a control of one defect; one for each
+ other rule of §8.5, each refused `receipt_invalid`; a cue by a principal whose grant does
+ not permit `cue`, which the room refuses `kind_not_permitted`; and a decider grant revoked
+ through the key log, and one whose window ends before the break;
+ - a room with a gap.
+
+A conforming verifier gives every room the outcome, the reason, the place and everything else
+the corpus states, and every tag file byte for byte. `wai_live_ads_vectors verify` and the test
+`tests/live_ads.rs` check that every file regenerates byte for byte, that `wai-rs` gives every
+case its outcome, that every reason of §10 appears, and that §10 lists exactly the reference's
+codes. `live-conformance/live_ads_verify.py` is the second implementation, written from this
+text: it shares no code with `wai-rs`; it verifies each room with the live-rooms corpus's
+second implementation (`live_rooms_verify.py`), every signature with WAI's strict rule
+(`claim-conformance/_ed25519.py`) and every BLAKE3 hash with `claim-conformance/_blake3.py`; it
+writes every tag, event and asset list from §5 to §7 and requires their bytes; and it mutates
+copies of the corpus and requires every copy to fail.
+
+## 12. Security and privacy considerations
+
+- Every break is a cue its author signed, under a grant that permits `cue`; a relay or an edge
+ can withhold a cue, which is a gap in its author's chain, but cannot add or move a break. The
+ date ranges and events an edge writes are recomputable from the signed records, and a
+ playlist whose interstitials differ from them is one the edge altered.
+- The decision is a pure function of its inputs, and every input is bound: the pod by its
+ hash, the avail by the break, the slots by the policy, the funds by the receipt, and the
+ asset list served by its digest, whose URIs name each creative by its hash. A decider that
+ serves an ad the decision did not select, or a creative other than the one it bills, signs a
+ receipt that fails §9.3.
+- The funds a decision states are the decider's statement. What is checked is the bound: no
+ receipt spends past the ceiling, and no receipts together do, so an overspent room fails
+ verification at the receipt that crosses the ceiling.
+- `quartiles_reached` is the decider's statement of the beacons it received; a receipt binds it
+ and its billing, not the beacons. The impressions a buyer and a seller reconcile are the same
+ signed bytes.
+- A receipt names a session by 16 bytes the decider assigns. A decider **SHOULD** assign a
+ value with no meaning outside its own records, and **MUST NOT** derive it from a viewer's key
+ or another identifier a third party holds, so that receipts do not link a viewer across
+ rooms; within a room, one session's receipts are linkable to one another by design, so that a
+ viewer is filled once for a break (§9.3 step 11).
+- The asset-list request carries the viewer's playback-session parameter (Appendix D.4) to the
+ decider, and nowhere else: the manifests every viewer shares carry nothing of any viewer.
+- The policy is signed by the room authority and the receipts by the decider. A decider whose
+ grant is revoked or out of its window has its receipts refused (§9.2, §9.3).
diff --git a/wai/extensions/live-events.md b/wai/extensions/live-events.md
index 177c453ac..0bb625358 100644
--- a/wai/extensions/live-events.md
+++ b/wai/extensions/live-events.md
@@ -34,8 +34,9 @@ Out of scope: a transport; the room descriptor, which fixes a room's id and its
the room grant, which carries a principal's policy (both belong to a later extension);
the contents of a `take_commit` (§4.12), which [capture binding](capture-binding.md)
§5 defines; moderation
-workflow, which [live-moderation](live-moderation.md) defines; and how a sink renders any
-event.
+workflow, which [live-moderation](live-moderation.md) defines; the ad breaks a room's cues
+open, and their interstitials and asset lists, which [live-ads](live-ads.md) defines; and how a
+sink renders any event.
Relationship to the core spec: `wai.live.events` is a **record** ([SPEC](../SPEC.md) §5):
a signed record a WAI tool emits and verifies. It names no payload format and is not
diff --git a/wai/live-conformance/README.md b/wai/live-conformance/README.md
index fa15c30de..eff884d3d 100644
--- a/wai/live-conformance/README.md
+++ b/wai/live-conformance/README.md
@@ -14,6 +14,7 @@ and the independent verifier of each, beside it.
| [`metering/`](metering/) | [live-metering](../extensions/live-metering.md) (`wai.live.metering`) | `live_metering_verify.py` |
| [`steering/`](steering/) | [energy-steering](../extensions/energy-steering.md) (`wai.live.steering`) | `energy_steering_verify.py` |
| [`moderation/`](moderation/) | [live-moderation](../extensions/live-moderation.md) (`wai.live.moderation`) | `live_moderation_verify.py` |
+| [`ads/`](ads/) | [live-ads](../extensions/live-ads.md) (`wai.live.ads`) | `live_ads_verify.py` |
| [`e2ee/`](e2ee/) | [confidentiality](../extensions/confidentiality.md) (`wai.live.e2ee`) | `e2ee_verify.py` |
| [`watermark/`](watermark/) | [forensic-watermark](../extensions/forensic-watermark.md) (`wai.live.watermark`) | `live_watermark_verify.py` |
@@ -22,11 +23,12 @@ and the independent verifier of each, beside it.
`takes/`, `wai_live_compose_vectors gen` writes `compose/`, `wai_live_record_vectors gen`
writes `recordings/`, `wai_timed_text_vectors gen` writes `captions/` and
`wai_live_metering_vectors gen` writes `metering/`, `wai_energy_steering_vectors gen` writes
-`steering/`, `wai_live_moderation_vectors gen` writes `moderation/` and
-`wai_live_watermark_vectors gen` writes `watermark/`, each from records authored in code, from
-fixed seeds (`events/README.md`, `rooms/README.md`, `takes/README.md`, `compose/README.md`,
-`recordings/README.md`, `captions/README.md`, `metering/README.md`, `steering/README.md`,
-`moderation/README.md`, `watermark/README.md`). `joule_moq_e2ee_vectors gen` (in
+`steering/`, `wai_live_moderation_vectors gen` writes `moderation/`,
+`wai_live_watermark_vectors gen` writes `watermark/` and `wai_live_ads_vectors gen` writes
+`ads/`, each from records authored in code, from fixed seeds (`events/README.md`,
+`rooms/README.md`, `takes/README.md`, `compose/README.md`, `recordings/README.md`,
+`captions/README.md`, `metering/README.md`, `steering/README.md`, `moderation/README.md`,
+`watermark/README.md`, `ads/README.md`). `joule_moq_e2ee_vectors gen` (in
`joule-code/joule-code-rs/crates/joule-moq-mls`) writes `e2ee/vectors.json` from fixed seeds,
and `e2ee/` also holds the test vectors RFC 9605 and `draft-ietf-moq-secure-objects-01`
publish, as printed (`e2ee/README.md`). One recorded record: `metering/relay-record.wmh` and
@@ -46,6 +48,7 @@ python3 timed_text_verify.py captions
python3 live_metering_verify.py metering
python3 energy_steering_verify.py steering
python3 live_moderation_verify.py moderation
+python3 live_ads_verify.py ads
python3 e2ee_verify.py e2ee
python3 live_watermark_verify.py watermark
```
diff --git a/wai/live-conformance/ads/README.md b/wai/live-conformance/ads/README.md
new file mode 100644
index 000000000..087f687b8
--- /dev/null
+++ b/wai/live-conformance/ads/README.md
@@ -0,0 +1,106 @@
+# live-conformance/ads — live ads
+
+The corpus of [`extensions/live-ads.md`](../../extensions/live-ads.md) §11: rooms whose `cue`
+events open ad breaks, each a room (its `WRD1` descriptor, `WRG1` grants, key-log head or none,
+and each principal's `WEV1` chain, the break annotations `WAB1` among them), its `WAP1` ad
+policy, the presentation time of `t0` on the DASH Period (`pt0_ms`), and the break-form
+`wai.video.ssai` receipts of its viewers, each with the pod its decision ran over; what a
+verifier states of them; and each accepted room's HLS tags, byte for byte.
+
+**Ads equivalence.** For each vector, a conforming ads verifier given its `descriptor`,
+`grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts` and `pods` (§9.1) reaches the
+outcome the vector states (§9.4):
+
+- `refuse`: the `reason` and where, `at`: `["room", …]` with the room verifier's own place
+ (live-rooms §9.1), `["policy"]`, `["record", c, i]` for record `i` of chain `c`, or
+ `["receipt", k]`;
+- `gap`: the room's `gaps`, each `principal`, `first` and `last`;
+- `accept`: the `room` id; the `breaks` (§4.3), each its `digest`, `id` and `event_id` (§4.4),
+ `author`, `cue_id`, `t`, `type` (`out` or `break`), `placement` (`mid` or `post`),
+ `planned_ms`, `avail_ms`, `duration_ms`, `closed_by` and `end` (`null` while it is open),
+ and its `hls_list_uri` and `dash_list_uri` (§7.2, §7.3); the `chapters`, each `digest`, `t`
+ and `duration_ms`; `hls`, the file holding the room's HLS tags; `dash`, the Period's event
+ streams (§6.3); `dash_open`, each out's event as written before its in, by the out's index
+ among the `breaks`; `receipt_facts`, each receipt's `identity` (§8.4), the `asset_list` it
+ binds as the verifier writes it, and its `spend_micros`; and `spend_micros`, the spend of
+ every receipt together.
+
+Each tag file, `NAME.m3u8`, is the HLS tags of vector `NAME` (§5.1 to §5.3), one tag a line in
+the order §5.4 gives, each line ended by a line feed. It is the date ranges of the room's Media
+Playlists, not a whole playlist: the segments are the room's tracks.
+
+A verifier that reaches another outcome, reason, place, break, tag byte, event byte, asset-list
+byte, identity or spend does not conform.
+
+**Provenance.** Generated here: `wai_live_ads_vectors gen` writes every file from rooms
+authored in code, from fixed seeds. Each principal's key is SHA-256 of a fixed label and the
+principal's index; each creative's hash and each session id likewise; every time is a
+constant (the rooms' `t0` is 2026-10-16T00:00:00Z); every origin and base URI is an
+`.example` name (RFC 2606); and Ed25519 signing is deterministic (RFC 8032), so every file is
+the same bytes on every machine. The generator asserts that each vector reaches the outcome it
+was written to have, and that the corpus gives every reason of §10, before it writes. No
+recorded data.
+
+## Layout
+
+- `vectors.json`: `principals` (the keys, by name, informative) and `vectors`.
+- `NAME.m3u8`: the HLS tags of each accepted room.
+
+Every room has a producer, whose grant permits `cue`, `meta` and `chat`; a viewer, whose grant
+permits `chat`; and the decider, whose grant permits no event and carries the room's ad budget,
+2 minor units of EUR (2 000 000 millionths), and which never joins. The clock admits the
+producer and the viewer at the room's start.
+
+The rooms:
+
+- `live_break`: an out at 60 s with no planned duration and its in at 90 s, under a policy
+ whose open avail is 30 s and whose preload is 4 s. One viewer, present at the break's start,
+ is filled with two 15 s ads from an HLS asset list (its decision energy model-based); a
+ second joins 12 s into the break and is filled with what remains, one ad, from a DASH list
+ MPD (its decision energy an on-chip counter's reading). No pre-roll.
+- `mid_roll`: a 30 s break, annotated mid-roll, under a policy that restricts skipping and
+ jumping, snaps both ways and offers a skip control after 5 s, with `pt0_ms` 5000. One
+ viewer's pod has two ads at one price, ordered by `ad_id`, and three fill the avail; another
+ viewer's only ad costs more than its funds, so its fill is empty and its DASH list MPD is no
+ bytes.
+- `post_roll`: two chapters, a 15 s mid-roll at 30 s, and a 20 s break annotated post-roll at
+ 120 s; one viewer filled in both breaks, another in the post-roll only.
+- `chapters`: a chapter of a stated length and one of none, then an out with a planned
+ duration of 15 s and its in, which states no duration.
+- `gap`: `live_break`'s room as a subscriber holds it when the out was not delivered.
+- refused, one defect each: `cue_kind_not_permitted` (refused by the room,
+ `kind_not_permitted`), `policy_slots_zero`, `policy_wrong_room`, `policy_bad_signature`,
+ `decider_grant_unknown`, `decider_grant_revoked` (a room whose key log revokes the decider
+ grant, `grant_revoked`), `annotation_placement`, `annotation_on_out`, `cue_after_post_roll`,
+ `overlapping_breaks`, `open_break_no_avail`, `break_of_no_duration`, `in_without_out`,
+ `in_duration_mismatch`, `receipt_extra_member`, `receipt_wrong_room`,
+ `receipt_bad_signature`, `receipt_quartile_six` and, for the other rules of §8.5 each
+ broken alone, `receipt_format_three`, `receipt_work_below_fill`,
+ `receipt_figure_without_class`, `receipt_below_resolution` and `receipt_over_its_ceiling`
+ (all `receipt_invalid`), `receipt_not_decider`,
+ `receipt_grant_mismatch`, `receipt_names_the_in`, `decider_grant_expired`
+ (`outside_grant_window`), `pod_mismatch`, `decision_mismatch`, `asset_list_mismatch`,
+ `receipt_duplicate` and `funds_exceeded` (a budget of 1 minor unit, and two viewers each
+ billed 600 000 millionths).
+
+## Running
+
+```
+cd ../../wai-rs
+cargo run --no-default-features --features live_ads --bin wai_live_ads_vectors -- verify ../live-conformance/ads
+cargo run --no-default-features --features live_ads --bin wai_live_ads_vectors -- gen ../live-conformance/ads # regenerate
+cargo test --no-default-features --features live_ads --test live_ads
+cd ../live-conformance
+python3 -I live_ads_verify.py ads
+```
+
+`wai_live_ads_vectors verify` and `tests/live_ads.rs` require every file to regenerate byte for
+byte, no other file but this one to be there, and `wai-rs` to give every case its outcome; the
+test also requires §10 to list exactly the reasons the reference reports, and the generator
+that the corpus gives each of them. `live_ads_verify.py` is the second implementation, written
+from the extension's text and sharing no code with `wai-rs`: it verifies each room with
+`live_rooms_verify.py`, writes every tag, event and asset list itself, verifies every receipt's
+signature with WAI's strict Ed25519 rule and computes its BLAKE3 hashes from the specification,
+reads §10 from the extension, gives every case the same outcome, requires every reason of §10
+to appear, and then mutates copies of the corpus, one defect each, each of which must fail.
+Pass = an exit status of 0 from each.
diff --git a/wai/live-conformance/ads/chapters.m3u8 b/wai/live-conformance/ads/chapters.m3u8
new file mode 100644
index 000000000..8d6f4d1af
--- /dev/null
+++ b/wai/live-conformance/ads/chapters.m3u8
@@ -0,0 +1,2 @@
+#EXT-X-DATERANGE:ID="wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:01:10.000Z",CUE="ONCE",PLANNED-DURATION=15.000,X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2.json?_HLS_interstitial_id=wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2"
+#EXT-X-DATERANGE:ID="wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",CLASS="com.apple.hls.interstitial",DURATION=15.000,X-RESUME-OFFSET=15.000,X-PLAYOUT-LIMIT=15.000
diff --git a/wai/live-conformance/ads/live_break.m3u8 b/wai/live-conformance/ads/live_break.m3u8
new file mode 100644
index 000000000..3311075f2
--- /dev/null
+++ b/wai/live-conformance/ads/live_break.m3u8
@@ -0,0 +1,3 @@
+#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:56.000Z",DURATION=4.000,X-URI="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",X-TARGET-CLASS="com.apple.hls.interstitial"
+#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:01:00.000Z",CUE="ONCE",X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0"
+#EXT-X-DATERANGE:ID="wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",CLASS="com.apple.hls.interstitial",DURATION=30.000,X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000
diff --git a/wai/live-conformance/ads/mid_roll.m3u8 b/wai/live-conformance/ads/mid_roll.m3u8
new file mode 100644
index 000000000..2cf673070
--- /dev/null
+++ b/wai/live-conformance/ads/mid_roll.m3u8
@@ -0,0 +1 @@
+#EXT-X-DATERANGE:ID="wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:02:00.000Z",CUE="ONCE",DURATION=30.000,X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895.json?_HLS_interstitial_id=wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000,X-SNAP="OUT,IN",X-RESTRICT="SKIP,JUMP",X-SKIP-CONTROL-OFFSET=5
diff --git a/wai/live-conformance/ads/post_roll.m3u8 b/wai/live-conformance/ads/post_roll.m3u8
new file mode 100644
index 000000000..652e762fe
--- /dev/null
+++ b/wai/live-conformance/ads/post_roll.m3u8
@@ -0,0 +1,4 @@
+#EXT-X-DATERANGE:ID="wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:00:28.000Z",DURATION=2.000,X-URI="https://ads.rooms.example/sgai/wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836.json?_HLS_interstitial_id=wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",X-TARGET-ID="wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",X-TARGET-CLASS="com.apple.hls.interstitial"
+#EXT-X-DATERANGE:ID="wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:00:30.000Z",CUE="ONCE",DURATION=15.000,X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836.json?_HLS_interstitial_id=wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",X-RESUME-OFFSET=15.000,X-PLAYOUT-LIMIT=15.000,X-RESTRICT="JUMP",X-SKIP-CONTROL-OFFSET=10
+#EXT-X-DATERANGE:ID="wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d-preload",CLASS="com.apple.hls.preload",START-DATE="2026-10-16T00:01:58.000Z",DURATION=2.000,X-URI="https://ads.rooms.example/sgai/wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d.json?_HLS_interstitial_id=wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",X-TARGET-ID="wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",X-TARGET-CLASS="com.apple.hls.interstitial"
+#EXT-X-DATERANGE:ID="wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-16T00:02:00.000Z",CUE="POST,ONCE",DURATION=20.000,X-ASSET-LIST="https://ads.rooms.example/sgai/wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d.json?_HLS_interstitial_id=wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",X-PLAYOUT-LIMIT=20.000,X-RESTRICT="JUMP",X-SKIP-CONTROL-OFFSET=10
diff --git a/wai/live-conformance/ads/vectors.json b/wai/live-conformance/ads/vectors.json
new file mode 100644
index 000000000..ba05de684
--- /dev/null
+++ b/wai/live-conformance/ads/vectors.json
@@ -0,0 +1,3138 @@
+{
+ "about": "extensions/live-ads.md §11: rooms whose cues open ad breaks, their ad policies, the HLS interstitials and DASH alternative-presentation events each break maps onto, each viewer's asset list and break-form wai.video.ssai receipt, and the spend against the decider grant's ceiling",
+ "principals": {
+ "authority": "98925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed4",
+ "clock": "96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f99",
+ "producer": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "viewer": "5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf",
+ "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "stranger": "934b90192cc4c745a8147599c6a594678ee3a9993cade600fafecd8d23ba78c7"
+ },
+ "vectors": [
+ {
+ "name": "live_break",
+ "about": "an out with no planned duration and its in 30 s later: the policy's open avail is decided; a viewer at the break's start is filled from an HLS asset list, and one who joins 12 s into it from a DASH list MPD with what remains; no pre-roll",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "0f041639e63167e41cc74c151102c01b",
+ "start_offset_ms": 12000,
+ "format": 2,
+ "asset_list_sha256": "0c1b410693e1a2fb79b2c2d8c0bb3ec16baa450f8a4936464675dc2c1ac768a7",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 1930000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ }
+ ],
+ "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09",
+ "budget_spent_micros": 40000,
+ "work_decide_ops": 3,
+ "joules_micro": 900,
+ "energy_provenance": "OnChipCounter",
+ "energy_uncertainty": {
+ "relative_ppm": 30000,
+ "window_us": 2000000
+ },
+ "energy_counter_filtering": "Unknown",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "506ea6b499543a9e55efb78bd7f5f60595dc49f7bfc066b504f59989cd2cfeeca26efcb4a1b809ec2fd17a11bd0a43b5d0b31a701ec9e1c72e363a928e4dc602"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "accept",
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "breaks": [
+ {
+ "digest": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "id": "wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "event_id": 5068147555119423,
+ "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "cue_id": 101,
+ "t": 60000000,
+ "type": "out",
+ "placement": "mid",
+ "planned_ms": 0,
+ "avail_ms": 30000,
+ "duration_ms": 30000,
+ "closed_by": "e97d8be15b595855a21ac9fc5a5820d64303f96f71d860c587cbb4069ac08b6b",
+ "end": 90000000,
+ "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.json?_HLS_interstitial_id=wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0.mpd"
+ }
+ ],
+ "chapters": [],
+ "hls": "live_break.m3u8",
+ "dash": "",
+ "dash_open": [
+ {
+ "break": 0,
+ "event": ""
+ }
+ ],
+ "receipt_facts": [
+ {
+ "identity": "1c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95",
+ "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000},{\"URI\":\"https://cdn.rooms.example/creatives/ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814.m3u8\",\"DURATION\":15.000}]}",
+ "spend_micros": 70000
+ },
+ {
+ "identity": "64cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da8345",
+ "asset_list": "\nhttps://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.mpd",
+ "spend_micros": 40000
+ }
+ ],
+ "spend_micros": 110000
+ },
+ {
+ "name": "mid_roll",
+ "about": "a 30 s break, annotated mid-roll, under a policy that restricts skipping and jumping, snaps both ways and offers a skip control after 5 s: three ads fill one viewer's avail (two at one price, ordered by ad_id); another viewer's only candidate costs more than its funds, so its fill is empty and its DASH list MPD is no bytes",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000000e27070000000000020a000407000000307500000073ba37ad9204b810d1fb44049c44f81bc1ab1cfcce2dbc515506d7e532c6b9f8fb62ed341a307ff51718c66bee80e45da37c050ddba291be29ca311ca246180e",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895000e270700000000000126005741423101b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895017cc9df4cf8521ebdc5dd7e2cfe807daaba6002b610a55b104e68ec16d0c212e22dc5f44044c302d2301a48708394500a7d621642811b8b0e4bc5adef73d72a03"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237030000000000000000030500031f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732ff412d473047444c452427332d6233b5a64f41d17d4dfb582a9ec7de9ac58f9a05b30b255c797b19cee174b774f7172f098998c520bff658b937b9102906a2c0a",
+ "pt0_ms": 5000,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",
+ "session": "b0b3896c34bfe7250bd38546e62ef63f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "ed10443758dd144e6e8be2ba9257cc698661fbfd81a2698dd9cf79dc40085d14",
+ "avail_ms": 30000,
+ "slots": 3,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "63597ba8002a28b1dad1b579d836afc4a5de884b094a6a1807072baf1a8ba17b",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-d",
+ "creative_hash": "5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475",
+ "duration_ms": 10000,
+ "price_micros": 50000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-e",
+ "creative_hash": "6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f",
+ "duration_ms": 10000,
+ "price_micros": 50000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 2,
+ "ad_id": "ad-g",
+ "creative_hash": "97741c3ac04ec2bf35cab401743a57c68351d5063881a5340853103fe43f2fdf",
+ "duration_ms": 5000,
+ "price_micros": 10000,
+ "quartiles_reached": 2
+ }
+ ],
+ "merkle_root": "de9bb230f7ad4e69e93bc60ec62154ed96e15e62bafee613acbcb75bbeb4da62",
+ "budget_spent_micros": 110000,
+ "work_decide_ops": 4,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "91a005aa8c48d8eaafb1a88b02e2bbc742e3949ec9ad2c19fd59f90913710fff1ea18871cb9d40282a0aa1c90851c53872ba8ab5177fe023f75a66c1dcd93700"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",
+ "session": "b7aafeb758115b214b15e0722ec1848f",
+ "start_offset_ms": 0,
+ "format": 2,
+ "asset_list_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "avail_ms": 30000,
+ "slots": 3,
+ "funds_remaining_micros": 50000,
+ "pod_hash": "a2815ecefd54ef052037a416a00fac002c679ec449efaa4463de26ba6ff4ab33",
+ "impressions": [],
+ "merkle_root": "0000000000000000000000000000000000000000000000000000000000000000",
+ "budget_spent_micros": 0,
+ "work_decide_ops": 1,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "9ee4575767cc99394943645c000e5b96636dfd57a1ed661af7a0aeb3cda986abb51af83106435343c7540fb54996d59c10e1df5cc5df323f4994aa81f9422409"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-g",
+ "creative_hash": "97741c3ac04ec2bf35cab401743a57c68351d5063881a5340853103fe43f2fdf",
+ "duration_ms": 5000,
+ "price_micros": 10000
+ },
+ {
+ "ad_id": "ad-e",
+ "creative_hash": "6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f",
+ "duration_ms": 10000,
+ "price_micros": 50000
+ },
+ {
+ "ad_id": "ad-f",
+ "creative_hash": "6e1c485a0a892288745d4e42d7f84d2a6badfd10562d9b64883c35c2b1c61249",
+ "duration_ms": 15000,
+ "price_micros": 45000
+ },
+ {
+ "ad_id": "ad-d",
+ "creative_hash": "5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475",
+ "duration_ms": 10000,
+ "price_micros": 50000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-h",
+ "creative_hash": "983f1091e2ac46c0581547de74600643dec577c96c273c5bdbae3855f8a91bbd",
+ "duration_ms": 10000,
+ "price_micros": 60000
+ }
+ ]
+ ],
+ "outcome": "accept",
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "breaks": [
+ {
+ "digest": "b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",
+ "id": "wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",
+ "event_id": 4628904484385540,
+ "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "cue_id": 7,
+ "t": 120000000,
+ "type": "break",
+ "placement": "mid",
+ "planned_ms": 30000,
+ "avail_ms": 30000,
+ "duration_ms": 30000,
+ "closed_by": null,
+ "end": 150000000,
+ "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895.json?_HLS_interstitial_id=wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895",
+ "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-b4d071f6cf7db304a33e1ceeb94785cf65b30e2413c021a57c24070686e89895.mpd"
+ }
+ ],
+ "chapters": [],
+ "hls": "mid_roll.m3u8",
+ "dash": "",
+ "dash_open": [],
+ "receipt_facts": [
+ {
+ "identity": "acb47777d1f660e462ea28e63a186c4f8d665b0435bf778f1b43f7d77ed620e9",
+ "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475.m3u8\",\"DURATION\":10.000},{\"URI\":\"https://cdn.rooms.example/creatives/6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f.m3u8\",\"DURATION\":10.000},{\"URI\":\"https://cdn.rooms.example/creatives/97741c3ac04ec2bf35cab401743a57c68351d5063881a5340853103fe43f2fdf.m3u8\",\"DURATION\":5.000}]}",
+ "spend_micros": 110000
+ },
+ {
+ "identity": "ae16e5dfa1c752f9325b1c6dcc672284166116cee6c71b320f70165369e4c387",
+ "asset_list": "",
+ "spend_micros": 0
+ }
+ ],
+ "spend_micros": 110000
+ },
+ {
+ "name": "post_roll",
+ "about": "a program of two chapters with a 15 s mid-roll, closed by a 20 s post-roll at 120 s: the post-roll is an insertion played after the program ends; one viewer is filled in both breaks, another in the post-roll only",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000040420f000000000000020a00030100000000000000004b39829b021ed49fdb26a44907258d1550c54d72fc5cd17a2ed3c20cb36d62574245ea610bd5891280ac17046b9764b18facd9afb6efcfea16616b2321b9d109",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000bebe936b7b40ce3243994f7397c4e5454f8091cebbcc9db5b7433f68e3df7ed980c3c9010000000000020a000402000000983a000000770aaafb8a7ba4d80206309925dabb1f4e910c625925f9ed116a80bae328953e7e51960a2d41fb680fc76311b1d015a77d61a901e016da22478469455fe8740c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e902000000000000007b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed83680f0fa020000000000020a00030300000000000000003a4823ea2ee47ff8dfbc49a807b523cd2a509fae1d89ce264aa527288977b2701e14e5920dad8af6cf9900d398d1cea3ccb0f2383f9d3e562186383c04260c0d",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90300000000000000e1b65c919765a570f58a2b836ebf972688200dc67b4662cff597ee3d5bddca73000e27070000000000020a000404000000204e000000bfea7186b6cc84620bd3096528c25f0356d9d06de7408edb6e5c42f585a7d8827f477618b0cf6f0d6cdf1725038f25b377d098d604f7cc67be9056be2f773b06",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e904000000000000009900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d000e2707000000000001260057414231019900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d022995af4ffa22a68cd66ba9d12edbf3789436df9eadc82e3f8cfdb9d418615a1ddb185fdc64341d3082feaea3bafd913535642088a6ea03fb5787758a485c0509"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000d0070000020a00001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fa57be1b05aafc2300929652f001c4660e72ada1e64936c409afe10f8963e4843a077e8de06643329b4be0ff36700f15fbe30d60c17d4c7c3e1f918dbdde7b80d",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",
+ "session": "430d5a160a41cc22d5d1a9cb80cd328c",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad",
+ "avail_ms": 15000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ }
+ ],
+ "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09",
+ "budget_spent_micros": 40000,
+ "work_decide_ops": 3,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "802b2147a30e04f32e2a137ce76144f0d9866ac4049f1f09d8ce489fa02c6c10bd68cf1d0509b42c936a6f5cb552ab444ccbcd606548ecea3fc0d594cdd70b0f"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",
+ "session": "430d5a160a41cc22d5d1a9cb80cd328c",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad",
+ "avail_ms": 20000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 4
+ }
+ ],
+ "merkle_root": "e4d8f92a96b60a0fe0af513e4276dbde35953b09c64f5c36f9811b92ee602dce",
+ "budget_spent_micros": 40000,
+ "work_decide_ops": 3,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "6bd8a7c6d0011f324bc2b69ad8684904bf848dd3d425308e439d23399da092f73c8e146a507e6e823d993660b2670800f8433dd2e3048313aa2f1e7138155f0c"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",
+ "session": "4551e2b0e8aaf1839bae756fc06c6138",
+ "start_offset_ms": 0,
+ "format": 2,
+ "asset_list_sha256": "142b0f0cc473638717eecf1e6e5301264ec2f8e8bbedc4148af7fe1eaa5f6c4d",
+ "avail_ms": 20000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "63597ba8002a28b1dad1b579d836afc4a5de884b094a6a1807072baf1a8ba17b",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-d",
+ "creative_hash": "5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475",
+ "duration_ms": 10000,
+ "price_micros": 50000,
+ "quartiles_reached": 1
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-e",
+ "creative_hash": "6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f",
+ "duration_ms": 10000,
+ "price_micros": 50000,
+ "quartiles_reached": 0
+ }
+ ],
+ "merkle_root": "1fa5ab4c2fca960ab3024d591e79e7c9b5538cd963ef558721c11c8138e9cca3",
+ "budget_spent_micros": 50000,
+ "work_decide_ops": 4,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "e65a7861e0c0f993821796871b971ca33714cc293a312863baafd9084edce6fa2e91850197bb8a3f4cbd5fe64aaf75cf9aed2863f32fb2820262cd520f2a150d"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-g",
+ "creative_hash": "97741c3ac04ec2bf35cab401743a57c68351d5063881a5340853103fe43f2fdf",
+ "duration_ms": 5000,
+ "price_micros": 10000
+ },
+ {
+ "ad_id": "ad-e",
+ "creative_hash": "6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f",
+ "duration_ms": 10000,
+ "price_micros": 50000
+ },
+ {
+ "ad_id": "ad-f",
+ "creative_hash": "6e1c485a0a892288745d4e42d7f84d2a6badfd10562d9b64883c35c2b1c61249",
+ "duration_ms": 15000,
+ "price_micros": 45000
+ },
+ {
+ "ad_id": "ad-d",
+ "creative_hash": "5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475",
+ "duration_ms": 10000,
+ "price_micros": 50000
+ }
+ ]
+ ],
+ "outcome": "accept",
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "breaks": [
+ {
+ "digest": "7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",
+ "id": "wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",
+ "event_id": 7505217605500235,
+ "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "cue_id": 2,
+ "t": 30000000,
+ "type": "break",
+ "placement": "mid",
+ "planned_ms": 15000,
+ "avail_ms": 15000,
+ "duration_ms": 15000,
+ "closed_by": null,
+ "end": 45000000,
+ "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836.json?_HLS_interstitial_id=wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836",
+ "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-7b3aa9f4a5567d4b5874a57115d6b3b7656f857fbdf73f4c8cb86b1c647ed836.mpd"
+ },
+ {
+ "digest": "9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",
+ "id": "wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",
+ "event_id": 186936416143654,
+ "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "cue_id": 4,
+ "t": 120000000,
+ "type": "break",
+ "placement": "post",
+ "planned_ms": 20000,
+ "avail_ms": 20000,
+ "duration_ms": 20000,
+ "closed_by": null,
+ "end": 140000000,
+ "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d.json?_HLS_interstitial_id=wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d",
+ "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-9900aa0486ae09264795cd65d94f8b4931b671d035856b9746b16f7f3390e61d.mpd"
+ }
+ ],
+ "chapters": [
+ {
+ "digest": "bebe936b7b40ce3243994f7397c4e5454f8091cebbcc9db5b7433f68e3df7ed9",
+ "t": 1000000,
+ "duration_ms": 0
+ },
+ {
+ "digest": "e1b65c919765a570f58a2b836ebf972688200dc67b4662cff597ee3d5bddca73",
+ "t": 50000000,
+ "duration_ms": 0
+ }
+ ],
+ "hls": "post_roll.m3u8",
+ "dash": "",
+ "dash_open": [],
+ "receipt_facts": [
+ {
+ "identity": "f14dd6ea6fdf325deb2b9c4441e625672f3070dce28bb69c69dc32892f5d8254",
+ "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000}]}",
+ "spend_micros": 40000
+ },
+ {
+ "identity": "e3d6a7f489813de358504064701cc4dce26f5f1b79eb8c300bffcba3e272ab31",
+ "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000}]}",
+ "spend_micros": 40000
+ },
+ {
+ "identity": "456e27ca685d8487b9639eb8add2135992fa3d6a3473eb4d33b922983fc3c238",
+ "asset_list": "\nhttps://cdn.rooms.example/creatives/5aff736691177f33a953c5624421323c93e0708b01a283f43d61314baeea6475.mpdhttps://cdn.rooms.example/creatives/6ea5b5c39fe8fcd4e1fb754d11042d5554c7b2b74d4b3676146a635cd611e73f.mpd",
+ "spend_micros": 50000
+ }
+ ],
+ "spend_micros": 130000
+ },
+ {
+ "name": "chapters",
+ "about": "two chapters, the first of a stated length, then an out with a planned duration of 15 s and its in, which states no duration: the interstitial carries the planned duration and its closing tag the actual one",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000040420f000000000000020a00030a00000060ea00000041accd40acba52d1abcd009b17420ff7fc20a2b455a0cadba25ea30cb94c85f17973b5c54658b4aa0f27a2d069f26ff1da7899450e94a2af30dd68d62a9f180b",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9010000000000000074a069792bd2a9e635c9ac862a7d7e51e1d9977a88942bfcd61036cdbb26544540c9a2030000000000020a00030b0000000000000000255d8c2f893da89b67620180a5a3a3e25dc3a0d5338f83d4f5a3206b93b752fa132673f94ca3d6cc949e4fb73277c6ac7ef3ff510277c354112a507d5a017603",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e902000000000000001704931bb12bbdd0dd4d54ff76d705a3350b482c57b73edc8bffa2a5ecd1ed6a801d2c040000000000020a00010c000000983a00000075eb25bf6325df0d47a9a0dd49f0620bad9e1f81b129e58c50b0ac3c4a05251a32dc0c8e36e1e99553862b459c41ff361c2aa923803f90e943db251daa7d450b",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9030000000000000002a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae240ff10050000000000020a00020c0000000000000000a01bbd00f34232f66f0849472e8c20a8fadfae03623e33a532cb6551beb8cb9c6c9a4ea638f44050ae11103b2903cd9f2602e435e661242f74254d41608cde07"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b416023702000000000000000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f69b1a9c9eb12c1af8791f88328fa5c762baba33b97f9ce6e6d46d494ba356597ff0f5b82c09799bdbdf92a31cf8f1c4a7fec7f0a167916fbd9e14a702e6faf03",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",
+ "session": "de96f376b3fc5a24506a867d5bdd8a9c",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "1edcb37a85fc3d98630f51a37f1e95568d7a3fe84ed073de9129af0061d35fad",
+ "avail_ms": 15000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 2
+ }
+ ],
+ "merkle_root": "f155dd49c8ba69be7551eb5575573f6debb98dd4e901cdd020044563adba2e47",
+ "budget_spent_micros": 40000,
+ "work_decide_ops": 3,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "a66c8e51a175aa088c71d9335c0edeccd3207b092e39aef2fdcce9d2078a07972b2af67b87c9b4e4d6c93c312423de5afd2410f53536c88dfdd5704ddada900e"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "accept",
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "breaks": [
+ {
+ "digest": "02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",
+ "id": "wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",
+ "event_id": 1857411439274967,
+ "author": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "cue_id": 12,
+ "t": 70000000,
+ "type": "out",
+ "placement": "mid",
+ "planned_ms": 15000,
+ "avail_ms": 15000,
+ "duration_ms": 15000,
+ "closed_by": "e35a38e6bbe2cad877c03e35e6caf3a4e9059a1bbf4779f103bdae15ca0e9b55",
+ "end": 85000000,
+ "hls_list_uri": "https://ads.rooms.example/sgai/wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2.json?_HLS_interstitial_id=wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2",
+ "dash_list_uri": "https://ads.rooms.example/sgai/wai-ad-02a6994e4d0a2bd745d9d31786abb0f4571978aa9360ed75707b375cbdcbcae2.mpd"
+ }
+ ],
+ "chapters": [
+ {
+ "digest": "74a069792bd2a9e635c9ac862a7d7e51e1d9977a88942bfcd61036cdbb265445",
+ "t": 1000000,
+ "duration_ms": 60000
+ },
+ {
+ "digest": "1704931bb12bbdd0dd4d54ff76d705a3350b482c57b73edc8bffa2a5ecd1ed6a",
+ "t": 61000000,
+ "duration_ms": 0
+ }
+ ],
+ "hls": "chapters.m3u8",
+ "dash": "",
+ "dash_open": [
+ {
+ "break": 0,
+ "event": ""
+ }
+ ],
+ "receipt_facts": [
+ {
+ "identity": "ccd5f069510d84544155208db9c32c348005bb4cada9361c2f8deea724980abb",
+ "asset_list": "{\"ASSETS\":[{\"URI\":\"https://cdn.rooms.example/creatives/8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60.m3u8\",\"DURATION\":15.000}]}",
+ "spend_micros": 40000
+ }
+ ],
+ "spend_micros": 40000
+ },
+ {
+ "name": "gap",
+ "about": "the live break's room as a subscriber holds it when the out was not delivered to it: the in follows a gap in the producer's chain, so no schedule is read",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "gap",
+ "gaps": [
+ {
+ "principal": "ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9",
+ "first": 1,
+ "last": 1
+ }
+ ]
+ },
+ {
+ "name": "cue_kind_not_permitted",
+ "about": "the viewer, whose grant permits chat alone, sends an out: the room refuses it",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a0001010000003075000000eb8ef4064af8edd62d7e2b082d4ae8f26d736220415f77b6b8db92aaec0eebd117a8965079b1b8f603aaa25b06d3c98c580f81d4dbb137b3c4e7aad7011b6000"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "kind_not_permitted",
+ "at": [
+ "room",
+ "record",
+ 1,
+ 0
+ ]
+ },
+ {
+ "name": "policy_slots_zero",
+ "about": "a policy whose slots is 0",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370030750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732faea1c5dc904057c4e772489b1b502c7ea26e4b2b580030a0bf6ee2f6968a79c7a1d2e5b06847104aaa4d9b397dd45c27aefbc28eb95ee0326116bba6ea5a030d",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "ad_policy_malformed",
+ "at": [
+ "policy"
+ ]
+ },
+ {
+ "name": "policy_wrong_room",
+ "about": "a policy naming another room",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "574150310146ded2ecf6106a3f61d958d5de39a8353e827036176f41a539f41273df6a069376d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f8502e441d05544613dde31a78a3158cc54211fac7965a6c32a491d1c6f19bd7a27231faeffd4e9d2fea02f663c2fc74c531ad29fba5eb3243768479889ebf30a",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "wrong_room",
+ "at": [
+ "policy"
+ ]
+ },
+ {
+ "name": "policy_bad_signature",
+ "about": "a policy signed by the producer, not the room authority",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fa2764d286e3616fb7b2f102999b5e7a029466daf96668d15d07a49f942a429bebce39698c9c7f2080af4208d786ea8f1de6c8d4b3b26849a29093a066e91000f",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "bad_signature",
+ "at": [
+ "policy"
+ ]
+ },
+ {
+ "name": "decider_grant_unknown",
+ "about": "a policy naming a decider grant the room does not have",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc1c9905a8a2468c4144ef8b25e67f6c7e3a38e2ced4829c50c33c3a7d93f5675b0230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fb7cca0844695aea0786f4a9d381fc96f884f0700c6f646b3401213a377b7719472e844669fa0b683739d7362a1edf3f71f5f5e93cbf404de35a2d3a1e7927a0b",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "decider_grant_unknown",
+ "at": [
+ "policy"
+ ]
+ },
+ {
+ "name": "decider_grant_revoked",
+ "about": "a room whose key log's head revokes the decider grant",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f0000000000150068747470733a2f2f726f6f6d732e6578616d706c65100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e6365000101000800000000000000000000000000000000000000000000000000000000000000076465636964657200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046b82793f106db53b9d585d80cdbc6dff02657c12734cdd99eb1e1eccba8cd0866c1ab07fb3c5faa40190f8d2d68b0bf3a5886b4833512aeffbadaf8498c4200",
+ "grants": [
+ "5752473101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab371ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000000000a78a2ee475f92958d501d7184d570108f2ab05b63090635396ef280b9188148fb318da73b975032bbdc37c9990b5bd0f7a7baf1fc5c59202eb4f8b573d921e03",
+ "5752473101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab3715bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000000944e74a7fe76a60f42592980ed789fa0798dd19a717ec5a8c1dcfd1653e2452363b15b2017fc17dab36d43cc1458de5d8253d57be6991eae3f8645506a7280e",
+ "5752473101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab371dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000c5362882c1dad3c5884780f9042cf62fc712590d84509ee21797cedf757a9c94b0717d05d8a4b1c09ef2dde2ddf656981e49cc1cf4a3580857e50b3e7d39420e"
+ ],
+ "key_log": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"98925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed4\",\"may_sign\":[\"live/room\"],\"status\":\"active\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"00cd9fcc8d2905e6ab0a163906d5b245f6d46116c5e443f43cb723259e753e94\",\"origin\":\"https://rooms.example\",\"prev\":null,\"revoked_modules\":[\"27e56da40ee881653c9e64b613d3432dd94e65292abef4a0a018e0f534352c61\"],\"root\":\"b50d665f063e9cc84c9a19a4920be7320454fad90260d0592ff792302c19e36d\",\"seq\":0,\"sig\":\"3597483aac6f583ab42753266c07b96eba1d89d193724f6f63723b36fd41603563cbc326b082664d67dd89204e22dbe808d98da7fc9a07dcfe8af61efd013f0c\",\"status_max_age_s\":3600}",
+ "chains": [
+ [
+ "5745563101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab37196a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e97b404d1a52dd38104f5412445d3bc25fd4338519b2bb3b7a8b0197564a6288b512e6db50732480df351c6a9559166d73d02a87b7063c809255e828489b3b68181f5f486cdb6b3b5eaea2a2665d2782b21b5fdebe39fea0431c86a657f81d440a",
+ "5745563101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab37196a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000c5061d50ab280305834cc051bae9ea5d6c0149a8c7c09075a865d907d0702b620000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf4a3662e379acd7522a90ed488b07ff4763662e5652704d49479b4b44675381a33350d539a463b4f0b4d3708cd85775696b4c6eff51f9fc80e1b899e2aa3bc23f10307cb631d5777f33c10044b278e00fa8c69781dcff23427afa59d26071fa09"
+ ],
+ [
+ "5745563101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab371ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66669e3243c6b9c9311d3e6e85cf87d1f661a761cabf3f29da87ed7ecde080f6ceff850efd71bc055755bbd2628388959352b4f58ac204d965c05bf4673eda490805",
+ "5745563101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab371ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e901000000000000008d543504b00c507ba5100a3efe99ecb9baafa8029ef3a171deca25940066cea4008793030000000000020a0001650000000000000000f52ab5deeb909638c2be9e803a3bca3e8a5db62536303eb6d503eba3dc6a82ca4fe96a0c5bca8692870e638ba0b1b452b37d1c436907574b49840e99a112d70a",
+ "5745563101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab371ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000e5ba74249d73ed15d680977fdf3a46667a7f99bd2cfcbdcdd7791df7201d4a52804a5d050000000000020a000265000000307500000043163b3f0cc5e2e5429f203e326ba0bc2768ae9e6ee62d26f24774516fe04efe27da9dd3a8986dd5fc8eefe2547749cc0a8e66bfde5185f5f52deffc901fdb0b"
+ ]
+ ],
+ "policy": "5741503101994d37d181150b88daaedae9bfdfea7083d466f6234cfc00fe42242dec1ab37127e56da40ee881653c9e64b613d3432dd94e65292abef4a0a018e0f534352c610230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f40a1a8543f3e379e19be338fcf1690e1c385290b0ecc929a9cd20e57a7128883d5717ec4d45696fb461da82a7c32d07e9f84e9bcf2ca2001e451e878a6a8200b",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "grant_revoked",
+ "at": [
+ "policy"
+ ]
+ },
+ {
+ "name": "annotation_placement",
+ "about": "a break annotation of placement 3",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a0004010000003075000000ae4ce22292c0d136019ea629c0ec15e963c003a2031802e6a0d7a5ccf9cddcdeae408231c37768f3bc5c5d888a3ebfe869d2d005ab14f6939cf3b1735b1c460a",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e901000000000000006907cbe8998a464ac8abc8314d7403ef7f3ba82dc94ac61fef085c0cc8904a8600879303000000000001260057414231016907cbe8998a464ac8abc8314d7403ef7f3ba82dc94ac61fef085c0cc8904a8603012bf46496cf8b4373d3f05bfe5bbf3238871efc2fa109e7aeaa8ee91ea4a5dd2e6699dfbb19659caa90d19f97acf5f6ed8cfb6c76da906b73c29913cf513d01"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "annotation_malformed",
+ "at": [
+ "record",
+ 1,
+ 1
+ ]
+ },
+ {
+ "name": "annotation_on_out",
+ "about": "a post-roll annotation after an out: only a break of known duration is a post-roll",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a0001010000003075000000a567dac7ec30e6968fbdd851180128a9d5904e3359399f8d065101d41f8e9cd41cfcd44df4f177dd28edff2f95f46005a17c728d3eb92d68ac882ceecbe3f105",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000035fc1869bd2fa52e1cc2549a6e9c394355b5fe5ec324006f1cbad52207872100087930300000000000126005741423101035fc1869bd2fa52e1cc2549a6e9c394355b5fe5ec324006f1cbad5220787210029ed6c1c2b1a379ba487de4c4386ed0b74afabd8f4ab698c7ed909eaad199c67e003d26f4a1625cf555e442b9da0e089e9f41f9967606f05659d516866952bc09"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "annotation_misplaced",
+ "at": [
+ "record",
+ 1,
+ 1
+ ]
+ },
+ {
+ "name": "cue_after_post_roll",
+ "about": "a chapter after the post-roll",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000000e27070000000000020a000401000000204e000000f756192a65b2e5a148b3c5ca25063f310e85f3a19f67057fc5f309c8883daefd25e2b2342ed6d99b40689c39dcf144b0c535da714ceac8209ca7d1565486a10a",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000ce864aa46f8dfecf66e4d38f7c9d826690acb29673e0b1857e2fce6dd978b617000e270700000000000126005741423101ce864aa46f8dfecf66e4d38f7c9d826690acb29673e0b1857e2fce6dd978b6170217771328ac48b927adcac7d225c1ebc0a0834d25dda6ec0c8d8a1ccac659e305a58935455682cf73166de8340f725193bc78ddb273bc5ec887caf09ec48d9b0b",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000f84e7f6bc38936514ebeefe1c7dc914376d66c7bbf0827832a744099201c7a8980d1f0080000000000020a0003020000000000000000b57e8ed27724a953b09eba8af85f864e15b693c33ee1cf0660c550c66de285366ef89654b479b68a386f2ffde4f6a34f8e5f46c3be4888527f7057a55f878900"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "cue_after_close",
+ "at": [
+ "record",
+ 1,
+ 2
+ ]
+ },
+ {
+ "name": "overlapping_breaks",
+ "about": "an out 15 s into a 30 s break",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90000000000000000000000000000000000000000000000000000000000000000000000000000000080c3c9010000000000020a0004010000003075000000dcf2fdaaa4ca9a66c82dc892a3764ecf8e93c07b8ce570706aa9edcdfcf7cc34e3ebc720bd9a799a9544a73853be2327d93770b4e63db0ebd7c5a93666b27d0a",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000713418ad183d8109aac22d2dbc28f4bc36f98cbaeb6a1cda243ad8f193bbdde540a5ae020000000000020a000102000000983a0000003028d42789171173b82728ec5f50379f04ad48967fad35605037eeaa6996b093c1c2859a597f4af3df3f476751a1063702f9c3a5516ec42eda800ef06a9b9b08"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "break_overlap",
+ "at": [
+ "record",
+ 1,
+ 1
+ ]
+ },
+ {
+ "name": "open_break_no_avail",
+ "about": "an out with no planned duration under a policy with no open avail",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a00010100000000000000009e155e0ad529ef2ae1516e6f38768cb78df4a4a6a287da3a74609b39e82a9e4b3ac85c07847c29735035c6ff2ee86a579f293be11a62994f8cf3851618439904"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370200000000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f00f54b990266d170b2ebdf7a27fd96c4953c96777df8d15774231c9a56dbc23e2bee2eaf4f12df836d6569cfd72374fe067de68c52c817126136f7aa497b7605",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "break_duration_unknown",
+ "at": [
+ "record",
+ 1,
+ 0
+ ]
+ },
+ {
+ "name": "break_of_no_duration",
+ "about": "a break of type 4 with a duration of 0",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a00040100000000000000008ddaa40c42b577720f706e8e7ca4cd7a3aad7cec7ebe6b4ef880eb339045a90e0d061e5b3f11f8c7a261f49b0731ca65ce8744ddb25fcc3c1367d77983a89709"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "break_zero_duration",
+ "at": [
+ "record",
+ 1,
+ 0
+ ]
+ },
+ {
+ "name": "in_without_out",
+ "about": "an in with no open out",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a0002050000000000000000714b421a3624eb2dd341cffe92f1ca2c7d6c57ff14c39155cd099a3ce388e3a987dcb1bbea2e9701ed8cf92533b76234f355723e111d028c67e34df149199706"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "cue_in_unmatched",
+ "at": [
+ "record",
+ 1,
+ 0
+ ]
+ },
+ {
+ "name": "in_duration_mismatch",
+ "about": "an in stating 25 s when 30 s passed since its out",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000008793030000000000020a00010100000000000000009e155e0ad529ef2ae1516e6f38768cb78df4a4a6a287da3a74609b39e82a9e4b3ac85c07847c29735035c6ff2ee86a579f293be11a62994f8cf3851618439904",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e9010000000000000042d1a26f3f62590a66534c8a046903a1ece15c288007848f9cd1e8a5c881f2b5804a5d050000000000020a000201000000a861000000aa248f7dd029a46e2a83b6583a22d420615cedcb401c2f28900f8bf5e806b99540eb3642764a5b942e780ca07623035ad1949bd3540d497bff209d880d662b0a"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [],
+ "pods": [],
+ "outcome": "refuse",
+ "reason": "cue_duration_mismatch",
+ "at": [
+ "record",
+ 1,
+ 1
+ ]
+ },
+ {
+ "name": "receipt_extra_member",
+ "about": "a receipt carrying a member §8.2 does not define",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e",
+ "x_note": "an extra member"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_malformed",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_wrong_room",
+ "about": "a receipt naming another room",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "46ded2ecf6106a3f61d958d5de39a8353e827036176f41a539f41273df6a0693",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "032c6c0fa3f8d1d78b4e1029451e11c8842ed3418d9b155088341becc5bf10d3edcdc4df5186c43508ede9fb3dd35cf926601c9c756d1ac25ab92dccb7229107"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "wrong_room",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_bad_signature",
+ "about": "a receipt with one bit of its signature flipped",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afda25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "bad_signature",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_quartile_six",
+ "about": "a receipt stating an ad reached quartile 6",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 6
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "d6cc46c2c12ff4d03edad1bfdaae7ca348e9fca291d2876384e155490beab4f9",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "fa0b33d71c5f378de672a345766a2d16b96ba900c8c63c5e6fd16b09b4b857d82d067e64f6df724e2af249801cbb1c9774bea059c1cc607fb45dcb4cf07e420a"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_format_three",
+ "about": "a receipt of format 3, which names no asset list",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 3,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "316abd6fd9cf2110761b026c6e53219b388723f6af746a7a3d238b14e7624a70bdb4d21b57841795bf57af8482030f27e95bf5a591143a7d053dd1b2fcfa270a"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_work_below_fill",
+ "about": "a receipt stating it considered one candidate for a fill of two",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 1,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "c9f550618cf8ff38f0a8ac3b8b31f2161b3c3c72f2d29260f86ecace0ad9c63c7926f3d64ea5b31d41aa5263d4252991784a6bcb99f771c7e3f94b5f2d755708"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_figure_without_class",
+ "about": "a receipt stating 1 200 µJ of decision energy with no acquisition class",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "00f6e6169fc7801274474e7c7cb3a9788c7da5744c0445d3486eee7642d697773515e985d5deb3c6ffda5a4a8a941d19f30d9a9d349830b96e9e767b3ee35404"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_below_resolution",
+ "about": "a receipt stating 9 µJ read from an on-chip counter with a declared uncertainty of 30 000 ppm, below the resolution floor",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 9,
+ "energy_provenance": "OnChipCounter",
+ "energy_uncertainty": {
+ "relative_ppm": 30000,
+ "window_us": 2000000
+ },
+ "energy_counter_filtering": "Unknown",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "be485134304ae681b959e8fa541814415eb259d78c21d25142db2df225ca54e1ada938b3b2d1a0c1c17923f58117a70967435d65ffa83900fa8eefbca4f24b0f"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_over_its_ceiling",
+ "about": "a receipt billing 2 500 000 µ-units under a grant reference whose ceiling is 2 minor units",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "46c9b299171da99d00851f575fc44a691645aa20bf4ce525cf2ccc2b8af3fd51",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 3000000,
+ "pod_hash": "73fbe3edafd39d175b93baa21fd4020ec7d224c6c3ec6c239cf0b0217373aa5e",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-q",
+ "creative_hash": "2479dee004b84fcb7daeff0b527061208867f8f3de54af97cf82a7a5af6cd583",
+ "duration_ms": 15000,
+ "price_micros": 2500000,
+ "quartiles_reached": 1
+ }
+ ],
+ "merkle_root": "55fbd16daf24ea59f17595e10413977f256a8ea61b083f62abf04cca9ce1ecc3",
+ "budget_spent_micros": 2500000,
+ "work_decide_ops": 1,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "02ca3f5834f32fd03275f03b6737d17180b0d550887f4f38084186ac3809683938aa5071204c3ad31a3ee5b688702bc4750c69afbbcebea454a57282f091af0f"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-q",
+ "creative_hash": "2479dee004b84fcb7daeff0b527061208867f8f3de54af97cf82a7a5af6cd583",
+ "duration_ms": 15000,
+ "price_micros": 2500000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_invalid",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_not_decider",
+ "about": "a receipt signed by a key other than the decider's",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "934b90192cc4c745a8147599c6a594678ee3a9993cade600fafecd8d23ba78c7",
+ "signer_id": "did:key:z6MkpNEof7EobfrcyEjfCgKdALYkGMEfuijj8FjpFy1pLEES",
+ "sig": "fd8a67573ca69cf9cf08d966ba2ba8db5c7d8c2e52483f4c21f5681da6a3d6adf3b9d0ad12a6a8141429ba7bb2315ef68d704b7fe56ca049f89c715db9523a09"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_not_decider",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_grant_mismatch",
+ "about": "a receipt naming the decider grant with another capability",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "video.ssai",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "5ffd64abdccfe0ad77c3f4ade2bf97407490ccc75736992bc3931608778c91d199e24c70c4e19000b14643a7fc6c5aaf017687ab0e6f60f9054595cc60205008"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_grant_mismatch",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_names_the_in",
+ "about": "a receipt naming the in, which opens no break",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "e97d8be15b595855a21ac9fc5a5820d64303f96f71d860c587cbb4069ac08b6b",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "e58a44020a34d4bb9c20f74bcbf276ef5b18e77810f3e98f37d605990c62519e480fec95752bbbd86beefc91c3d55fc18782f6371aa584a9ad72e0921a7cd600"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_break_unknown",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "decider_grant_expired",
+ "about": "a decider grant that ends 30 s into the room, and a receipt for the break at 60 s",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000001e69d16a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff02000000000000004555520000000012c392beb264e6b9f09cddb508ba6ceae9ed028033d0f8fde4672a575940fbd351901c8b71c0f8f30361931885694e42129683b12a9cf37e10646d8c7369bd05"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcc7910ca18ee64b3ac6121a6dfeef0799419147ffc7734f0786a49d782144e4c10230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f32ae00ca1e55b6abe424349f52406440c31bfbe48ebccbb345604b2dbbf70d91c39472cf947a1410507b76d26fcfb142b96712f0b09f6a4e2a16612bc82f1f0a",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "c7910ca18ee64b3ac6121a6dfeef0799419147ffc7734f0786a49d782144e4c1",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "60e68c30855b4b84df338168474a12d6c937b50e4913bf630e1e4e21411320ed6547e05bd7195ef0abeaf073624cb92e5e2596ea55fa64418bb3b9de4101f900"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "outside_grant_window",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "pod_mismatch",
+ "about": "a receipt given with a pod short of its last candidate",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "pod_mismatch",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "decision_mismatch",
+ "about": "a receipt whose fill is the decision over 2 000 000 µ-units of funds, stating 50 000: over 50 000 the decision takes ad-a alone",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 50000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "e338cb1b2f1affe8a3216c37f225617d197d70da5ba208579ac2b0d5ef3f07f7d1c2be85cf34b886b42d9fc99eb9e4ddb402c16481e3bacec3efc09544ba2206"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "decision_mismatch",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "asset_list_mismatch",
+ "about": "a receipt of the DASH format binding the HLS asset list",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 2,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "14e967239e36fea652e74756d0d62ea1e27410062e57b3569f19260e43d3d4812208b2ec1fa2f1b2be3e4d42d078e4be05dab0b85a57ad717e927f5da633ce09"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "asset_list_mismatch",
+ "at": [
+ "receipt",
+ 0
+ ]
+ },
+ {
+ "name": "receipt_duplicate",
+ "about": "one viewer's receipt for the break given twice",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "72d537a7839c90be3593e83fafee3a2f",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 2000000,
+ "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000,
+ "quartiles_reached": 5
+ },
+ {
+ "slot": 1,
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000,
+ "quartiles_reached": 3
+ }
+ ],
+ "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c",
+ "budget_spent_micros": 70000,
+ "work_decide_ops": 3,
+ "joules_micro": 1200,
+ "energy_provenance": "ModelBased",
+ "grant": {
+ "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 2
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-a",
+ "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60",
+ "duration_ms": 15000,
+ "price_micros": 40000
+ },
+ {
+ "ad_id": "ad-b",
+ "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814",
+ "duration_ms": 15000,
+ "price_micros": 30000
+ },
+ {
+ "ad_id": "ad-c",
+ "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906",
+ "duration_ms": 20000,
+ "price_micros": 25000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "receipt_duplicate",
+ "at": [
+ "receipt",
+ 1
+ ]
+ },
+ {
+ "name": "funds_exceeded",
+ "about": "a decider grant of 1 minor unit, and two viewers each billed 600 000 µ-units: the second receipt takes the room's spend past the ceiling",
+ "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d",
+ "grants": [
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605",
+ "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff01000000000000004555520000000042d93313a0f5f3922a482385164cf6113495f0c4f36a984e1bcb89aa993c92da33b7fcb1736b6fc20e987e706e5378ca83d48417a52f98eafd2d56be6ab1200d"
+ ],
+ "key_log": null,
+ "chains": [
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04"
+ ],
+ [
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01",
+ "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504"
+ ]
+ ],
+ "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f0230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f30d03f517322e01378085469cb0e7246a89459fa572675093f3b23b8d0d192be7e50f627d2c87316302b9bab8ab512b617492105dd7ad9505ca7fb8bb3576403",
+ "pt0_ms": 0,
+ "receipts": [
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "2436dcfbc17a984fa0c27b9803c742a1",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "9b5ebc0aa8d4d6c636a6d3f3a6ddaeaf7743c55edc561afa039daa01659bc556",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 1000000,
+ "pod_hash": "122a68c694d6225ece06cdde4973f4d8a6cf5daca55105b2561ffe1617dbe8c0",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-p",
+ "creative_hash": "a22d7ffb1b3c00a6636cd4a5b44362fe4fae80e27b812af4205ae692430c71de",
+ "duration_ms": 15000,
+ "price_micros": 600000,
+ "quartiles_reached": 1
+ }
+ ],
+ "merkle_root": "9d093f43e26c9e07a7485c8e6d82a8fc53fd37f65450c5ea64444dfd95b14872",
+ "budget_spent_micros": 600000,
+ "work_decide_ops": 1,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 1
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "c5c238a657bce83e7cb1ba60135b5690e44a203db9f780852184e325b12b9ec5c02c2bacb3e7b032dc418fe643a876fec2daf6d452cc9379ac4af5f287ccd606"
+ },
+ {
+ "form": 2,
+ "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc",
+ "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0",
+ "session": "83e089c4c263e49499071718abf30525",
+ "start_offset_ms": 0,
+ "format": 1,
+ "asset_list_sha256": "9b5ebc0aa8d4d6c636a6d3f3a6ddaeaf7743c55edc561afa039daa01659bc556",
+ "avail_ms": 30000,
+ "slots": 2,
+ "funds_remaining_micros": 1000000,
+ "pod_hash": "122a68c694d6225ece06cdde4973f4d8a6cf5daca55105b2561ffe1617dbe8c0",
+ "impressions": [
+ {
+ "slot": 0,
+ "ad_id": "ad-p",
+ "creative_hash": "a22d7ffb1b3c00a6636cd4a5b44362fe4fae80e27b812af4205ae692430c71de",
+ "duration_ms": 15000,
+ "price_micros": 600000,
+ "quartiles_reached": 1
+ }
+ ],
+ "merkle_root": "9d093f43e26c9e07a7485c8e6d82a8fc53fd37f65450c5ea64444dfd95b14872",
+ "budget_spent_micros": 600000,
+ "work_decide_ops": 1,
+ "joules_micro": 0,
+ "energy_provenance": null,
+ "grant": {
+ "grant_hash": "11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f",
+ "capability": "wai.live.room",
+ "joule_ceiling_micro": 18446744073709551615,
+ "funds_ceiling": 1
+ },
+ "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575",
+ "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v",
+ "sig": "260d18d38ee0e239c3315460bdb39707c382c70358daf96af83b505e448a3925dc1707bd287c45ff39464fbcb24286f20e1cbb69238147309163f1a848c60702"
+ }
+ ],
+ "pods": [
+ [
+ {
+ "ad_id": "ad-p",
+ "creative_hash": "a22d7ffb1b3c00a6636cd4a5b44362fe4fae80e27b812af4205ae692430c71de",
+ "duration_ms": 15000,
+ "price_micros": 600000
+ }
+ ],
+ [
+ {
+ "ad_id": "ad-p",
+ "creative_hash": "a22d7ffb1b3c00a6636cd4a5b44362fe4fae80e27b812af4205ae692430c71de",
+ "duration_ms": 15000,
+ "price_micros": 600000
+ }
+ ]
+ ],
+ "outcome": "refuse",
+ "reason": "funds_exceeded",
+ "at": [
+ "receipt",
+ 1
+ ]
+ }
+ ]
+}
diff --git a/wai/live-conformance/live_ads_verify.py b/wai/live-conformance/live_ads_verify.py
new file mode 100644
index 000000000..7559deee6
--- /dev/null
+++ b/wai/live-conformance/live_ads_verify.py
@@ -0,0 +1,762 @@
+#!/usr/bin/env python3
+"""Independent verifier of the live-ads corpus (extensions/live-ads.md).
+
+Written from the extension's text. It shares no code with wai-rs, which generates the corpus,
+and never runs it. It checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's
+strict rule (SPEC §7 *Signatures*) written from RFC 8032 with the standard library, and
+computes BLAKE3 with ../claim-conformance/_blake3.py, written from its specification. Each room
+is verified by the live-rooms corpus's own second implementation, live_rooms_verify.py
+(live-rooms §9), written from its text alone. Everything else is here:
+
+- the ad policy (§3): its layout, rules and signature, and the decider grant;
+- the break annotation and the schedule (§4), with its refusals;
+- the HLS interstitial, closing and preload tags (§5) and the DASH events (§6), byte for byte;
+- the decision and the asset lists (§7), byte for byte;
+- the break form of the wai.video.ssai receipt (§8): its JSON carriage, hashes, signing
+ payload, identity and rules;
+- verification, from the room to the spend (§9).
+
+For every room it requires the outcome, reason and place, and everything else the corpus
+states; every HLS tag file, byte for byte; of the corpus, that every reason of §10 appears in
+it and that no other file is there. Then it mutates copies of the corpus, one defect each, and
+requires every copy to fail.
+
+ python3 live_ads_verify.py
+
+Exit status 0 only if every case verifies and every mutated copy fails. Python 3.8 or later;
+standard library only.
+"""
+
+import copy
+import hashlib
+import json
+import os
+import struct
+import sys
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, HERE)
+sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
+import live_events_verify as LEV # noqa: E402
+import live_rooms_verify as LRV # noqa: E402
+import _ed25519 as ED # noqa: E402
+import _blake3 as B3 # noqa: E402
+
+Refuse = LEV.Refuse
+EXTENSION = os.path.join(HERE, "..", "extensions", "live-ads.md")
+
+POLICY_MAGIC, ANNOTATION_MAGIC = b"WAP1", b"WAB1"
+POLICY_DOMAIN = b"wai:live-ads-policy\x01" # §3.2
+BREAK_DOMAIN = b"wai:video-ssai-break-receipt\x01" # §8.4
+ID_DOMAIN = b"wai:video-ssai-receipt-id\x01"
+POD_DOMAIN, IMP_DOMAIN = b"wai:video-ssai-pod\x01", b"wai:video-ssai-imp\x01" # §8.3
+LEAF, NODE = b"moq-jwp:merkle-leaf\x01", b"moq-jwp:merkle-node\x01" # jwp-receipts §2.2
+INTERSTITIAL = "com.apple.hls.interstitial" # allow-name: the interstitial date-range class, a protocol identifier
+PRELOAD = "com.apple.hls.preload" # allow-name: the preload date-range class, a protocol identifier
+REPLACE = "urn:mpeg:dash:event:alternativeMPD:replace:2025" # §6.1
+INSERT = "urn:mpeg:dash:event:alternativeMPD:insert:2025"
+OUT, IN, CHAPTER, BREAK = 1, 2, 3, 4
+META, CUE = 0x01, 0x02
+MID, POST = 1, 2
+HLS, DASH = 1, 2
+NO_SKIP = 65535
+NO_CEILING = 2 ** 64 - 1
+U64 = 2 ** 64 - 1
+FLOOR = 288676 # §8.5 rule 5
+# Unicode White_Space, the code points a calibration reference must hold more than (§8.5).
+WHITE_SPACE = set(map(chr, [0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x85, 0xA0, 0x1680] + list(range(0x2000, 0x200B))
+ + [0x2028, 0x2029, 0x202F, 0x205F, 0x3000]))
+
+
+def sha(*parts):
+ h = hashlib.sha256()
+ for p in parts:
+ h.update(p)
+ return h.digest()
+
+
+# ---------------------------------------------------------------- the extension's own table
+
+def reasons():
+ """§10's codes, from the text."""
+ with open(EXTENSION, encoding="utf-8") as f:
+ text = f.read()
+ at = text.index("\n## 10. Reasons")
+ end = text.index("\n## ", at + 1)
+ return [line.split("`")[1] for line in text[at:end].splitlines() if line.startswith("| `")]
+
+
+REASONS = reasons()
+
+
+# ---------------------------------------------------------------- §3 the ad policy
+
+def is_base(b):
+ """§3.1."""
+ if not b.startswith(b"https://"):
+ return False
+ rest = b[8:]
+ return (1 <= len(rest) <= 492 and rest[-1:] == b"/"
+ and all(0x21 <= c <= 0x7E and c not in b"\"#&'<>?\\" for c in rest))
+
+
+def read_policy(b):
+ """§3.3: the policy, or Refuse."""
+ if len(b) < 4:
+ raise Refuse("truncated")
+ if b[:4] != POLICY_MAGIC:
+ raise Refuse("malformed")
+ if len(b) < 5:
+ raise Refuse("truncated")
+ if b[4] != 1:
+ raise Refuse("unknown_version")
+ r = LEV.Reader(b, "truncated")
+ r.take(5)
+ p = {"room": r.take(32), "decider_grant": r.take(32), "slots": r.u8(), "open_avail_ms": r.u32(),
+ "preload_ms": r.u32(), "restrict": r.u8(), "skip_after_s": r.u16(), "snap": r.u8()}
+ p["asset_base"] = r.take(r.u16())
+ p["creative_base"] = r.take(r.u16())
+ p["body"] = b[:r.p]
+ p["sig"] = r.take(64)
+ if not r.done():
+ raise Refuse("malformed")
+ if (not 1 <= p["slots"] <= 16 or p["restrict"] > 3 or p["snap"] > 3
+ or not is_base(p["asset_base"]) or not is_base(p["creative_base"])):
+ raise Refuse("ad_policy_malformed")
+ p["asset_base"], p["creative_base"] = p["asset_base"].decode(), p["creative_base"].decode()
+ return p
+
+
+# ---------------------------------------------------------------- §4 breaks
+
+def read_annotation(b):
+ """§4.2."""
+ if len(b) != 38 or b[:4] != ANNOTATION_MAGIC or b[4] != 1 or b[37] not in (MID, POST):
+ raise Refuse("annotation_malformed")
+ return {"cue": b[5:37], "placement": b[37]}
+
+
+def cue_of(ev):
+ return LEV.read_payload(CUE, ev["payload"], 65535)
+
+
+def schedule(held, p):
+ """§4.3 over the held events in the merged order: (breaks, chapters, written) or Stop."""
+ breaks, chapters, written = [], [], []
+ open_, last_end, closed = None, None, False
+ for k, (c, i, ev) in enumerate(held):
+ where = ["record", c, i]
+ if ev["kind"] == CUE:
+ q = cue_of(ev)
+ if closed:
+ raise Stop("cue_after_close", where)
+ t = ev["t"]
+ overlaps = open_ is not None or (last_end is not None and t < last_end)
+ if q["type"] == OUT:
+ if overlaps:
+ raise Stop("break_overlap", where)
+ avail = q["duration_ms"] or p["open_avail_ms"]
+ if avail == 0:
+ raise Stop("break_duration_unknown", where)
+ open_ = len(breaks)
+ written.append(("open", len(breaks)))
+ breaks.append({"digest": ev["digest"], "author": ev["principal"], "cue_id": q["cue_id"], "t": t,
+ "type": OUT, "placement": MID, "planned_ms": q["duration_ms"], "avail_ms": avail,
+ "duration_ms": None, "closed_by": None, "end": None})
+ elif q["type"] == IN:
+ b = breaks[open_] if open_ is not None else None
+ if b is None or b["cue_id"] != q["cue_id"] or b["author"] != ev["principal"]:
+ raise Stop("cue_in_unmatched", where)
+ duration = (t - b["t"]) // 1000
+ if duration == 0:
+ raise Stop("break_zero_duration", where)
+ if q["duration_ms"] != 0 and q["duration_ms"] != duration:
+ raise Stop("cue_duration_mismatch", where)
+ b.update(duration_ms=duration, closed_by=ev["digest"], end=t)
+ written.append(("close", open_))
+ open_, last_end = None, t
+ elif q["type"] == CHAPTER:
+ chapters.append({"digest": ev["digest"], "t": t, "duration_ms": q["duration_ms"]})
+ else:
+ if q["duration_ms"] == 0:
+ raise Stop("break_zero_duration", where)
+ if overlaps:
+ raise Stop("break_overlap", where)
+ last_end = t + q["duration_ms"] * 1000
+ written.append(("open", len(breaks)))
+ breaks.append({"digest": ev["digest"], "author": ev["principal"], "cue_id": q["cue_id"], "t": t,
+ "type": BREAK, "placement": MID, "planned_ms": q["duration_ms"],
+ "avail_ms": q["duration_ms"], "duration_ms": q["duration_ms"], "closed_by": None,
+ "end": last_end})
+ elif ev["kind"] == META and ev["payload"][:4] == ANNOTATION_MAGIC:
+ try:
+ a = read_annotation(ev["payload"])
+ except Refuse as e:
+ raise Stop(e.reason, where)
+ before = next((x for _, _, x in reversed(held[:k])
+ if x["principal"] == ev["principal"] and x["seq"] == ev["seq"] - 1), None)
+ if (before is None or before["digest"] != a["cue"] or before["kind"] != CUE or before["t"] != ev["t"]
+ or cue_of(before)["type"] != BREAK):
+ raise Stop("annotation_misplaced", where)
+ if a["placement"] == POST:
+ for b in breaks:
+ if b["digest"] == a["cue"]:
+ b["placement"] = POST
+ closed = True
+ return breaks, chapters, written
+
+
+# ---------------------------------------------------------------- §4.4, §5, §6, §7
+
+def break_id(d):
+ return "wai-ad-" + d.hex()
+
+
+def event_id(d):
+ return int.from_bytes(d[:8], "big") & ((1 << 53) - 1)
+
+
+def secs(ms):
+ return "%d.%03d" % (ms // 1000, ms % 1000)
+
+
+def hls_uri(p, d):
+ i = break_id(d)
+ return "%s%s.json?_HLS_interstitial_id=%s" % (p["asset_base"], i, i)
+
+
+def dash_uri(p, d):
+ return "%s%s.mpd" % (p["asset_base"], break_id(d))
+
+
+def date(t0, t, less=0):
+ return LEV.iso((t0 + t) // 1000 - less)
+
+
+def bits(v, names):
+ return ",".join(n for k, n in enumerate(names) if v & (1 << k))
+
+
+def interstitial(p, t0, b):
+ """§5.1."""
+ post = b["placement"] == POST
+ a = ['ID="%s"' % break_id(b["digest"]), 'CLASS="%s"' % INTERSTITIAL, 'START-DATE="%s"' % date(t0, b["t"]),
+ 'CUE="%s"' % ("POST,ONCE" if post else "ONCE")]
+ known = b["planned_ms"] if b["type"] == BREAK else None
+ if known is not None:
+ a.append("DURATION=" + secs(known))
+ elif b["planned_ms"]:
+ a.append("PLANNED-DURATION=" + secs(b["planned_ms"]))
+ a.append('X-ASSET-LIST="%s"' % hls_uri(p, b["digest"]))
+ if known is not None:
+ if not post:
+ a.append("X-RESUME-OFFSET=" + secs(known))
+ a.append("X-PLAYOUT-LIMIT=" + secs(known))
+ if p["snap"]:
+ a.append('X-SNAP="%s"' % bits(p["snap"], ["OUT", "IN"]))
+ if p["restrict"]:
+ a.append('X-RESTRICT="%s"' % bits(p["restrict"], ["SKIP", "JUMP"]))
+ if p["skip_after_s"] != NO_SKIP:
+ a.append("X-SKIP-CONTROL-OFFSET=%d" % p["skip_after_s"])
+ return "#EXT-X-DATERANGE:" + ",".join(a)
+
+
+def closing(b):
+ """§5.2."""
+ s = secs(b["duration_ms"])
+ return '#EXT-X-DATERANGE:ID="%s",CLASS="%s",DURATION=%s,X-RESUME-OFFSET=%s,X-PLAYOUT-LIMIT=%s' % (
+ break_id(b["digest"]), INTERSTITIAL, s, s, s)
+
+
+def preload(p, t0, b):
+ """§5.3."""
+ i = break_id(b["digest"])
+ return ('#EXT-X-DATERANGE:ID="%s-preload",CLASS="%s",START-DATE="%s",DURATION=%s,X-URI="%s",X-TARGET-ID="%s",'
+ 'X-TARGET-CLASS="%s"' % (i, PRELOAD, date(t0, b["t"], p["preload_ms"]), secs(p["preload_ms"]),
+ hls_uri(p, b["digest"]), i, INTERSTITIAL))
+
+
+def hls_tags(p, t0, breaks, written):
+ """§5.4: in the order a packager writes them."""
+ out = []
+ for what, i in written:
+ if what == "open":
+ if p["preload_ms"]:
+ out.append(preload(p, t0, breaks[i]))
+ out.append(interstitial(p, t0, breaks[i]))
+ else:
+ out.append(closing(breaks[i]))
+ return out
+
+
+def dash_event(p, pt0, b, closed):
+ """§6.2."""
+ known = b["duration_ms"] if closed else None
+ post = b["placement"] == POST
+ e = '' % event_id(b["digest"])
+ child = "InsertPresentation" if post else "ReplacePresentation"
+ e += '<%s uri="%s"' % (child, dash_uri(p, b["digest"]))
+ if p["preload_ms"]:
+ e += ' earliestResolutionTimeOffset="%d"' % p["preload_ms"]
+ if known is not None:
+ e += ' maxDuration="%d"' % known
+ e += ' executeOnce="true"'
+ skip = p["skip_after_s"] if p["skip_after_s"] != NO_SKIP else -(-b["avail_ms"] // 1000)
+ e += ' skipAfter="PT%dS"' % skip
+ if known is not None and not post:
+ e += ' returnOffset="%d"' % known
+ return e + "/>"
+
+
+def dash_streams(p, pt0, breaks):
+ """§6.3."""
+ out = ""
+ for scheme, post in ((REPLACE, False), (INSERT, True)):
+ evs = [dash_event(p, pt0, b, True) for b in breaks if (b["placement"] == POST) == post]
+ if evs:
+ out += '%s' % (scheme, "".join(evs))
+ return out
+
+
+def decide(pod, slots, avail, funds):
+ """§7.1: the fill, each (slot, ad_id, creative_hash, duration_ms, price_micros)."""
+ fill = []
+ for c in sorted(pod, key=lambda c: (-c["price_micros"], c["ad_id"].encode())):
+ if len(fill) >= slots:
+ break
+ if c["duration_ms"] <= avail and c["price_micros"] <= funds:
+ fill.append((len(fill), c["ad_id"], c["creative_hash"], c["duration_ms"], c["price_micros"]))
+ avail -= c["duration_ms"]
+ funds -= c["price_micros"]
+ return fill
+
+
+def asset_list(p, fmt, fill):
+ """§7.2, §7.3: the exact bytes."""
+ if fmt == HLS:
+ assets = ['{"URI":"%s%s.m3u8","DURATION":%s}' % (p["creative_base"], h.hex(), secs(d)) for _, _, h, d, _ in fill]
+ return ('{"ASSETS":[%s]}' % ",".join(assets)).encode()
+ if not fill:
+ return b""
+ periods = "".join('%s%s.mpd'
+ % (s, secs(d), p["creative_base"], h.hex()) for s, _, h, d, _ in fill)
+ return ('\n%s' % periods).encode()
+
+
+# ---------------------------------------------------------------- §8 the break form
+
+MEMBERS = ["form", "room", "cue", "session", "start_offset_ms", "format", "asset_list_sha256", "avail_ms", "slots",
+ "funds_remaining_micros", "pod_hash", "impressions", "merkle_root", "budget_spent_micros",
+ "work_decide_ops", "joules_micro", "energy_provenance", "grant", "signer_pubkey", "signer_id", "sig"]
+EVIDENCE = {"OnChipCounter": ["energy_uncertainty", "energy_counter_filtering"],
+ "CalibratedInstrument": ["energy_uncertainty", "energy_calibration_ref"],
+ "ModelBased": [], "Estimator": []}
+CLASS_TAG = {"ModelBased": 2, "Estimator": 3, "OnChipCounter": 4, "CalibratedInstrument": 5}
+FILTERING = {"Unknown": 0, "Off": 1, "On": 2}
+
+
+def u(v, top=U64):
+ if not isinstance(v, int) or isinstance(v, bool) or not 0 <= v <= top:
+ raise Refuse("receipt_malformed")
+ return v
+
+
+def hexn(v, n):
+ if not isinstance(v, str) or len(v) != 2 * n or any(c not in "0123456789abcdef" for c in v):
+ raise Refuse("receipt_malformed")
+ return bytes.fromhex(v)
+
+
+def text(v):
+ if not isinstance(v, str):
+ raise Refuse("receipt_malformed")
+ return v
+
+
+def read_receipt(o):
+ """§8.2: the receipt, or Refuse receipt_malformed."""
+ if not isinstance(o, dict):
+ raise Refuse("receipt_malformed")
+ label = o.get("energy_provenance")
+ if label is not None and label not in EVIDENCE:
+ raise Refuse("receipt_malformed")
+ want = set(MEMBERS) | set(EVIDENCE.get(label, []))
+ if set(o) != want or u(o["form"]) != 2:
+ raise Refuse("receipt_malformed")
+ r = {"room": hexn(o["room"], 32), "cue": hexn(o["cue"], 32), "session": hexn(o["session"], 16),
+ "start_offset_ms": u(o["start_offset_ms"]), "format": u(o["format"], 255),
+ "asset_list_sha256": hexn(o["asset_list_sha256"], 32), "avail_ms": u(o["avail_ms"]), "slots": u(o["slots"]),
+ "funds_remaining_micros": u(o["funds_remaining_micros"]), "pod_hash": hexn(o["pod_hash"], 32),
+ "merkle_root": hexn(o["merkle_root"], 32), "budget_spent_micros": u(o["budget_spent_micros"]),
+ "work_decide_ops": u(o["work_decide_ops"]), "joules_micro": u(o["joules_micro"]),
+ "signer_pubkey": hexn(o["signer_pubkey"], 32), "signer_id": text(o["signer_id"]), "sig": hexn(o["sig"], 64)}
+ if not isinstance(o["impressions"], list):
+ raise Refuse("receipt_malformed")
+ r["impressions"] = []
+ for i in o["impressions"]:
+ if not isinstance(i, dict) or set(i) != {"slot", "ad_id", "creative_hash", "duration_ms", "price_micros",
+ "quartiles_reached"}:
+ raise Refuse("receipt_malformed")
+ r["impressions"].append({"slot": u(i["slot"]), "ad_id": text(i["ad_id"]),
+ "creative_hash": hexn(i["creative_hash"], 32), "duration_ms": u(i["duration_ms"]),
+ "price_micros": u(i["price_micros"]), "quartiles_reached": u(i["quartiles_reached"], 255)})
+ g = o["grant"]
+ if not isinstance(g, dict) or set(g) != {"grant_hash", "capability", "joule_ceiling_micro", "funds_ceiling"}:
+ raise Refuse("receipt_malformed")
+ r["grant"] = {"grant_hash": hexn(g["grant_hash"], 32), "capability": text(g["capability"]),
+ "joule_ceiling_micro": u(g["joule_ceiling_micro"]),
+ "funds_ceiling": None if g["funds_ceiling"] is None else u(g["funds_ceiling"])}
+ r["class"] = None
+ if label is not None:
+ c = {"label": label}
+ if label in ("OnChipCounter", "CalibratedInstrument"):
+ unc = o["energy_uncertainty"]
+ if not isinstance(unc, dict) or set(unc) != {"relative_ppm", "window_us"}:
+ raise Refuse("receipt_malformed")
+ c["ppm"], c["window"] = u(unc["relative_ppm"], 2 ** 32 - 1), u(unc["window_us"])
+ if label == "OnChipCounter":
+ if o["energy_counter_filtering"] not in FILTERING:
+ raise Refuse("receipt_malformed")
+ c["filtering"] = FILTERING[o["energy_counter_filtering"]]
+ if label == "CalibratedInstrument":
+ c["ref"] = text(o["energy_calibration_ref"])
+ r["class"] = c
+ return r
+
+
+def b3(*parts):
+ return B3.blake3(b"".join(parts))
+
+
+def be64(v):
+ return struct.pack(">Q", v)
+
+
+def pod_hash(pod):
+ """§8.3."""
+ out = [POD_DOMAIN, be64(len(pod))]
+ for c in pod:
+ a = c["ad_id"].encode()
+ out += [struct.pack(">I", len(a)), a, c["creative_hash"], be64(c["duration_ms"]), be64(c["price_micros"])]
+ return b3(*out)
+
+
+def leaf(i):
+ a = i["ad_id"].encode()
+ return b3(IMP_DOMAIN, be64(i["slot"]), struct.pack(">I", len(a)), a, i["creative_hash"], be64(i["duration_ms"]),
+ be64(i["price_micros"]), bytes([i["quartiles_reached"]]))
+
+
+def merkle(hashes):
+ """jwp-receipts §2.2, each leaf of its first form."""
+ if not hashes:
+ return bytes(32)
+ level = [b3(LEAF, h) for h in hashes]
+ while len(level) > 1:
+ if len(level) % 2:
+ level.append(level[-1])
+ level = [b3(NODE, level[k], level[k + 1]) for k in range(0, len(level), 2)]
+ return level[0]
+
+
+def class_bytes(c):
+ """energy-measurement §2.7's signed encoding."""
+ if c is None:
+ return b"\x00"
+ out = bytes([CLASS_TAG[c["label"]]])
+ if c["label"] == "OnChipCounter":
+ out += struct.pack(">IQ", c["ppm"], c["window"]) + bytes([c["filtering"]])
+ elif c["label"] == "CalibratedInstrument":
+ ref = c["ref"].encode()
+ out += struct.pack(">IQ", c["ppm"], c["window"]) + struct.pack(">I", len(ref)) + ref
+ return out
+
+
+def payload(r):
+ """§8.4."""
+ g = r["grant"]
+ cap, sid = g["capability"].encode(), r["signer_id"].encode()
+ out = [BREAK_DOMAIN, r["room"], r["cue"], r["session"], be64(r["start_offset_ms"]), bytes([r["format"]]),
+ r["asset_list_sha256"], be64(r["avail_ms"]), be64(r["slots"]), be64(r["funds_remaining_micros"]),
+ r["pod_hash"], r["merkle_root"], be64(r["budget_spent_micros"]), be64(r["work_decide_ops"]),
+ be64(r["joules_micro"]), class_bytes(r["class"]), g["grant_hash"], struct.pack(">I", len(cap)), cap,
+ be64(g["joule_ceiling_micro"])]
+ out.append(b"\x00" if g["funds_ceiling"] is None else b"\x01" + be64(g["funds_ceiling"]))
+ out.append(be64(len(r["impressions"])))
+ out += [leaf(i) for i in r["impressions"]]
+ out += [r["signer_pubkey"], struct.pack(">I", len(sid)), sid]
+ return b"".join(out)
+
+
+def own_rules(r):
+ """§8.5: None, or the refusal."""
+ if r["format"] not in (HLS, DASH):
+ return "receipt_invalid"
+ bad = ED.verify(r["signer_pubkey"], payload(r), r["sig"])
+ if bad:
+ return bad
+ imps = r["impressions"]
+ spend = sum(i["price_micros"] for i in imps if i["quartiles_reached"] >= 1)
+ if merkle([leaf(i) for i in imps]) != r["merkle_root"] or spend != r["budget_spent_micros"]:
+ return "receipt_invalid"
+ f = r["grant"]["funds_ceiling"]
+ if f is not None and spend > f * 10 ** 6:
+ return "receipt_invalid"
+ if any(i["slot"] != k or i["quartiles_reached"] > 5 for k, i in enumerate(imps)) or r["work_decide_ops"] < len(imps):
+ return "receipt_invalid"
+ c, j = r["class"], r["joules_micro"]
+ if c is None:
+ return None if j == 0 else "receipt_invalid"
+ if j == 0:
+ return "receipt_invalid"
+ if c["label"] in ("OnChipCounter", "CalibratedInstrument"):
+ if c["ppm"] == 0 or c["window"] == 0 or j * c["ppm"] < FLOOR:
+ return "receipt_invalid"
+ if c["label"] == "CalibratedInstrument" and all(ch in WHITE_SPACE for ch in c["ref"]):
+ return "receipt_invalid"
+ return None
+
+
+def identity(r):
+ return b3(ID_DOMAIN, payload(r), r["sig"])
+
+
+# ---------------------------------------------------------------- §9 verification
+
+class Stop(Exception):
+ def __init__(self, reason, at):
+ super().__init__(reason)
+ self.reason, self.at = reason, at
+
+
+def ads(v):
+ """§9: a dict of what the corpus states for vector v, and the run's HLS tags."""
+ descriptor = bytes.fromhex(v["descriptor"])
+ grants = [bytes.fromhex(g) for g in v["grants"]]
+ chains = [[bytes.fromhex(h) for h in c] for c in v["chains"]]
+ room = LRV.verify_room(descriptor, grants, v["key_log"], chains) # step 1
+ if room["outcome"] == "refuse":
+ return {"outcome": "refuse", "reason": room["reason"], "at": ["room"] + room["at"]}, None
+ if room["outcome"] == "gap":
+ return {"outcome": "gap", "gaps": room["gaps"]}, None
+ try:
+ return accept(v, descriptor, grants, chains, room)
+ except Stop as e:
+ return {"outcome": "refuse", "reason": e.reason, "at": e.at}, None
+
+
+def accept(v, descriptor, grants, chains, room):
+ d = LRV.read_descriptor(descriptor)
+ held_grants, _ = LRV.check_grants(d, grants)
+ try: # step 2
+ p = read_policy(bytes.fromhex(v["policy"]))
+ if p["room"] != d["room"]:
+ raise Refuse("wrong_room")
+ bad = ED.verify(d["authority"], POLICY_DOMAIN + p["body"], p["sig"])
+ if bad:
+ raise Refuse(bad)
+ decider = next((g for g in held_grants if g["digest"] == p["decider_grant"]), None)
+ if decider is None:
+ raise Refuse("decider_grant_unknown")
+ if LRV.revoked(decider, LRV.check_key_log(d, v["key_log"])):
+ raise Refuse("grant_revoked")
+ except Refuse as e:
+ raise Stop(e.reason, ["policy"])
+ held, seen = [], set() # step 3
+ for c, i, _ in room["order"]:
+ ev = LEV.read_record(chains[c][i])
+ if ev["digest"] not in seen:
+ seen.add(ev["digest"])
+ held.append((c, i, ev))
+ breaks, chapters, written = schedule(held, p)
+ t0, pt0 = d["t0"], v["pt0_ms"] # step 4
+ tags = hls_tags(p, t0, breaks, written)
+ funds = None if decider["funds"] == NO_CEILING else decider["funds"]
+ stated, sessions, total = [], set(), 0 # step 5
+ for k, (o, pod_json) in enumerate(zip(v["receipts"], v["pods"] + [None] * len(v["receipts"]))):
+ at = ["receipt", k]
+ try:
+ r = read_receipt(o)
+ except Refuse as e:
+ raise Stop(e.reason, at)
+ if r["room"] != d["room"]:
+ raise Stop("wrong_room", at)
+ bad = own_rules(r)
+ if bad:
+ raise Stop(bad, at)
+ if r["signer_pubkey"] != decider["principal"]:
+ raise Stop("receipt_not_decider", at)
+ g = r["grant"]
+ if (g["grant_hash"] != decider["digest"] or g["capability"] != "wai.live.room"
+ or g["joule_ceiling_micro"] != decider["joules"] or g["funds_ceiling"] != funds):
+ raise Stop("receipt_grant_mismatch", at)
+ b = next((x for x in breaks if x["digest"] == r["cue"]), None)
+ if b is None:
+ raise Stop("receipt_break_unknown", at)
+ if not LRV.good_at(decider, d, b["t"]):
+ raise Stop("outside_grant_window", at)
+ pod = [{"ad_id": c["ad_id"], "creative_hash": bytes.fromhex(c["creative_hash"]), "duration_ms": c["duration_ms"],
+ "price_micros": c["price_micros"]} for c in (pod_json or [])]
+ if r["pod_hash"] != pod_hash(pod):
+ raise Stop("pod_mismatch", at)
+ fill = decide(pod, p["slots"], max(b["avail_ms"] - r["start_offset_ms"], 0), r["funds_remaining_micros"])
+ got = [(i["slot"], i["ad_id"], i["creative_hash"], i["duration_ms"], i["price_micros"]) for i in r["impressions"]]
+ if r["avail_ms"] != b["avail_ms"] or r["slots"] != p["slots"] or got != fill:
+ raise Stop("decision_mismatch", at)
+ listed = asset_list(p, r["format"], fill)
+ if r["asset_list_sha256"] != sha(listed):
+ raise Stop("asset_list_mismatch", at)
+ if (r["cue"], r["session"]) in sessions:
+ raise Stop("receipt_duplicate", at)
+ sessions.add((r["cue"], r["session"]))
+ total += r["budget_spent_micros"]
+ if funds is not None and (r["funds_remaining_micros"] > funds * 10 ** 6 or total > funds * 10 ** 6):
+ raise Stop("funds_exceeded", at)
+ stated.append({"identity": identity(r).hex(), "asset_list": listed.decode(),
+ "spend_micros": r["budget_spent_micros"]})
+ out = {
+ "outcome": "accept",
+ "room": d["room"].hex(),
+ "breaks": [{"digest": b["digest"].hex(), "id": break_id(b["digest"]), "event_id": event_id(b["digest"]),
+ "author": b["author"].hex(), "cue_id": b["cue_id"], "t": b["t"],
+ "type": "out" if b["type"] == OUT else "break", "placement": "post" if b["placement"] == POST else "mid",
+ "planned_ms": b["planned_ms"], "avail_ms": b["avail_ms"], "duration_ms": b["duration_ms"],
+ "closed_by": b["closed_by"].hex() if b["closed_by"] else None, "end": b["end"],
+ "hls_list_uri": hls_uri(p, b["digest"]), "dash_list_uri": dash_uri(p, b["digest"])} for b in breaks],
+ "chapters": [{"digest": c["digest"].hex(), "t": c["t"], "duration_ms": c["duration_ms"]} for c in chapters],
+ "hls": v["name"] + ".m3u8",
+ "dash": dash_streams(p, pt0, breaks),
+ "dash_open": [{"break": k, "event": dash_event(p, pt0, b, False)} for k, b in enumerate(breaks) if b["type"] == OUT],
+ "receipt_facts": stated,
+ "spend_micros": min(total, U64),
+ }
+ return out, "".join(t + "\n" for t in tags).encode()
+
+
+# ---------------------------------------------------------------- the corpus
+
+def check(doc, files, quiet=False):
+ """Every room against its statement, and every tag file: the failures, and the files written."""
+ fails, written = [], {}
+ for v in doc.get("vectors", []):
+ try:
+ got, tags = ads(v)
+ except Exception as e: # a crash on a mutated copy is a failure to read it
+ got, tags = None, None
+ bad = ["the vector does not verify: %s: %s" % (type(e).__name__, e)]
+ else:
+ bad = [k for k in got if v.get(k) != got[k]]
+ bad += [k for k in ("outcome", "reason", "at", "gaps", "breaks", "hls", "dash", "receipt_facts")
+ if k in v and k not in got]
+ if tags is not None:
+ name = v["name"] + ".m3u8"
+ written[name] = tags
+ if files.get(name) != tags:
+ bad.append("its tag file %s" % name)
+ if not quiet:
+ what = "?" if got is None else got["outcome"] + (" " + got["reason"] if got["outcome"] == "refuse" else "")
+ print(("PASS" if not bad else "FAIL") + " room %s: %s" % (v.get("name"), what))
+ fails += ["room %s: %s differs from the corpus" % (v.get("name"), k) for k in bad]
+ return fails, written
+
+
+def flip(h, at):
+ b = bytearray(bytes.fromhex(h))
+ b[at] ^= 0x01
+ return b.hex()
+
+
+def mutations(doc, files):
+ """Copies of the corpus, each with one defect a conforming verifier must catch."""
+ vn = {v["name"]: k for k, v in enumerate(doc["vectors"])}
+ live, mid, post, chap, dup = vn["live_break"], vn["mid_roll"], vn["post_roll"], vn["chapters"], vn["receipt_duplicate"]
+
+ def mutate(label, f):
+ d, fs = copy.deepcopy(doc), dict(files)
+ f(d["vectors"], fs)
+ return label, d, fs
+
+ def drop_last(vs, chain_of):
+ """The last record of the chain holding a producer's cues."""
+ ch = vs[chain_of]["chains"]
+ longest = max(range(len(ch)), key=lambda c: len(ch[c]))
+ ch[longest].pop()
+
+ def swap_pod(vs, fs):
+ pod = vs[mid]["pods"][0]
+ pod[1], pod[3] = pod[3], pod[1]
+
+ def edit_file(name, a, b):
+ return lambda vs, fs: fs.update({name: fs[name].replace(a, b, 1)})
+
+ return [
+ mutate("a byte of the live break's policy signature", lambda vs, fs: vs[live].update(policy=flip(vs[live]["policy"], -2))),
+ mutate("the live break's in withheld from the end of its chain", lambda vs, fs: drop_last(vs, live)),
+ mutate("the post-roll's annotation withheld", lambda vs, fs: drop_last(vs, post)),
+ mutate("a stated asset list with one ad's duration changed",
+ lambda vs, fs: vs[live]["receipt_facts"][0].update(asset_list=vs[live]["receipt_facts"][0]["asset_list"].replace("15.000", "14.000", 1))),
+ mutate("the DASH events stated with executeOnce false",
+ lambda vs, fs: vs[live].update(dash=vs[live]["dash"].replace('executeOnce="true"', 'executeOnce="false"'))),
+ mutate("the open out's DASH event stated with its duration",
+ lambda vs, fs: vs[live]["dash_open"][0].update(event=vs[live]["dash"].split(">", 1)[1].rsplit("", 1)[0])),
+ mutate("a byte of the mid-roll's tag file", edit_file("mid_roll.m3u8", b"X-RESUME-OFFSET=30.000", b"X-RESUME-OFFSET=31.000")),
+ mutate("the post-roll's tag stated without POST", edit_file("post_roll.m3u8", b'CUE="POST,ONCE"', b'CUE="ONCE"')),
+ mutate("the mid-roll pod given in another order", swap_pod),
+ mutate("the mid-roll's presentation time of t0 changed", lambda vs, fs: vs[mid].update(pt0_ms=vs[mid]["pt0_ms"] + 1)),
+ mutate("a live break receipt's quartile raised",
+ lambda vs, fs: vs[live]["receipts"][0]["impressions"][1].update(quartiles_reached=4)),
+ mutate("a live break receipt's session changed",
+ lambda vs, fs: vs[live]["receipts"][1].update(session="00" * 16)),
+ mutate("the stated spend", lambda vs, fs: vs[live].update(spend_micros=vs[live]["spend_micros"] + 1)),
+ mutate("a chapter's stated length", lambda vs, fs: vs[chap]["chapters"][0].update(duration_ms=59999)),
+ mutate("the duplicated receipt given once", lambda vs, fs: vs[dup]["receipts"].pop()),
+ mutate("the funds control's second receipt given once",
+ lambda vs, fs: vs[vn["funds_exceeded"]]["receipts"].pop()),
+ mutate("a tag file removed", lambda vs, fs: fs.pop("chapters.m3u8")),
+ ]
+
+
+def main(corpus_dir):
+ fails = []
+ st = ED._self_test()
+ print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st))
+ fails += ["ed25519 self-test"] if st else []
+ ok = len(REASONS) == 20
+ print(("PASS" if ok else "FAIL") + " §10 read from the extension: %d reasons" % len(REASONS))
+ fails += [] if ok else ["§10 does not read"]
+ with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
+ doc = json.load(f)
+ files = {}
+ for name in sorted(os.listdir(corpus_dir)):
+ if name.endswith(".m3u8"):
+ with open(os.path.join(corpus_dir, name), "rb") as f:
+ files[name] = f.read()
+ got, written = check(doc, files)
+ fails += got
+ extra = sorted(set(os.listdir(corpus_dir)) - set(written) - {"vectors.json", "README.md"})
+ print(("PASS" if not extra else "FAIL") + " no file but vectors.json, README.md and the tag files the rooms write")
+ fails += ["an extra file: %s" % n for n in extra]
+ seen = {v.get("reason") for v in doc["vectors"]}
+ missing = [r for r in REASONS if r not in seen]
+ print(("PASS" if not missing else "FAIL") + " every reason of §10 appears in the corpus")
+ fails += ["no case gives %s" % r for r in missing]
+ for label, d, fs in mutations(doc, files):
+ caught, _ = check(d, fs, quiet=True)
+ print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:100] if caught else ""))
+ if not caught:
+ fails.append("a mutated copy verifies: %s" % label)
+ for f in fails:
+ print("FAIL " + f)
+ print(("PASS" if not fails else "FAIL") + " live-ads corpus: %d rooms, %d tag files" % (len(doc["vectors"]), len(files)))
+ return 1 if fails else 0
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("usage: live_ads_verify.py ", file=sys.stderr)
+ sys.exit(2)
+ sys.exit(main(sys.argv[1]))
diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json
index 95900f08c..f3a940393 100644
--- a/wai/registry/capabilities.json
+++ b/wai/registry/capabilities.json
@@ -602,6 +602,20 @@
"edition": null,
"registered": "2026-06-12"
},
+ {
+ "capability": "wai.live.ads",
+ "kind": "record",
+ "status": "registered",
+ "media": null,
+ "role": null,
+ "tier": null,
+ "licence": null,
+ "replicate": null,
+ "sets": [],
+ "spec_section": "§5 Interactive, temporal & computed content",
+ "edition": null,
+ "registered": "2026-10-20"
+ },
{
"capability": "wai.live.compose",
"kind": "record",
@@ -1885,6 +1899,13 @@
"section": "§4.3",
"status": "defined"
},
+ {
+ "name": "WAB1",
+ "kind": "magic",
+ "document": "extensions/live-ads.md",
+ "section": "§4.2",
+ "status": "defined"
+ },
{
"name": "WAI-Room-Challenge",
"kind": "http-header",
@@ -1927,6 +1948,13 @@
"section": "§5",
"status": "defined"
},
+ {
+ "name": "WAP1",
+ "kind": "magic",
+ "document": "extensions/live-ads.md",
+ "section": "§3.1",
+ "status": "defined"
+ },
{
"name": "WCD1",
"kind": "magic",
diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl
index 8ff0794a9..7dc643ce1 100644
--- a/wai/vocab/v2.ttl
+++ b/wai/vocab/v2.ttl
@@ -534,6 +534,13 @@ wai:wai.image.png a wai:Capability ;
wai:specSection "§5 Mandatory floor" ;
wai:registeredOn "2026-06-12"^^xsd:date .
+wai:wai.live.ads a wai:Capability ;
+ rdfs:label "wai.live.ads" ;
+ wai:kind wai:kind-record ;
+ wai:status wai:status-registered ;
+ wai:specSection "§5 Interactive, temporal & computed content" ;
+ wai:registeredOn "2026-10-20"^^xsd:date .
+
wai:wai.live.compose a wai:Capability ;
rdfs:label "wai.live.compose" ;
wai:kind wai:kind-record ;
diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml
index b810cf1fb..a56552f96 100644
--- a/wai/wai-rs/Cargo.toml
+++ b/wai/wai-rs/Cargo.toml
@@ -247,6 +247,14 @@ live_moderation = ["live_events", "live_room", "live_record"]
# deterministic-CBOR reader of its own and RFC 9052's COSE_Mac0 / COSE_Sign1 structure. SHA-256,
# Ed25519 and the key log only; no new dependency.
live_watermark = ["live_room"]
+# Live ads (extensions/live-ads): `wai.live.ads` — server-guided ad insertion for rooms: the
+# room authority's signed ad policy (WAP1), the schedule of breaks the room's `cue` events open
+# and close with the post-roll annotation (WAB1), each break's HLS interstitial and preload date
+# ranges and DASH alternative-presentation event written exactly, the HLS asset lists and DASH
+# list MPDs each viewer is served, and the room verifier extended to the break form of
+# wai.video.ssai's receipt: the decision recomputed, the asset list bound, the spend held to the
+# decider grant's ceiling. SHA-256, BLAKE3, Ed25519 and the key log only; no new dependency.
+live_ads = ["live_room", "video_ssai"]
# Timed text and live captions (extensions/timed-text): `wai.text.timed` — the cue model,
# live caption revisions (`caption` events of wai.live.events with the WTX1 annotation),
# and their exact mappings onto WebVTT documents and segments, the IMSC1 Text Profile,
@@ -726,6 +734,11 @@ name = "wai_live_watermark_vectors"
path = "src/bin/wai_live_watermark_vectors.rs"
required-features = ["live_watermark"]
+[[bin]]
+name = "wai_live_ads_vectors"
+path = "src/bin/wai_live_ads_vectors.rs"
+required-features = ["live_ads"]
+
[[bin]]
name = "wai_timed_text_vectors"
path = "src/bin/wai_timed_text_vectors.rs"
@@ -1412,6 +1425,11 @@ name = "live_watermark"
path = "tests/live_watermark.rs"
required-features = ["live_watermark"]
+[[test]]
+name = "live_ads"
+path = "tests/live_ads.rs"
+required-features = ["live_ads"]
+
[[test]]
name = "timed_text"
path = "tests/timed_text.rs"
diff --git a/wai/wai-rs/src/bin/wai_live_ads_vectors.rs b/wai/wai-rs/src/bin/wai_live_ads_vectors.rs
new file mode 100644
index 000000000..a025eeb5c
--- /dev/null
+++ b/wai/wai-rs/src/bin/wai_live_ads_vectors.rs
@@ -0,0 +1,75 @@
+//! Conformance corpus for live ads (`live-conformance/ads/`, extensions/live-ads.md §11).
+//!
+//! wai_live_ads_vectors gen write vectors.json and each accepted room's HLS
+//! tags (NAME.m3u8), from fixed seeds
+//! wai_live_ads_vectors verify every file regenerates byte for byte, no other
+//! file but README.md is there, and every case gets
+//! the outcome it states
+//!
+//! The generator and this verifier are one implementation. The corpus's second
+//! implementation is `live-conformance/live_ads_verify.py`, written from the extension's
+//! text and sharing no code with this binary.
+
+use std::collections::BTreeMap;
+use std::path::Path;
+use wai::live_ads::corpus;
+
+fn main() {
+ let a: Vec = std::env::args().collect();
+ match (a.get(1).map(String::as_str), a.get(2)) {
+ (Some("gen"), Some(d)) => {
+ if let Err(e) = std::fs::create_dir_all(d) {
+ eprintln!("create {d}: {e}");
+ std::process::exit(1);
+ }
+ for (name, body) in corpus::generate() {
+ let p = Path::new(d).join(&name);
+ if let Err(e) = std::fs::write(&p, body) {
+ eprintln!("write {}: {e}", p.display());
+ std::process::exit(1);
+ }
+ }
+ println!("wrote {d}");
+ }
+ (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))),
+ _ => {
+ eprintln!("usage: wai_live_ads_vectors gen|verify ");
+ std::process::exit(2);
+ }
+ }
+}
+
+fn verify(dir: &Path) -> i32 {
+ let mut files = BTreeMap::new();
+ let entries = match std::fs::read_dir(dir) {
+ Ok(e) => e,
+ Err(e) => {
+ eprintln!("read {}: {e}", dir.display());
+ return 1;
+ }
+ };
+ for e in entries.flatten() {
+ let name = e.file_name().to_string_lossy().into_owned();
+ if name == "README.md" {
+ continue;
+ }
+ match std::fs::read_to_string(e.path()) {
+ Ok(s) => {
+ files.insert(name, s);
+ }
+ Err(err) => {
+ eprintln!("read {name}: {err}");
+ return 1;
+ }
+ }
+ }
+ let generated: BTreeMap = corpus::generate().into_iter().collect();
+ let same = files == generated;
+ println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" });
+ let fails = corpus::verify(&files);
+ for f in &fails {
+ println!("FAIL {f}");
+ }
+ println!("{} every case gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" });
+ (!same || !fails.is_empty()) as i32
+}
diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs
index d0d3362ec..4cd0e0c41 100644
--- a/wai/wai-rs/src/codecs/registry.rs
+++ b/wai/wai-rs/src/codecs/registry.rs
@@ -220,6 +220,7 @@ pub const CAP_TEXT_TIMED: &str = "wai.text.timed";
pub const CAP_LIVE_MODERATION: &str = "wai.live.moderation";
pub const CAP_LIVE_E2EE: &str = "wai.live.e2ee";
pub const CAP_LIVE_WATERMARK: &str = "wai.live.watermark";
+pub const CAP_LIVE_ADS: &str = "wai.live.ads";
/// One row per string SPEC §5 registers that is a payload, companion or record:
/// `(string, SPEC section, edition, registered)`.
@@ -308,6 +309,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[
(CAP_LIVE_MODERATION, S_INTERACTIVE, None, "2026-10-19"),
(CAP_LIVE_E2EE, S_INTERACTIVE, None, "2026-10-19"),
(CAP_LIVE_WATERMARK, S_INTERACTIVE, None, "2026-10-20"),
+ (CAP_LIVE_ADS, S_INTERACTIVE, None, "2026-10-20"),
(CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"),
(CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"),
(CAP_AUDIO_IVAS, S_IMMERSIVE, None, "2026-06-17"),
@@ -359,6 +361,7 @@ const RECORDS: &[&str] = &[
CAP_LIVE_EVENTS, CAP_LIVE_ROOM, CAP_CAPTURE_TAKE, CAP_LIVE_COMPOSE, CAP_LIVE_RECORDING,
CAP_LIVE_METERING, CAP_LIVE_STEERING, CAP_TEXT_TIMED, CAP_LIVE_MODERATION, CAP_LIVE_E2EE,
CAP_LIVE_WATERMARK,
+ CAP_LIVE_ADS,
];
/// A named identifier SPEC uses that has the shape of a capability string but is
@@ -514,6 +517,7 @@ const ENERGY_STEERING: &str = "extensions/energy-steering.md";
const LIVE_MODERATION: &str = "extensions/live-moderation.md";
const CONFIDENTIALITY: &str = "extensions/confidentiality.md";
const FORENSIC_WATERMARK: &str = "extensions/forensic-watermark.md";
+const LIVE_ADS: &str = "extensions/live-ads.md";
const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str,
status: WireStatus) -> WireIdentifier {
@@ -524,8 +528,8 @@ const fn wire(name: &'static str, kind: WireKind, document: &'static str, sectio
/// (http-delivery, moq-streaming-format), of the key discovery those bindings serve
/// (jwp-receipts §7), of packaging, of live events, of live rooms, of capture binding, of
/// live composition, of live recording, of timed text, of live metering, of energy steering,
-/// of live moderation, of confidentiality and of forensic watermark sessions, in document
-/// order. The export lists them sorted by name and kind.
+/// of live moderation, of confidentiality, of forensic watermark sessions and of live ads, in
+/// document order. The export lists them sorted by name and kind.
pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[
wire("WAI1", WireKind::Magic, "SPEC.md", "§2", WireStatus::Defined),
wire("WAI2", WireKind::Magic, "SPEC.md", "§7.1", WireStatus::Defined),
@@ -609,6 +613,8 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[
wire("WWS1", WireKind::Magic, FORENSIC_WATERMARK, "§5.1", WireStatus::Defined),
wire("WWQ1", WireKind::Magic, FORENSIC_WATERMARK, "§7.1", WireStatus::Defined),
wire("WWD1", WireKind::Magic, FORENSIC_WATERMARK, "§8.1", WireStatus::Defined),
+ wire("WAP1", WireKind::Magic, LIVE_ADS, "§3.1", WireStatus::Defined),
+ wire("WAB1", WireKind::Magic, LIVE_ADS, "§4.2", WireStatus::Defined),
];
/// A string with the shape of a capability that the reference implementation uses
diff --git a/wai/wai-rs/src/energy_figure.rs b/wai/wai-rs/src/energy_figure.rs
index 66380f8c3..80987c08d 100644
--- a/wai/wai-rs/src/energy_figure.rs
+++ b/wai/wai-rs/src/energy_figure.rs
@@ -267,6 +267,8 @@ macro_rules! single_figure {
single_figure!("video_receipt", crate::video_receipt::VideoReceipt, "wai:video-receipt-id");
single_figure!("video_manifest", crate::wai_video_manifest::ManifestReceipt, "wai:video-manifest-receipt-id");
single_figure!("video_ssai", crate::wai_video_ssai::SsaiReceipt, "wai:video-ssai-receipt-id");
+// The break form (live-ads §8) shares the stitched form's identity domain, so its profile.
+single_figure!("video_ssai", crate::wai_video_ssai::SsaiBreakReceipt, "wai:video-ssai-receipt-id");
single_figure!("video_keys", crate::wai_video_keys::KeyReleaseReceipt, "wai:video-keys-receipt-id");
single_figure!("video_package", crate::wai_video_package::PackageReceipt, "wai:video-package-receipt-id");
single_figure!("video_channel", crate::wai_video_channel::ChannelReceipt, "wai:video-channel-receipt-id");
diff --git a/wai/wai-rs/src/energy_figure/tests.rs b/wai/wai-rs/src/energy_figure/tests.rs
index 5f6e454c3..9aba41bd5 100644
--- a/wai/wai-rs/src/energy_figure/tests.rs
+++ b/wai/wai-rs/src/energy_figure/tests.rs
@@ -188,6 +188,36 @@ fn the_ott_receipts_are_viewed_under_their_profiles() {
}
}
+/// A `wai.video.ssai` receipt of the break form (live-ads §8) is viewed under the stitched
+/// form's profile: the two share the identity domain.
+#[cfg(feature = "video_ssai")]
+#[test]
+fn a_break_form_ssai_receipt_is_viewed_under_the_ssai_profile() {
+ use crate::wai_video_ssai::{asset_format, decide, pod_hash, AdCandidate, BreakBinding, SsaiBreakReceipt};
+ let pod = vec![AdCandidate { ad_id: "a".into(), creative_hash: [1; 32], duration_ms: 15_000, price_micros: 20_000 }];
+ let mut imps = decide(&pod, 1, 30_000, 50_000);
+ imps[0].quartiles_reached = 5;
+ let binding = BreakBinding {
+ room: [2; 32],
+ cue: [3; 32],
+ session: [4; 16],
+ start_offset_ms: 0,
+ format: asset_format::HLS,
+ asset_list_sha256: [5; 32],
+ avail_ms: 30_000,
+ slots: 1,
+ funds_remaining_micros: 50_000,
+ };
+ let grant = wai_quantum::quantum_ops::GrantRef { grant_hash: [6; 32], capability: "wai.live.room".into(), joule_ceiling_micro: u64::MAX, funds_ceiling: None };
+ let r = SsaiBreakReceipt::seal(&key(4), "d", binding, pod_hash(&pod), imps, 1, 700, Some(EnergyProvenance::ModelBased), grant);
+ let v = r.figure_view().unwrap();
+ assert_eq!((v.profile.as_str(), v.identity), ("wai:video-ssai-receipt-id", r.receipt_hash()));
+ assert_eq!(v.figures[0].class, Some(EnergyProvenance::ModelBased));
+ let mut bad = r;
+ bad.sig[0] ^= 1;
+ assert!(bad.figure_view().is_none(), "a receipt that does not verify has no view");
+}
+
#[cfg(feature = "quantum_receipt")]
#[test]
fn a_quantum_receipt_is_viewed_in_either_form() {
diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs
index 17e3ce4cd..a9caedfc5 100644
--- a/wai/wai-rs/src/lib.rs
+++ b/wai/wai-rs/src/lib.rs
@@ -468,6 +468,15 @@ pub mod live_moderation;
/// cbindgen:ignore
#[cfg(feature = "live_watermark")]
pub mod live_watermark;
+// Live ads (extensions/live-ads.md): `wai.live.ads` — server-guided ad insertion for rooms:
+// the room authority's signed ad policy (WAP1), the room's breaks from its signed `cue` events
+// with the post-roll annotation (WAB1), each break's HLS interstitial date ranges and DASH
+// alternative-presentation event written exactly, the asset lists each viewer is served, and
+// verification from the events to the spend through the break form of wai.video.ssai's receipt.
+// Rust-only, like the other live modules: its constants must not become macros in include/wai.h.
+/// cbindgen:ignore
+#[cfg(feature = "live_ads")]
+pub mod live_ads;
// Timed text and live captions (extensions/timed-text.md): `wai.text.timed` — the cue model
// both WebVTT and the IMSC1 Text Profile express, live caption revisions as `caption`
// events of `wai.live.events` with the WTX1 annotation, and the exact mappings onto WebVTT
diff --git a/wai/wai-rs/src/live_ads.rs b/wai/wai-rs/src/live_ads.rs
new file mode 100644
index 000000000..7bdc56148
--- /dev/null
+++ b/wai/wai-rs/src/live_ads.rs
@@ -0,0 +1,1060 @@
+//! Live ads — `wai.live.ads` ([extensions/live-ads.md]).
+//!
+//! Server-guided ad insertion for rooms. The room authority signs an ad policy (`WAP1`)
+//! naming the grant its decider runs under and how breaks are presented. A room's breaks
+//! are its signed `cue` events of `wai.live.events`, an out and its in or a break of known
+//! duration, with a post-roll marked by a break annotation (`WAB1`) in the cue author's
+//! chain. Each break maps, as a pure function of the signed records, onto one interstitial
+//! date range of HLS and one alternative-presentation event of DASH, so manifests stay
+//! shared; each viewer's decision is made behind the asset list the break names, by
+//! `wai.video.ssai`'s deterministic decision, and sealed in that capability's receipt of
+//! the break form ([`crate::wai_video_ssai::SsaiBreakReceipt`]).
+//!
+//! What this module implements, by section of the extension:
+//!
+//! - §3: the ad policy, its rules and signature;
+//! - §4: the break annotation and the schedule, with its refusals;
+//! - §5, §6: the HLS tags and the DASH events, written exactly;
+//! - §7: the asset lists, HLS and DASH;
+//! - §9: verification from the room's events to the spend.
+//!
+//! The corpus `live-conformance/ads/` is written by [`corpus::generate`] and checked by
+//! [`corpus::verify`], and by `live-conformance/live_ads_verify.py`, which shares no code
+//! with this module.
+//!
+//! [extensions/live-ads.md]: ../../extensions/live-ads.md
+
+use std::collections::BTreeSet;
+
+use ed25519_dalek::{Signer, SigningKey};
+use sha2::{Digest, Sha256};
+
+use crate::live_events::{self as events, cue_type, iso8601_ms, kind, Event, Payload};
+use crate::live_room as room;
+use crate::wai_video_ssai::{asset_format, decide, pod_hash, within_ceiling, AdCandidate, BreakReceiptFault, Impression, SsaiBreakReceipt};
+
+pub mod corpus;
+
+/// The capability string this extension registers.
+pub const CAPABILITY: &str = "wai.live.ads";
+/// The magic of an ad policy (§3.1).
+pub const POLICY_MAGIC: [u8; 4] = *b"WAP1";
+/// The magic of a break annotation (§4.2).
+pub const ANNOTATION_MAGIC: [u8; 4] = *b"WAB1";
+/// The version of every record this revision defines.
+pub const RECORD_VERSION: u8 = 1;
+/// The domain a policy's signature is made under (§3.2).
+pub const POLICY_DOMAIN: &[u8] = b"wai:live-ads-policy\x01";
+/// The length of a break annotation (§4.2).
+pub const ANNOTATION_LEN: usize = 38;
+/// `skip_after_s` when the policy offers no skip control (§3.1).
+pub const NO_SKIP_CONTROL: u16 = u16::MAX;
+/// The Interstitial date-range class (`draft-pantos-hls-rfc8216bis-22` Appendix D.2).
+pub const INTERSTITIAL_CLASS: &str = "com.apple.hls.interstitial"; // allow-name: the interstitial date-range class, a protocol identifier
+/// The Preload date-range class (`draft-pantos-hls-rfc8216bis-22` Appendix F).
+pub const PRELOAD_CLASS: &str = "com.apple.hls.preload"; // allow-name: the preload date-range class, a protocol identifier
+/// The DASH event scheme of a replacement (ISO/IEC 23009-1:2026 §5.16).
+pub const REPLACE_SCHEME: &str = "urn:mpeg:dash:event:alternativeMPD:replace:2025";
+/// The DASH event scheme of an insertion (ISO/IEC 23009-1:2026 §5.16).
+pub const INSERT_SCHEME: &str = "urn:mpeg:dash:event:alternativeMPD:insert:2025";
+/// The capability a receipt's grant reference names (live-rooms §4.5).
+pub const ROOM_CAPABILITY: &str = room::CAPABILITY;
+
+/// A break's placement (§4.2).
+pub mod placement {
+ pub const MID: u8 = 1;
+ pub const POST: u8 = 2;
+}
+
+/// The bits of a policy's `restrict` (§3.1).
+pub mod restrict {
+ pub const SKIP: u8 = 1;
+ pub const JUMP: u8 = 2;
+}
+
+/// The bits of a policy's `snap` (§3.1).
+pub mod snap {
+ pub const OUT: u8 = 1;
+ pub const IN: u8 = 2;
+}
+
+/// Why a policy, a schedule or a receipt is refused (§10).
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub enum Reason {
+ /// A code of live-rooms §11, or through it of live-events §9.
+ Room(room::Reason),
+ AdPolicyMalformed,
+ DeciderGrantUnknown,
+ AnnotationMalformed,
+ AnnotationMisplaced,
+ CueAfterClose,
+ BreakOverlap,
+ BreakDurationUnknown,
+ BreakZeroDuration,
+ CueInUnmatched,
+ CueDurationMismatch,
+ ReceiptMalformed,
+ ReceiptInvalid,
+ ReceiptNotDecider,
+ ReceiptGrantMismatch,
+ ReceiptBreakUnknown,
+ PodMismatch,
+ DecisionMismatch,
+ AssetListMismatch,
+ ReceiptDuplicate,
+ FundsExceeded,
+}
+
+impl Reason {
+ /// The codes this extension adds, in §10's order.
+ pub const ALL: [Reason; 20] = [
+ Reason::AdPolicyMalformed,
+ Reason::DeciderGrantUnknown,
+ Reason::AnnotationMalformed,
+ Reason::AnnotationMisplaced,
+ Reason::CueAfterClose,
+ Reason::BreakOverlap,
+ Reason::BreakDurationUnknown,
+ Reason::BreakZeroDuration,
+ Reason::CueInUnmatched,
+ Reason::CueDurationMismatch,
+ Reason::ReceiptMalformed,
+ Reason::ReceiptInvalid,
+ Reason::ReceiptNotDecider,
+ Reason::ReceiptGrantMismatch,
+ Reason::ReceiptBreakUnknown,
+ Reason::PodMismatch,
+ Reason::DecisionMismatch,
+ Reason::AssetListMismatch,
+ Reason::ReceiptDuplicate,
+ Reason::FundsExceeded,
+ ];
+
+ /// The reason's code.
+ pub fn code(self) -> &'static str {
+ match self {
+ Reason::Room(r) => r.code(),
+ Reason::AdPolicyMalformed => "ad_policy_malformed",
+ Reason::DeciderGrantUnknown => "decider_grant_unknown",
+ Reason::AnnotationMalformed => "annotation_malformed",
+ Reason::AnnotationMisplaced => "annotation_misplaced",
+ Reason::CueAfterClose => "cue_after_close",
+ Reason::BreakOverlap => "break_overlap",
+ Reason::BreakDurationUnknown => "break_duration_unknown",
+ Reason::BreakZeroDuration => "break_zero_duration",
+ Reason::CueInUnmatched => "cue_in_unmatched",
+ Reason::CueDurationMismatch => "cue_duration_mismatch",
+ Reason::ReceiptMalformed => "receipt_malformed",
+ Reason::ReceiptInvalid => "receipt_invalid",
+ Reason::ReceiptNotDecider => "receipt_not_decider",
+ Reason::ReceiptGrantMismatch => "receipt_grant_mismatch",
+ Reason::ReceiptBreakUnknown => "receipt_break_unknown",
+ Reason::PodMismatch => "pod_mismatch",
+ Reason::DecisionMismatch => "decision_mismatch",
+ Reason::AssetListMismatch => "asset_list_mismatch",
+ Reason::ReceiptDuplicate => "receipt_duplicate",
+ Reason::FundsExceeded => "funds_exceeded",
+ }
+ }
+}
+
+impl std::fmt::Display for Reason {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(self.code())
+ }
+}
+
+impl std::error::Error for Reason {}
+
+fn ev(r: events::Reason) -> Reason {
+ Reason::Room(room::Reason::Events(r))
+}
+
+fn sha256(parts: &[&[u8]]) -> [u8; 32] {
+ let mut h = Sha256::new();
+ for p in parts {
+ h.update(p);
+ }
+ h.finalize().into()
+}
+
+fn hex(b: &[u8]) -> String {
+ b.iter().map(|x| format!("{x:02x}")).collect()
+}
+
+// ---------------------------------------------------------------------------------------
+// §3 The ad policy
+// ---------------------------------------------------------------------------------------
+
+/// An ad policy (§3).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct AdPolicy {
+ pub room: [u8; 32],
+ /// The digest of the room grant the decider runs under (§3.4).
+ pub decider_grant: [u8; 32],
+ pub slots: u8,
+ pub open_avail_ms: u32,
+ pub preload_ms: u32,
+ /// [`restrict`].
+ pub restrict: u8,
+ /// [`NO_SKIP_CONTROL`] for none.
+ pub skip_after_s: u16,
+ /// [`snap`].
+ pub snap: u8,
+ pub asset_base: String,
+ pub creative_base: String,
+ pub sig: [u8; 64],
+}
+
+/// Is this a base (§3.1)? `https://`, then 1 to 492 bytes of `!` to `~` other than the eight
+/// that quoted strings, JSON or XML would escape, the last of them `/`.
+pub fn base_ok(b: &[u8]) -> bool {
+ let rest = match b.strip_prefix(b"https://") {
+ Some(r) => r,
+ None => return false,
+ };
+ !rest.is_empty()
+ && rest.len() <= 492
+ && rest.last() == Some(&b'/')
+ && rest.iter().all(|&c| (0x21..=0x7e).contains(&c) && !b"\"#&'<>?\\".contains(&c))
+}
+
+/// A cursor that reports running out as `None`.
+struct Rd<'a> {
+ b: &'a [u8],
+ p: usize,
+}
+
+impl<'a> Rd<'a> {
+ fn take(&mut self, n: usize) -> Option<&'a [u8]> {
+ let end = self.p.checked_add(n)?;
+ let s = self.b.get(self.p..end)?;
+ self.p = end;
+ Some(s)
+ }
+ fn u8(&mut self) -> Option {
+ Some(self.take(1)?[0])
+ }
+ fn u16(&mut self) -> Option {
+ Some(u16::from_le_bytes(self.take(2)?.try_into().ok()?))
+ }
+ fn u32(&mut self) -> Option {
+ Some(u32::from_le_bytes(self.take(4)?.try_into().ok()?))
+ }
+ fn d32(&mut self) -> Option<[u8; 32]> {
+ self.take(32)?.try_into().ok()
+ }
+ fn long(&mut self) -> Option<&'a [u8]> {
+ let n = self.u16()? as usize;
+ self.take(n)
+ }
+}
+
+fn put_long(o: &mut Vec, b: &[u8]) {
+ assert!(b.len() <= u16::MAX as usize, "a long field is at most 65535 bytes");
+ o.extend_from_slice(&(b.len() as u16).to_le_bytes());
+ o.extend_from_slice(b);
+}
+
+impl AdPolicy {
+ /// The policy without `sig` (§3.1).
+ pub fn body(&self) -> Vec {
+ let mut o = POLICY_MAGIC.to_vec();
+ o.push(RECORD_VERSION);
+ o.extend_from_slice(&self.room);
+ o.extend_from_slice(&self.decider_grant);
+ o.push(self.slots);
+ o.extend_from_slice(&self.open_avail_ms.to_le_bytes());
+ o.extend_from_slice(&self.preload_ms.to_le_bytes());
+ o.push(self.restrict);
+ o.extend_from_slice(&self.skip_after_s.to_le_bytes());
+ o.push(self.snap);
+ put_long(&mut o, self.asset_base.as_bytes());
+ put_long(&mut o, self.creative_base.as_bytes());
+ o
+ }
+
+ pub fn to_bytes(&self) -> Vec {
+ let mut o = self.body();
+ o.extend_from_slice(&self.sig);
+ o
+ }
+
+ /// The policy's digest (§3.2).
+ pub fn digest(&self) -> [u8; 32] {
+ sha256(&[&self.body()])
+ }
+
+ /// Sign under the room authority's key (§3.2).
+ pub fn sign(mut self, authority: &SigningKey) -> AdPolicy {
+ let mut m = POLICY_DOMAIN.to_vec();
+ m.extend_from_slice(&self.body());
+ self.sig = authority.sign(&m).to_bytes();
+ self
+ }
+
+ /// Read a policy (§3.3): its reading, then its rules.
+ pub fn parse(b: &[u8]) -> Result {
+ if b.len() < 4 {
+ return Err(ev(events::Reason::Truncated));
+ }
+ if b[..4] != POLICY_MAGIC {
+ return Err(ev(events::Reason::Malformed));
+ }
+ if b.len() < 5 {
+ return Err(ev(events::Reason::Truncated));
+ }
+ if b[4] != RECORD_VERSION {
+ return Err(ev(events::Reason::UnknownVersion));
+ }
+ let mut r = Rd { b, p: 5 };
+ let read = |r: &mut Rd| -> Option<(AdPolicy, Vec, Vec)> {
+ let room = r.d32()?;
+ let decider_grant = r.d32()?;
+ let slots = r.u8()?;
+ let open_avail_ms = r.u32()?;
+ let preload_ms = r.u32()?;
+ let restrict = r.u8()?;
+ let skip_after_s = r.u16()?;
+ let snap = r.u8()?;
+ let asset = r.long()?.to_vec();
+ let creative = r.long()?.to_vec();
+ let sig: [u8; 64] = r.take(64)?.try_into().ok()?;
+ let p = AdPolicy {
+ room,
+ decider_grant,
+ slots,
+ open_avail_ms,
+ preload_ms,
+ restrict,
+ skip_after_s,
+ snap,
+ asset_base: String::new(),
+ creative_base: String::new(),
+ sig,
+ };
+ Some((p, asset, creative))
+ };
+ let (mut p, asset, creative) = read(&mut r).ok_or(ev(events::Reason::Truncated))?;
+ if r.p != b.len() {
+ return Err(ev(events::Reason::Malformed));
+ }
+ if !(1..=16).contains(&p.slots) || p.restrict > 3 || p.snap > 3 || !base_ok(&asset) || !base_ok(&creative) {
+ return Err(Reason::AdPolicyMalformed);
+ }
+ // A base is ASCII by its rule, so it is UTF-8.
+ p.asset_base = String::from_utf8(asset).map_err(|_| Reason::AdPolicyMalformed)?;
+ p.creative_base = String::from_utf8(creative).map_err(|_| Reason::AdPolicyMalformed)?;
+ Ok(p)
+ }
+}
+
+// ---------------------------------------------------------------------------------------
+// §4 Breaks
+// ---------------------------------------------------------------------------------------
+
+/// A break annotation (§4.2).
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Annotation {
+ /// The digest of the cue it annotates.
+ pub cue: [u8; 32],
+ /// [`placement`].
+ pub placement: u8,
+}
+
+impl Annotation {
+ pub fn encode(&self) -> Vec {
+ let mut o = ANNOTATION_MAGIC.to_vec();
+ o.push(RECORD_VERSION);
+ o.extend_from_slice(&self.cue);
+ o.push(self.placement);
+ o
+ }
+
+ /// Is a `meta` payload an annotation, by its first four bytes?
+ pub fn is_annotation(payload: &[u8]) -> bool {
+ payload.starts_with(&ANNOTATION_MAGIC)
+ }
+
+ /// Read an annotation (§4.2): `annotation_malformed` unless it is 38 bytes of version 1
+ /// with a placement of 1 or 2.
+ pub fn parse(b: &[u8]) -> Result {
+ if b.len() != ANNOTATION_LEN || b[..4] != ANNOTATION_MAGIC || b[4] != RECORD_VERSION || !matches!(b[37], placement::MID | placement::POST) {
+ return Err(Reason::AnnotationMalformed);
+ }
+ Ok(Annotation { cue: b[5..37].try_into().expect("32 bytes"), placement: b[37] })
+ }
+}
+
+/// A break of the schedule (§4.3).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Break {
+ /// The digest of the cue that opened it.
+ pub digest: [u8; 32],
+ pub author: [u8; 32],
+ pub cue_id: u32,
+ pub t: u64,
+ /// [`cue_type::OUT`] or [`cue_type::BREAK`].
+ pub cue_type: u8,
+ /// [`placement`].
+ pub placement: u8,
+ /// An out's planned duration, 0 when none; a break's duration.
+ pub planned_ms: u32,
+ pub avail_ms: u64,
+ /// The duration, once known: a break's, or an out's once its in closed it.
+ pub duration_ms: Option,
+ /// The in that closed an out.
+ pub closed_by: Option<[u8; 32]>,
+ /// The room-clock end, once known.
+ pub end: Option,
+}
+
+impl Break {
+ /// An out its in has not closed.
+ pub fn is_open(&self) -> bool {
+ self.cue_type == cue_type::OUT && self.closed_by.is_none()
+ }
+}
+
+/// A chapter of the schedule (§4.3).
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Chapter {
+ pub digest: [u8; 32],
+ pub t: u64,
+ pub duration_ms: u32,
+}
+
+/// Where a tag of §5 is written, in the order a packager writes them (§5.4).
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Written {
+ /// A break's preload and interstitial tags, at its cue.
+ Opened(usize),
+ /// An out's closing tag, at its in.
+ Closed(usize),
+}
+
+/// The room's breaks and chapters (§4.3).
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Schedule {
+ pub breaks: Vec,
+ pub chapters: Vec,
+ pub written: Vec,
+}
+
+/// One event the room accepted, in the merged order.
+pub struct Held {
+ pub chain: usize,
+ pub record: usize,
+ pub ev: Event,
+ pub digest: [u8; 32],
+}
+
+/// The schedule over the events the room accepted, in the merged order, each once (§4.3).
+/// A refusal names the record, by its chain and its index in that chain.
+pub fn schedule(held: &[Held], policy: &AdPolicy) -> Result {
+ let mut s = Schedule::default();
+ let mut open: Option = None;
+ let mut last_end: Option = None;
+ let mut closed = false;
+ for (k, h) in held.iter().enumerate() {
+ let refuse = |r: Reason| (h.chain, h.record, r);
+ match h.ev.kind {
+ kind::CUE => {
+ let c = match events::parse_payload(kind::CUE, &h.ev.payload, u16::MAX) {
+ Ok(Payload::Cue(c)) => c,
+ _ => unreachable!("a cue the room accepted reads"),
+ };
+ if closed {
+ return Err(refuse(Reason::CueAfterClose));
+ }
+ let t = h.ev.t;
+ let overlaps = open.is_some() || last_end.is_some_and(|e| t < e);
+ match c.cue_type {
+ cue_type::OUT => {
+ if overlaps {
+ return Err(refuse(Reason::BreakOverlap));
+ }
+ let avail = if c.duration_ms > 0 { c.duration_ms } else { policy.open_avail_ms };
+ if avail == 0 {
+ return Err(refuse(Reason::BreakDurationUnknown));
+ }
+ open = Some(s.breaks.len());
+ s.written.push(Written::Opened(s.breaks.len()));
+ s.breaks.push(Break {
+ digest: h.digest,
+ author: h.ev.principal,
+ cue_id: c.cue_id,
+ t,
+ cue_type: cue_type::OUT,
+ placement: placement::MID,
+ planned_ms: c.duration_ms,
+ avail_ms: avail as u64,
+ duration_ms: None,
+ closed_by: None,
+ end: None,
+ });
+ }
+ cue_type::IN => {
+ let i = match open {
+ Some(i) if s.breaks[i].cue_id == c.cue_id && s.breaks[i].author == h.ev.principal => i,
+ _ => return Err(refuse(Reason::CueInUnmatched)),
+ };
+ let duration = (t - s.breaks[i].t) / 1000;
+ if duration == 0 {
+ return Err(refuse(Reason::BreakZeroDuration));
+ }
+ if c.duration_ms != 0 && c.duration_ms as u64 != duration {
+ return Err(refuse(Reason::CueDurationMismatch));
+ }
+ let b = &mut s.breaks[i];
+ b.duration_ms = Some(duration);
+ b.closed_by = Some(h.digest);
+ b.end = Some(t);
+ open = None;
+ last_end = Some(t);
+ s.written.push(Written::Closed(i));
+ }
+ cue_type::CHAPTER => s.chapters.push(Chapter { digest: h.digest, t, duration_ms: c.duration_ms }),
+ _ => {
+ if c.duration_ms == 0 {
+ return Err(refuse(Reason::BreakZeroDuration));
+ }
+ if overlaps {
+ return Err(refuse(Reason::BreakOverlap));
+ }
+ let end = t.saturating_add(c.duration_ms as u64 * 1000);
+ last_end = Some(end);
+ s.written.push(Written::Opened(s.breaks.len()));
+ s.breaks.push(Break {
+ digest: h.digest,
+ author: h.ev.principal,
+ cue_id: c.cue_id,
+ t,
+ cue_type: cue_type::BREAK,
+ placement: placement::MID,
+ planned_ms: c.duration_ms,
+ avail_ms: c.duration_ms as u64,
+ duration_ms: Some(c.duration_ms as u64),
+ closed_by: None,
+ end: Some(end),
+ });
+ }
+ }
+ }
+ kind::META if Annotation::is_annotation(&h.ev.payload) => {
+ let a = Annotation::parse(&h.ev.payload).map_err(refuse)?;
+ // The event before it in its author's chain: a cue of type break, at its t,
+ // whose digest it names.
+ let before = h.ev.seq.checked_sub(1).and_then(|seq| held[..k].iter().rev().find(|x| x.ev.principal == h.ev.principal && x.ev.seq == seq));
+ let placed = before.is_some_and(|x| {
+ x.digest == a.cue
+ && x.ev.kind == kind::CUE
+ && x.ev.t == h.ev.t
+ && matches!(events::parse_payload(kind::CUE, &x.ev.payload, u16::MAX), Ok(Payload::Cue(c)) if c.cue_type == cue_type::BREAK)
+ });
+ if !placed {
+ return Err(refuse(Reason::AnnotationMisplaced));
+ }
+ if a.placement == placement::POST {
+ if let Some(b) = s.breaks.iter_mut().find(|b| b.digest == a.cue) {
+ b.placement = placement::POST;
+ }
+ closed = true;
+ }
+ }
+ _ => {}
+ }
+ }
+ Ok(s)
+}
+
+// ---------------------------------------------------------------------------------------
+// §4.4 Identity; §5 HLS; §6 DASH; §7 asset lists
+// ---------------------------------------------------------------------------------------
+
+/// A break's HLS ID (§4.4).
+pub fn break_id(digest: &[u8; 32]) -> String {
+ format!("wai-ad-{}", hex(digest))
+}
+
+/// A break's DASH event id (§4.4): the digest's first eight bytes, big-endian, low 53 bits.
+pub fn event_id(digest: &[u8; 32]) -> u64 {
+ u64::from_be_bytes(digest[..8].try_into().expect("8 bytes")) & ((1u64 << 53) - 1)
+}
+
+/// `S.mmm` seconds for a count of milliseconds (§5.1).
+pub fn seconds(ms: u64) -> String {
+ format!("{}.{:03}", ms / 1000, ms % 1000)
+}
+
+/// The break's HLS asset-list URI (§7.2).
+pub fn hls_list_uri(p: &AdPolicy, digest: &[u8; 32]) -> String {
+ let id = break_id(digest);
+ format!("{}{id}.json?_HLS_interstitial_id={id}", p.asset_base)
+}
+
+/// The break's DASH asset-list URI (§7.3).
+pub fn dash_list_uri(p: &AdPolicy, digest: &[u8; 32]) -> String {
+ format!("{}{}.mpd", p.asset_base, break_id(digest))
+}
+
+/// `t0` + `t`, less `less_ms`, as `START-DATE` writes it. A date past year 9999, which no
+/// room of this revision's corpus reaches, is written empty rather than refused.
+fn start_date(t0_us: u64, t: u64, less_ms: u64) -> String {
+ iso8601_ms((t0_us.saturating_add(t) / 1000).saturating_sub(less_ms)).unwrap_or_default()
+}
+
+fn bits_list(v: u8, names: [&str; 2]) -> String {
+ names.iter().enumerate().filter(|(i, _)| v & (1 << i) != 0).map(|(_, n)| *n).collect::>().join(",")
+}
+
+/// A break's Interstitial tag (§5.1).
+pub fn hls_interstitial(p: &AdPolicy, t0_us: u64, b: &Break) -> String {
+ let post = b.placement == placement::POST;
+ let mut a = vec![
+ format!("ID=\"{}\"", break_id(&b.digest)),
+ format!("CLASS=\"{INTERSTITIAL_CLASS}\""),
+ format!("START-DATE=\"{}\"", start_date(t0_us, b.t, 0)),
+ format!("CUE=\"{}\"", if post { "POST,ONCE" } else { "ONCE" }),
+ ];
+ let known = (b.cue_type == cue_type::BREAK).then_some(b.planned_ms as u64);
+ if let Some(d) = known {
+ a.push(format!("DURATION={}", seconds(d)));
+ } else if b.planned_ms > 0 {
+ a.push(format!("PLANNED-DURATION={}", seconds(b.planned_ms as u64)));
+ }
+ a.push(format!("X-ASSET-LIST=\"{}\"", hls_list_uri(p, &b.digest)));
+ if let Some(d) = known {
+ if !post {
+ a.push(format!("X-RESUME-OFFSET={}", seconds(d)));
+ }
+ a.push(format!("X-PLAYOUT-LIMIT={}", seconds(d)));
+ }
+ if p.snap != 0 {
+ a.push(format!("X-SNAP=\"{}\"", bits_list(p.snap, ["OUT", "IN"])));
+ }
+ if p.restrict != 0 {
+ a.push(format!("X-RESTRICT=\"{}\"", bits_list(p.restrict, ["SKIP", "JUMP"])));
+ }
+ if p.skip_after_s != NO_SKIP_CONTROL {
+ a.push(format!("X-SKIP-CONTROL-OFFSET={}", p.skip_after_s));
+ }
+ format!("#EXT-X-DATERANGE:{}", a.join(","))
+}
+
+/// An out's closing tag (§5.2).
+pub fn hls_closing(b: &Break) -> String {
+ let d = seconds(b.duration_ms.expect("a closed out has its duration"));
+ format!("#EXT-X-DATERANGE:ID=\"{}\",CLASS=\"{INTERSTITIAL_CLASS}\",DURATION={d},X-RESUME-OFFSET={d},X-PLAYOUT-LIMIT={d}", break_id(&b.digest))
+}
+
+/// A break's Preload tag (§5.3).
+pub fn hls_preload(p: &AdPolicy, t0_us: u64, b: &Break) -> String {
+ let id = break_id(&b.digest);
+ format!(
+ "#EXT-X-DATERANGE:ID=\"{id}-preload\",CLASS=\"{PRELOAD_CLASS}\",START-DATE=\"{}\",DURATION={},X-URI=\"{}\",X-TARGET-ID=\"{id}\",X-TARGET-CLASS=\"{INTERSTITIAL_CLASS}\"",
+ start_date(t0_us, b.t, p.preload_ms as u64),
+ seconds(p.preload_ms as u64),
+ hls_list_uri(p, &b.digest)
+ )
+}
+
+/// Every HLS tag of the schedule, in the order a packager writes them (§5.4).
+pub fn hls_tags(p: &AdPolicy, t0_us: u64, s: &Schedule) -> Vec {
+ let mut out = Vec::new();
+ for w in &s.written {
+ match *w {
+ Written::Opened(i) => {
+ if p.preload_ms > 0 {
+ out.push(hls_preload(p, t0_us, &s.breaks[i]));
+ }
+ out.push(hls_interstitial(p, t0_us, &s.breaks[i]));
+ }
+ Written::Closed(i) => out.push(hls_closing(&s.breaks[i])),
+ }
+ }
+ out
+}
+
+/// A break's DASH event (§6.2): as written before its in (`closed` false, an out only), or
+/// once its duration is known.
+pub fn dash_event(p: &AdPolicy, pt0_ms: u64, b: &Break, closed: bool) -> String {
+ let known = if closed { b.duration_ms } else { None };
+ let mut e = format!("", event_id(&b.digest));
+ let post = b.placement == placement::POST;
+ let child = if post { "InsertPresentation" } else { "ReplacePresentation" };
+ e += &format!("<{child} uri=\"{}\"", dash_list_uri(p, &b.digest));
+ if p.preload_ms > 0 {
+ e += &format!(" earliestResolutionTimeOffset=\"{}\"", p.preload_ms);
+ }
+ if let Some(d) = known {
+ e += &format!(" maxDuration=\"{d}\"");
+ }
+ e += " executeOnce=\"true\"";
+ let skip = if p.skip_after_s != NO_SKIP_CONTROL { p.skip_after_s as u64 } else { b.avail_ms.div_ceil(1000) };
+ e += &format!(" skipAfter=\"PT{skip}S\"");
+ if let (Some(d), false) = (known, post) {
+ e += &format!(" returnOffset=\"{d}\"");
+ }
+ e += "/>";
+ e
+}
+
+/// The Period's event streams once every break of the schedule is written (§6.3).
+pub fn dash_streams(p: &AdPolicy, pt0_ms: u64, s: &Schedule) -> String {
+ let mut out = String::new();
+ for (scheme, post) in [(REPLACE_SCHEME, false), (INSERT_SCHEME, true)] {
+ let evs: Vec = s.breaks.iter().filter(|b| (b.placement == placement::POST) == post).map(|b| dash_event(p, pt0_ms, b, true)).collect();
+ if !evs.is_empty() {
+ out += &format!("{}", evs.concat());
+ }
+ }
+ out
+}
+
+/// The HLS asset list of a fill (§7.2).
+pub fn hls_asset_list(p: &AdPolicy, fill: &[Impression]) -> Vec {
+ let assets: Vec =
+ fill.iter().map(|i| format!("{{\"URI\":\"{}{}.m3u8\",\"DURATION\":{}}}", p.creative_base, hex(&i.creative_hash), seconds(i.duration_ms))).collect();
+ format!("{{\"ASSETS\":[{}]}}", assets.join(",")).into_bytes()
+}
+
+/// The DASH list MPD of a fill (§7.3); no bytes for an empty fill.
+pub fn dash_asset_list(p: &AdPolicy, fill: &[Impression]) -> Vec {
+ if fill.is_empty() {
+ return Vec::new();
+ }
+ let periods: String = fill
+ .iter()
+ .map(|i| format!("{}{}.mpd", i.slot, seconds(i.duration_ms), p.creative_base, hex(&i.creative_hash)))
+ .collect();
+ format!(
+ "\n{periods}"
+ )
+ .into_bytes()
+}
+
+/// The asset list of a fill in a format (§7.2, §7.3).
+pub fn asset_list(p: &AdPolicy, format: u8, fill: &[Impression]) -> Vec {
+ if format == asset_format::DASH {
+ dash_asset_list(p, fill)
+ } else {
+ hls_asset_list(p, fill)
+ }
+}
+
+// ---------------------------------------------------------------------------------------
+// §9 Verification
+// ---------------------------------------------------------------------------------------
+
+/// What an ads verifier is given (§9.1).
+#[derive(Clone, Copy, Debug)]
+pub struct Input<'a> {
+ pub descriptor: &'a [u8],
+ pub grants: &'a [Vec],
+ pub head: Option<&'a str>,
+ pub chains: &'a [Vec>],
+ pub policy: &'a [u8],
+ pub pt0_ms: u64,
+ /// The receipts in their JSON carriage (§8.2).
+ pub receipts: &'a [serde_json::Value],
+ /// Each receipt's pod, in the order the decider gave it.
+ pub pods: &'a [Vec],
+}
+
+/// Where a verification stopped (§9.2).
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum At {
+ Room(room::At),
+ Policy,
+ Record { chain: usize, record: usize },
+ Receipt(usize),
+}
+
+/// The outcome of a verification (§9.4).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Outcome {
+ Accept,
+ Gap(Vec),
+ Refuse { at: At, reason: Reason },
+}
+
+/// A receipt, as a verification states it (§9.4).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ReceiptFacts {
+ /// Its identity (§8.4).
+ pub identity: [u8; 32],
+ /// The asset list it binds, as the verifier writes it.
+ pub asset_list: Vec,
+ pub spend_micros: u64,
+}
+
+/// What a verification holds at its end.
+#[derive(Clone, Debug, Default)]
+pub struct Run {
+ pub outcome: Option,
+ pub room: Option<[u8; 32]>,
+ pub t0_us: u64,
+ pub policy: Option,
+ pub schedule: Schedule,
+ pub hls: Vec,
+ pub dash: String,
+ /// Each out's event as written before its in, by the out's index among the breaks.
+ pub dash_open: Vec<(usize, String)>,
+ pub receipts: Vec,
+ pub spend_micros: u64,
+}
+
+impl Run {
+ fn refuse(mut self, at: At, reason: Reason) -> Run {
+ self.outcome = Some(Outcome::Refuse { at, reason });
+ self
+ }
+
+ /// The outcome; a run always has one.
+ pub fn outcome(&self) -> &Outcome {
+ self.outcome.as_ref().expect("a finished run has its outcome")
+ }
+}
+
+/// Verify a room's ads (§9).
+pub fn verify(inp: &Input) -> Run {
+ let mut run = Run::default();
+ // Step 1: the room.
+ let rr = room::verify_room(inp.descriptor, inp.grants, inp.head, inp.chains);
+ match &rr.outcome {
+ room::RoomOutcome::Refuse { at, reason } => return run.refuse(At::Room(*at), Reason::Room(*reason)),
+ room::RoomOutcome::Gap(g) => {
+ run.outcome = Some(Outcome::Gap(g.clone()));
+ return run;
+ }
+ room::RoomOutcome::Accept => {}
+ }
+ let d = rr.descriptor.clone().expect("a room that did not refuse has its descriptor");
+ let room_id = d.room_id();
+ run.room = Some(room_id);
+ run.t0_us = d.t0_us;
+ // Step 2: the policy.
+ let p = match AdPolicy::parse(inp.policy) {
+ Ok(p) => p,
+ Err(r) => return run.refuse(At::Policy, r),
+ };
+ if p.room != room_id {
+ return run.refuse(At::Policy, ev(events::Reason::WrongRoom));
+ }
+ let mut m = POLICY_DOMAIN.to_vec();
+ m.extend_from_slice(&p.body());
+ if let Err(e) = crate::sig::verify_strict(&d.authority, &m, &p.sig) {
+ let r = match e {
+ crate::sig::SigError::BadKey => events::Reason::BadKey,
+ crate::sig::SigError::BadSignature => events::Reason::BadSignature,
+ };
+ return run.refuse(At::Policy, ev(r));
+ }
+ let decider = match rr.grants.iter().find(|g| g.digest == p.decider_grant) {
+ Some(g) => g.clone(),
+ None => return run.refuse(At::Policy, Reason::DeciderGrantUnknown),
+ };
+ let log = room::check_key_log(&d, inp.head).expect("the room verifier read this key log");
+ if log.revokes(&decider) {
+ return run.refuse(At::Policy, Reason::Room(room::Reason::GrantRevoked));
+ }
+ // Step 3: the schedule, over the events the room accepted, once each.
+ let mut held: Vec = Vec::new();
+ let mut seen = BTreeSet::new();
+ for &(c, i, _) in &rr.order {
+ let e = Event::parse(&inp.chains[c][i]).expect("a record the room accepted reads");
+ let digest = e.digest();
+ if seen.insert(digest) {
+ held.push(Held { chain: c, record: i, ev: e, digest });
+ }
+ }
+ let s = match schedule(&held, &p) {
+ Ok(s) => s,
+ Err((chain, record, r)) => return run.refuse(At::Record { chain, record }, r),
+ };
+ // Step 4: the renderings.
+ run.hls = hls_tags(&p, d.t0_us, &s);
+ run.dash = dash_streams(&p, inp.pt0_ms, &s);
+ run.dash_open = s.breaks.iter().enumerate().filter(|(_, b)| b.cue_type == cue_type::OUT).map(|(i, b)| (i, dash_event(&p, inp.pt0_ms, b, false))).collect();
+ run.schedule = s;
+ run.policy = Some(p.clone());
+ // Step 5: the receipts.
+ let (grant_hash, capability, joule_ceiling, funds) = decider.grant.grant_ref();
+ let mut sessions: BTreeSet<([u8; 32], [u8; 16])> = BTreeSet::new();
+ let mut total: u128 = 0;
+ for (k, v) in inp.receipts.iter().enumerate() {
+ let at = At::Receipt(k);
+ let r = match SsaiBreakReceipt::from_json(v) {
+ Some(r) => r,
+ None => return run.refuse(at, Reason::ReceiptMalformed),
+ };
+ let b = &r.binding;
+ if b.room != room_id {
+ return run.refuse(at, ev(events::Reason::WrongRoom));
+ }
+ match r.check() {
+ Ok(()) => {}
+ Err(BreakReceiptFault::BadKey) => return run.refuse(at, ev(events::Reason::BadKey)),
+ Err(BreakReceiptFault::BadSignature) => return run.refuse(at, ev(events::Reason::BadSignature)),
+ Err(BreakReceiptFault::Invalid) => return run.refuse(at, Reason::ReceiptInvalid),
+ }
+ if r.signer_pubkey != decider.grant.principal {
+ return run.refuse(at, Reason::ReceiptNotDecider);
+ }
+ let g = &r.grant;
+ if g.grant_hash != grant_hash || g.capability != capability || g.joule_ceiling_micro != joule_ceiling || g.funds_ceiling != funds {
+ return run.refuse(at, Reason::ReceiptGrantMismatch);
+ }
+ let brk = match run.schedule.breaks.iter().find(|x| x.digest == b.cue) {
+ Some(x) => x.clone(),
+ None => return run.refuse(at, Reason::ReceiptBreakUnknown),
+ };
+ if !decider.grant.covers(d.t0_us, brk.t) {
+ return run.refuse(at, Reason::Room(room::Reason::OutsideGrantWindow));
+ }
+ let pod = inp.pods.get(k).map(Vec::as_slice).unwrap_or(&[]);
+ if r.pod_hash != pod_hash(pod) {
+ return run.refuse(at, Reason::PodMismatch);
+ }
+ let fill = decide(pod, p.slots as u64, brk.avail_ms.saturating_sub(b.start_offset_ms), b.funds_remaining_micros);
+ let stated: Vec = r.impressions.iter().map(|i| Impression { quartiles_reached: 0, ..i.clone() }).collect();
+ if b.avail_ms != brk.avail_ms || b.slots != p.slots as u64 || stated != fill {
+ return run.refuse(at, Reason::DecisionMismatch);
+ }
+ let list = asset_list(&p, b.format, &fill);
+ if b.asset_list_sha256 != sha256(&[&list]) {
+ return run.refuse(at, Reason::AssetListMismatch);
+ }
+ if !sessions.insert((b.cue, b.session)) {
+ return run.refuse(at, Reason::ReceiptDuplicate);
+ }
+ total += r.budget_spent_micros as u128;
+ if let Some(c) = funds
+ && (!within_ceiling(b.funds_remaining_micros, c) || total > c as u128 * 1_000_000)
+ {
+ return run.refuse(at, Reason::FundsExceeded);
+ }
+ run.receipts.push(ReceiptFacts { identity: r.receipt_hash(), asset_list: list, spend_micros: r.budget_spent_micros });
+ }
+ run.spend_micros = u64::try_from(total).unwrap_or(u64::MAX);
+ run.outcome = Some(Outcome::Accept);
+ run
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn policy() -> AdPolicy {
+ AdPolicy {
+ room: [1; 32],
+ decider_grant: [2; 32],
+ slots: 2,
+ open_avail_ms: 30_000,
+ preload_ms: 4_000,
+ restrict: restrict::SKIP | restrict::JUMP,
+ skip_after_s: 5,
+ snap: snap::OUT,
+ asset_base: "https://ads.example/r/".into(),
+ creative_base: "https://cdn.example/c/".into(),
+ sig: [0; 64],
+ }
+ .sign(&SigningKey::from_bytes(&[7; 32]))
+ }
+
+ #[test]
+ fn a_policy_round_trips_and_its_rules_refuse() {
+ let p = policy();
+ assert_eq!(AdPolicy::parse(&p.to_bytes()), Ok(p.clone()));
+ let with = |f: &dyn Fn(&mut AdPolicy)| {
+ let mut q = p.clone();
+ f(&mut q);
+ AdPolicy::parse(&q.to_bytes())
+ };
+ assert_eq!(with(&|q| q.slots = 0), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.slots = 17), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.restrict = 4), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.snap = 8), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.asset_base = "http://ads.example/".into()), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.asset_base = "https://ads.example/x".into()), Err(Reason::AdPolicyMalformed), "a base ends with /");
+ assert_eq!(with(&|q| q.creative_base = "https://cdn.example/?q/".into()), Err(Reason::AdPolicyMalformed));
+ assert_eq!(with(&|q| q.creative_base = "https:///".into()), Ok(AdPolicy { creative_base: "https:///".into(), ..p.clone() }));
+ let b = p.to_bytes();
+ assert_eq!(AdPolicy::parse(&b[..b.len() - 1]), Err(ev(events::Reason::Truncated)));
+ assert_eq!(AdPolicy::parse(&[b.as_slice(), &[0]].concat()), Err(ev(events::Reason::Malformed)));
+ assert_eq!(AdPolicy::parse(b"WAP2"), Err(ev(events::Reason::Malformed)));
+ assert_eq!(AdPolicy::parse(b"WAP1\x02"), Err(ev(events::Reason::UnknownVersion)));
+ }
+
+ #[test]
+ fn an_annotation_reads_exactly() {
+ let a = Annotation { cue: [9; 32], placement: placement::POST };
+ assert_eq!(Annotation::parse(&a.encode()), Ok(a));
+ let mut b = a.encode();
+ b[37] = 3;
+ assert_eq!(Annotation::parse(&b), Err(Reason::AnnotationMalformed));
+ assert_eq!(Annotation::parse(&a.encode()[..37]), Err(Reason::AnnotationMalformed));
+ let mut v = a.encode();
+ v[4] = 2;
+ assert_eq!(Annotation::parse(&v), Err(Reason::AnnotationMalformed));
+ }
+
+ #[test]
+ fn the_identity_and_the_times_are_written_exactly() {
+ let mut d = [0u8; 32];
+ d[0] = 0xff;
+ d[1] = 0xff;
+ d[7] = 0x01;
+ assert_eq!(event_id(&d), 0x001f_0000_0000_0001, "the low 53 bits of the first eight bytes");
+ assert!(break_id(&d).starts_with("wai-ad-ffff00") && break_id(&d).len() == 71);
+ assert_eq!(seconds(30_000), "30.000");
+ assert_eq!(seconds(1_005), "1.005");
+ }
+
+ #[test]
+ fn the_tags_and_events_of_a_break_follow_its_kind() {
+ let p = policy();
+ let b = Break {
+ digest: [3; 32],
+ author: [4; 32],
+ cue_id: 1,
+ t: 60_000_000,
+ cue_type: cue_type::BREAK,
+ placement: placement::MID,
+ planned_ms: 30_000,
+ avail_ms: 30_000,
+ duration_ms: Some(30_000),
+ closed_by: None,
+ end: Some(90_000_000),
+ };
+ let t = hls_interstitial(&p, 1_792_108_800_000_000, &b);
+ assert!(t.contains(",CUE=\"ONCE\",DURATION=30.000,X-ASSET-LIST=\""), "{t}");
+ assert!(t.ends_with(",X-RESUME-OFFSET=30.000,X-PLAYOUT-LIMIT=30.000,X-SNAP=\"OUT\",X-RESTRICT=\"SKIP,JUMP\",X-SKIP-CONTROL-OFFSET=5"), "{t}");
+ assert!(t.contains("START-DATE=\"2026-10-16T00:01:00.000Z\""), "{t}");
+ let post = Break { placement: placement::POST, ..b.clone() };
+ let t = hls_interstitial(&p, 0, &post);
+ assert!(t.contains("CUE=\"POST,ONCE\"") && !t.contains("X-RESUME-OFFSET"), "{t}");
+ let e = dash_event(&p, 0, &post, true);
+ assert!(e.contains("", event_id(&[3; 32]), break_id(&[3; 32])));
+ }
+
+ #[test]
+ fn asset_lists_are_written_exactly() {
+ let p = policy();
+ let fill = vec![Impression { slot: 0, ad_id: "a".into(), creative_hash: [0xab; 32], duration_ms: 15_000, price_micros: 1, quartiles_reached: 0 }];
+ let h = String::from_utf8(hls_asset_list(&p, &fill)).unwrap();
+ assert_eq!(h, format!("{{\"ASSETS\":[{{\"URI\":\"https://cdn.example/c/{}.m3u8\",\"DURATION\":15.000}}]}}", "ab".repeat(32)));
+ assert_eq!(hls_asset_list(&p, &[]), b"{\"ASSETS\":[]}");
+ assert!(dash_asset_list(&p, &[]).is_empty(), "an MPD has a Period, so an empty fill has none");
+ let m = String::from_utf8(dash_asset_list(&p, &fill)).unwrap();
+ assert!(m.starts_with("\n"), "{m}");
+ }
+}
diff --git a/wai/wai-rs/src/live_ads/corpus.rs b/wai/wai-rs/src/live_ads/corpus.rs
new file mode 100644
index 000000000..dd786e417
--- /dev/null
+++ b/wai/wai-rs/src/live_ads/corpus.rs
@@ -0,0 +1,935 @@
+//! The conformance corpus of extensions/live-ads.md §11: `live-conformance/ads/`.
+//!
+//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and an
+//! index, every creative hash SHA-256 of a label and a number, every time a constant, and
+//! Ed25519 signing is deterministic (RFC 8032), so every file is the same bytes on every
+//! machine. Each vector states the outcome it was written to have, and the generator asserts
+//! that this module reaches it before writing it, and that the corpus gives every reason of
+//! §10.
+//!
+//! [`verify`] checks a corpus directory's files against this module.
+//! `live-conformance/live_ads_verify.py` checks the same files from the extension's text,
+//! sharing no code with this module.
+
+use std::collections::BTreeMap;
+
+use super::*;
+use crate::energy_class::{CounterFiltering, EnergyProvenance, Uncertainty};
+use crate::key_log::{commitment, KeyEntry, Revision};
+use crate::live_events::Policy as EventPolicy;
+use crate::live_room::{Caps, Descriptor, Detail, Grant, Limits, Recording, Review, Role, KEY_LOG_AUTHORITY, NO_CEILING};
+use crate::quantum_ops::GrantRef;
+use crate::wai_video_ssai::BreakBinding;
+use serde_json::{json, Map, Value};
+
+const T0_US: u64 = 1_792_108_800_000_000;
+const T0_S: u64 = T0_US / 1_000_000;
+const S: u64 = 1_000_000;
+
+const AUTHORITY: u8 = 0;
+const CLOCK: u8 = 1;
+const PRODUCER: u8 = 2;
+const VIEWER: u8 = 3;
+const DECIDER: u8 = 4;
+const LOG_ROOT: u8 = 5;
+const LOG_NEXT: u8 = 6;
+const STRANGER: u8 = 7;
+const PRINCIPALS: [(&str, u8); 6] =
+ [("authority", AUTHORITY), ("clock", CLOCK), ("producer", PRODUCER), ("viewer", VIEWER), ("decider", DECIDER), ("stranger", STRANGER)];
+
+const ORIGIN: &str = "https://rooms.example";
+const ASSET_BASE: &str = "https://ads.rooms.example/sgai/";
+const CREATIVE_BASE: &str = "https://cdn.rooms.example/creatives/";
+/// The decider grant's ceiling, in minor units of EUR: 2 000 000 millionths.
+const BUDGET_MINOR: u64 = 2;
+
+fn sha(parts: &[&[u8]]) -> [u8; 32] {
+ sha256(parts)
+}
+
+fn key(i: u8) -> SigningKey {
+ SigningKey::from_bytes(&sha(&[b"wai:live-ads/corpus/key\x00", &[i]]))
+}
+
+fn pk(i: u8) -> [u8; 32] {
+ key(i).verifying_key().to_bytes()
+}
+
+/// A digest naming nothing of any room.
+fn nothing(label: &str) -> [u8; 32] {
+ sha(&[b"wai:live-ads/corpus/nothing\x00", label.as_bytes()])
+}
+
+fn session(i: u8) -> [u8; 16] {
+ sha(&[b"wai:live-ads/corpus/session\x00", &[i]])[..16].try_into().expect("16 bytes")
+}
+
+fn bits(ks: &[u8]) -> [u8; 32] {
+ let mut b = [0u8; 32];
+ for &k in ks {
+ b[(k / 8) as usize] |= 1 << (k % 8);
+ }
+ b
+}
+
+// ---- rooms ----
+
+/// A principal's chain being written.
+#[derive(Clone)]
+struct Author {
+ pk: [u8; 32],
+ seq: u64,
+ prev: [u8; 32],
+ records: Vec>,
+}
+
+/// How a room is set up.
+#[derive(Clone, Copy)]
+struct Spec {
+ /// The decider grant's `exp`, seconds since 1970.
+ decider_exp: u64,
+ /// The decider grant's ceiling, in minor units.
+ budget_minor: u64,
+ /// The descriptor names an origin and the key log's head revokes the decider grant.
+ revoked: bool,
+}
+
+const SPEC: Spec = Spec { decider_exp: T0_S + 86_400, budget_minor: BUDGET_MINOR, revoked: false };
+
+/// A room being written.
+#[derive(Clone)]
+struct Room {
+ descriptor: Vec,
+ id: [u8; 32],
+ grants: Vec>,
+ head: Option,
+ authors: Vec,
+ decider: Grant,
+}
+
+impl Room {
+ fn raw(&mut self, k: &SigningKey, t: u64, kind: u8, payload: Vec) -> [u8; 32] {
+ let me = k.verifying_key().to_bytes();
+ let i = match self.authors.iter().position(|a| a.pk == me) {
+ Some(i) => i,
+ None => {
+ self.authors.push(Author { pk: me, seq: 0, prev: [0; 32], records: vec![] });
+ self.authors.len() - 1
+ }
+ };
+ let a = &mut self.authors[i];
+ let e = Event::sign(k, self.id, a.seq, a.prev, t, None, kind, payload);
+ a.seq += 1;
+ a.prev = e.digest();
+ a.records.push(e.to_bytes());
+ a.prev
+ }
+
+ fn cue(&mut self, who: u8, t: u64, ctype: u8, cue_id: u32, duration_ms: u32) -> [u8; 32] {
+ let c = Payload::Cue(events::Cue { cue_type: ctype, cue_id, duration_ms, scte: None });
+ self.raw(&key(who), t, kind::CUE, c.encode())
+ }
+
+ fn annotate(&mut self, who: u8, t: u64, cue: [u8; 32], placement: u8) -> [u8; 32] {
+ self.raw(&key(who), t, kind::META, Annotation { cue, placement }.encode())
+ }
+
+ fn annotate_raw(&mut self, who: u8, t: u64, bytes: Vec) -> [u8; 32] {
+ self.raw(&key(who), t, kind::META, bytes)
+ }
+
+ fn chains(&self) -> Vec>> {
+ self.authors.iter().map(|a| a.records.clone()).collect()
+ }
+
+ /// The room as a subscriber holds it when these records are not delivered to it.
+ fn without(&self, digests: &[[u8; 32]]) -> Room {
+ let mut r = self.clone();
+ for a in r.authors.iter_mut() {
+ a.records.retain(|b| !digests.contains(&Event::parse(b).expect("a corpus record").digest()));
+ }
+ r
+ }
+
+ /// The decider grant's reference (live-rooms §4.5).
+ fn decider_ref(&self) -> GrantRef {
+ let (grant_hash, capability, joule_ceiling_micro, funds_ceiling) = self.decider.grant_ref();
+ GrantRef { grant_hash, capability: capability.into(), joule_ceiling_micro, funds_ceiling }
+ }
+}
+
+fn role(name: &str, subscribe: bool, events: &[u8]) -> Role {
+ Role { name: name.into(), caps: Caps { publish: 0, subscribe, max_layer: subscribe as u8, powers: 0 }, events: bits(events) }
+}
+
+/// A room: a producer that cues breaks, a viewer that chats, and the decider, which holds a
+/// grant with the room's ad budget and never joins. The clock admits the producer and the
+/// viewer at the room's start.
+fn room(spec: Spec) -> Room {
+ let d = Descriptor {
+ authority: pk(AUTHORITY),
+ clock: pk(CLOCK),
+ t0_us: T0_US,
+ tick_us: S,
+ origin: if spec.revoked { ORIGIN.into() } else { String::new() },
+ limits: Limits { max_present: 16, max_publishers: 4, reval_s: 0, ceiling: EventPolicy::default() },
+ roles: vec![role("producer", true, &[kind::META, kind::CUE, kind::CHAT]), role("audience", true, &[kind::CHAT]), role("decider", false, &[])],
+ slots: vec![],
+ profiles: vec![],
+ review: Review { kinds: [0; 32], deadline_ms: 0, fallback: 0 },
+ recording: Recording { mode: 0, retention_days: 0, commit_interval_ms: 0, jitter_ms: 0, regions: vec![] },
+ sig: [0; 64],
+ }
+ .sign(&key(AUTHORITY));
+ let id = d.room_id();
+ let grant = |who: u8, r: usize, exp: u64, funds: Option| {
+ let role = &d.roles[r];
+ let kinds: Vec = (0..=255u8).filter(|&k| role.events[(k / 8) as usize] & (1 << (k % 8)) != 0).collect();
+ Grant {
+ room: id,
+ principal: pk(who),
+ prev: [0; 32],
+ effective: 0,
+ nbf: T0_S - 3_600,
+ exp,
+ role: r as u8,
+ caps: role.caps,
+ precedence: 0,
+ once: [0; 16],
+ viewer: [0; 16],
+ policy: EventPolicy::with_kinds(&kinds),
+ joules_ceiling: NO_CEILING,
+ funds_ceiling: funds.unwrap_or(NO_CEILING),
+ currency: if funds.is_some() { *b"EUR" } else { [0; 3] },
+ profiles: vec![],
+ origins: vec![],
+ attrs: vec![],
+ sig: [0; 64],
+ }
+ .sign(&key(AUTHORITY))
+ };
+ let producer = grant(PRODUCER, 0, T0_S + 86_400, None);
+ let viewer = grant(VIEWER, 1, T0_S + 86_400, None);
+ let decider = grant(DECIDER, 2, spec.decider_exp, Some(spec.budget_minor));
+ let head = spec.revoked.then(|| {
+ let rev = Revision {
+ origin: ORIGIN.into(),
+ seq: 0,
+ prev: None,
+ issued_at: "2026-10-15T00:00:00Z".into(),
+ root: [0; 32],
+ next_root: commitment(&pk(LOG_NEXT)),
+ keys: vec![KeyEntry::active("room-authority", pk(AUTHORITY), &[KEY_LOG_AUTHORITY])],
+ revoked_modules: vec![decider.digest()],
+ grant_max_s: 86_400,
+ status_max_age_s: 3_600,
+ sig: [0; 64],
+ };
+ Revision::seal(&key(LOG_ROOT), rev).expect("a well-formed revision").to_json()
+ });
+ let mut room = Room {
+ descriptor: d.to_bytes(),
+ id,
+ grants: [&producer, &viewer, &decider].iter().map(|g| g.to_bytes()).collect(),
+ head,
+ authors: vec![],
+ decider,
+ };
+ for (who, g) in [(PRODUCER, &producer), (VIEWER, &viewer)] {
+ let j = Detail::Join { grant: g.digest() };
+ room.raw(&key(CLOCK), 0, j.kind(), Payload::Lifecycle { kind: j.kind(), subject: pk(who), detail: j.encode() }.encode());
+ }
+ room
+}
+
+/// The room's ad policy: the defaults, changed by `f`, then signed.
+fn policy(room: &Room, f: impl FnOnce(&mut AdPolicy)) -> AdPolicy {
+ let mut p = AdPolicy {
+ room: room.id,
+ decider_grant: room.decider.digest(),
+ slots: 2,
+ open_avail_ms: 30_000,
+ preload_ms: 4_000,
+ restrict: 0,
+ skip_after_s: NO_SKIP_CONTROL,
+ snap: 0,
+ asset_base: ASSET_BASE.into(),
+ creative_base: CREATIVE_BASE.into(),
+ sig: [0; 64],
+ };
+ f(&mut p);
+ p.sign(&key(AUTHORITY))
+}
+
+// ---- pods and receipts ----
+
+fn ad(id: &str, creative: u8, duration_ms: u64, price_micros: u64) -> AdCandidate {
+ AdCandidate { ad_id: id.into(), creative_hash: sha(&[b"wai:live-ads/corpus/creative\x00", &[creative]]), duration_ms, price_micros }
+}
+
+/// Three ads: two of 15 s and one of 20 s.
+fn pod_one() -> Vec {
+ vec![ad("ad-a", 1, 15_000, 40_000), ad("ad-b", 2, 15_000, 30_000), ad("ad-c", 3, 20_000, 25_000)]
+}
+
+/// Four ads, two at one price, so the tie is broken by `ad_id`.
+fn pod_two() -> Vec {
+ vec![ad("ad-g", 7, 5_000, 10_000), ad("ad-e", 5, 10_000, 50_000), ad("ad-f", 6, 15_000, 45_000), ad("ad-d", 4, 10_000, 50_000)]
+}
+
+fn chip() -> EnergyProvenance {
+ EnergyProvenance::OnChipCounter { uncertainty: Uncertainty { relative_ppm: 30_000, window_us: 2_000_000 }, filtering: CounterFiltering::Unknown }
+}
+
+/// What one receipt is sealed from, with the defects a control writes in.
+#[derive(Clone)]
+struct Seal {
+ cue: [u8; 32],
+ avail_ms: u64,
+ session: u8,
+ start_offset_ms: u64,
+ format: u8,
+ pod: Vec,
+ funds: u64,
+ quartiles: Vec,
+ energy: Option<(u64, EnergyProvenance)>,
+ signer: u8,
+ // Defects.
+ stated_funds: Option,
+ stated_room: Option<[u8; 32]>,
+ capability: Option<&'static str>,
+ list_format: Option,
+ work_ops: Option,
+ /// A figure sealed with no class.
+ joules_only: Option,
+}
+
+impl Seal {
+ fn new(cue: [u8; 32], avail_ms: u64, session: u8, format: u8, pod: Vec, quartiles: &[u8]) -> Seal {
+ Seal {
+ cue,
+ avail_ms,
+ session,
+ start_offset_ms: 0,
+ format,
+ pod,
+ funds: BUDGET_MINOR * 1_000_000,
+ quartiles: quartiles.to_vec(),
+ energy: None,
+ signer: DECIDER,
+ stated_funds: None,
+ stated_room: None,
+ capability: None,
+ list_format: None,
+ work_ops: None,
+ joules_only: None,
+ }
+ }
+}
+
+fn seal(room: &Room, p: &AdPolicy, s: &Seal) -> SsaiBreakReceipt {
+ let fill = decide(&s.pod, p.slots as u64, s.avail_ms.saturating_sub(s.start_offset_ms), s.funds);
+ assert_eq!(fill.len(), s.quartiles.len(), "a corpus receipt states a quartile for each ad of its fill");
+ let mut imps = fill.clone();
+ for (i, q) in imps.iter_mut().zip(&s.quartiles) {
+ i.quartiles_reached = *q;
+ }
+ let list = asset_list(p, s.list_format.unwrap_or(s.format), &fill);
+ let binding = BreakBinding {
+ room: s.stated_room.unwrap_or(room.id),
+ cue: s.cue,
+ session: session(s.session),
+ start_offset_ms: s.start_offset_ms,
+ format: s.format,
+ asset_list_sha256: sha(&[&list]),
+ avail_ms: s.avail_ms,
+ slots: p.slots as u64,
+ funds_remaining_micros: s.stated_funds.unwrap_or(s.funds),
+ };
+ let mut grant = room.decider_ref();
+ if let Some(c) = s.capability {
+ grant.capability = c.into();
+ }
+ let (joules, class) = match (&s.energy, s.joules_only) {
+ (Some((j, c)), _) => (*j, Some(c.clone())),
+ (None, j) => (j.unwrap_or(0), None),
+ };
+ let signer = key(s.signer);
+ let id = crate::signer_id::did_key(&signer.verifying_key().to_bytes());
+ SsaiBreakReceipt::seal(&signer, id, binding, pod_hash(&s.pod), imps, s.work_ops.unwrap_or(s.pod.len() as u64), joules, class, grant)
+}
+
+// ---- vectors ----
+
+/// What a vector was written to do.
+#[derive(Clone, Copy, Debug)]
+enum Intent {
+ Accept,
+ Gap,
+ /// Refused, at a place of this kind (`room`, `policy`, `record`, `receipt`), with this reason.
+ Refuse(&'static str, Reason),
+}
+
+struct Vector {
+ name: &'static str,
+ about: &'static str,
+ room: Room,
+ policy: Vec,
+ pt0_ms: u64,
+ receipts: Vec,
+ pods: Vec>,
+ intent: Intent,
+}
+
+impl Vector {
+ fn run(&self) -> Run {
+ let chains = self.room.chains();
+ super::verify(&Input {
+ descriptor: &self.room.descriptor,
+ grants: &self.room.grants,
+ head: self.room.head.as_deref(),
+ chains: &chains,
+ policy: &self.policy,
+ pt0_ms: self.pt0_ms,
+ receipts: &self.receipts,
+ pods: &self.pods,
+ })
+ }
+}
+
+/// A live room's break: an out at 60 s with no planned duration and its in at 90 s.
+struct LiveBreak {
+ room: Room,
+ policy: AdPolicy,
+ out: [u8; 32],
+ in_: [u8; 32],
+ first: Seal,
+ joined: Seal,
+}
+
+fn live_break(spec: Spec) -> LiveBreak {
+ let mut room = room(spec);
+ room.raw(&key(PRODUCER), 10 * S, kind::CHAT, Payload::Chat { text: "kick-off".into() }.encode());
+ let out = room.cue(PRODUCER, 60 * S, cue_type::OUT, 101, 0);
+ let in_ = room.cue(PRODUCER, 90 * S, cue_type::IN, 101, 30_000);
+ let policy = policy(&room, |_| {});
+ let mut first = Seal::new(out, 30_000, 1, asset_format::HLS, pod_one(), &[5, 3]);
+ first.energy = Some((1_200, EnergyProvenance::ModelBased));
+ let mut joined = Seal::new(out, 30_000, 2, asset_format::DASH, pod_one(), &[5]);
+ joined.start_offset_ms = 12_000;
+ joined.funds = 1_930_000;
+ joined.energy = Some((900, chip()));
+ LiveBreak { room, policy, out, in_, first, joined }
+}
+
+fn vector(name: &'static str, about: &'static str, room: &Room, policy: &AdPolicy, receipts: Vec, pods: Vec>, intent: Intent) -> Vector {
+ Vector { name, about, room: room.clone(), policy: policy.to_bytes(), pt0_ms: 0, receipts: receipts.iter().map(SsaiBreakReceipt::to_json).collect(), pods, intent }
+}
+
+fn accepted() -> Vec {
+ let mut out = Vec::new();
+ // A live break: one viewer from its start, one who joins it running.
+ let lb = live_break(SPEC);
+ out.push(vector(
+ "live_break",
+ "an out with no planned duration and its in 30 s later: the policy's open avail is decided; a viewer at the break's start is filled from an HLS asset list, and one who joins 12 s into it from a DASH list MPD with what remains; no pre-roll",
+ &lb.room,
+ &lb.policy,
+ vec![seal(&lb.room, &lb.policy, &lb.first), seal(&lb.room, &lb.policy, &lb.joined)],
+ vec![pod_one(), pod_one()],
+ Intent::Accept,
+ ));
+ // A mid-roll of known duration under restrictions and a skip control.
+ let mut r = room(SPEC);
+ let b = r.cue(PRODUCER, 120 * S, cue_type::BREAK, 7, 30_000);
+ r.annotate(PRODUCER, 120 * S, b, placement::MID);
+ let p = policy(&r, |p| {
+ p.slots = 3;
+ p.open_avail_ms = 0;
+ p.preload_ms = 0;
+ p.restrict = restrict::SKIP | restrict::JUMP;
+ p.skip_after_s = 5;
+ p.snap = snap::OUT | snap::IN;
+ });
+ let full = Seal::new(b, 30_000, 3, asset_format::HLS, pod_two(), &[5, 5, 2]);
+ let mut none = Seal::new(b, 30_000, 4, asset_format::DASH, vec![ad("ad-h", 8, 10_000, 60_000)], &[]);
+ none.funds = 50_000;
+ let mut v = vector(
+ "mid_roll",
+ "a 30 s break, annotated mid-roll, under a policy that restricts skipping and jumping, snaps both ways and offers a skip control after 5 s: three ads fill one viewer's avail (two at one price, ordered by ad_id); another viewer's only candidate costs more than its funds, so its fill is empty and its DASH list MPD is no bytes",
+ &r,
+ &p,
+ vec![seal(&r, &p, &full), seal(&r, &p, &none)],
+ vec![pod_two(), vec![ad("ad-h", 8, 10_000, 60_000)]],
+ Intent::Accept,
+ );
+ v.pt0_ms = 5_000;
+ out.push(v);
+ // A program with chapters, a mid-roll, and a post-roll at its close.
+ let mut r = room(SPEC);
+ r.cue(PRODUCER, S, cue_type::CHAPTER, 1, 0);
+ let mid = r.cue(PRODUCER, 30 * S, cue_type::BREAK, 2, 15_000);
+ r.cue(PRODUCER, 50 * S, cue_type::CHAPTER, 3, 0);
+ let post = r.cue(PRODUCER, 120 * S, cue_type::BREAK, 4, 20_000);
+ r.annotate(PRODUCER, 120 * S, post, placement::POST);
+ let p = policy(&r, |p| {
+ p.preload_ms = 2_000;
+ p.restrict = restrict::JUMP;
+ p.skip_after_s = 10;
+ });
+ out.push(vector(
+ "post_roll",
+ "a program of two chapters with a 15 s mid-roll, closed by a 20 s post-roll at 120 s: the post-roll is an insertion played after the program ends; one viewer is filled in both breaks, another in the post-roll only",
+ &r,
+ &p,
+ vec![
+ seal(&r, &p, &Seal::new(mid, 15_000, 5, asset_format::HLS, pod_one(), &[5])),
+ seal(&r, &p, &Seal::new(post, 20_000, 5, asset_format::HLS, pod_one(), &[4])),
+ seal(&r, &p, &Seal::new(post, 20_000, 6, asset_format::DASH, pod_two(), &[1, 0])),
+ ],
+ vec![pod_one(), pod_one(), pod_two()],
+ Intent::Accept,
+ ));
+ // Chapters, and an out with a planned duration closed by its in.
+ let mut r = room(SPEC);
+ r.cue(PRODUCER, S, cue_type::CHAPTER, 10, 60_000);
+ r.cue(PRODUCER, 61 * S, cue_type::CHAPTER, 11, 0);
+ let out_ = r.cue(PRODUCER, 70 * S, cue_type::OUT, 12, 15_000);
+ r.cue(PRODUCER, 85 * S, cue_type::IN, 12, 0);
+ let p = policy(&r, |p| {
+ p.preload_ms = 0;
+ p.open_avail_ms = 0;
+ });
+ out.push(vector(
+ "chapters",
+ "two chapters, the first of a stated length, then an out with a planned duration of 15 s and its in, which states no duration: the interstitial carries the planned duration and its closing tag the actual one",
+ &r,
+ &p,
+ vec![seal(&r, &p, &Seal::new(out_, 15_000, 7, asset_format::HLS, pod_one(), &[2]))],
+ vec![pod_one()],
+ Intent::Accept,
+ ));
+ // A subscriber that missed the out: a gap, and no schedule.
+ let withheld = lb.room.without(&[lb.out]);
+ out.push(vector(
+ "gap",
+ "the live break's room as a subscriber holds it when the out was not delivered to it: the in follows a gap in the producer's chain, so no schedule is read",
+ &withheld,
+ &lb.policy,
+ vec![],
+ vec![],
+ Intent::Gap,
+ ));
+ out
+}
+
+/// A room of cues for a schedule control: `f` writes them after the joins.
+fn cue_room(f: impl FnOnce(&mut Room)) -> Room {
+ let mut r = room(SPEC);
+ f(&mut r);
+ r
+}
+
+fn refused() -> Vec {
+ let mut out = Vec::new();
+ let lb = live_break(SPEC);
+ let (r, p) = (&lb.room, &lb.policy);
+ let one = |s: &Seal| seal(r, p, s);
+ let refuse = |name, about, room: &Room, policy: &AdPolicy, receipts: Vec, pods: Vec>, at, reason| {
+ vector(name, about, room, policy, receipts, pods, Intent::Refuse(at, reason))
+ };
+ // The room refuses a cue by a principal its grant does not let send one.
+ let rv = cue_room(|r| {
+ r.cue(VIEWER, 60 * S, cue_type::OUT, 1, 30_000);
+ });
+ out.push(refuse("cue_kind_not_permitted", "the viewer, whose grant permits chat alone, sends an out: the room refuses it", &rv, &policy(&rv, |_| {}), vec![], vec![], "room", Reason::Room(room::Reason::Events(events::Reason::KindNotPermitted))));
+ // The policy.
+ out.push(refuse("policy_slots_zero", "a policy whose slots is 0", r, &policy(r, |p| p.slots = 0), vec![], vec![], "policy", Reason::AdPolicyMalformed));
+ out.push(refuse("policy_wrong_room", "a policy naming another room", r, &policy(r, |p| p.room = nothing("room")), vec![], vec![], "policy", ev(events::Reason::WrongRoom)));
+ let forged = policy(r, |_| {}).sign(&key(PRODUCER));
+ out.push(refuse("policy_bad_signature", "a policy signed by the producer, not the room authority", r, &forged, vec![], vec![], "policy", ev(events::Reason::BadSignature)));
+ out.push(refuse("decider_grant_unknown", "a policy naming a decider grant the room does not have", r, &policy(r, |p| p.decider_grant = nothing("grant")), vec![], vec![], "policy", Reason::DeciderGrantUnknown));
+ let rk = live_break(Spec { revoked: true, ..SPEC });
+ out.push(refuse("decider_grant_revoked", "a room whose key log's head revokes the decider grant", &rk.room, &rk.policy, vec![], vec![], "policy", Reason::Room(room::Reason::GrantRevoked)));
+ // The schedule.
+ let rv = cue_room(|r| {
+ let b = r.cue(PRODUCER, 60 * S, cue_type::BREAK, 1, 30_000);
+ let mut bytes = Annotation { cue: b, placement: placement::POST }.encode();
+ bytes[37] = 3;
+ r.annotate_raw(PRODUCER, 60 * S, bytes);
+ });
+ out.push(refuse("annotation_placement", "a break annotation of placement 3", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::AnnotationMalformed));
+ let rv = cue_room(|r| {
+ let o = r.cue(PRODUCER, 60 * S, cue_type::OUT, 1, 30_000);
+ r.annotate(PRODUCER, 60 * S, o, placement::POST);
+ });
+ out.push(refuse("annotation_on_out", "a post-roll annotation after an out: only a break of known duration is a post-roll", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::AnnotationMisplaced));
+ let rv = cue_room(|r| {
+ let b = r.cue(PRODUCER, 120 * S, cue_type::BREAK, 1, 20_000);
+ r.annotate(PRODUCER, 120 * S, b, placement::POST);
+ r.cue(PRODUCER, 150 * S, cue_type::CHAPTER, 2, 0);
+ });
+ out.push(refuse("cue_after_post_roll", "a chapter after the post-roll", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::CueAfterClose));
+ let rv = cue_room(|r| {
+ r.cue(PRODUCER, 30 * S, cue_type::BREAK, 1, 30_000);
+ r.cue(PRODUCER, 45 * S, cue_type::OUT, 2, 15_000);
+ });
+ out.push(refuse("overlapping_breaks", "an out 15 s into a 30 s break", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::BreakOverlap));
+ let rv = cue_room(|r| {
+ r.cue(PRODUCER, 60 * S, cue_type::OUT, 1, 0);
+ });
+ out.push(refuse("open_break_no_avail", "an out with no planned duration under a policy with no open avail", &rv, &policy(&rv, |p| p.open_avail_ms = 0), vec![], vec![], "record", Reason::BreakDurationUnknown));
+ let rv = cue_room(|r| {
+ r.cue(PRODUCER, 60 * S, cue_type::BREAK, 1, 0);
+ });
+ out.push(refuse("break_of_no_duration", "a break of type 4 with a duration of 0", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::BreakZeroDuration));
+ let rv = cue_room(|r| {
+ r.cue(PRODUCER, 60 * S, cue_type::IN, 5, 0);
+ });
+ out.push(refuse("in_without_out", "an in with no open out", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::CueInUnmatched));
+ let rv = cue_room(|r| {
+ r.cue(PRODUCER, 60 * S, cue_type::OUT, 1, 0);
+ r.cue(PRODUCER, 90 * S, cue_type::IN, 1, 25_000);
+ });
+ out.push(refuse("in_duration_mismatch", "an in stating 25 s when 30 s passed since its out", &rv, &policy(&rv, |_| {}), vec![], vec![], "record", Reason::CueDurationMismatch));
+ // The receipts, each over the live break.
+ let good = one(&lb.first);
+ let mut extra = good.to_json();
+ extra.as_object_mut().expect("an object").insert("x_note".into(), json!("an extra member"));
+ let mut v = refuse("receipt_extra_member", "a receipt carrying a member §8.2 does not define", r, p, vec![], vec![pod_one()], "receipt", Reason::ReceiptMalformed);
+ v.receipts = vec![extra];
+ out.push(v);
+ let mut s = lb.first.clone();
+ s.stated_room = Some(nothing("room"));
+ out.push(refuse("receipt_wrong_room", "a receipt naming another room", r, p, vec![one(&s)], vec![pod_one()], "receipt", ev(events::Reason::WrongRoom)));
+ let mut bad = good.clone();
+ bad.sig[10] ^= 0x01;
+ out.push(refuse("receipt_bad_signature", "a receipt with one bit of its signature flipped", r, p, vec![bad], vec![pod_one()], "receipt", ev(events::Reason::BadSignature)));
+ let mut s = lb.first.clone();
+ s.quartiles = vec![6, 3];
+ out.push(refuse("receipt_quartile_six", "a receipt stating an ad reached quartile 6", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptInvalid));
+ // The other rules of §8.5, each broken alone.
+ let mut s = lb.first.clone();
+ s.format = 3;
+ s.list_format = Some(asset_format::HLS);
+ out.push(refuse("receipt_format_three", "a receipt of format 3, which names no asset list", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptInvalid));
+ let mut s = lb.first.clone();
+ s.work_ops = Some(1);
+ out.push(refuse("receipt_work_below_fill", "a receipt stating it considered one candidate for a fill of two", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptInvalid));
+ let mut s = lb.first.clone();
+ s.energy = None;
+ s.joules_only = Some(1_200);
+ out.push(refuse("receipt_figure_without_class", "a receipt stating 1 200 µJ of decision energy with no acquisition class", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptInvalid));
+ let mut s = lb.first.clone();
+ s.energy = Some((9, chip()));
+ out.push(refuse(
+ "receipt_below_resolution",
+ "a receipt stating 9 µJ read from an on-chip counter with a declared uncertainty of 30 000 ppm, below the resolution floor",
+ r,
+ p,
+ vec![one(&s)],
+ vec![pod_one()],
+ "receipt",
+ Reason::ReceiptInvalid,
+ ));
+ let dear = vec![ad("ad-q", 10, 15_000, 2_500_000)];
+ let mut s = Seal::new(lb.out, 30_000, 1, asset_format::HLS, dear.clone(), &[1]);
+ s.funds = 3_000_000;
+ out.push(refuse("receipt_over_its_ceiling", "a receipt billing 2 500 000 µ-units under a grant reference whose ceiling is 2 minor units", r, p, vec![one(&s)], vec![dear], "receipt", Reason::ReceiptInvalid));
+ let mut s = lb.first.clone();
+ s.signer = STRANGER;
+ out.push(refuse("receipt_not_decider", "a receipt signed by a key other than the decider's", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptNotDecider));
+ let mut s = lb.first.clone();
+ s.capability = Some("video.ssai");
+ out.push(refuse("receipt_grant_mismatch", "a receipt naming the decider grant with another capability", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptGrantMismatch));
+ let mut s = lb.first.clone();
+ s.cue = lb.in_;
+ out.push(refuse("receipt_names_the_in", "a receipt naming the in, which opens no break", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::ReceiptBreakUnknown));
+ let lx = live_break(Spec { decider_exp: T0_S + 30, ..SPEC });
+ out.push(refuse(
+ "decider_grant_expired",
+ "a decider grant that ends 30 s into the room, and a receipt for the break at 60 s",
+ &lx.room,
+ &lx.policy,
+ vec![seal(&lx.room, &lx.policy, &lx.first)],
+ vec![pod_one()],
+ "receipt",
+ Reason::Room(room::Reason::OutsideGrantWindow),
+ ));
+ out.push(refuse("pod_mismatch", "a receipt given with a pod short of its last candidate", r, p, vec![good.clone()], vec![pod_one()[..2].to_vec()], "receipt", Reason::PodMismatch));
+ let mut s = lb.first.clone();
+ s.stated_funds = Some(50_000);
+ out.push(refuse("decision_mismatch", "a receipt whose fill is the decision over 2 000 000 µ-units of funds, stating 50 000: over 50 000 the decision takes ad-a alone", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::DecisionMismatch));
+ let mut s = lb.first.clone();
+ s.format = asset_format::DASH;
+ s.list_format = Some(asset_format::HLS);
+ out.push(refuse("asset_list_mismatch", "a receipt of the DASH format binding the HLS asset list", r, p, vec![one(&s)], vec![pod_one()], "receipt", Reason::AssetListMismatch));
+ out.push(refuse("receipt_duplicate", "one viewer's receipt for the break given twice", r, p, vec![good.clone(), good], vec![pod_one(), pod_one()], "receipt", Reason::ReceiptDuplicate));
+ let lf = live_break(Spec { budget_minor: 1, ..SPEC });
+ let costly = vec![ad("ad-p", 9, 15_000, 600_000)];
+ let mut a = Seal::new(lf.out, 30_000, 8, asset_format::HLS, costly.clone(), &[1]);
+ a.funds = 1_000_000;
+ let mut b = a.clone();
+ b.session = 9;
+ out.push(refuse(
+ "funds_exceeded",
+ "a decider grant of 1 minor unit, and two viewers each billed 600 000 µ-units: the second receipt takes the room's spend past the ceiling",
+ &lf.room,
+ &lf.policy,
+ vec![seal(&lf.room, &lf.policy, &a), seal(&lf.room, &lf.policy, &b)],
+ vec![costly.clone(), costly],
+ "receipt",
+ Reason::FundsExceeded,
+ ));
+ out
+}
+
+fn build() -> Vec<(Vector, Run)> {
+ let mut out = Vec::new();
+ for v in accepted().into_iter().chain(refused()) {
+ let run = v.run();
+ let ok = match (v.intent, run.outcome()) {
+ (Intent::Accept, Outcome::Accept) => true,
+ (Intent::Gap, Outcome::Gap(_)) => true,
+ (Intent::Refuse(place, reason), Outcome::Refuse { at, reason: got }) => {
+ *got == reason
+ && place
+ == match at {
+ At::Room(_) => "room",
+ At::Policy => "policy",
+ At::Record { .. } => "record",
+ At::Receipt(_) => "receipt",
+ }
+ }
+ _ => false,
+ };
+ assert!(ok, "vector {} was written to {:?} and gives {:?}", v.name, v.intent, run.outcome());
+ out.push((v, run));
+ }
+ out
+}
+
+// ---- JSON ----
+
+fn hexes(v: &[Vec]) -> Vec {
+ v.iter().map(|b| hex(b)).collect()
+}
+
+fn opt_hex(d: &Option<[u8; 32]>) -> Value {
+ d.map_or(Value::Null, |d| json!(hex(&d)))
+}
+
+fn place_json(at: &At) -> Value {
+ match at {
+ At::Room(room::At::Descriptor) => json!(["room", "descriptor"]),
+ At::Room(room::At::KeyLog) => json!(["room", "key_log"]),
+ At::Room(room::At::Grant(i)) => json!(["room", "grant", i]),
+ At::Room(room::At::Record { chain, record }) => json!(["room", "record", chain, record]),
+ At::Policy => json!(["policy"]),
+ At::Record { chain, record } => json!(["record", chain, record]),
+ At::Receipt(k) => json!(["receipt", k]),
+ }
+}
+
+fn pod_json(pod: &[AdCandidate]) -> Value {
+ Value::Array(pod.iter().map(|c| json!({"ad_id": c.ad_id, "creative_hash": hex(&c.creative_hash), "duration_ms": c.duration_ms, "price_micros": c.price_micros})).collect())
+}
+
+fn pod_of(v: &Value) -> Option> {
+ v.as_array()?
+ .iter()
+ .map(|c| {
+ Some(AdCandidate {
+ ad_id: c["ad_id"].as_str()?.into(),
+ creative_hash: unhex(c["creative_hash"].as_str()?)?.try_into().ok()?,
+ duration_ms: c["duration_ms"].as_u64()?,
+ price_micros: c["price_micros"].as_u64()?,
+ })
+ })
+ .collect()
+}
+
+fn hls_file(name: &str) -> String {
+ format!("{name}.m3u8")
+}
+
+/// The HLS file of an accepted run: one tag a line.
+fn hls_body(run: &Run) -> String {
+ run.hls.iter().map(|t| format!("{t}\n")).collect()
+}
+
+/// What a vector states of its run (§9.4).
+fn run_json(name: &str, run: &Run) -> Map {
+ let mut o = Map::new();
+ match run.outcome() {
+ Outcome::Refuse { at, reason } => {
+ o.insert("outcome".into(), json!("refuse"));
+ o.insert("reason".into(), json!(reason.code()));
+ o.insert("at".into(), place_json(at));
+ return o;
+ }
+ Outcome::Gap(g) => {
+ o.insert("outcome".into(), json!("gap"));
+ o.insert("gaps".into(), Value::Array(g.iter().map(|g| json!({"principal": hex(&g.principal), "first": g.first, "last": g.last})).collect()));
+ return o;
+ }
+ Outcome::Accept => {
+ o.insert("outcome".into(), json!("accept"));
+ }
+ }
+ let p = run.policy.as_ref().expect("an accepted run has its policy");
+ o.insert("room".into(), json!(hex(&run.room.expect("an accepted run has its room"))));
+ o.insert(
+ "breaks".into(),
+ Value::Array(
+ run.schedule
+ .breaks
+ .iter()
+ .map(|b| {
+ json!({
+ "digest": hex(&b.digest),
+ "id": break_id(&b.digest),
+ "event_id": event_id(&b.digest),
+ "author": hex(&b.author),
+ "cue_id": b.cue_id,
+ "t": b.t,
+ "type": if b.cue_type == cue_type::OUT { "out" } else { "break" },
+ "placement": if b.placement == placement::POST { "post" } else { "mid" },
+ "planned_ms": b.planned_ms,
+ "avail_ms": b.avail_ms,
+ "duration_ms": b.duration_ms,
+ "closed_by": opt_hex(&b.closed_by),
+ "end": b.end,
+ "hls_list_uri": hls_list_uri(p, &b.digest),
+ "dash_list_uri": dash_list_uri(p, &b.digest),
+ })
+ })
+ .collect(),
+ ),
+ );
+ o.insert(
+ "chapters".into(),
+ Value::Array(run.schedule.chapters.iter().map(|c| json!({"digest": hex(&c.digest), "t": c.t, "duration_ms": c.duration_ms})).collect()),
+ );
+ o.insert("hls".into(), json!(hls_file(name)));
+ o.insert("dash".into(), json!(run.dash));
+ o.insert("dash_open".into(), Value::Array(run.dash_open.iter().map(|(i, e)| json!({"break": i, "event": e})).collect()));
+ o.insert(
+ "receipt_facts".into(),
+ Value::Array(
+ run.receipts
+ .iter()
+ .map(|r| {
+ json!({
+ "identity": hex(&r.identity),
+ "asset_list": String::from_utf8(r.asset_list.clone()).expect("an asset list is UTF-8"),
+ "spend_micros": r.spend_micros,
+ })
+ })
+ .collect(),
+ ),
+ );
+ o.insert("spend_micros".into(), json!(run.spend_micros));
+ o
+}
+
+fn vector_json(v: &Vector, run: &Run) -> Value {
+ let chains: Vec> = v.room.chains().iter().map(|c| hexes(c)).collect();
+ let mut o = Map::new();
+ o.insert("name".into(), json!(v.name));
+ o.insert("about".into(), json!(v.about));
+ o.insert("descriptor".into(), json!(hex(&v.room.descriptor)));
+ o.insert("grants".into(), json!(hexes(&v.room.grants)));
+ o.insert("key_log".into(), json!(v.room.head));
+ o.insert("chains".into(), json!(chains));
+ o.insert("policy".into(), json!(hex(&v.policy)));
+ o.insert("pt0_ms".into(), json!(v.pt0_ms));
+ o.insert("receipts".into(), Value::Array(v.receipts.clone()));
+ o.insert("pods".into(), Value::Array(v.pods.iter().map(|p| pod_json(p)).collect()));
+ o.extend(run_json(v.name, run));
+ Value::Object(o)
+}
+
+/// Write the corpus: `vectors.json` and each accepted room's HLS tags.
+pub fn generate() -> Vec<(String, String)> {
+ let rooms = build();
+ let seen: BTreeSet<&str> = rooms
+ .iter()
+ .filter_map(|(_, run)| match run.outcome() {
+ Outcome::Refuse { reason, .. } => Some(reason.code()),
+ _ => None,
+ })
+ .collect();
+ for r in Reason::ALL {
+ assert!(seen.contains(r.code()), "no vector gives {}", r.code());
+ }
+ let mut files = Vec::new();
+ for (v, run) in &rooms {
+ if matches!(run.outcome(), Outcome::Accept) {
+ files.push((hls_file(v.name), hls_body(run)));
+ }
+ }
+ let principals: Map = PRINCIPALS.iter().map(|(n, i)| (n.to_string(), json!(hex(&pk(*i))))).collect();
+ let doc = json!({
+ "about": "extensions/live-ads.md §11: rooms whose cues open ad breaks, their ad policies, the HLS interstitials and DASH alternative-presentation events each break maps onto, each viewer's asset list and break-form wai.video.ssai receipt, and the spend against the decider grant's ceiling",
+ "principals": principals,
+ "vectors": rooms.iter().map(|(v, run)| vector_json(v, run)).collect::>(),
+ });
+ let mut body = serde_json::to_string_pretty(&doc).expect("JSON");
+ body.push('\n');
+ files.push(("vectors.json".into(), body));
+ files.sort();
+ files
+}
+
+fn unhex(s: &str) -> Option> {
+ if !s.len().is_multiple_of(2) {
+ return None;
+ }
+ (0..s.len()).step_by(2).map(|i| u8::from_str_radix(s.get(i..i + 2)?, 16).ok()).collect()
+}
+
+fn hex_list(v: &Value) -> Option>> {
+ v.as_array()?.iter().map(|h| unhex(h.as_str()?)).collect()
+}
+
+/// Check a corpus's files against this module: every room gets the outcome and everything
+/// else it states, and every tag file is the run's. Returns the failures.
+pub fn verify(files: &BTreeMap) -> Vec {
+ let mut fails = Vec::new();
+ let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_str::(s).ok()) else {
+ return vec!["vectors.json does not read".into()];
+ };
+ for v in doc["vectors"].as_array().into_iter().flatten() {
+ let name = v["name"].as_str().unwrap_or("?").to_string();
+ let parsed = (|| {
+ let chains: Vec>> = v["chains"].as_array()?.iter().map(hex_list).collect::