LiveOut held every object's path, hex SHA-256 and first piece for the whole session. It now holds: - no listing record per file. Each file's listing line is appended to a hidden .LISTING.lines beside the package as the file is written. At the end the lines are read back, sorted into LISTING, and the lines file is removed; that is the one moment it holds a line per file. - no set of written objects. An object already written is known by its file on disk. - for the abandon path, only the objects the unsealed pieces published, as a piece number and a 32-byte content hash. Those of a window are dropped when the window is sealed. packaging §9.2 and the CLI help now say exactly what remains: - what no window has sealed yet: those pieces' entry fields and their objects' hashes, which with --window 0 means every piece's until the end; - the frames of the piece being cut; - eight bytes per piece for the plan. Co-Authored-By: Claude Opus 5.5 <[email protected]>
77 KiB
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, and a piece's ladder (§5), the stage index (§6), the floor (§7), what each class is offered over HTTP and MoQ and how its delivery is simulated (§8), the package receipt and live windows (§9), and the package and its verification (§11). Reference impl:
wai-pack—policy,capcheck,source,scenecut,classify,plan,encode,png,floor,cmaf,catalog,pack,live,delivery,verify; inwai-rs, the encoderint_video_enc, the refinement encoderstaged::wir1, the stage indexstage_index, the receiptpackageand the delivery simulatorstage_measure::sim; the corpuspack-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).
- 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 / denunits 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 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). - Ids unique among components, rungs, classes, moments and rules taken together, so
a reference never names two things (
policy-duplicate-id). leadnames a component (policy-lead).segmentation.closedis true: every piece is closed, §4.1 (policy-open-gop).1 ≤ min_ms ≤ target_ms ≤ max_ms ≤ 600000, andmax_msis at most2^24units on every component's timebase (§4.4) (policy-piece-bounds).- Every rung names a component (
policy-ladder-component). - A rung carries
refinesexactly when its role isrefinement(policy-refines-role);refinesnames a rung (policy-dangling-refines); no chain ofrefinesreturns 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). - Every component has a base rung (
policy-no-base). - 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_layerexceeds the highest (policy-class-layer). - A rule's
then.laddernames known rungs, all of one component, each once: exactly one base, 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). live.layer_lag_mshas one entry per layer, the first 0, the entries non-decreasing (policy-layer-lag).- The deadline values (
defaults.deadline_msand every rule'sthen.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:
- SPEC §5 does not register the capability:
capability-unknown:<cap>. - SPEC §5 registers it as a record or a derivation operation, which names no payload
format:
capability-not-payload:<cap>. - It is a candidate, whose payload format no revision has pinned (SPEC §5,
Candidate capabilities):
capability-candidate:<cap>. - A base or a floor whose capability is not standalone media, or a refinement whose
capability is not refinement-only:
capability-role:<cap>. - Under
royalty_free_only, a capability the registry gives no licence class:license-unclassified:<cap>; one of any class other than royalty-free:license-refused:<cap>:<label>, with the class's SPEC §5 label. The class is the registry's, "licensed royalty-free by its standards body"; it is not an opinion on freedom to operate, and a package states nothing about patents.
4. Pieces
4.1 Every piece MUST be closed: its first frame, sample or tick is coded with no reference outside the piece, and no later object of the package references into it.
4.2 Piece boundaries are a pure function of the source frames and the effective
policy. Where the policy names wai.pack.scenecut/1, the boundaries MUST be those
§4.3 computes. The list of piece starts, in timebase units, is the plan, and its
digest is
BLAKE3("wai:pack-plan\x01" ‖ u64 LE n ‖ n × u64 LE start)
4.3 wai.pack.scenecut/1. All arithmetic is integer; >> is a shift and every
division rounds down. For each RGB8 frame f of w × h pixels:
- Luma
Y = (77R + 150G + 29B + 128) >> 8, and the histogramH_fof 64 bins,Y >> 2. D(a, b) = (Σ_i |H_a[i] − H_b[i]|) · 1000 / (2wh): the share of pixels, in thousandths, whose bin differs. The score of frame f ≥ 1 iss_f = D(f, f − 1).- The current piece starts at frame p, and
len = f − p. Its windowW_fis the scoress_gforp < g < f, the lastcut_windowof them;m_fis their lower median (sorted ascending, the element at index(|W_f| − 1) / 2). - Hard cut at f:
len ≥ min,s_f ≥ cut_abs, and either|W_f| < 3ors_f · 1000 ≥ cut_rel · m_f. - Soft cut at f:
len ≥ targetand2 · s_f ≥ cut_abs. - Return clause. A cut at f is rejected when
D(f + k, f − 1) < cut_absfor some k in1..=min(L, last − f): the content came back within L frames, so f starts a flash. - Revisit clause. A cut at f is rejected when
D(f, f − 1 − j) < cut_absfor some j in1..=min(L, f − 1): f returns to content seen within L frames, so it ends a flash. Frames before p count. - A hard or soft cut that clauses 6 and 7 do not reject starts a piece at f.
Otherwise, when
len ≥ max, a piece starts at f.
Frame 0 starts the first piece. L is lookahead_frames, last the index of the
source's last frame, cut_abs and cut_rel the policy's permille values, and min,
target and max the policy's milliseconds on the component's timebase (§4.4). A
decision about frame f reads no frame after f + L, so a live segmenter decides it
once frame f + L has arrived, and a live and an on-demand segmenter with equal L
produce equal boundaries. A piece that follows a cut is flagged a scene cut; one
started at max, forced.
4.4 A piece's duration is an integer number of timebase units, and lies between
min and max except the last piece of a source, which may be shorter than min.
A duration in milliseconds converts to timebase units as
floor(ms · num / (1000 · den)); a result of 0 becomes 1, and one above 2^64 − 1
becomes 2^64 − 1.
A piece is at most ten minutes long (max_ms at most 600000) and at most 2^24 units
on its component's timebase (§3.4 item 5). A verifier takes a piece whole: it decodes
every frame of a base, holds a still's one frame for every frame of its piece and hashes
each (§5.7, §6.5), and reads one floor unit per frame (§7.2). So the longest piece a
policy allows is the most work a verifier does for one piece, and a package states it.
Ten minutes is far past any piece a delivery cuts (the default max_ms is four
seconds); 2^24 units bounds that work on any timebase, since a timebase may have up to
2^32 − 1 units a second.
4.5 Components. Every component of a package shares one presentation timeline
that starts at 0. The policy names one lead component; every other component cuts
its pieces at the lead's boundary instants, each converted to its own timebase as
floor(start_lead · num_c · den_lead / (den_c · num_lead)) (above 2^64 − 1,
2^64 − 1). A sink presents piece p of every component from that start.
4.6 Classification. Each video piece has a kind, which rules match
(rule.when.piece_kind). Over the piece's consecutive frame pairs, with Y the luma
of §4.3:
mad_milli = Σ |Y_f − Y_{f−1}| · 1000 / (wh · (n − 1)), and 0 when the piece has one frame (n = 1);changed_permille= the pixels whose luma differs,· 1000 / (wh · (n − 1)), and 0 when n = 1;occupancy= the non-empty histogram bins of the piece's first frame.
The kind is the first that holds of: static (mad_milli ≤ static_mad_milli and
changed_permille ≤ static_permille); synthetic (occupancy ≤ synthetic_bins);
motion_high (mad_milli ≥ motion_high_milli); natural.
4.7 Sources.
-
Y4M: 8-bit 4:2:0 (
C420jpeg,C420paldv,C420mpeg2,C420, or noCparameter) or 4:4:4 (C444), progressive. A header that is notYUV4MPEG2with a width and a height is refusedy4m-header; an interlaced stream or frame,y4m-interlaced; another colour space,y4m-colorspace; aFRAMEline that sets the width, height or colour space,y4m-size-change; a frame that ends early or a line that is notFRAME,y4m-truncated. Each pixel converts to RGB8 by BT.709 limited range, with 4:2:0 chroma taken at(y >> 1, x >> 1):R = clip((298(Y−16) + 459(Cr−128) + 128) >> 8)G = clip((298(Y−16) − 55(Cb−128) − 136(Cr−128) + 128) >> 8)B = clip((298(Y−16) + 541(Cb−128) + 128) >> 8)
where
>>is the arithmetic shift andclipclamps to 0..=255. -
WAV: RIFF/WAVE with a PCM
fmtchunk (format tag 1) of 16-bit samples, block alignment2 · channels. Anything else is refusedwav-format; a chunk that ends early,wav-truncated.
5. Objects
5.1 One envelope. Each object is exactly one WAI1 or WAI2 envelope and
nothing after it (http-delivery §5.1).
5.2 Position independence. An object's bytes MUST NOT depend on its position
in a package or on the package it belongs to: no member of its manifest names a piece,
a time, an asset, a class or a component, so two pieces with identical content are one
object. The reference packager writes a base's manifest as SPEC §3 orders it, with
media the component's media, intent replicate, model_requirement the rung's
capability with no fallback member, conditioning.kind the capability's last
dot-separated segment (int_motion, av1), and target {"frames": n, "h": h, "w": w}
(the piece's frame count and dimensions, for a still too, whose one frame is held for
the piece, §5.7; for a floor, its sequence header's maximum frame height and width); a
floor's derived_from (§7.2) comes after conditioning.
5.3 Name. An object is named by its content hash (jwp-receipts
§2.1), BLAKE3("moq-jwp:object\x01" ‖ envelope), as objects/<64 lowercase hex>.wai:
the same value is its stage-index entry (§6), its receipt leaf, its MoQ content hash and
its HTTP strong ETag (http-delivery §5.2).
5.4 Refinements. A refinement object carries refines
(staged-delivery §3): base the content hash of its piece's base,
layer its layer, and as producers in prev only objects of the layers below it, the
object its entry refines (§6.2) among them.
The reference packager writes, for each refinement rung of a piece's ladder (§5.7), one
object of wai.video.int_refine over the piece's wai.video.int_motion base, layer by
layer. Its manifest is the one wai stage refine writes, as SPEC §3 orders it: media
the component's, intent replicate, the capability with no fallback,
conditioning.kind wir1, refines with base the base's content hash, prev the
content hash of the object of the layer below (the base for layer 1) as a string, and
layer, and target null. Its payload is a WIR1 (staged-delivery Appendix A) of unit
kind RGB8, one unit per frame of the piece, the identity predictor and one table,
written by wai-rs's refinement encoder (staged::wir1::encode) at the rung's step
q:
- each sample's residual is
rdiv(source − below, q), the quotient rounded half away from zero, where below is the stage the layer refines as a sink reconstructs it; no frame predicts another; - the table's
offsetis the least residual and it spans every residual (where they span more than 4095, the 4095 around the lower median), fitted by §5.5's rule, the escape at 1; a residual past it is coded through the escape; - one rANS stream per frame.
The stage the layer reconstructs, clamp(below + r·q, 0, 255), is what the next layer
refines. The packer applies each layer to the decoded base with the reference sink
(staged-delivery §4) before it keeps it, and the sink's output must be the encoder's.
5.5 Integer video. An object of wai.video.int_motion carries a WIV1 payload
with keyframe 0, as SPEC §5 pins it (integer-payloads §3); this
extension defines no other layout for it, and its decode is that section's. The
reference packager writes these with one encoder (wai-rs int_video_enc) at the step
§5.6 chooses. The encoder's choices
make a payload a function of the source frames, the rung's block, search and
mv_precision (§3.3), and the step q:
- Motion search (frames 1 on): blocks in raster order; for each, every vector
(dy, dx)with both components in−R..=R,R = search · mv_precision(at most 127),dyouter anddxinner, each ascending. Its cost is the sum, over the block's pixels inside the frame and the three channels, of|source − prediction|, where the prediction is the decode's (integer-payloads §3) from the previous frame as decoded. The least cost wins; a tie goes to the smaller|dy| + |dx|, then to the earlier vector. - Residual: each frame, each pixel in raster order, each of R, G, B: the
prediction (128 for frame 0) is subtracted from the source and divided by
q, rounding halves away from zero; the frame as decoded isclamp(prediction + r·q, 0, 255), and the next frame predicts from it. - Table: one residual table for the clip, with
offsetthe least residual,topthe greatest, andh_ithe count of residualoffset + i, forn = min(top − offset + 1, 4095)symbols. WithR = 2^16 − n − 1andH = Σ h_i, symbol i gets frequency1 + ⌊R·h_i / H⌋; theR − Σ⌊R·h_i / H⌋left over go one each to the symbols with the largestR·h_i mod H, the lower symbol first on a tie; the escape gets 1. A residual past the table's span is coded through the bypass escape (integer-payloads §2). - A clip of one frame is written with
block1 andmv_precision1, as the format requires.
The encoder also states the SSE of its reconstruction against the source: the sum of
squared differences over every sample. It codes in two passes (the first finds the
vectors, the reconstruction and the residuals' counts; the second derives each
residual again and codes it last first), so beyond its input it holds the
reconstruction, the vectors, the stream and the residual table: at most
n·h·w·3 + 24n + 12·v + 3·(2·n·h·w·3 + 16) + T bytes for n frames and v motion
vectors (int_video_enc::working_bytes). T is the widest table's, of S = 2·255 + 1
symbols: its fit, 16·(S + 1) + 2·8·S + 4·(S + 2) bytes (a frequency for each symbol
and the escape, an index for each symbol and the sort's scratch, the table), and the
table's three copies and the payload's header, 3·4·(S + 2) + 64. For a clip past the
format's caps, which the encoder refuses before it allocates, the figure is 2^64 − 1.
5.6 Choosing the step. For each piece, the base encode of a wai.video.int_motion
rung chooses its step from q_candidates, read as a set (a value listed twice is one
candidate). It encodes the piece at each candidate, from the largest down, and chooses
the first whose floor(SSE · 1000 / (3·w·h·n)) ≤ max_sse_px_milli, where SSE is the
encoder's (§5.5) and n the piece's frames: the largest step that meets the bound, so
candidates that meet it alike are decided by their step. Where no candidate meets it,
it chooses the smallest. The function the reference packager's base encode calls is
wai_pack::encode::choose_q.
5.7 A piece's ladder, and stills. A piece is coded in the rungs of its ladder:
those the matching rules' then.ladder names (§3.4 folds them: the last that sets it),
where they are rungs of the piece's component; otherwise the component's default ladder,
its first base rung in ladder order, the chain of refinement rungs over it, and its floor
rungs. A rule's ladder therefore chooses a piece's representation: its base, how many
layers refine it, and nothing else (§3.4 item 10). A piece has one entry per rung of its
ladder: its base, each refinement in layer order, then each floor (§6.2), and a
verifier holds an index to that (§11.3).
A base of wai.image.png is a still: the piece's first frame, held for the piece. A
sink presents that frame for each of the piece's frames, so its prefix is the frame
repeated and its digest the clip digest of the repeats (§6.5), its sse is over the
repeats against the piece's frames (§6.6), and its manifest's target names the piece's
frames (§5.2). The reference packager writes the frame as a PNG every byte of which is a
pure function of the frame, so an independent implementation writes the same file:
- the signature,
IHDR(the width, the height, bit depth 8, colour type 2, compression 0, filter 0, interlace 0), oneIDATholding the whole compressed stream, andIEND; each chunk a big-endian length, its type, its data and the CRC-32 of type and data; - every row filtered
Sub(filter byte 1): each byte less the byte three before it in the row, modulo 256, the first pixel's bytes less 0; - a zlib stream (RFC 1950) of
0x78 0x01, one final deflate block (RFC 1951) of the fixed Huffman codes, and the Adler-32 of the filtered rows; - the block's symbols, from the first byte of the filtered rows: where at least three bytes remain and an earlier position within 32768 bytes starts with the same three bytes, the latest such position is the match, as long as the two runs agree (at most 258 bytes), written as that length and the distance to it; otherwise one literal. Every position a symbol covers that has three bytes left becomes the latest position of its three bytes. The block ends with symbol 256, and the last byte is padded with zero bits.
A verifier reads a still as a PNG of 8-bit RGB without interlace, by PNG's chunk rules:
every chunk's type four letters and its CRC checked; IHDR first; an optional PLTE
(a suggested palette) before the image data; the IDATs consecutive; ancillary chunks
read past; any other critical chunk refused; IEND last, empty, and nothing after it.
It inflates the zlib stream as the zlib library's inflate does, so two verifiers agree
on every stream: the header's check bits, method 8, a window of at most 32 KiB and no
preset dictionary; a stored block's length and its complement agree; at most 286
literal/length and 30 distance codes, a complete code-length code, a repeat with a
length before it and within the code lengths, and a code for the end of the block; no
Huffman code over-subscribed, and none incomplete but a literal/length or distance code
whose longest code is one bit (a lone one-bit code, or none); a code with no symbol, or
a distance past the bytes written, refused where it is read; the Adler-32, and nothing
after it. The rows are exactly 1 + 3·width bytes each, under the five filters. A still
a verifier does not read is prefix-digest (§11.3).
6. Stage index
6.1 A package carries, per component, one stage index, the file index-<id>.wsix
(WSIX), per window: one window for on-demand content, a chain of windows for live
(§9.2). A window holds whole pieces. Integers are little-endian.
6.2 Layout. A 160-byte header, then n_pieces piece records of 32 bytes,
n_entries entry records of 128 bytes, n_classes class records of 16 bytes, the class
plan, and the string table. Nothing follows the string table. Reserved and padding
bytes are zero.
| Offset | Size | Header field |
|---|---|---|
| 0 | 4 | "WSIX" |
| 4 | 2 | version, 1 |
| 6 | 2 | media_class: 0 video, 1 image, 2 audio, 3 world, 4 feed, 5 avatar, 6 film |
| 8 | 4 | tb_num |
| 12 | 4 | tb_den |
| 16 | 4 | n_pieces |
| 20 | 4 | n_entries |
| 24 | 4 | n_strings |
| 28 | 4 | n_classes |
| 32 | 32 | policy_digest (§3.2) |
| 64 | 32 | prev_index_digest: the previous window's index digest; zero for on-demand content and a first window |
| 96 | 8 | first_piece: the absolute number of the window's first piece |
| 104 | 8 | first_entry: the absolute ordinal of the window's first entry across the chain |
| 112 | 32 | plan_digest (§4.2) of the pieces' starts |
| 144 | 2 | component: the component's id, as a string index |
| 146 | 14 | reserved |
| Offset | Size | Piece record field |
|---|---|---|
| 0 | 8 | start, in timebase units |
| 8 | 4 | duration, in timebase units |
| 12 | 4 | first_entry, window-relative |
| 16 | 2 | n_entries |
| 18 | 1 | kind: 0 natural, 1 static, 2 synthetic, 3 motion_high (§4.6), 4 world segment, 5 audio |
| 19 | 1 | flags: bit 0 scene cut, bit 1 forced at max (§4.3), bit 2 started by a cue, bit 3 aligned to the lead (§4.5) |
| 20 | 4 | rules: bit i set when rule i matched the piece (§8.1) |
| 24 | 4 | moments: bit i set when the piece lies in moment i (§8.1) |
| 28 | 4 | reserved |
| Offset | Size | Entry record field |
|---|---|---|
| 0 | 32 | content_hash (§5.3) |
| 32 | 8 | byte_len: the whole envelope's length |
| 40 | 8 | piece: the absolute piece number |
| 48 | 4 | refines: the window-relative index of the entry this one refines; 0xFFFFFFFF for none |
| 52 | 2 | capability, as a string index |
| 54 | 1 | role: 0 base, 1 refinement, 2 floor, 3 snapshot, 4 ops, 5 receipt, 6 asset |
| 55 | 1 | layer |
| 56 | 1 | prefix_tier: 0 none, 1 entropy-consistency, 2 decode-equivalence (§6.4) |
| 57 | 1 | flags: bit 0 deferrable, bit 1 duplicate (an earlier entry of the index has the content hash), bit 2 the manifest carries derived_from |
| 58 | 2 | reserved |
| 60 | 4 | deadline_ms: 0 for none (§8.6) |
| 64 | 32 | prefix_digest (§6.5) |
| 96 | 8 | sse (§6.6); 2^64 − 1 when not computed |
| 104 | 4 | manifest_len: the envelope's man_len |
| 108 | 4 | reserved |
| 112 | 8 | payload_len: the envelope's payload bytes (for a WAI2, every component's) |
| 120 | 8 | reserved |
byte_len, manifest_len and payload_len are the envelope's own lengths; for an
object whose payload format WAI checks they are its counted bytes
(staged-measurement §3), so a verifier also requires each such
payload to parse as its format, after the other object rules of §6.3
(object-lengths).
A class record is u16 name (a string index), u8 max_layer, u8 flags (bit 0
floor only), u32 max_kbps (0 for none) and 8 reserved bytes, in the policy's class
order. The class plan is n_classes rows of n_pieces bytes, each the highest layer
the class lists for the piece or 0xFF for the floor only (§8.1), zero-padded to a
multiple of 8. The string table is n_strings strings, each a u16 length and that
many bytes of UTF-8, at least one, zero-padded to a multiple of 8. It is the component's
id, then the class ids in policy order, then each capability at its first use, and a
package's verifier holds an index to that (§11.3).
index_digest = BLAKE3("wai:pack-stage-index\x01" ‖ file)
leaf_i = BLAKE3("wai:pack-stage\x01" ‖ entry record i)
stage_root = the jwp-receipts §2.2 Merkle root over the leaves
A reader MUST refuse a file that is not this layout, the first that applies, in this order:
- its first four bytes are not
WSIX, a file of fewer included (index-magic); - a file of fewer than six bytes (
index-truncated), or a version other than 1 (index-version); - a file shorter than the header, or than the tables its counts give: the piece, entry
and class records, the class plan with its padding, each string's length and bytes,
and the string table's padding, every size taken from its count before anything is
allocated (
index-truncated); - a reserved or padding byte that is not zero (
index-reserved); - a media class with no meaning (
index-media-class); then, entry by entry, a role, then a tier, with none (index-role,index-tier); - a string that is empty or not UTF-8, then a string index past the table
(
index-string); - bytes after the string table's padding (
index-trailing).
6.3 Rules. An index lists every WAI object of its component, in piece order. Files that are not WAI envelopes (CMAF initialization segments and fragments, FLAC files) MUST NOT appear in it. A verifier MUST refuse an index that breaks one of these, checked in this order:
- A timebase of zero (
index-timebase). An index with no previous window (prev_index_digestzero: on-demand content, or a chain's first window) whosefirst_pieceorfirst_entryis not 0, or whose first piece does not start at 0, the start of the presentation timeline (§4.5) (window-start). Then piece by piece: a piece that lasts no time or does not start where the one before it ended (piece-order); whosefirst_entryis not the number of entries before it, or that has none (piece-entries); of a kind or with a flag that has no meaning (piece-kind). Then pieces whose entries are not the index's entries (piece-entries). - Entry by entry, in order: an entry that does not name its piece's absolute number
(
piece-entries); a flag with no meaning (entry-flags); within its piece, a role before one that comes earlier in the order base, refinement, floor, any other, or a refinement at a lower layer than the refinement before it (entry-order); an entry that is not a refinement above layer 0, a refinement at layer 0, orrefineson an entry that is not a refinement or missing from one (layer-role); a refinement whoserefinesis outside the window (refines-outside-window), is not an earlier entry of its own piece (refines-mismatch) or is not one layer below it (layer-gap); its tier (§6.4:tier-entropy-passthrough,tier-weakest, thentier-mismatch); a non-zeroprefix_digestwith a tier other than decode-equivalence (digest-without-tier); a deadline on an entry that is not a refinement (deadline-role); a floor without flag bit 2 (floor-derived-from); a content hash an earlier entry has at another length (dup-length); the duplicate flag other than set exactly when an earlier entry has the content hash (dup-flag). - Class by class: a flag with no meaning (
class-flags); a plan value other than0xFFfor a floor-only class, or other than0xFFor at most both the class'smax_layerand the piece's highest layer otherwise (class-plan).
With the objects in hand, a verifier MUST also refuse, entry by entry: bytes of
another length (object-length) or that do not hash to the entry's content hash
(object-hash); bytes that are not one WAI1 or WAI2 envelope and nothing else
(non-envelope-entry); a man_len or a payload length other than the entry's
(object-lengths); a capability other than the entry's (object-capability; for a
WAI2, its first entry's); a refinement whose object is not a WAI1 whose refines
names its piece's base, its layer and producers of the layers below among which the
entry it refines (§5.4, object-refines); and derived_from (SPEC §7.1) present
otherwise than bit 2 says, or not well formed (object-derived-from). A member whose
value is null is absent; for a WAI2, derived_from is present when any entry
carries it, and well formed when every entry that carries it is. A manifest that does
not parse, or names a member twice, is not an envelope (non-envelope-entry).
6.4 Tier. The tier a capability's own decode holds is decode-equivalence for a
capability whose registry class reconstructs exactly (StandardDefined, and
NeuralIntegerExact, whose pin a sink verifies before it decodes),
entropy-consistency for NeuralEntropyExact, and none otherwise (SPEC §5, §7).
- An entry at layer 0 states its capability's tier, except a floor, which states none (§7.4).
- A prefix with one or more refinements holds decode-equivalence only if every object along it does, and none otherwise (derived-tracks §3, rules 1 and 2): a refinement's tier is decode-equivalence exactly when the entry it refines states decode-equivalence and its own capability's tier is decode-equivalence.
- An entry with
layer> 0 MUST NOT state entropy-consistency (tier-entropy-passthrough); one withlayer> 0 that states decode-equivalence over a prefix that does not hold it is refusedtier-weakest; any entry stating another tier than the one this section gives,tier-mismatch.
6.5 prefix_digest is the SHA-256 decoded-output digest (SPEC §7, Digests) of the
prefix the entry completes when prefix_tier is decode-equivalence, and all zero
otherwise: for video, the clip digest over RGB8 frames (for a still, of its frame held
for the piece's frames, §5.7); for an image, the RGB8 frame digest; for audio, the digest of the s16le interleaved samples; for world, feed, avatar
and film entries, zero (their state hashes live in their receipts). A verifier decodes
the prefix and compares.
6.6 sse is the integer sum, over every output sample of the prefix, of the
squared difference from the source sample the packager coded (RGB8 channels, or s16
samples). It is the producer's statement: a verifier holding the source recomputes it,
and one without the source cannot. A floor's sse is 2^64 − 1.
7. Floor and derived_from
7.1 Every piece of a video, image or audio component MUST have a floor rendition
in a capability of SPEC §5's mandatory floor or recommended set; a packager refuses a
video component with no floor rung (floor-missing). A film or world package SHOULD
have one. This revision packs a supplied AV1 floor (wai.video.av1 or
wai.video.av1.lossless, source = "supplied").
7.2 Floor objects. Each piece's floor is one object of the floor rung's capability
(§5.2), whose manifest carries derived_from (SPEC §7.1, Derived renditions) naming
the piece's base: motif the base's content hash, motif_capability its capability,
renderer the floor's maker, and settings BLAKE3("wai:render-settings\x01" ‖ JCS(settings)) (§3.6). For a supplied floor, renderer is supplied@<the supplied listing's SHA-256> and the settings are {"source": "supplied", "supplied_listing": "<that SHA-256>"}, so neither names a position. This profile fixes these members: they
are not the producer's statement here (SPEC §7.1), and a verifier holds every floor
object to them, to its framing header and to its target (§11.3). An AV1 floor object's payload is the
length-prefixed framing the reference sink decodes (wai-rs codecs::video): n,
h, w, fps_num, fps_den as u32 little-endian (the piece's temporal units, the
sequence header's maximum frame height and width, the component's timebase num and
den), then each temporal unit as a u32 little-endian length and its bytes as the
stream holds them.
7.3 A supplied floor is a directory holding, per video component, <id>.obu: an
AV1 stream in the low-overhead OBU format (every OBU with its size field), one temporal
unit per source frame. Its listing (SPEC §3.1) of every file is pinned by the policy's
floor.supplied_listing. A packager MUST refuse, before it writes anything:
- a directory whose listing does not hash to
floor.supplied_listing(floor-listing-mismatch), and a component with no stream in it (floor-stream-missing); - a stream that does not split into temporal units each holding a frame, whose first
unit holds no sequence header, with a sequence header whose bytes differ from the
first's, or with a unit that starts at a shown key frame (below) without a sequence
header (
floor-av1-malformed); - another number of temporal units than source frames (
floor-frame-count); - a unit that does not start at a shown key frame at a piece's first frame, or one that
does elsewhere unless
floor.extra_keyframesis true (floor-keyframe-mismatch). A unit starts at a shown key frame when its first frame header hasframe_typeKEY_FRAMEandshow_frame1, or the sequence uses the reduced still-picture header; a unit that shows an existing frame does not.
The plan a supplied floor is made against is the keyframe plan, the file
plan-<id>.txt: each piece's first frame in decimal, one per line, each line ended by a
line feed. Its pieces' starts are the plan of §4.2, and wai-pack plan writes it from the
source and the policy before any floor is encoded.
7.4 The floor is presentation. Its output is never compared with the primary's, and
no tier or replicate claim covers it.
7.5 A floor produced by an encoder that is not a pure function of its input (§11)
MUST name that encoder and its version in derived_from.renderer.
7.6 CMAF. The floor of a video component is also written as CMAF files, which no
stage index lists: floor/<id>/init.mp4, and one fragment per piece,
floor/<id>/<SHA-256 of the fragment>.m4s. Box layouts are ISO/IEC 14496-12's, with the
AV1 binding's sample entry; every integer is big-endian and every time 0:
init.mp4:ftyp(major brandcmfc, minor version 0, compatiblecmfc,iso6,av01), thenmoovholdingmvhd(version 0, timescaletb_num, rate 1, volume 1, the unity matrix, next track 2),trak(tkhdversion 0 with flags 3, track 1, layer, alternate group and volume 0, the unity matrix, the width and height in 16.16;mdia:mdhdversion 0, timescaletb_num, languageund;hdlrvidenamedvideowith its terminating zero byte;minf:vmhdwith flags 1,dinfwith one self-containedurl, andstbl:stsdwith oneav01sample entry of the width and height, data reference 1, resolution 72 dpi, frame count 1, an empty compressor name, depth 24 and pre-defined −1, holdingav1C, then emptystts,stsc,stszandstco), andmvexwithtrex(track 1, sample description 1).av1Cis marker and version 1, then the sequence header's profile, level, tier, bit depth, monochrome and chroma fields, no initial presentation delay, then the sequence header OBU as the stream holds it. The width and height are the sequence header's maximum frame width and height.- A fragment:
moofholdingmfhd(sequence number piece + 1) andtraf(tfhdwith default-base-is-moof, track 1;tfdtversion 1, the piece's start ×tb_den;trunversion 0 with data offset, sample duration, size and flags, the offset pointing at themdatpayload), thenmdat. Each sample is a temporal unit without its temporal delimiter, lastingtb_den; its flags are0x02000000at a shown key frame and0x01010000otherwise.
The codecs string of the floor is the AV1 binding's short form,
av01.<profile>.<level, two digits><M or H>.<bit depth, two digits>.
8. Classes, catalogs and delivery
8.1 What a class lists. The stages listed for a class are a pure function of the class, the piece and the effective policy, so every viewer of a class receives the same catalog bytes.
- A piece that starts at unit
startand lastsdurationunits spans the milliseconds froms_msup toe_ms, the floors ofstart · den · 1000 / numand of(start + duration) · den · 1000 / num; it lies in moment i whens_ms < to_msande_ms > from_ms. Rule i matches it when each condition the rule states holds: its kind is inwhen.piece_kind, and a tag of a moment it lies in is inwhen.moment_tag. Matching rules fold in document order (§3.4). - A floor-only class lists the floor (
0xFF). Otherwise the class listsmin(max_layer, the rules' then.max_layer, the piece's highest layer); undermax_kbps, while the listed prefix needs more thanmax_kbps, that isceil(8 · bytes · num / (1000 · duration · den))kilobits per second over the bytes of the listed bases and refinements, the top layer is dropped, and a base that alone needs more makes the piece floor-only for the class.
8.2 A catalog MUST NOT list a refinement without the stages below it.
8.3 HTTP. A package is offered in http-delivery's Mode N (§11.1 there): the classical presentation is the floor alone, and WAI objects are offered in playlists of their own.
-
A WAI object is none of the media segment formats of RFC 8216 §3. A WAI playlist is offered only to a sink that recognizes its codecs value from its own capability list, and a packager MUST write a classical master that names nothing of WAI.
-
A browser's media-capabilities query answers only for the browser's own decoders, and MUST NOT be used to decide whether a
wai1.*value can be decoded (http-delivery §4.3). -
Each class's WAI playlist MUST name its class and its stage view.
-
A variant lists no bases without a capability in common (http-delivery §4.2,
codecs_no_common_capability). A class's HTTP view lists a piece only where the piece's base is of the component's default base capability (§5.7, its first base rung's), and plays the floor for any other piece, as for a piece the class takes as floor only: a still (§5.7) among motion bases is carried over HTTP by the floor. The class's stage view (§8.8) and its MoQ tracks (§8.4) list it. Durations: a piece of d units lastsfloor(d · den · 1000 / num)milliseconds, writtenS.mmm(whole seconds, a full stop, three digits). A target duration is the largestceil(d · den / num). A rate isceil(8 · bytes · num / (d · den))bits per second; a variant'sBANDWIDTHis the largest over the pieces it lists andAVERAGE-BANDWIDTHthe rate of all their bytes over all their durations. A floor variant's bytes per piece are its fragment's; a WAI variant's are the listed bases' and refinements', over the pieces its HTTP view lists (above). Every playlist line ends with a line feed. For component c, of frame sizeW×H, whose floor has codecs string F and maximum frame sizeFW×FH: -
hls/classic/master.m3u8:#EXTM3U,#EXT-X-VERSION:7,#EXT-X-INDEPENDENT-SEGMENTS,#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH,../classic/floor-<c>.m3u8: a URI that names the same playlist from every class's directory, so each class's master differs from this one only by the session-data line, as http-delivery §11.1 and §11.8 require. -
hls/classic/floor-<c>.m3u8:#EXTM3U,#EXT-X-VERSION:7,#EXT-X-TARGETDURATION:<t>,#EXT-X-MEDIA-SEQUENCE:0,#EXT-X-PLAYLIST-TYPE:VOD,#EXT-X-INDEPENDENT-SEGMENTS,#EXT-X-MAP:URI="../../floor/<c>/init.mp4", then per piece#EXTINF:<S.mmm>,and../../floor/<c>/<sha256>.m4s, then#EXT-X-ENDLIST. -
hls/<class>/master.m3u8: the classical master, byte for byte, with, when the class lists a WAI object over HTTP,#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"after#EXT-X-INDEPENDENT-SEGMENTS, and no other change. -
hls/<class>/wai.m3u8, when the class's HTTP view lists a WAI object:#EXTM3U,#EXT-X-VERSION:7,#EXT-X-INDEPENDENT-SEGMENTS,#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE="<class>",#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.stages",URI="../../stages/<class>.json",#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="<union>",X-WAI-FLOOR="<floor>",RESOLUTION=WxH,wai-<c>.m3u8. The union and the floor are http-delivery §4.2's over the objects the class lists. -
hls/<class>/wai-<c>.m3u8: the floor playlist's lines without#EXT-X-MAP, adjacent to it (http-delivery §11.2); per piece,#EXT-X-GAPwhen the class's HTTP view lists nothing of it (a floor-only piece, or one whose base is not of the default base capability),#EXTINF:<S.mmm>,, each refinement the class lists, in entry order, as#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>", then its base as../../objects/<hash>.wai#n=<byte_len>: a layer line follows its segment'sEXTINF(http-delivery §10.4).
Every HLS file above and every MPD below is a rendering of http-delivery's (Appendix G for a media playlist): the reference packager writes them with the binding's one HLS and one DASH renderer.
DASH, the same as two MPDs (http-delivery §11.1), each
<?xml version="1.0" encoding="UTF-8"?>, then
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT<S.mmm>S" minBufferTime="PT<t>S">
over the component's whole duration and its target duration, <Period id="p0" start="PT0S">,
one AdaptationSet, </Period> and </MPD>, indented by two spaces a level:
dash/classic.mpd:<AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="F" width="FW" height="FH" segmentAlignment="true" startWithSAP="1">holding<Representation id="<c>-floor" bandwidth="<b>">with<SegmentList timescale="<num>">:<Initialization sourceURL="../floor/<c>/init.mp4"/>, aSegmentTimelineof one<S t="<(start − first start)·den>" d="<d·den>"/>per piece, and one<SegmentURL media="../floor/<c>/<sha256>.m4s"/>per piece.dash/<class>.mpd, when the class's HTTP view lists a WAI object:<AdaptationSet id="1" contentType="video" mimeType="application/wai" codecs="<union>" width="W" height="H">holding<EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>,<SupplementalProperty>s of schemestag:transaction.science,2026:wai/floor(value the floor),tag:transaction.science,2026:wai/class(the class) andtag:transaction.science,2026:wai/stages(../stages/<class>.json), then per layer k up to the highest the class lists, aRepresentationwith attributesid="<c>-l<k>", for k > 0dependencyId="<c>-l<k−1>", andbandwidththe peak rate of its own layer's objects over the pieces it lists, whoseSegmentListhas a timeline entry and aSegmentURL media="../objects/<hash>.wai"for each piece the class lists to layer k (a gap where it does not).
The media type application/wai is proposed, not registered
(http-delivery §3). This revision writes the catalogs of a package
of one component; how the variants and groups of several components are written is
defined with the packing of several components.
8.4 MoQ. moq/catalog.json is RFC 8785 canonical JSON and a line feed:
{"tracks": [...], "version": 1}. A component's bases and refinements are a stage set of
moq-streaming-format §4.1, one track per (layer, deadline),
by layer, then by deadline ascending, each written as that section's entry: namespace
the policy's asset_id; name <c>/l<k>, or <c>/l<k>@<deadline> for a refinement
whose deadline is not defaults.deadline_ms; packaging "wai"; isLive false;
role video; renderGroup 1; wai_staged with set the component's id, layer k
and lead_ms null (an on-demand package has no live schedule); wai_capabilities its
entries' capabilities by first appearance (a still among them on the base track, §5.7);
wai_determinism the weakest tier of its entries, absent when that is none; and
receipts declared: wai_receipts true, wai_receipt_alias the first eight bytes of
BLAKE3("wai:pack-receipt-alias\x01" ‖ namespace ‖ 0x00 ‖ name) as a little-endian
integer, less than 2^53 (so two packages published under one key keep apart chains),
and wai_receipts_track <name>/receipts. A track depends on every track that
carries an entry below one of its own entries, down the chain to the base, in track
order: a track of each layer below it, none at or above it, and never one the catalog
does not hold. A sink receives every track a track depends on (moq-streaming-format
§4.1), so a dependency is all of them. A refinement track carries
wai_delivery_timeout_ms (§8.6), a catalog field this extension defines under the wai_
prefix of moq-streaming-format §4. The base track has altGroup 1 when the component has
a floor: the floor is a track <c>/floor of packaging "cmaf", in the same namespace,
with altGroup 1, renderGroup 1, codec, width and height, and a sink that
selects the floor subscribes to no layer track. The receipts tracks follow, one per media
track in its order, each {"isLive": false, "name": "<name>/receipts", "namespace", "packaging": "wai-receipts"}. The catalog passes moq-streaming-format §4.1's check, and a
packager writes no other. A package holds no receipt: the publisher signs each track's
groups (a group a piece) on that track's chain as it publishes them (jwp-receipts §2.4.1;
§8.9 simulates it).
8.5 Priority. The base track's publisher priority is 64 and layer k's
64 + 32·k: a refinement track's value is strictly greater than that of every track it
depends on, so it is delivered after them (MOQT: a lower value is delivered first), and
moq-streaming-format §4.1's rule holds; a receipts track's is its media track's. The
priority is MOQT's (DEFAULT_PUBLISHER_PRIORITY), set by the publisher, not a catalog
field.
8.6 Deadline. An entry's deadline_ms is the longest a refinement may arrive
after the base of its piece was published and still be presented; the rules' or
defaults.deadline_ms. A refinement track carries DELIVERY_TIMEOUT
deadline_ms − live.layer_lag_ms[k] (§3.4 has refused a policy where that is below 1).
Entries of one layer with different deadlines travel on different tracks. A relay may
drop a late refinement (MOQT §5.2), so a refinement never delays the base.
8.7 Presentation. A sink presents piece p at the highest prefix whose objects all arrived and verified by p's presentation instant, and MUST NOT delay the base to wait for a refinement. What the receipts of a dropped object say is jwp-receipts' to define, not this extension's.
8.8 Stage views. stages/<class>.json is RFC 8785 canonical JSON and a line feed:
{"class": "<class>", "components": {"<c>": {"media_playlist": "../hls/<class>/wai-<c>.m3u8", "pieces": [...]}}, "policy_digest": "<hex>"}, with
media_playlist only when the class's HTTP view lists a WAI object (§8.3), and each piece
{"dur", "p", "start", "stages": [...]} with "floor_only": true when the class takes
it as floor only (and stages empty), and each listed stage {"bytes", "h", "layer"},
a refinement's with "refines" (the content hash of the entry it refines) and
"deadline_ms".
8.9 Delivery, simulated. How a package's objects reach a subscriber over a link is
simulated by staged-measurement's delivery simulator (staged-measurement
§10, Appendix B); its figures are labelled simulated, with their plan, and are never a
measurement of a network. The reference packager's delivery states the model and the
plan, and reads the outcome:
- Model. Each object a class lists of each piece (§8.1; a floor-only piece gives none),
on the component's frames as units (a frame of a whole number of microseconds): base the
piece, its layer, units the piece's frames, bytes the whole envelope, and its capture
time the instant it is published. A live packer (§9.2) publishes a piece's base when the
frame that decides the piece's end has arrived (the next piece's first frame plus
lookahead_frames, or the source's last frame, §4.3; frame f arriving atf · frame_us), plus the encoder's delay, and layer klayer_lag_ms[k]after its base. Each refinement takes its track's delivery timeout,deadline_ms − layer_lag_ms[k]milliseconds (§8.6), as its own (staged-measurement Appendix B). - Plan. Live; the link's round trip, rate and chunk; strict priority, which is §8.5's order; each (piece, layer) a subgroup of its own; no base timeout; the subscriber's layer cap (moq-streaming-format §4.1, Capping); one decode worker, bases first, each decode 1 µs (the simulation is of the link); playout from the start-up, and a refinement that arrives after its piece's instant discarded (§8.7).
- Outcome. Per piece, at its first frame's presentation instant (the start-up plus its start): the highest layer presented by then, or none where its base arrived later; its base's arrival; the layers dropped or abandoned. A class that lists nothing of any piece sends nothing.
Under §8.5's priorities a refinement delays a base by at most one chunk and one decode
(staged-measurement §10.4). The publisher seals each track's group with a group receipt on
the track's chain (wai_receipt_alias, §8.4); a subscriber accepts a delivered object by
its Merkle proof, and holds a dropped one by inclusion alone, never as delivered
(jwp-receipts §4.4).
9. Package receipt
9.1 A version-2 package receipt binds the asset id; the policy digest; its parent
kind and parent receipt hash; per component its id, media class, index digest and stage
root; the entry, distinct-object, referenced-byte and stored-byte counts, which a
verifier recomputes from the indexes; the packager builtin:<crate>@<version>; the
energy figure with its class, or the unmetered form; the grant; and the signer. Its
signing payload, big-endian:
labelled_domain("wai:pack-receipt\x01", label)
u32 len ‖ asset_id ‖ policy_digest[32]
u8 parent_kind (0 none, 1 live-window, 2 re-pack) ‖ [parent_receipt_hash[32] if not 0]
u32 n ‖ n × ( u32 len ‖ id ‖ u8 media_class ‖ index_digest[32] ‖ stage_root[32] )
u64 total_entries ‖ u64 unique_objects ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_units
u32 len ‖ packager
u64 joules_micro ‖ write_label(label)
grant_hash[32] ‖ u32 len ‖ capability ‖ u64 joule_ceiling_micro ‖ u8 0 | u8 1 ‖ u64 funds_ceiling
signer_pubkey[32] ‖ u32 len ‖ signer_id
receipt_hash = BLAKE3("wai:video-package-receipt-id\x01" ‖ payload ‖ sig)
labelled_domain and write_label are energy-measurement
§2.8's: unmetered, the domain as written and no label; metered, its final byte \x02
and the class's signed encoding. total_entries counts every entry; unique_objects
the distinct content hashes over every component; total_bytes the entries'
byte_len; stored_bytes the byte_len of each content hash's first entry;
work_units equals total_entries. A receipt whose content hash comes at two lengths
has no counts. The signature is Ed25519 by signer_pubkey, verified as SPEC §7
Signatures requires; a signature or a key that rule refuses is receipt-signature.
Its JSON form, package.receipt.json, is RFC 8785 canonical, hexadecimal lowercase,
integers as numbers no greater than 2^53 − 1 (a packager refuses to seal anything
else, receipt-json): asset_id; components, each {"id", "index": "index-<id>.wsix", "index_digest", "media_class", "stage_root"}; grant
{"capability", "funds_ceiling", "grant_hash", "joule_ceiling_micro"} with null for
no ceiling (a joule ceiling of 2^64 − 1); joules_micro; kind "package";
packager; parent_kind (none, live-window or re-pack); parent_receipt_hash
(null for none); policy_digest; receipt_hash; sig; signer_id;
signer_pubkey; stored_bytes; total_bytes; total_entries; unique_objects;
version 2; work_units; energy_provenance, the class's label (energy-measurement
§2.7) or null when unmetered; and the class's evidence members exactly as its class
carries them. Every member is present, with null where it has no value
(parent_receipt_hash, energy_provenance, a grant's ceilings) and nowhere else.
A reader refuses, before anything else, JSON that is not RFC 8259 or that names a member
twice in one object, at any depth (receipt-json). It then reads "version": absent or
1 is a version-1 receipt (§9.3), 2 a version-2 one, anything else receipt-version. It
then refuses (receipt-json) a member it does not define or one missing, null where
the member has a value, a value of another form (a number that is not an integer of its
range, -0 among them; a string that is not Unicode scalar values), evidence other than
exactly what the class carries, a class label energy-measurement §2.7 refuses, a parent
kind and hash that disagree, an index other than index-<id>.wsix, and a
receipt_hash that is not the receipt's. A reader does not require the canonical form;
a writer writes it, and a package's package.receipt.json must be it (§11.3).
A verifier of a receipt against its indexes, in its components' order, refuses, the
first that applies: no component, or another number of indexes than components, a live
window's receipt included (receipt-index-count); an index that breaks §6 (its
reason); one that does not hash to its index_digest (receipt-index-digest), has
another stage root (receipt-stage-root), names another component or media class
(receipt-component) or another policy digest (receipt-policy-digest); two components
of one id (receipt-component); other counts, or a content hash at two lengths in any of the
indexes (receipt-counts); a parent kind that does not
match the indexes' prev_index_digest, which is zero in every index for none and a
re-pack and non-zero in every one for a live window (receipt-parent-kind); an energy
figure and class that do not cohere, or a legacy label (receipt-energy); a signature
that does not verify (receipt-signature).
9.2 Live windows chain by the parent link with parent kind live-window. A re-pack
of an earlier package MAY name it with parent kind re-pack.
The reference live packer (wai-pack live, wai_pack::live) takes a source a frame at a
time, cuts it with the segmenter of §4.3, which decides a frame once lookahead_frames
more have arrived, and codes each piece as it closes with the coder the on-demand packer
uses (§5.4–§5.7). So a live and an on-demand pack of one source under one policy write
the same plan and the same bases and refinements, byte for byte. It writes no floor: a
supplied floor is made from the whole source (§7.3), so it is the on-demand package's.
Its pieces are gathered into windows: each a stage index of its pieces' bases and
refinements (first_piece and first_entry continuing the chain, prev_index_digest
the previous window's index digest, zero for the first) sealed by a version-2 receipt
whose parent is the previous window's (live-window; none for the first). A piece's
objects are published when the frame that decides its end has arrived: its base then,
and layer k layer_lag_ms[k] after it (§8.6, §8.9).
It writes as it goes: each object when it is published, and each window's index and
then its receipt when the window is sealed, every file whole (written beside its place
and renamed into it); the plan file and LISTING at the end of the source. It keeps no
record per file of the session: each file's listing line goes to a hidden file beside
the package as the file is written, and is sorted into LISTING at the end (the one
moment it holds a line per file); an object already written is known by its file; an
object's bytes are not held once written. What it holds is what no window has sealed
yet: those pieces' entry fields and the content hash of each object they published
first. So with windows of n pieces that is at most n pieces', and with one window
over the whole source (the default) every piece's until the end. Besides those it holds
the frames of the piece being cut and each piece's first frame, eight bytes a piece, for
the plan file. A source that fails before its end (a short frame, a frame of another
size) leaves every sealed window and the objects it lists, removes the objects only
unsealed pieces published, writes no plan file or LISTING, and is an error that names
what was kept.
9.3 A version-1 receipt (wai.video.package's segment map) keeps its bytes and
still verifies. Its JSON form is RFC 8785 canonical with version 1: asset_id,
energy_provenance (null when unmetered) with its evidence, grant, joules_micro,
kind "package", parent_receipt_hash, receipt_hash, segment_map_root,
segments (each {"byte_len", "content_hash", "duration_ms", "index"}), sig,
signer_id, signer_pubkey, stored_bytes, total_bytes, total_segments,
unique_segments, work_segment_ops. A reader reads one without version as version 1,
and, because version-1 receipts were issued before this form, keeps them verifying:
energy_provenance may be absent (read as unmetered), an evidence member written null
is absent, and an on-chip counter's filtering, absent, reads as unknown. It refuses a
member named twice as version 2's reader does.
Its signing payload, big-endian, is
"wai:video-package-receipt\x01"
u32 len ‖ asset_id ‖ segment_map_root[32]
u64 total_segments ‖ u64 unique_segments ‖ u64 total_bytes ‖ u64 stored_bytes ‖ u64 work_segment_ops
u64 joules_micro ‖ class (u8 0 when unmetered, else the class's signed encoding)
the grant as §9.1 writes it
u8 0 | u8 1 ‖ parent_receipt_hash[32]
u64 n ‖ n × BLAKE3("wai:video-package-seg\x01" ‖ u64 index ‖ content_hash[32] ‖ u64 duration_ms ‖ u64 byte_len)
signer_pubkey[32] ‖ u32 len ‖ signer_id
segment_map_root is the jwp-receipts §2.2 Merkle root over those segment leaves, the
counts are the segments' (the bytes of one copy per content hash for stored_bytes), and
receipt_hash is version 2's.
Both versions share the identity domain, so the figure profile
wai:video-package-receipt-id (energy-measurement §6.1)
covers both; a version-2 receipt's figure is read only with the indexes it verifies
against.
9.4 Where the energy figure is not the unmetered form, the packager MUST publish
a measurement claim about it (jwp-receipts §2.4.8,
energy-measurement §6) as
claims/<receipt_hash>.measurement.json: subject the receipt, figure profile
wai:video-package-receipt-id, signed with the package key and checked against the
receipt before it is written. A verifier refuses a metered package without it
(measurement-claim-missing) or with one that does not verify and check against the
receipt (measurement-claim-invalid).
10. Royalty-free packaging
A packager run with royalty_free_only = true MUST refuse to emit any object whose
capability the registry does not classify as royalty-free, including one it does not
classify at all (§3.6, rule 5). A package receipt says nothing about patents.
royalty_free_only records the registry class the packager read, "licensed
royalty-free by its standards body"; it is not a freedom-to-operate opinion.
11. Package, re-pack equivalence and verification
11.1 The package is a directory of these files and no other file or directory (every directory below it holds one of them):
policy.toml policy.json plan-<c>.txt index-<c>.wsix package.receipt.json
objects/<content hash>.wai …
floor/<c>/init.mp4 floor/<c>/<sha256>.m4s …
hls/classic/… hls/<class>/… dash/… moq/catalog.json stages/<class>.json
claims/<receipt_hash>.measurement.json (a metered package)
LISTING
policy.toml is the policy as given; policy.json its effective canonical JSON
(§3.2). LISTING is the listing (SPEC §3.1) of every other file, and its SHA-256 is the
pack digest. A component or class id that cannot be one path segment of a listing,
two class ids equal once A–Z are mapped to a–z, and a class named classic in
any case (ids name paths, and paths fold case) are refused (pack-id).
11.2 Re-pack equivalence. Given the same source bytes, effective policy, supplied floor, signing key and packager identity, a conforming reference packager MUST produce byte-identical package files on every machine, except the energy figure and measurement claim of a metered package and floor objects §7.5 covers. A verifier MAY pack again and compare listings. A packager whose identity differs is not held to this.
11.3 Verification. A verifier of a package, without its sources, applies these checks in this order and refuses at the first that fails; a check that reads files a failed check names is not applied:
- Every file below the package's directory, read strictly — a name beginning with
.and a*.sha256file included, as a listing (SPEC §3.1) would not skip them — is a regular file whose path a listing can carry; a symbolic link, a directory that cannot be read or holds nothing, or anything else is refused (unexpected-file). LISTINGis present (listing-missing) and is the listing of every other file (listing-mismatch).package.receipt.jsonreads as a version-2 receipt (§9.1:receipt-json,receipt-version) and is that receipt's canonical form, byte for byte (receipt-json).policy.jsonhashes to the receipt's policy digest (policy-digest-mismatch);policy.tomlis a policy (its own reason, §3.2–§3.4);policy.jsonis its effective canonical JSON,royalty_free_onlyforced on wherepolicy.jsonsaystrue(policy-json-mismatch; an override never turns it off, §3.2, so apolicy.jsonthat saysfalseover apolicy.tomlthat saystrueis refused here); its ids can name files (pack-id, §11.1); and under that effective policy it asks for nothing a packager may not emit (§3.6's reason; underroyalty_free_only, a patent-pool floor islicense-refused).- The receipt's components are the policy's, by id, in order, each of the policy's
media (
policy-component-mismatch), so a package is never a receipt over fewer components than its policy has; each index is present (index-missing); the receipt verifies against them (§9.1, the index rules of §6 among its reasons). - Per component, in order:
- the index against the policy (
index-policy-mismatch): its timebase and media class are the component's; its string table is the component's id, the class ids in policy order, then each capability at its first use; its class records are the policy's classes, in order, with theirmax_layer, floor-only flag andmax_kbps; every piece's kind is one a video piece has; its rules and moments are §8.1's from its kind and span; its entries are the rungs of its ladder (§5.7), by role, capability and layer: its base, each refinement in layer order, each floor; each refinement's deadline is its plan's (§8.6); and its class plan is §8.1's from its entries; - the plan file holds the index's starts and its digest is the header's
(
plan-mismatch); - every entry's object is present (
object-missing) and keeps the object rules of §6.3, its counted bytes included; - before any frame is decoded, each piece's floor object, where the policy has a floor
rung and the object's payload reads as §7.2's framing, holds one unit per frame of
its piece (
floor-object-mismatch); - every base and refinement, in entry order: a base decodes by its capability (a
still is a PNG §5.7 reads, its one frame held for the piece), or it is
prefix-digest; its manifest is the one §5.2 states for its piece's frames and its frame size (object-manifest); a refinement's manifest is §5.4's (object-manifest), and it applies in layer order, to every frame of the stage below it, by a staged-delivery §4 sink under the reference tools' cost limit and chain budget, or it isprefix-digest; a base that does not decode, or a layer that does not apply, ends the check; - then every decode-equivalent prefix decodes to its
prefix_digest(prefix-digest); - when all of that holds, the floor: per piece, one floor object of the floor rung's
capability whose renderer, settings, framing header, unit count and target are
§7.2's (
floor-object-mismatch) and which presents the piece's base (floor-motif-mismatch); its units each one temporal unit holding a frame, the first of the first piece carrying a sequence header, every sequence header the first's bytes, every shown key frame carrying one (floor-av1-malformed); each piece opening on a shown key frame, and no other unit being one unless the policy allows extra key frames (floor-keyframe-mismatch); and the CMAF files are the ones its temporal units give (§7.6,floor-cmaf-mismatch); - then the catalogs are the ones the index, the objects and the floor give, and an
index no catalog can be written for (a start past
2^53 − 1) is refused (catalog-mismatch).
- the index against the policy (
- A metered package's measurement claim (§9.4:
measurement-claim-missing,measurement-claim-invalid); an object file no entry names (object-unlisted); a catalog file past the ones given (catalog-unexpected); any other file (unexpected-file).
11.4 What this revision packs. The reference packager packs one video component:
bases of wai.video.int_motion (§5.5, §5.6) and stills of wai.image.png (§5.7);
refinements of wai.video.int_refine over an int_motion base (§5.4); each piece by its
ladder, a rule's ladder subset included (§5.7); and a supplied AV1 floor (§7.3). Its live
packer packs the same pieces in windows, without a floor (§9.2). It refuses by name,
before writing anything, what it does not pack yet: another medium
(pack-media-unsupported), a floor to encode (pack-floor-encode-unsupported), and a rung
of a capability it does not encode, or a refinement over a base it does not refine
(pack-capability-unsupported:<cap>, every one naming the capability of the rung it does
not pack); and a component with no source (pack-source-missing) or whose source's frame
rate is not its timebase (pack-source-rate). Where its own work fails it
refuses by name too, and writes nothing: an encode the encoder refuses, such as a piece
past the samples it holds, or an object it wrote that does not count (pack-encode); a
piece past 2^32 − 1 units (pack-piece-length); catalogs it cannot write, the case
§11.3 refuses as catalog-mismatch (pack-catalog); a meter that was asked for and
gives no figure, a span too short to reach a microjoule or a figure past 2^64 − 1
(pack-meter, never an unmetered package in its place); a measurement claim it cannot
write (pack-measurement-claim); and a listing it cannot write (pack-listing). It
writes a package only into a directory that is new or holds nothing, and refuses any
other before it packs (pack-out-not-empty): written among another package's files, it
would leave behind the ones it does not write.
12. Reasons
| Reason | Where |
|---|---|
policy-toml (not UTF-8, a byte-order mark, not TOML 1.0, nesting past 32), policy-float, policy-datetime, policy-negative, policy-integer-range |
§3.2 |
policy-unknown-key, policy-missing-key, policy-type, policy-value, policy-schema |
§3.3 |
policy-too-many-components, policy-too-many-rungs, policy-too-many-classes, policy-too-many-rules, policy-too-many-moments, policy-duplicate-id, policy-lead, policy-open-gop, policy-piece-bounds, policy-ladder-component, policy-refines-role, policy-dangling-refines, policy-refines-cycle, policy-refines-base, policy-refines-branch, policy-no-base, policy-class-layer, policy-rule-ladder, policy-layer-lag, policy-too-many-deadlines, policy-deadline-unreachable |
§3.4 |
capability-unknown, capability-not-payload, capability-candidate, capability-role, license-unclassified, license-refused, each naming the capability (and the licence label) |
§3.6 |
y4m-header, y4m-interlaced, y4m-colorspace, y4m-size-change, y4m-truncated, wav-format, wav-truncated |
§4.7 |
index-magic, index-version, index-truncated, index-reserved, index-media-class, index-role, index-tier, index-string, index-trailing |
§6.2 |
index-timebase, window-start, piece-order, piece-entries, piece-kind, entry-flags, entry-order, layer-role, refines-outside-window, refines-mismatch, layer-gap, tier-entropy-passthrough, tier-weakest, tier-mismatch, digest-without-tier, deadline-role, floor-derived-from, dup-length, dup-flag, class-plan, class-flags |
§6.3, §6.4 |
object-length, object-hash, non-envelope-entry, object-lengths, object-capability, object-refines, object-derived-from |
§6.3 |
floor-missing, floor-listing-mismatch, floor-stream-missing, floor-av1-malformed, floor-frame-count, floor-keyframe-mismatch |
§7.1, §7.3 |
floor-object-mismatch, floor-motif-mismatch, floor-av1-malformed, floor-keyframe-mismatch, floor-cmaf-mismatch, against a package |
§11.3 |
receipt-json, receipt-version, receipt-index-count, receipt-index-digest, receipt-stage-root, receipt-component, receipt-policy-digest, receipt-counts, receipt-parent-kind, receipt-energy, receipt-signature |
§9.1 |
measurement-claim-missing, measurement-claim-invalid |
§9.4 |
unexpected-file, listing-missing, listing-mismatch, policy-digest-mismatch, policy-json-mismatch, policy-component-mismatch, index-missing, index-policy-mismatch, plan-mismatch, object-missing, object-manifest, prefix-digest, catalog-mismatch, catalog-unexpected, object-unlisted |
§11.3 |
pack-id, pack-source-missing, pack-source-rate, pack-media-unsupported, pack-floor-encode-unsupported, pack-capability-unsupported (naming the capability) |
§11.1, §11.4 |
pack-encode, pack-piece-length, pack-catalog, pack-meter, pack-measurement-claim, pack-listing, pack-out-not-empty |
§11.4 |