From 019a9f955385f7d68b3ec97e435f82f1c989b958 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 17:36:19 -0700 Subject: [PATCH 1/3] quest: subscribe ranges replace FETCH in lite-07 Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 + quest/m1/quic/scheduler.md | 4 +- quest/m1/subscribe-ranges/README.md | 58 +++++++++++++++++++++++++++++ quest/m1/subscribe-ranges/ietf.md | 21 +++++++++++ quest/m1/subscribe-ranges/js.md | 16 ++++++++ quest/m1/subscribe-ranges/lite.md | 22 +++++++++++ quest/m1/subscribe-ranges/model.md | 22 +++++++++++ quest/m1/track-priority-scope.md | 4 +- 8 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 quest/m1/subscribe-ranges/README.md create mode 100644 quest/m1/subscribe-ranges/ietf.md create mode 100644 quest/m1/subscribe-ranges/js.md create mode 100644 quest/m1/subscribe-ranges/lite.md create mode 100644 quest/m1/subscribe-ranges/model.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 91eebd1ecb..f2959b3237 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -37,6 +37,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Track tail hardening](/quest/m1/track-tail-hardening.md) - Rust and JS wait out a track's tail by the same rules, with the known hang, count, truncation, grace, and memory holes closed - [JS close end](/quest/m1/js-track-close-end.md) - a JS track's clean close ends at its own last group, not a sibling producer's - [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - every stream group in a lite subscription arrives or is dropped by name, and lite-07 drops its stream count for it +- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - on dev, a lite-07 SUBSCRIBE asks for past and live ranges in either order and replaces FETCH; relays fill misses by range, including over moq-transport - [FIN wait expiry](/quest/m1/fin-wait-expiry.md) - a group awaiting its FIN ack still expires and follows priority updates on lite and IETF - [Cross-relay bursts](/quest/m1/cross-relay-bursts.md) - bursty small-group tracks cross two relays without lost groups, unanswered FETCHes, or stalls - [Session death parity](/quest/m1/session-death.md) - a local close ends tracks cleanly in both languages, and JS group readers see the session's error on session death diff --git a/quest/m1/quic/scheduler.md b/quest/m1/quic/scheduler.md index 58bcb30b19..a2d95db74d 100644 --- a/quest/m1/quic/scheduler.md +++ b/quest/m1/quic/scheduler.md @@ -27,8 +27,8 @@ integer. Use byte-accounted deficit round robin, or an equivalent bounded-quantum algorithm, between backlogged groups at equal priority. Round robin by stream count is insufficient because audio, video, and data streams have different -sizes. Within the chosen group, order streams by the MoQ group order: newest -first, fixed by the draft and never inverted. A blocked stream must not consume +sizes. Within the chosen group, order streams by the subscription's group order +(newest first by default). A blocked stream must not consume the group's turn, and opening newer groups must not reset its accumulated fair-share credit. diff --git a/quest/m1/subscribe-ranges/README.md b/quest/m1/subscribe-ranges/README.md new file mode 100644 index 0000000000..901f6629bf --- /dev/null +++ b/quest/m1/subscribe-ranges/README.md @@ -0,0 +1,58 @@ +# Subscribe ranges + +## Goal + +A moq-lite-07 SUBSCRIBE replaces FETCH. One subscription asks for any number +of group ranges, past or live, in ascending or descending order, and the +publisher serves their union from one cursor without sending a group twice. +Every sequence in range either arrives or is named by a SUBSCRIBE_DROP, so +nothing blocks on a cache miss. Relays fill misses upstream with ranges, not +one request per group, including over moq-transport where group numbers may be +sparse. Lite FETCH is removed from lite-07. + +## Plan + +Decided in planning (09-29), after the fetch-span agent (#4558) found that a +relay bounds a sparse FETCH only if the model can request ranges: + +- **Wire (lite-07, unpublished).** SUBSCRIBE carries a list of group ranges + (an open end means live) in place of Group Start/End, plus + `order: asc | desc` (desc, newest first, is today's rule and the default). + Max Age stays as a cap alongside the ranges, since a group's timestamp isn't + always known. SUBSCRIBE_UPDATE replaces the list. Lite FETCH is removed from + lite-07; published versions keep it, answered through the same model. +- **Holes.** SUBSCRIBE_DROP (restored in lite-07 by + [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md)) names every sequence in the + requested ranges the publisher won't deliver: never existed, expired, or + refused upstream. Datagram groups are always dropped in a past range (the datagrams-are-live-only quest, #4551). The + publisher delivers what it has and fills the rest as upstream answers. +- **Model.** `Subscription` gains `ranges` and `order`, mirroring the wire; + `request_groups` as a separate API was rejected. A publisher's + `track::Dynamic` receives range requests for misses, replacing per-group + `requested_group`, and `fetch_group` becomes a one-range subscription or is + removed. This breaks published moq-net APIs, so the line lands on `dev`. +- **moq-transport.** Some deployments use non-contiguous group numbers, so a + relay never probes upstream one group at a time. Upstream, each run of + locally missing groups becomes one standalone range FETCH, capped below the + upstream's live group (which arrives via SUBSCRIBE), so it never blocks. + Fetching the live group through FETCH is what moq-transport forbids today. + Downstream, an IETF FETCH is served from the model's ranges, capped at the + Largest Object. +- The relay half of [fetch-span](/quest/m1/moxygen/README.md) (#4558) moves + here; #4558 lands only the no-handler skip. + +This line owns the end-to-end test: a relay with a sparse cache answers a +wide past range plus live in both orders, over lite-07 and moq-transport, with +every sequence delivered or dropped. + +## Required + +- [Model ranges](/quest/m1/subscribe-ranges/model.md) - `Subscription` carries ranges and order, and `Dynamic` fills misses by range +- [Lite-07 ranges](/quest/m1/subscribe-ranges/lite.md) - the lite-07 wire, Rust publisher and subscriber, and the draft +- [moq-transport ranges](/quest/m1/subscribe-ranges/ietf.md) - range FETCH upstream per missing run, and non-blocking FETCH served downstream +- [JS ranges](/quest/m1/subscribe-ranges/js.md) - `@moq/net` model and lite-07 parity +- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - publishers name every group they won't deliver + +## Related + +- [Track priority scope](/quest/m1/track-priority-scope.md) - group order within a track now follows the subscription diff --git a/quest/m1/subscribe-ranges/ietf.md b/quest/m1/subscribe-ranges/ietf.md new file mode 100644 index 0000000000..08dcd281fc --- /dev/null +++ b/quest/m1/subscribe-ranges/ietf.md @@ -0,0 +1,21 @@ +# [M] moq-transport ranges + +## Goal + +A relay fills past-range misses from a moq-transport upstream with one +standalone FETCH per run of locally missing groups, never including the +upstream's live group, so a sparse track costs a handful of requests. It +serves a downstream IETF FETCH from the model's ranges without blocking on the +live group. + +## Plan + +This absorbs the relay half of fetch-span (#4558) and builds on fetch-fill's +checks (#4544). Receiving a multi-group FETCH stream means several groups on +one stream. Absent sequences between the FETCH's groups become drops, and a +group already cached is discarded as a duplicate. Cap a downstream FETCH at +the Largest Object, as the moq-transport drafts require. + +## Required + +- [Model ranges](/quest/m1/subscribe-ranges/model.md) - the range requests this answers diff --git a/quest/m1/subscribe-ranges/js.md b/quest/m1/subscribe-ranges/js.md new file mode 100644 index 0000000000..2112b13432 --- /dev/null +++ b/quest/m1/subscribe-ranges/js.md @@ -0,0 +1,16 @@ +# [M] JS ranges + +## Goal + +`@moq/net` matches Rust: `Subscription` carries ranges and order, the lite-07 +SUBSCRIBE carries them on the wire, FETCH is gone from lite-07, and +`fetchGroup` becomes a one-range subscription or is removed. + +## Plan + +Mirror the Rust names. The JS FETCH cancel and JS FETCH quests shape the +current `fetchGroup` surface; fold whatever of them is still open into this. + +## Required + +- [Lite-07 ranges](/quest/m1/subscribe-ranges/lite.md) - the wire this speaks diff --git a/quest/m1/subscribe-ranges/lite.md b/quest/m1/subscribe-ranges/lite.md new file mode 100644 index 0000000000..39d0a04719 --- /dev/null +++ b/quest/m1/subscribe-ranges/lite.md @@ -0,0 +1,22 @@ +# [L] Lite-07 ranges + +## Goal + +moq-lite-07's SUBSCRIBE carries group ranges and an order, SUBSCRIBE_UPDATE +replaces them, and FETCH is gone from lite-07. The Rust publisher serves +ranges from the model and drops what it won't deliver, and a relay fills a +lite upstream by subscribing to the missing ranges. + +## Plan + +Update `drafts/draft-lcurley-moq-lite.md` in the same PR: the SUBSCRIBE and +SUBSCRIBE_UPDATE fields, the Group Order section (the order is now a field), +the removal of FETCH, and the lite-07 changelog. Fix the stale "offset by 1" +sentence on Group Start while there. Published versions (lite-03..06) keep +FETCH, answered through the model's ranges. Run `just drafts check` and +`just test interop --all`. + +## Required + +- [Model ranges](/quest/m1/subscribe-ranges/model.md) - the model this serves +- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - lite-07's DROP for holes diff --git a/quest/m1/subscribe-ranges/model.md b/quest/m1/subscribe-ranges/model.md new file mode 100644 index 0000000000..0670631633 --- /dev/null +++ b/quest/m1/subscribe-ranges/model.md @@ -0,0 +1,22 @@ +# [L] Model ranges + +## Goal + +The Rust model's `Subscription` carries a list of group ranges and an order, +aggregated across subscribers. A track serves the union from one cursor +without duplicating a group, in the requested order, and reports every +sequence it won't deliver. A publisher's `track::Dynamic` receives range +requests for misses instead of one request per group. + +## Plan + +See the [line](/quest/m1/subscribe-ranges/README.md) for the decisions. +Guidance: + +- Aggregating several subscribers' ranges and orders onto one upstream + request needs a rule. For example, the union of ranges, and desc wins when + orders conflict. Decide it and write it down. +- Replace `requested_group` and `fetch_group` rather than keep both. Update + every caller (relay, moq-archive, hls, the ladder) in the same PR. +- Max Age caps every range. +- Benchmark range count and span as separate axes (AGENTS.md fan-out rule). diff --git a/quest/m1/track-priority-scope.md b/quest/m1/track-priority-scope.md index 4e4552e432..ff15c46b39 100644 --- a/quest/m1/track-priority-scope.md +++ b/quest/m1/track-priority-scope.md @@ -22,8 +22,8 @@ Where the model stands today, all in `rs/moq-net`: packs track priority and the group's position within its own subscription into one send order (`js/net/src/lite/priority.ts`), so two tracks at equal priority interleave rather than one draining first. -- The draft fixes group order within a track: newest first, with no wire field - to invert it (`drafts/draft-lcurley-moq-lite.md`, Prioritization). +- Group order within a track follows the subscription's `order` (newest first + by default; see [Subscribe ranges](/quest/m1/subscribe-ranges/README.md)). - A relay forwards the max of its downstream subscriber priorities upstream (`model/subscription.rs`, `lite/subscriber.rs`), never the publisher's track priority, so one viewer asking for 255 raises that track above every other From ecbd304f264fe4fdc31b899f4f08fa7793aee9b8 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 17:48:17 -0700 Subject: [PATCH 2/3] quest(subscribe-ranges): FETCH may return the live group's prefix Co-Authored-By: Claude Opus 5.5 --- quest/m1/subscribe-ranges/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quest/m1/subscribe-ranges/README.md b/quest/m1/subscribe-ranges/README.md index 901f6629bf..4c13bea0f3 100644 --- a/quest/m1/subscribe-ranges/README.md +++ b/quest/m1/subscribe-ranges/README.md @@ -35,9 +35,12 @@ relay bounds a sparse FETCH only if the model can request ranges: relay never probes upstream one group at a time. Upstream, each run of locally missing groups becomes one standalone range FETCH, capped below the upstream's live group (which arrives via SUBSCRIBE), so it never blocks. - Fetching the live group through FETCH is what moq-transport forbids today. - Downstream, an IETF FETCH is served from the model's ranges, capped at the - Largest Object. + moq-transport caps a FETCH at the Largest Object, so it can return the live + group's existing prefix but never wait for its future objects. Open: whether + the current joining FETCH for that prefix (a SUBSCRIBE starting mid-group + whose head isn't cached) stays, which today's bridge relies on. Downstream, + an IETF FETCH is served from the model's ranges, capped at the Largest + Object. - The relay half of [fetch-span](/quest/m1/moxygen/README.md) (#4558) moves here; #4558 lands only the no-handler skip. From 7a80c5524032f098832a7a9203352e3009ba9932 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 19:27:08 -0700 Subject: [PATCH 3/3] quest(subscribe-ranges): ranges are frame-precise; the joining FETCH stays Co-Authored-By: Claude Opus 5.5 --- quest/m1/subscribe-ranges/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quest/m1/subscribe-ranges/README.md b/quest/m1/subscribe-ranges/README.md index 4c13bea0f3..fdc8065442 100644 --- a/quest/m1/subscribe-ranges/README.md +++ b/quest/m1/subscribe-ranges/README.md @@ -41,6 +41,9 @@ relay bounds a sparse FETCH only if the model can request ranges: whose head isn't cached) stays, which today's bridge relies on. Downstream, an IETF FETCH is served from the model's ranges, capped at the Largest Object. +- Ranges are frame-precise (`Position`), not whole groups. The IETF joining + FETCH for a mid-group SUBSCRIBE's uncached prefix stays, because today's + bridge relies on it (maintainer, 09-29). - The relay half of [fetch-span](/quest/m1/moxygen/README.md) (#4558) moves here; #4558 lands only the no-handler skip.