# WAI Extension: Packaging > Status: Draft. A packager turns sources into WAI objects under a declared policy; > this extension fixes what a verifier recomputes. This revision defines the policy, > its digest and what it may ask a packager to emit (§3), how a source is cut into > pieces (§4), the objects a packager writes, refinements and stills among them, a > piece's ladder, and the components a package carries beside its lead, audio, a score, > vector graphics, timed text and a feed, each in the capability that suits it (§5), the > stage index (§6), the floor, one or several, supplied as OBU streams or as an existing > ladder's CMAF files, and a sound track's Opus floor (§7), the protected floor, those CMAF > files under Common Encryption (`cenc` and `cbcs`) with W3C Clear Key and content protection > systems signalled by their system IDs (§7.10), what each class is offered > over HTTP and MoQ and how its delivery is simulated (§8), the package receipt and live > windows (§9), the package, its verification and a re-pack from a parent (§11), and > where each step of a pack or a verify may run (§11.6), and how a programme's audio > renditions are written for HLS and DASH, audio description among them (§8.10), a lead's > marks, its shots, chapters, break candidates and clips, derived from its stage index and > written as WebVTT chapters, HLS date ranges and DASH event streams (§8.11), and a > programme's loudness, measured in integers, with the gain to a target in a signed > statement (§12). > Reference impl: `wai-pack` — > `policy`, `capcheck`, `source`, `scenecut`, `classify`, `plan`, `encode`, `png`, > `floor`, `cmaf`, `cmaf_read`, `opus_floor`, `cenc`, `av1_tiles`, `protect`, `catalog`, `files`, `pack`, `multi`, `live`, `delivery`, `verify`, > `fabric`, `fabric_meter`, `floor_decode`, and `wai-pack-gpu` for the steps a GPU runs; > in `wai-rs`, the > encoder `int_video_enc`, the refinement encoder `staged::wir1`, the stage index > `stage_index`, the receipt `package`, the delivery simulator `stage_measure::sim` and the > marks `pack_marks`; > the corpus [`pack-conformance/`](../pack-conformance/), whose `multi/` holds packages of > several components (§5.8), and beside it the composing sink's reference package > [`wai-web/compose/`](../wai-web/compose/), five components written by `wai-pack` and held > to the corpus check. Keywords **MUST**, **MUST NOT**, > **SHOULD**, **MAY** are RFC 2119/8174. ## 1. Scope A **packager** turns one or more sources into WAI objects, stage indexes, catalogs and a package receipt, under a declared **policy**. This extension fixes what a package contains and what a verifier recomputes. It fixes an encoder's choices only where it names them. ## 2. Terms - **Component**: one media stream of a package (for example video, or audio). - **Piece**: a span of a component that decodes with no reference outside itself. - **Stage**: one object of a piece in staged order: the base is layer 0, refinement *k* is layer *k* ([staged-delivery](staged-delivery.md)). - **Rung**: one entry of the policy's ladder: a capability a component's pieces are coded in, with its role (base, refinement or floor) and parameters. - **Class**: a delivery profile the policy names. - **Moment**: a declared span of presentation time with tags. - **Floor**: a rendition in a standard codec, for sinks that lack the primary. - **Floor name**: the name a floor rung's files and catalog entries carry (§7.1): its component's id when the component has one floor rung, the rung's id when it has several. - **Timebase** of a component: `num / den` units per second. For video one unit is one frame; for audio, one sample. - **Step**: one of the computations of a pack or a verify (§11.6), such as the base's motion search or an object's content hash. - **Fabric**: the kind of processor a step runs on: `cpu`, `gpu`, `media` (a hardware video engine) or `npu` (a neural processor) (§11.6). ## 3. Policy **3.1** A package is made under exactly one policy, a TOML 1.0 document of schema `wai.pack.policy/1`, in UTF-8 with no byte-order mark. **3.2** The **effective policy** is the policy with every default written in (§3.3) and every override the packager applies (such as a command line forcing `royalty_free_only`). An override only ever forces `royalty_free_only` on; it never turns off what the policy asks for. Its JSON form is §3.3's members, with an optional member that has no default omitted when absent, and arrays of tables in document order. Its **policy digest** is BLAKE3("wai:pack-policy\x01" ‖ JCS(effective policy as JSON)) where JCS is RFC 8785. Two spellings of one policy (member order, inline or standard tables, dotted keys, string or integer notation, comments, an omitted default or the default written out) therefore have one digest; the order of an array of tables is part of the policy. A packager **MUST** refuse with `policy-toml`, before anything else, a policy whose bytes are not UTF-8, that begins with a byte-order mark, that is not TOML 1.0 (an integer outside TOML's 64-bit range included), or that nests arrays and tables more than 32 deep: the document's own table is at depth 1, and an array or a table directly inside one at depth *k* is at depth *k* + 1. Then, before it reads any key, it **MUST** refuse one holding anywhere a float (`policy-float`), a date or time (`policy-datetime`), a negative integer (`policy-negative`), or an integer above `2^53 − 1` (`policy-integer-range`), visiting members in byte order of their keys and arrays in order. **3.3** *Schema.* Within each table, a key this section does not define is refused first (`policy-unknown-key`, the first in byte order), then each member in the order listed: a required member that is absent (`policy-missing-key`), a member of another type (`policy-type`), a value outside its range or vocabulary (`policy-value`). An absent table reads as empty. | Member | Type | Default | Range | |---|---|---|---| | `schema` | string | required | `wai.pack.policy/1` (else `policy-schema`) | | `asset_id` | string | required | 1 to 255 bytes | | `royalty_free_only` | boolean | `false` | | | `lead` | string | required | 1 to 255 bytes | | `component` | array of tables | required | | | `component.id` | string | required | 1 to 255 bytes | | `component.media` | string | required | `video`, `image`, `audio`, `world`, `feed`, `avatar`, `film`, `graphics`, `text` | | `component.source` | string | absent | | | `component.timebase` | table | required | members `num`, `den`, each 1 to `2^32 − 1`, required | | `component.profile` | string | absent | `floor-only` (§7.8); absent, the standard profile | | `component.lang` | string | absent | 1 to 63 ASCII letters, digits and hyphens, none first, last or doubled: a text component's language, a BCP 47 tag (§5.8) | | `segmentation.target_ms` | integer | 2000 | | | `segmentation.min_ms` | integer | 1000 | | | `segmentation.max_ms` | integer | 4000 | | | `segmentation.scene_cut` | string | `wai.pack.scenecut/1` | `wai.pack.scenecut/1` | | `segmentation.cut_abs_permille` | integer | 300 | 1 to 1000 | | `segmentation.cut_rel_permille` | integer | 3000 | 0 to 1000000 | | `segmentation.cut_window` | integer | 8 | 3 to 64 (a window of fewer than 3 scores never applies `cut_rel_permille`, §4.3) | | `segmentation.lookahead_frames` | integer | 6 | 0 to 64 | | `segmentation.closed` | boolean | `true` | | | `ladder` | array of tables | empty | | | `ladder.id` | string | required | 1 to 255 bytes | | `ladder.component` | string | required | 1 to 255 bytes | | `ladder.capability` | string | required | | | `ladder.role` | string | required | `base`, `refinement`, `floor` | | `ladder.refines` | string | absent | | | `ladder.params` | table | empty | by capability, below | | `floor.supplied_listing` | string | absent | 64 lowercase hexadecimal digits | | `floor.extra_keyframes` | boolean | `false` | | | `floor.supplied_form` | string | absent | `cmaf` (§7.7); absent, the OBU form (§7.3) | | `protection` | table | absent | absent, the package has no protected floor; present, `cpix_sha256` and `key` are required (§7.10) | | `protection.cpix_sha256` | string | required | 64 lowercase hexadecimal digits: the SHA-256 of the CPIX document the keys come in | | `protection.systems` | array of strings | empty | system IDs of the DASH-IF registry in canonical lowercase UUID text, none twice, none all zero, neither `1077efec-c0b2-4d02-ace3-3c1e52e2fb4b` nor `e2719d58-a985-b3c9-781a-b030af78d30e` | | `protection.licence_uri` | string | absent | 1 to 1024 of RFC 3986's characters, no brace: the Clear Key licence URI | | `protection.key_uri` | string | absent | as `licence_uri`, holding `{kid}` exactly once and no other brace: the HLS key URI of an `identity` key | | `protection.key` | array of tables | required | at least one | | `protection.key.floor` | string | required | 1 to 255 bytes: a floor name (§7.1) | | `protection.key.scheme` | string | required | `cenc`, `cbcs` | | `protection.key.kid` | string | required | a KID in canonical lowercase UUID text, not all zero | | `live.layer_lag_ms` | array of integers | one 0 per layer | §3.4 | | `class` | array of tables | empty | | | `class.id` | string | required | 1 to 255 bytes | | `class.max_layer` | integer | 0 | 0 to 255 | | `class.max_kbps` | integer | absent | 1 to `2^32 − 1` | | `class.floor_only` | boolean | `false` | | | `moment` | array of tables | empty | | | `moment.id` | string | required | 1 to 255 bytes | | `moment.from_ms`, `moment.to_ms` | integer | required | `to_ms` above `from_ms` | | `moment.tags` | array of strings | empty | | | `rule` | array of tables | empty | | | `rule.id` | string | required | 1 to 255 bytes | | `rule.when.piece_kind` | array of strings | absent | `natural`, `static`, `synthetic`, `motion_high` | | `rule.when.moment_tag` | array of strings | absent | | | `rule.then.ladder` | array of strings | absent | | | `rule.then.max_layer` | integer | absent | 0 to 255 | | `rule.then.deadline_ms` | integer | absent | 1 to `2^32 − 1` | | `defaults.deadline_ms` | integer | 1500 | 1 to `2^32 − 1` | | `classify.static_permille` | integer | 5 | 0 to 1000 | | `classify.static_mad_milli` | integer | 300 | | | `classify.synthetic_bins` | integer | 12 | 0 to 64 | | `classify.motion_high_milli` | integer | 9000 | | A rung's `params` are those of its capability; every other capability takes none. | Capability | Parameter | Default | Range | |---|---|---|---| | `wai.video.int_motion` | `block` | 16 | 4, 8, 16, 32 | | | `search` | 7 | 0 to 32 | | | `mv_precision` | 2 | 1, 2 | | | `q_candidates` | `[8]` | a non-empty array, each 1 to 255 | | | `max_sse_px_milli` | 0 | | | `wai.video.int_refine` | `q` | required | 1 to 255 | | `wai.video.av1`, `wai.video.av1.lossless` | `source` | `supplied` | `supplied`, `encode` | | `wai.audio.int_refine` | `to_rate`, `q` | required | 1 to 384000; 1 to 65535 | | `wai.audio.flac` | `source` | `encode` | `encode`, `supplied` | | `wai.audio.opus` | `source` | `supplied` | `supplied` | | `wai.text.zstd` | `source` | `encode` | `encode` | `wai.audio.int_layered` is reserved for the layered integer audio base: it is not registered yet, and its parameters are defined when it is. **3.4** *Constraints.* A policy that passes §3.3 is checked in this order, and the first that fails is the refusal: 1. 1 to 8 components (`policy-too-many-components`); at most 64 rungs (`policy-too-many-rungs`), 32 classes (`policy-too-many-classes`), 32 rules (`policy-too-many-rules`) and 32 moments (`policy-too-many-moments`). 2. Ids unique among components, rungs, classes, moments and rules taken together, so a reference never names two things (`policy-duplicate-id`). 3. `lead` names a component (`policy-lead`). 4. `segmentation.closed` is true: every piece is closed, §4.1 (`policy-open-gop`). 5. `1 ≤ min_ms ≤ target_ms ≤ max_ms ≤ 600000`, and `max_ms` is at most `2^24` units on every component's timebase (§4.4) (`policy-piece-bounds`). 6. Every rung names a component (`policy-ladder-component`). 7. A rung carries `refines` exactly when its role is `refinement` (`policy-refines-role`); `refines` names a rung (`policy-dangling-refines`); no chain of `refines` returns to a rung it passed (`policy-refines-cycle`); every chain stays in its component and ends at a base (`policy-refines-base`); no rung is refined twice (`policy-refines-branch`). 8. Every component has a base rung (`policy-no-base`), but a floor-only one (§7.8), which has no base and no refinement rung, no `source`, and at least one floor rung (`policy-floor-only`). 9. A rung's **layer** is 0 for a base or a floor, and one more than the rung it refines for a refinement; the policy has one more layer than its highest rung layer. No class's and no rule's `max_layer` exceeds the highest (`policy-class-layer`). 10. A rule's `then.ladder` names known rungs, all of one component, each once: exactly one base (none, for a floor-only component), every refinement over a rung it names (so its layers are a chain from that base), and every floor rung of the component (every piece has a floor, §7.1) (`policy-rule-ladder`). 11. `live.layer_lag_ms` has one entry per layer, the first 0, the entries non-decreasing (`policy-layer-lag`). 12. The deadline values (`defaults.deadline_ms` and every rule's `then.deadline_ms`) number at most 4 (`policy-too-many-deadlines`), and for every refinement layer *k* and every deadline value *d*, `d − layer_lag_ms[k] ≥ 1` (`policy-deadline-unreachable`). 13. With a `protection` table (§7.10): every key names a floor with CMAF files, the lead's AV1 floors and each sound track's Opus floor, by its floor name; and for each scheme the keys name, every such floor has exactly one key of it, the floors of one component the same KID (`policy-protection`). Rules fold in document order: every matching rule applies, and a later `then` member overrides an earlier one. **3.5** A package receipt carries the policy digest. A verifier holding the effective policy recomputes it; it cannot check that the packager followed the policy, except by packing again. **3.6** *What a packager may emit.* A packager **MUST** refuse a policy with a rung it may not emit, reading SPEC §5's registry, never the policy. For each rung in ladder order the first rule that holds is its refusal, and a policy's refusal is its first rung's: 1. SPEC §5 does not register the capability: `capability-unknown:`. One capability is read before this rule with the classes stated here, and with no licence class: `wai.text.timed`, which SPEC §5 registers as a record, as the base of a text component, whose payload is a WebVTT segment (§5.8): standalone media of class `StandardDefined`. Rules 2 to 4 read those classes, and under `royalty_free_only` rule 5 refuses it `license-unclassified:`. (`wai.graphics.vector` is registered, as standalone media of class `StandardDefined`, royalty-free: [graphics-vector](graphics-vector.md) §1.8.) 2. SPEC §5 registers it as a record or a derivation operation, which names no payload format: `capability-not-payload:`. 3. It is a candidate, whose payload format no revision has pinned (SPEC §5, *Candidate capabilities*): `capability-candidate:`. 4. A base or a floor whose capability is not standalone media, or a refinement whose capability is not refinement-only: `capability-role:`. 5. Under `royalty_free_only`, a capability the registry gives no licence class: `license-unclassified:`; one of any class other than royalty-free: `license-refused::