### HTTP delivery (Draft)

- **The extension** [http-delivery](extensions/http-delivery.md) binds envelopes to
  HTTP, with no envelope byte and no manifest member changed:
  - the proposed media type `application/wai`, not registered;
  - `codecs` elements `wai1.`/`wai2.` that list the exhaustive union of the
    capabilities a variant may dispatch to or apply, with the floor every object of it
    offers carried separately (`X-WAI-FLOOR`, a DASH `SupplementalProperty`);
  - objects named by their receipt content hash (`<cid>.wai`), immutable, with no
    content coding and no `Vary`, and exactly one envelope each;
  - the `WHP1` head proof: the BLAKE3 chaining values that prove an envelope's head
    against its content hash without the rest of the object;
  - verified range fetch of one `WAI2` component that picks as whole-envelope
    selection does, and falls back to the whole object when a pick has no digest, a
    digest fails, or the plan needs most of the object;
  - receipt, key, grant, parameter-set and playlist sidecars; cache rules; priorities,
    deadlines and the carriage of enhancement companions, refinement components and
    layer objects ([staged-delivery](extensions/staged-delivery.md));
  - HLS and DASH adjacency for on-demand, live and low-latency live; a capability
    probe that never asks a user agent about a WAI element.
- **jwp-receipts**: §3 step 4 accepts the content hash a verified head proof
  establishes for an object fetched in part, and checks a receipt about a component's
  exploded envelope over the envelope the verifier builds. New §4.4 (objects not
  delivered) and §4.5 (an HTTP cache is not a relay). §1 and §7.9 point to the HTTP
  locations. moq-streaming-format §6 names the HTTP binding.
- **SPEC**: §2 and §7.1 name the HTTP object and the `WAI2` head; the
  `wai.video.manifest` row admits `render` and `extras` on a playlist receipt.
- **The registry's wire identifiers.** `codecs::registry::WIRE_IDENTIFIERS` lists the
  names the container, the two delivery bindings and key discovery put on the wire
  outside a manifest, each with its kind, defining section and status; `wai registry
  export` writes them to `registry/capabilities.json` (`wire_identifiers`) and the table
  in `REGISTRY.md`, and a library test checks each against the section it names.
- **The reference binding** (`wai-rs`, feature `http`): `http_binding::{head, head_proof,
  plan, session, codecs, origin}` — the head and §5.1, the `WHP1` proof built and checked
  with BLAKE3's tree, the root checked before any covered byte is read as a head; plans by
  priority class; a sans-IO fetch session with §7.1's response checks, component digests,
  the whole-object fallback and refinement deadlines; the `codecs` grammar, union, floor
  and support; an origin's headers and Range answers. `container::select_entries` is SPEC
  §7.1's walk with the digest test passed in, which `WaiMulti::pick` and a client holding
  only the head both run, and `companion_indices` / `refinement_indices` bind by entry.
  The `wai` tool gains `wai http publish|codecs|origin|vectors|bench`.
- **The corpus** [`http-conformance/`](http-conformance/): objects with their head proofs
  and ETag sidecars (a pinned learned image beside AVIF, JPEG XL and PNG of the one
  picture; AV1 at two rates with HDR10+ bound by id; a missing and a wrong digest; an AVC
  base with an LCEVC enhancement; a segment sequence; Appendix D's envelope; a head of
  several chunks) and cases for §4–§7. `wai_http_vectors verify` and the independent
  `http_verify.py` (standard library, written from the text, checked first against
  published BLAKE3 vectors) agree on every case. Falsification patches and Python
  mutations each must be caught, and a differential checks wai-rs's proofs of random
  envelopes in Python (`.github/workflows/wai-http.yml`).
- **Cache conformance** [`cache-conformance/`](cache-conformance/): a driver for §9's
  rules, run against the reference origin (which must pass), against four
  negative-control proxies (which must each be caught) and against labelled caches where
  an environment provides them; reports name a cache by label, version and configuration
  digest only, and CI uploads them.
- **Playlists and live** (http-delivery §8.6, §10.4, §11, Appendices G and H):
  - `hls_render::render_hls_with` renders on-demand, live and low-latency WAI media
    playlists (media and discontinuity sequences, a fixed target duration, parts with
    their server control, discontinuities, program date-times, `#WAI-LAYER` lines with
    `N` only, the open segment and its preload hint) and, under the legacy parameters
    with no extras, gives `render_hls`'s bytes; the module needs no dependency, and
    `wai_video_manifest` re-exports it under its old paths;
  - the playlist receipt (`ManifestReceipt`) carries `render` and `extras`; its
    signature binds a render digest only when a rendering is present, so every receipt
    sealed before verifies unchanged (one sealed by the earlier tree is pinned), and a
    rendering has one form; `seal_with`, the receipt file's strict JSON form and
    `mr/<manifest_hash>.json` are new, and a live playlist's revisions chain;
  - `http_binding::hls` reads media playlists, holds a WAI playlist adjacent to its
    classical one (durations to the millisecond, rounded half up, read exactly), renders
    the WAI media and multivariant playlists (bandwidths computed from the objects),
    and keeps the classical master to its one session-data line (`classical_changed`);
    `http_binding::dash` writes `wai.mpd` on the classical timeline;
    `http_binding::playlist_receipt` seals and checks playlists and live chains
    (`playlist_receipt_malformed`, `_mismatch`, `_invalid`, `_unchained`;
    `playlist_malformed`);
  - `wai http hls|mpd|seal-playlist|check-playlist`; the origin serves playlists to be
    revalidated on every use;
  - the corpus gains a staged pair with a layer over its second segment (O11), a
    low-latency set of two segments of twelve parts each, every part its own object
    (O12), a player fixture's WAI segments (O13) beside a playable classical
    presentation, the WAI playlists beside their classical ones, their receipts (the
    live revisions chained) and the published classical master. O10 was already the
    layer set, so these took the next numbers.
- **Adaptation** (http-delivery Appendix F): `http_binding::abr`'s four reference
  policies (throughput, buffer, utility over buffer, look-ahead) in integers, the budget
  rule that never uses an unlabelled figure, an entry order the session applies on both
  paths (the selector's order now sees the verified head), and layer depth; seeded
  vectors per policy, the integer log, entry orders and depths, which `http_verify.py`
  reproduces.
- **Playlists and adaptation, after review** (http-delivery §8.6, §11, Appendices F, H
  and I):
  - a live client walks back to the head it holds across revisions it skipped: every
    receipt is also served at `mr/<receipt_hash>.json`, the head fetched again is
    unchanged, and a walk is bounded (`playlist_receipt_parent_missing`,
    `playlist_receipt_walk_exceeded`); no revision lists another cid for an alias or a
    media sequence number an earlier one bound (`playlist_listing_rebound`);
  - Ed25519 is verified strictly (`wai::sig::verify_strict`: a canonical key of large
    order, `S` < L, `R` of large order compared byte for byte), by the playlist receipt
    and `http_verify.py`;
  - DASH adjacency is checked over an XML subset (Appendix I); `wai.mpd` takes the
    classical MPD's period, timescale, `t` and `d` and leaves a gap out; MPD values are
    escaped and checked as XML;
  - adaptation states have stated bounds (`abr_state_out_of_bounds`) and are computed
    exactly within them; under a budget only a labelled option within it is chosen;
  - a gap segment carries no parts or layer lines; the session-data line has one place
    in the classical master; a receipt file holds no member twice, no lone surrogate
    and no number past 2^53 − 1, a grant of no joule ceiling written `null`;
    `seal-playlist --grant`; the origin answers a far-future `_HLS_msn` with 400.
  - an MPD is held as written: timelines in runs, never expanding `@r`, compared run by
    run, at most 256 per document, and an element's attributes read in linear time
    (Appendix I);
  - a budget never raises the policy's choice: with nothing at or below it within the
    budget, option 0 and the budget `unmet` (replacing `floor`);
  - past the walk bound, a revision ahead of the head re-anchors the chain
    (`reanchored`), keeping its bindings, the revisions between unsigned; bindings key a
    line by its URL resolved against the playlist's (RFC 3986), a gap never withdraws a
    listed segment, and a binding is kept while one of the last 256 revisions lists it.
- **What review found, fixed** (http-delivery §3.1, §4, §7, §7.1, §10, §13, Appendix D):
  - a layer object (a `WAI1` whose capability is refinement-only) is never dispatched:
    it is fetched whole at refinement urgency, verified against its cid and handed to
    staged delivery, and joins a union after `+` through `refines.base`
    (`not_a_layer`, `codecs_layer_unbound`, `layer_tag_malformed`). A `WAI1` whose
    capability is a payload is an ordinary object even when it carries `refines`: it is
    dispatched, described by its capability, and `not_a_layer` when listed as a layer.
    A layer is declared only in the `alt` of its own base's capability;
  - a refinement never moves its base: only base-class bytes count toward the 90 %
    rule, a refinement is always requested on its own, a refinement with no digest or a
    failed one is dropped alone on the range path and on the whole path alike, and a
    refused refinement response abandons only it;
  - a client presents a base as soon as it verifies and applies its refinements as they
    arrive; a refinement's deadline runs from that moment on the caller's clock
    (`FetchSession::base_ready`, `tick`), and `wai http bench` reports the time to the
    verified base apart from the time to the complete fetch;
  - after a component mismatch every byte past `covered_len` is discarded before the
    rest is fetched again; a base request never answered is `base_unanswered`;
  - `Content-Range` numbers are ASCII digits only; an element beginning `wai` in any
    case must be a well-formed WAI element; an empty element or a capability named
    twice is malformed; a `WAI1` naming a record beside a payload fallback is described
    by its fallback; a `codecs` value with non-ASCII text, and a head proof whose lengths
    reach the end of 64 bits, are refused rather than panicking;
  - the reference origin caps what it reads of a request head (16 KiB); gives the whole
    head a deadline from the connection's accept (default 10 s, `--read-timeout-ms`),
    answered by 408 however slowly its bytes trickle in; bounds each connection's whole
    life from its accept, writing included (default 60 s, `--max-connection-ms`), so a
    peer reading a large response slowly is cut off then; serves at most 64 connections
    at once (`--max-connections`), answering 503 to one more, with a cap per peer
    address only when `--max-per-peer` is given (it listens on 127.0.0.1, where every
    client has one address, and a default cap would let a few reconnecting clients shut
    the rest out); decodes `/__hits` as bytes; and refuses `\`, `:` and NUL in a path.
    The wire-identifier test reads each name in its section's body.
  - a verified base is decided: a component that verified is never read again, and of a
    refinement's response only the range requested is kept, so a refinement answered
    `200` with the whole object, a byte changed, never reaches the base. Every response
    carries the caller's clock (`FetchSession::on_response(…, now_ms)`), the base is
    stamped with the time of the response that verified it, and the deadline's
    arithmetic saturates. The corpus's `fetch_timed` cases run the session on a clock;
    `http_verify.py` agrees on them.
  The corpus gains a case for each, and `http_verify.py` agrees with wai-rs on them and
  on a seeded differential outside the corpus, in which a refusal must match by its
  code and each family reports how many cases it generated and how many were refused.
- **Receipts, keys, grants and parameter sets** (http-delivery §8.1–§8.5, §10.5):
  - a receipt file `rcpt/<h>.json` holds one group receipt (in the labelled payload) or
    claim in JCS, at most 1 MiB, read as jwp-receipts reads one and refused unless its
    link hash is its name (`receipt_name_mismatch`, `receipt_not_served`,
    `receipt_too_large`); a legacy group, whose name does not cover its parent, is
    refused (`receipt_legacy_group`);
  - an index, at most 4 MiB with at most 256 delivery items and 256 claims
    (`receipt_index_too_large`), is read item by item (`receipt_index_malformed`), each
    receipt file fetched once per index, at most 1024 in all
    (`receipt_fetch_budget_exhausted`) and 64 MiB read in all, each file read no further
    than what remains of that (`receipt_fetch_bytes_exhausted`), each cap with its reason
    stated: each delivery item's group goes through
    jwp-receipts §3 against the client's chains, a missing parent fetched by its link up
    to 64 of them, never more, and never skipped (`parent_not_held`, with where the walk
    stopped), a joining client anchoring instead; its object receipt through steps 4
    and 5 against what the client holds — the hash recomputed over the envelope, the cid
    a head proof established (§8.3), or nothing, which is `inclusion-verified` and gives
    no property reading (§10.5); each claim by its subject against the items that
    passed, its parent fetched by its link, then §3.1 step 6 against what those items
    deliver and the receipts it names (`figure_not_held`, `context_not_held` where the
    client does not hold them; an energy claim naming an OER receipt, which this revision
    does not carry over HTTP, is `context_not_held`), a declaration it names read as
    §8.1 reads every file fetched by name before step 6 compares it, a recovery record
    checked with the client's matchers whether or not it holds the object, and a soft
    binding of another object attesting the ancestor only through §3.2's recovery,
    under the held publisher key that signed it;
  - a key log anchors only from the origin's own location over HTTPS, answered `200`
    with no redirect (`key_log_not_from_origin`), from a retrieval the verifier made
    itself, asking with `Cache-Control: no-cache`; a key log is read to 1 MiB and a longer
    one refused, anchored or cached (`key_log_too_large`); a log through any cache adds revisions
    and never moves the anchor, an older copy reported (`key_log_rolled_back`); a grants
    file is a locator (`grants_file_malformed`); a key status is served for a quarter of
    the head's `status_max_age_s` at most; a module grant is §7.9's form, and a JCP grant
    is carried only by reference in the receipt that names it;
  - a parameter set served by its digest is held only by its pin and §7.9
    (`pin::verify_bytes_with`, `int_payload::resolve`), never by its name.
  - `wai-rs`: `http_binding::receipts` and `http_binding::keys`;
    `jwp_hash::EstablishedHash`, built only from bytes or by a fetch session whose head
    proof verified (`FetchSession::established`); `ObjectReceipt::accept_hash` and
    `verify_against_hash`, `WaiReceiptProperty::check_against_object_hash` and
    `ReceiptChains::verify_admitted_object`, which the existing checks call;
    `energy_figure::HeldObject`, an object receipt's figure for step 6; `wai http
    receipts`, which fetches under a deadline and the caps above, follows no redirect,
    and with `--key-origin` retrieves the key log from the origin itself over TLS
    (the `cli_tls` feature: a TLS client validating against the operating system's
    trust store; SNI and the certificate check name the origin's host, port 443 by
    default, each address the host resolves to tried in turn), every step within one
    deadline however slowly a peer answers; a build
    without it refuses to anchor rather than fetch the log over plaintext, and the
    test-only `--test-plaintext-connect` anchors with its own path, `test-plaintext`,
    wherever the anchor or a confirmation is reported, as anchoring nothing a verifier
    may rely on. A group or
    object receipt whose `kind` is not a string is `wrong_kind`
    (jwp-receipts §3 step 1), as the independent verifier already read it.
  - The corpus serves receipts, indexes, a key log, a status, a grants file and a set;
    `http_receipts.py` checks them for `http_verify.py` through the jwp-receipts, claim
    and key-discovery verifiers, and the differential adds receipt files, indexes,
    grants files and key logs.
- No envelope byte or receipt byte changes for existing material.

