# 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 (§5), the stage index (§6), the floor > (§7), what each class is offered over HTTP and MoQ (§8), the package receipt (§9), > and the package and its verification (§11). Reference impl: `wai-pack` — `policy`, > `capcheck`, `source`, `scenecut`, `classify`, `encode`, `floor`, `cmaf`, `catalog`, > `pack`, `verify`; in `wai-rs`, the encoder `int_video_enc`, the stage index > `stage_index` and the receipt `package`; the corpus > [`pack-conformance/`](../pack-conformance/). 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. - **Timebase** of a component: `num / den` units per second. For video one unit is one frame; for audio, one sample. ## 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` | | `component.source` | string | absent | | | `component.timebase` | table | required | members `num`, `den`, each 1 to `2^32 − 1`, required | | `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` | | | `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.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` (`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`). 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, at least one of them a base (`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`). 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:`. 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::