diff --git a/quest/m1/README.md b/quest/m1/README.md index 3e620566850..0eed1abb522 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -114,6 +114,7 @@ surface and zero-copy texture import, land before the cut. ## Required - [Resume checked against a model](/quest/m1/resume-model-test.md) - a randomized test checks every resumed reader against a one-track reference model in Rust and JS, and passes before the cut +- [Address groups by timestamp](/quest/m1/time-addressing/README.md) - group start timestamps replace sequence numbers in the moq-net API and lite-07 wire, while old subscribers, old publishers, and published JS clients keep working - [Release audit findings](/quest/m1/release-audit/README.md) - resolve newly identified resource, parser, playback, and interoperability risks before the cut - [Pre-cut audit findings](/quest/m1/cut-audit/README.md) - fix the crashes, hostile-input costs, and correctness bugs the 2026-10-24 audit of main found - [Cancelled moq-transport requests stop](/quest/m1/ietf-request-cancel.md) - a cancelled request releases its state even when the peer withholds credit diff --git a/quest/m1/lite07-finalize.md b/quest/m1/lite07-finalize.md index 922d2dbdf66..0b3bdff98e2 100644 --- a/quest/m1/lite07-finalize.md +++ b/quest/m1/lite07-finalize.md @@ -77,6 +77,9 @@ type for the m2 browser [padding probes](/quest/m2/probe/padding.md) landed in [#6233](https://github.com/moq-dev/moq/pull/6233). The `Time(pts)` range end rides in Subscribe ranges, already Required. +Decided 2026-10-25 (maintainer): addressing groups by timestamp gates the +cut, since it removes group sequence numbers from the lite-07 wire. + Rust's lite-07 varints already carry the full 64 bits the draft specifies, so no codec work waits on the cut. @@ -97,6 +100,7 @@ timestamp bound in [#6168](https://github.com/moq-dev/moq/pull/6168). - [Routes and announces](/quest/m1/cluster-routing/routes.md) - lite-07 carries the route layer: ROUTE per origin node and path-less ANNOUNCE, with the hop list gone - [Drop set](/quest/m1/drop-set.md) - lite-07's SUBSCRIBE_DROP carries `Max Age`, so a widening re-offers groups reported dropped - [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - SUBSCRIBE carries ranges and an order and lite FETCH is gone, in Rust and JS +- [Spike: timestamps replace group sequence numbers](/quest/m1/time-addressing/spike.md) - decides the lite-07 wire without group sequence numbers and adds the implementation quests here - [Announcement takeover](/quest/m1/announce-takeover.md) - ANNOUNCE_TAKEOVER preserves a proven instance under a new independent route ## Related diff --git a/quest/m1/subscribe-ranges/README.md b/quest/m1/subscribe-ranges/README.md index a93b0a48574..44960b76c29 100644 --- a/quest/m1/subscribe-ranges/README.md +++ b/quest/m1/subscribe-ranges/README.md @@ -131,3 +131,4 @@ generations of "start". When ranges land: ## Related - [IETF fill timeout](/quest/m1/ietf-fill-timeout.md) - bounds upstream waiting independently of content age +- [Timestamp spike](/quest/m1/time-addressing/spike.md) - the cut reworks `Absolute(group)` ends and sequence-range drops onto times diff --git a/quest/m1/time-addressing/README.md b/quest/m1/time-addressing/README.md new file mode 100644 index 00000000000..a4030866ad8 --- /dev/null +++ b/quest/m1/time-addressing/README.md @@ -0,0 +1,15 @@ +# Address groups by timestamp + +## Goal + +Groups are addressed by their start timestamp instead of a sequence number, +in the moq-net API (Rust and `@moq/net`) and the lite-07 wire. A reader seeks +or starts from a time, nothing maps between group numbers and time, and no +producer has to remember group numbers. Old subscribers, old publishers, and +published JS clients keep working. VOD, FETCH, and archives need no backwards +compatibility. + +## Required + +- [Spike](/quest/m1/time-addressing/spike.md) - a Rust and JS prototype measures what breaks, and the spike writes the design and the implementation quests for the cut +- [Guide](/quest/m1/time-addressing/guide.md) - a page explaining seek by time, group ends, restarts, strictly increasing starts, and how legacy peers see group IDs diff --git a/quest/m1/time-addressing/guide.md b/quest/m1/time-addressing/guide.md new file mode 100644 index 00000000000..5479cfac736 --- /dev/null +++ b/quest/m1/time-addressing/guide.md @@ -0,0 +1,22 @@ +# [S] Addressing by time guide + +## Goal + +A documentation page explains how applications address groups after the +timestamp cut: seeking and starting from a time, why group starts strictly +increase, how a group ends (whole, incomplete, or aborted), how a restart +mints a new epoch instead of continuing numbers, and what group IDs +lite-01..06 and moq-transport peers see. Publishers and player +authors can follow it without reading the drafts. + +## Plan + +Decided 2026-10-25 (maintainer): a separate page, not only inline doc +updates. Write it against the design the spike records. Link it from +`doc/concept/moq-lite.md` and from the +[Identity and recovery guide](/quest/m1/identity-guide.md), which covers +epochs and restarts. + +## Required + +- [Spike](/quest/m1/time-addressing/spike.md) - the design this page describes diff --git a/quest/m1/time-addressing/spike.md b/quest/m1/time-addressing/spike.md new file mode 100644 index 00000000000..397a9e51f91 --- /dev/null +++ b/quest/m1/time-addressing/spike.md @@ -0,0 +1,120 @@ +# [L] Spike: timestamps replace group sequence numbers + +## Goal + +A throwaway prototype in Rust moq-net and `@moq/net` drops group sequence +numbers from the API and the lite-07 wire, addressing each group by its start +timestamp, and measures what breaks. The spike ends by recording the design +in new implementation quests, adding them to +[Finalize moq-lite-07](/quest/m1/lite07-finalize.md)'s Required list and the +[guide](/quest/m1/time-addressing/guide.md)'s, and deleting itself. The +prototype is not merged. + +## Plan + +Decided 2026-10-25 (maintainer). Why: group numbers need timelines just to +map to and from time, and reusing a group number poisons caches, so resuming +a producer would need per-track memory of its last number. + +- **API and wire.** Sequence numbers leave the moq-net API and the lite-07 + wire, not just the API. This gates the lite-07 cut, since lite-07 is still + wip. +- **A group's address is its first frame's PTS**, known when the group opens, + so the GROUP header and legacy IDs can carry it. Frames inside a group stay + in decode order, and their PTS may go backwards (B-frames). Check that a + group's end and reach use its highest PTS, not its newest frame's + (`rs/moq-net/src/model/group.rs:2022`). +- **Starts strictly increase.** An equal start is refused loudly like a + rewind; today the rewind check allows ties (`track.rs:491`). +- **Three ways a group ends**, mirrored in the API and lite-07: + `finish(end)` sends GROUP_END with an exclusive end timestamp, meaning the + group is whole; `close()` is a bare FIN, meaning an incomplete group whose + frames all arrive (better than a reset, which drops data in flight); and + `abort(err)` resets. So a relay closes an expired or partial group instead of + resetting it (lite draft line 760 changes). GROUP_END maps to + moq-transport's END_OF_GROUP. It replaces hang's empty-frame duration + marker (`rs/moq-mux/src/container/producer.rs:523`), so moq-mux stops + writing empty frames. Evaluate whether a finished group's own end can set + its reach, so expiry stops waiting for a successor. +- **Discontinuity marker groups go away** + (`rs/moq-mux/src/container/producer.rs:650`). A gap is a previous end below + the next start; a missing group is a previous-start link that doesn't match. + Legacy egress synthesizes neither the empty frame nor marker groups: old + players lose end durations and pause markers, which they already tolerate + being absent. +- **Restarts keep minting a new epoch** with fresh media PTS, as today. + Replicas sharing an epoch agree by PTS without coordinating numbers. +- **Every track is timed.** Untimed goes away: the producer stamps an untimed + write with wall-clock time. +- **Gaps.** Each GROUP carries the previous group's start, so a cache or a + reader sees a hole without a round trip. The first group of an epoch has + none, and the gap before it is unknown. +- **Legacy egress** (lite-01..06 and moq-transport): the group ID is the + group's start in the track's timescale, so every relay agrees without a + table. Verify that lite-05/06 receivers and published `@moq/net` and + `@moq/watch` builds accept sparse, large IDs (JS numbers are exact to + 2^53). +- **Legacy ingest.** A timed source keeps its timestamps. An untimed source + (moq-transport without TIMESCALE, lite-01..04) uses the group ID as the + timestamp on a synthetic scale, so relays agree and legacy egress round-trips + the exact ID. Max age means nothing on those tracks. A timed moq-transport + group that starts at or before its predecessor ends the track with an + error. +- **moq-transport mapping.** The Group ID is the start ticks, and the + previous-start link rides in the Prior Group ID Gap property. Object IDs + stay +1 per frame, so no Prior Object ID Gap is sent; each object's PTS + rides in the TIMESTAMP property, and GROUP_END maps to END_OF_GROUP. + Only draft-14 lets a status object carry properties, so only there does + END_OF_GROUP carry the end in its TIMESTAMP; later drafts refuse them + (`rs/moq-net/src/ietf/object.rs:57`), so their END_OF_GROUP stays bare and + the end is lost unless an extension carries it. Keep it LOC compatible: + TIMESTAMP (0x10) and TIMESCALE (0x08) are the MOQ Properties shared with + draft-ietf-moq-loc. Rejected: Object ID as a PTS delta, because B-frames + run backwards in decode order and open-GOP leading frames present before + the group's start. +- **Extensions are optional.** moq-transport may carry the new information + through properties or a negotiated extension, but moq-transport without + them, and lite-01..06, must still work for live playback. Only end + precision and gap detection degrade. +- **Ranges.** [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) + proceeds in parallel; the cut reworks its `Absolute(group)` ends and + sequence-range SUBSCRIBE_DROP onto times. `Relative(n)` can stay, since it + counts groups rather than naming them. +- **Name reuse** landed in + [moq-dev/moq#6780](https://github.com/moq-dev/moq/pull/6780). The cut + removes its sequence half, leaving a re-created track to keep its PTS + moving forward. + +The prototype covers the model, the lite-07 codec, and the legacy mapping in +Rust and JS. Every current role of the sequence needs a replacement or a +reason to drop it: + +- The cache key, the duplicate check, and the hand-out-once record + (`track.rs:318`, `delivered.rs`), plus slot stamps. +- Successor, reach, expiry, the arrival ledger, tail accounting, fills and + misses, and SUBSCRIBE_DROP accounting, which all need a total order and a + visible gap. +- Resume across route flips (`resume.rs`), mid-group `Position` and + `frame_start`, priority order within a subscription, `Demand::wanted`, and + refresh. +- Wire fields: GROUP, SUBSCRIBE and SUBSCRIBE_UPDATE bounds, SUBSCRIBE_START + and its largest, SUBSCRIBE_END, SUBSCRIBE_DROP, datagrams (which share the + group space), and every IETF Location, Filter, FETCH, Largest, and + NEW_GROUP_REQUEST through the legacy mapping. +- Consumers that number groups: hang and moq-mux timelines, HLS/DASH export, + `seek` for GOP-aligned failover in moq-mux and the bindings, HLS import's + discontinuity packing, transcode's 1:1 numbering, archive keys and resume, + relay and CLI `fetch --group`, and player continuity checks in the + Rust and JS container consumers and `js/watch`. +- The E2EE nonce, which binds the group sequence + (`drafts/draft-lcurley-moq-e2ee.md`, `rs/moq-e2ee`). + +The implementation quests also update `doc/concept`, the lite, hang, and E2EE +drafts, every binding per the cross-package sync table, and `MIGRATE.md`. + +Public API: none (prototype). Wire: none (prototype). + +## Related + +- [Lite-05/06 timestamps pass through](/quest/m1/lite-timestamps-through.md) - lite-05/06 tracks become timed, which this builds on +- [Resume checked against a model](/quest/m1/resume-model-test.md) - its reference model is the oracle for resume after the cut