wai: refinement layers over Media over QUIC — a receipts track per media track, recovery, and late join #174
Loading…
Reference in a new issue
No description provided.
Delete branch "wai/w1-6-moq-layers"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refinement layers over Media over QUIC: each layer is its own track with its own receipts, so a subscriber caps layers and receipts together, recovers what a relay lost, and can join mid-stream.
wai_staged({set, layer, lead_ms}).dependsnames a track in the same namespace.wai_receipts_track) and the alias its receipts sign (wai_receipt_alias). MOQT's Track Alias is chosen per hop, so the catalog states the one a receipt signs.staged::moqin wai-rs.staged-conformance/moq/(catalogs and the trace), checked bystaged_verify.py.Checked locally on macOS arm64:
quic;The forge CI runs on this pull request.
🤖 Generated with Claude Code
moq-streaming-format, re-read against draft-ietf-moq-transport-22 and draft-ietf-moq-msf-01 (named in its status note): - §4.1 Staged tracks: one track per layer; wai_staged {set, layer, layers, lead_ms}; MSF isLive, renderGroup, depends, altGroup, temporalId/spatialId rules; a closed, ordered catalog check (staged-malformed, -set-incomplete, -depends, -grouping, -lead, -receipts); one object per group, its own subgroup, on live tracks; priority by MOQT's number order (lower is first); OBJECT_DELIVERY_TIMEOUT no more than the slack to the last unit covered, per object as an object property; timeouts run per hop and are never the lateness rule; capping by subscription; the receipts track (wai-receipts). - §4 gains wai_receipt_alias: MOQT's Track Alias is chosen per session by each publisher on the path (MOQT §3.1.3), so it is not the track_alias a receipt signs past the first hop. - §5: a relay that does not forward an object forwards its receipts; a delivery-gap claim is not added to the publisher's receipts track. - Corrections from the re-read: MSF's packaging values (no 'catalog' value), MSF does not set priorities (MOQT does), MOQT properties and MSF §12. staged-delivery §8, §9, §13, jwp-receipts §4.4 and http-delivery §10.2 and Appendix C point at it. Three wire identifiers join the registry. Co-Authored-By: Claude Opus 5.5 <[email protected]>Review items 2, 3, 4, 5 (catalog and text), 7, 8 and 9: - wai_staged is {set, layer, lead_ms}: a set's extent is its tracks, layers 0..max each with a track, so MSF delta updates and clones change it alone; lead_ms is read only while the set is live (as MSF §5.2.8 treats targetLatency). - renderGroup an integer fitting u64, depends an array of strings, a track's namespace|name unique, one namespace per set, every depends entry a catalog track in its own namespace (namespace inherited per MSF §5.2.2): staged-malformed / staged-depends / staged-grouping. - One receipts track per media track (wai_receipts_track), at its priority, group g carrying the receipts of media group g; an alias shared only by renditions of one chain; every wai_receipt_alias in the catalog an integer compared as a number. - §4.1 text: recovery by FETCH, a stated hold bound with re-anchoring, joining (MOQT-22 replaced Joining FETCH with fill fetch streams: cited so); the conditional form of what a receipts-track subscriber holds; subscriber priority first (§5.1.2); SUBGROUP_DELIVERY_TIMEOUT from FIN to acknowledgement; no datagrams (MSF §6); timeouts capped at 2^62−1; a longer sink latency cannot extend a publisher timeout; a MOQT relay carrying receipts as payload is not a §4 relay (jwp-receipts §4.1 amended); §6 says what §4.1 paraphrases; CMSF-01 pinned. - jwp-receipts §3 step 3: holding, re-anchoring, joining, history behind a labelled anchor, what an anchor does not prove; §3.3 parent_not_recovered; §6.1 anchors. (Implementation follows.) - staged::moq rewritten to match; layer_timeout_ms capped at 2^62−1; the corpus generator's trace check uses checked arithmetic and refuses rather than panics; catalogs.json gains the reviewed type and namespace cases and the delta and on-demand cases; staged_verify.py type-checks before use. catalog_diff.py: 100,000 catalogs, 0 differences, 0 crashes. - joule-moq StagedTrack drops layers. Co-Authored-By: Claude Opus 5.5 <[email protected]>Review item 1 (wai-rs and Python), P-W1: - jwp_delivery::ReceiptChains: with_hold(HoldBound {max_groups, max_age_ms}) holds a parent_not_held group and admits it when its parent arrives; holding past the count, or expire() past the time, refuses every group held for the chain (parent_not_recovered, a new §3.3 reason) and re-anchors it at its next group that passes steps 1-2, the freshness bound and a group_id above the head's. with_join() anchors a chain it holds nothing of at its first such group. Behind a labelled anchor, groups that link back are admitted as history. admit() returns the Admission (Linked, Anchor, History); take_events() reports what became of held groups; anchors() reports every anchor and the group before its gap. Without the options nothing changes, and every existing vector is byte-identical. - jwp-conformance: twelve chain vectors with a recovery input (fetched parent, release order, lost receipt with and without holding, re-anchor and history regained, a re-anchor refused behind its head, join mid-stream / at the start / on a legacy chain / with a stale group), appended after every existing vector; outcomes accept anchor and accept history. jwp_verify.py implements the same from the text; chain_gap_differential.py lets both verifiers recover on half its chains (seeds 1-3, 400 chains each: 0 differ). - Unit tests: a lost receipt stalls without recovery, re-anchors by count and by time, and is regained as history; a late joiner admits from its anchor. - jwp-receipts §3 step 3 History applies behind any labelled anchor; §5.9 lists the recovery vectors; README documents the input and outcomes. Co-Authored-By: Claude Opus 5.5 <[email protected]>Review items 1 (joule-moq) and 10: - ReceiptChains: with_hold(HoldBound {max_groups, max_age_ms}), with_join(), admit() -> Admission (Linked, Anchor, History), expire(), take_events(), anchors(), held(): a held group is admitted when its parent arrives; past the bound the chain's held groups are refused (ChainRefusal::ParentNotRecovered) and it re-anchors at its next group above its head; a joiner anchors at its first group; groups linking back to a labelled anchor are history. Without the options accept() behaves as before. - Tests: a lost receipt stalls without holding and recovers by re-anchoring (by count and by time), the lost group and the refused ones regained as history, a fetched parent releasing what waits on it (P-J2); a late joiner admits from its anchor and back to the chain's start (P-J3). - wai_dispatch.rs: the capability docs name capabilities and standards only, with no evaluative or comparative wording. The names and prose checks pass on every tracked joule-moq file, given by explicit path. - joule-code-conformance.yml: the C1-C4 pack's summary is matched as '^[1-9][0-9]* passed, 0 failed, ' ("10 failed" no longer reads as "0 failed"; an empty pack fails). Co-Authored-By: Claude Opus 5.5 <[email protected]>jwp-receipts §3 step 3: a group the verifier admitted to the chain before, offered again with the same bytes (a legacy group's unsigned parent among them), passes and changes nothing: MOQT can deliver one object twice, by a subscription and a fill (§3.4). ReceiptChains::admit returns Admission::AlreadyAdmitted ("accept again") before verifying; the chain keeps each admitted link with the parent it came with, so a legacy group whose parent differs is not that group and is checked as before. verify_object therefore succeeds for any admitted group, not only the head, without verifying the group again. jwp_verify.py does the same. Two vectors on main change their expected outcome, as the decision requires: chain_head_predecessor_replayed (b0 again after b1: accept again, was chain_break) and chain_legacy_replay_loop (g1 replayed with its parent unchanged: accept again, was chain_break); the rewritten g0 is still refused. New vectors from the review: chain_duplicate_after_the_head_moved and chain_join_anchor_delivered_again. New test: an_admitted_group_verifies_its_objects_after_the_head_moved. Co-Authored-By: Claude Opus 5.5 <[email protected]>