Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
31ecc04
quest: claim quest/m1/subscribe-ranges/dynamic
kixelated Oct 21, 2026
9312958
wip: fills replace group requests in moq-net
kixelated Oct 21, 2026
feb903e
wip: misses judged against the arrival ledger
kixelated Oct 21, 2026
5bfd23d
wip: refusals below a start wait on the feed; miss tests
kixelated Oct 21, 2026
3b400df
wip: callers and moq-ffi/moq-c move to fills
kixelated Oct 21, 2026
867d6ea
wip: bindings serve fills
kixelated Oct 21, 2026
823aca0
wip: migration notes, docs, and quest references
kixelated Oct 21, 2026
9391035
wip: finished copies serve fills; misses end with the feed; lints
kixelated Oct 21, 2026
68cf6de
Merge origin/main into quest/m1/subscribe-ranges/dynamic
kixelated Oct 21, 2026
2b2313b
fix(interop): point the Go client's MaxDelay at a duration
kixelated Oct 21, 2026
e8dec58
Merge remote-tracking branch 'origin/main' into pr-6171
kixelated Oct 21, 2026
6dc3135
fix(net): settle wide fills by run, not group by group
kixelated Oct 21, 2026
3e5ca7d
perf(net): bench fills over readers and runs, and keep a miss run flat
kixelated Oct 21, 2026
5e09300
style(net): rustfmt the fill tests
kixelated Oct 21, 2026
8712148
fix(net): a descending reader turns a queued fill newest first
kixelated Oct 21, 2026
8c342d1
Merge remote-tracking branch 'origin/main' into pr-6171
kixelated Oct 21, 2026
a65c8c6
quest: point the resume-outage links at the PR that finished it
kixelated Oct 21, 2026
6ab7523
fix(net): index a fill's answers so refusing it group by group stays …
kixelated Oct 21, 2026
42af07c
fix(net): a reader from an earlier group widens a queued fill to its …
kixelated Oct 21, 2026
bf30e38
quest: refill groups evicted after a reader judged them
kixelated Oct 21, 2026
f0e2b60
quest: claim archive consumer
kixelated Oct 22, 2026
ff3a197
Merge origin/main into quest/m1/subscribe-ranges/dynamic
kixelated Oct 22, 2026
9fa2979
fix(net): hand filled groups out in the reader's order
kixelated Oct 22, 2026
af91f58
Merge remote-tracking branch 'origin/quest/m1/subscribe-ranges/dynami…
kixelated Oct 22, 2026
074d512
feat(archive): serve an archive through archive::Consumer, replacing …
kixelated Oct 22, 2026
df2d4d6
feat(cli,hls): import archive and HLS tests serve through archive::Co…
kixelated Oct 22, 2026
8335b35
Merge origin/main into quest/m1/subscribe-ranges/dynamic
kixelated Oct 22, 2026
ba5ffbc
fix(net): build moq-net's tests without the async feature
kixelated Oct 22, 2026
3ce4b54
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 22, 2026
a098105
fix(bindings): saturate a peer's max delay instead of wrapping negative
kixelated Oct 22, 2026
738ba58
fix(net): a narrowed reader leaves the rest of its fill
kixelated Oct 22, 2026
1d3aced
fix(net): a fill owes only what its readers still read
kixelated Oct 23, 2026
1674f38
fix(net): a held feed fetch replaces an abandoned one under the limit
kixelated Oct 23, 2026
c9a7d7d
feat(net): fill handlers watch demand for a single group
kixelated Oct 23, 2026
da92a16
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
54e0a6e
fix(net): a SUBSCRIPTION_ENDED copy does not linger
kixelated Oct 23, 2026
83fea78
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
b5d2930
doc(ffi): migrate the generated Go and Dart bindings to fills
kixelated Oct 23, 2026
0016bab
fix(net): a wide fill forgets refusals its readers took
kixelated Oct 23, 2026
95b8f35
fix(net): a descending reader's split miss queues newest first
kixelated Oct 23, 2026
24aadef
fix(net): a reader forgets misses behind its window
kixelated Oct 23, 2026
a8f37e6
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
4a89d3d
fix(interop): the Go finish client passes an optional max delay
kixelated Oct 23, 2026
ddc9717
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
b8f6ceb
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
39dbe44
fix(net): narrowing releases accepted groups; time starts before the …
kixelated Oct 23, 2026
b60b1be
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
f8cfe46
style(net): format the new fill tests
kixelated Oct 23, 2026
30f0fda
fix(net): fill sessions watch an accepted group's own readers
kixelated Oct 23, 2026
5f8dba4
Merge remote-tracking branch 'origin/main' into pr-6171-iterate
kixelated Oct 23, 2026
434fa51
docs(net): a fill's per-group demand is what still waits for the group
kixelated Oct 23, 2026
8390fc1
Merge remote-tracking branch 'origin/main' into merge-6171-a99af
kixelated Oct 23, 2026
c8354c5
chore(lint): give the fill suppressions a reason
kixelated Oct 23, 2026
4450d46
Merge remote-tracking branch 'origin/main' into merge-6171-a99af
kixelated Oct 23, 2026
5f0d117
fix(net): a fill starts at the frame of its own range
kixelated Oct 23, 2026
f441052
fix(net): a range starting mid-group heads its own fill
kixelated Oct 23, 2026
d645dde
Merge origin/quest/m1/subscribe-ranges/dynamic into quest/m1/archive/…
kixelated Oct 23, 2026
137b83a
Merge origin/main into quest/m1/archive/consumer
kixelated Oct 23, 2026
4fac8e2
quest: plan an errored end that keeps history; list archive time rang…
kixelated Oct 23, 2026
0697579
style: rustfmt the archive tests
kixelated Oct 23, 2026
4bed286
Merge remote-tracking branch 'origin/main' into iterate-6262
kixelated Oct 23, 2026
c8ba72c
Merge remote-tracking branch 'origin/main' into take-6262
kixelated Oct 25, 2026
9a2c0fe
Merge remote-tracking branch 'origin/main' into take-6262
kixelated Oct 25, 2026
bf73de7
fix(archive): adapt the consumer to whole-group ranges and the trimme…
kixelated Oct 25, 2026
e9d7968
Merge remote-tracking branch 'origin/main' into take-6262
kixelated Oct 25, 2026
a9f0e23
fix(archive): refuse a group whose GET fails for now instead of dropp…
kixelated Oct 25, 2026
003a088
fix(archive): abort the served tracks when serving the archive fails
kixelated Oct 25, 2026
15ac5cb
fix(archive): cancel the tracks a consumer still serves live when it …
kixelated Oct 25, 2026
5eb0ba9
fix(archive): keep the open group in place while a continuation is wr…
kixelated Oct 25, 2026
b873079
fix(archive): settle a recorded end past the newest stored group
kixelated Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions doc/bin/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ equivalents.
# Record every broadcast announced under `event`
moq --connect https://relay.example.com/anon --broadcast event export archive s3://recordings

# Replay a timeline recording under another name
moq --connect https://relay.example.com/anon --broadcast event-replay.hang import archive s3://recordings/event
# Replay one recorded broadcast under another name
moq --connect https://relay.example.com/anon --broadcast event-replay.hang import archive s3://recordings/event/@<epoch>
```

`export archive` records every broadcast announced at or under `--broadcast`
Expand All @@ -325,10 +325,15 @@ or stitches into an older recording. Any recording that fails ends the export.
`--retention 1h` keeps only the last hour, a DVR, deleting older objects
`--retention-grace` (default 30 s) after they leave the window.

`import archive` republishes a timeline recording made by an older `moq` as
live tracks, fetching spans on request. It ends where the recording ends;
`--follow 2s` keeps polling a recording still being made. It does not yet read
the per-track layout that `export archive` writes.
`import archive` republishes one recorded broadcast, the `<broadcast>/@<epoch>`
prefix `export archive` wrote, as an ordinary broadcast under a new epoch: every
track, the catalog included, with its original groups, frames, timescale, and
priority. Each track is live from its newest stored group and serves older
groups from the store on request, with no max age, so the whole recording stays
reachable. A recording still being made is followed, checked every `--refresh`
(default 1 s), until `export archive` marks it complete; one whose export
stopped early stays live. It refuses a prefix with no recording, or with keys
`export archive` does not write.

Store URLs are `file:///absolute/path`, `s3://bucket/prefix`,
`gs://bucket/prefix`, or `az://container/prefix`, with credentials from the
Expand Down
13 changes: 11 additions & 2 deletions doc/bin/hls.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,17 @@ What that means for you:
- **URLs name the publisher instance.** Every URL the multivariant playlist and DASH manifest list sits under `@<instance>`, the publisher's epoch, so every edge and every reload derive the same URLs and a CDN in front of export caches them. A restarted publisher gets URLs of its own, whose numbering starts over. The multivariant playlist and manifest switch to it at once, while a player already on the old instance keeps playing until that publisher leaves; the player then has to reload the multivariant playlist. An instance without an epoch (one that reached export over moq-lite-06 or older) gets an id each export mints, so only that export serves its URLs.
- **Only a clean finish ends a playlist.** `EXT-X-ENDLIST` and a `static` DASH manifest mean the timeline finished. If the segment-boundary rendition's timeline fails instead, such as a publisher dropping its session, every media playlist and the DASH manifest answer HTTP 500 with `Cache-Control: no-store`, so a truncated recording never looks complete. A rendition whose own timeline fails answers the error alone and leaves the multivariant playlist and DASH manifest, so players switch to a sibling.

A [`moq-archive`](https://docs.rs/moq-archive) recording replayed through its
`Reader` is served the same way, with no second stored copy, and lists the
A [`moq-archive`](https://docs.rs/moq-archive) recording is served the same
way, with no second stored copy: record a broadcast, republish it, and export
the republished broadcast.

```bash
moq --connect https://relay.example.com/anon --broadcast event export archive s3://recordings
moq --connect https://relay.example.com/anon --broadcast event-replay.hang import archive s3://recordings/event/@<epoch>
moq --connect https://relay.example.com/anon --broadcast event-replay.hang export hls --listen '[::]:8089'
```

The archived catalog and timelines replay as recorded, so the export lists the
same window as a live broadcast.

`--listen-tls-cert`/`--listen-tls-key` or `--listen-tls-generate` serve HTTPS,
Expand Down
8 changes: 3 additions & 5 deletions quest/m1/api-audit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,9 @@ Rules every child follows:
- Each child lists its public API and wire impact in its PR, and its Impact
bullet says how to migrate.

Already gated elsewhere, so not children: the archive `Producer` and `Reader`
disagree on layout while both claim version 2
([archive consumer](/quest/m1/archive/consumer.md)), and the binary-to-flate
fold is followed by flate's removal ([compress migrate](/quest/m1/compress/migrate.md)).
Both are in m1, so the cut waits for them. hang's
Already gated elsewhere, so not a child: the binary-to-flate fold is followed
by flate's removal ([compress migrate](/quest/m1/compress/migrate.md)), in m1,
so the cut waits for it. hang's
`Archive::replay_epoch` goes with the root `archive` section in
[catalog HLS](/quest/m1/archive/catalog-hls.md).

Expand Down
1 change: 0 additions & 1 deletion quest/m1/archive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ from a host. Each child fixes the stale docs it touches.

## Required

- [Archive consumer](/quest/m1/archive/consumer.md) - `archive::Consumer` serves a stored prefix as a broadcast from its key listing, and `moq export/import archive` use the new pair
- [Catalog HLS field](/quest/m1/archive/catalog-hls.md) - the root `archive` section is gone; an opt-in per-rendition `hls` field marks each aligned HLS segment's first group and pts and declares its nominal duration
- [Fixed HLS target duration](/quest/m1/archive/hls-target.md) - one `EXT-X-TARGETDURATION` for the run, from the declared `hls` duration; an overrun is listed with a warning

Expand Down
4 changes: 0 additions & 4 deletions quest/m1/archive/catalog-hls.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,3 @@ segment is refused, and that a rendition without `hls` gets no playlist.
Public API: `hang::catalog::Archive` is deleted, the rendition types gain an
`hls` field in Rust and JS, and `moq-mux` gains the opt-in. Wire: the hang
catalog format.

## Required

- [Archive consumer](/quest/m1/archive/consumer.md) - the old writer and reader, which read the root `archive` section, are gone first
85 changes: 0 additions & 85 deletions quest/m1/archive/consumer.md

This file was deleted.

6 changes: 2 additions & 4 deletions quest/m1/compress/migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ See the [epic](/quest/m1/compress/README.md) for the decisions.

Decided 2026-10-20 (maintainer): the timeline is `<track>.hls` with
`compress: true`, never `<track>.hls.z`. [Catalog HLS field](/quest/m1/archive/catalog-hls.md)
renames it and publishes it uncompressed, and the
[archive consumer](/quest/m1/archive/consumer.md) replaces the CLI's archive
reader, so this quest lands after both and only sets the flag.
renames it and publishes it uncompressed, and the archive consumer replaced
the CLI's archive reader, so this quest lands after it and only sets the flag.

- Delete the `.z` names outright, with no transition release: hangz is opt-in
and `main` takes breaking changes. The Impact bullet says how to migrate.
Expand Down Expand Up @@ -61,4 +60,3 @@ Public API: removals listed above. Wire: the hang catalog loses
## Required

- [Catalog HLS field](/quest/m1/archive/catalog-hls.md) - renames the timeline to `<track>.hls`, which this compresses
- [Archive consumer](/quest/m1/archive/consumer.md) - replaces the CLI archive reader that reads `.z` timelines
1 change: 0 additions & 1 deletion quest/m1/release-audit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ child records whether its behavior is new on main or inherited from release.
- [JS subscription ranges](/quest/m1/subscribe-ranges/js.md) - range lists and frame offsets build on the bounds fixed here
- [OBS video source](/quest/m2/obs-moq-video/source.md) - the decoder replacement keeps the timing and EOF behavior fixed here
- [Compressed tracks](/quest/m1/compress/README.md) - application migration has an existing owner
- [Archive consumer](/quest/m1/archive/consumer.md) - current producer and CLI reader still need the new-format roundtrip
- [Measured release performance](/quest/m1/release-perf/README.md) - existing paired benchmark regressions and their confirmation runs
- [Transport performance audit](/quest/m2/audit-perf.md) - congestion-control coverage remains outstanding
- [Binding timestamps](/quest/m1/binding-timestamps.md) - exact timestamps across language APIs
Expand Down
2 changes: 2 additions & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ lite-07; bitrate enforcement is not part of this release.
- [QUIC on the sim](/quest/m2/quic-sim.md) - MoQ sessions and the relay run over `moq-quic` on the seeded sim, reusing its `Pair` and `Link`
- [Seeded sim](/quest/m2/sim-seed.md) - sim tests draw every jitter from one seeded generator and replay bit-exact

- [Archive time ranges](/quest/m2/archive-time-ranges.md) - a `Time(pts)` range on an archive consumer broadcast resolves from the archive's keys, however old
- [DVR rewind](/quest/m2/archive-dvr.md) - follow-on capability after the release contract is stable
- [An errored track end keeps its history](/quest/m2/errored-end.md) - a replayed archive ends with its source's error after serving every stored group, instead of a clean finish

- [JS HLS field](/quest/m2/js-hls.md) - follow-on capability after the release contract is stable

Expand Down
2 changes: 1 addition & 1 deletion quest/m2/archive-dvr.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ over missing groups, and return to live.
## Required

- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - the `Time(pts)` range a seek sends
- [Archive consumer](/quest/m1/archive/consumer.md) - serves a stored broadcast and resolves time ranges from its keys
- [Archive time ranges](/quest/m2/archive-time-ranges.md) - the archive consumer resolves time ranges from its keys
35 changes: 35 additions & 0 deletions quest/m2/archive-time-ranges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# [M] Archive time ranges

## Goal

A `Time(pts)` range on an `archive::Consumer` broadcast resolves from the
archive's key listing, not only from the groups the consumer's cache holds:
a start reaches the stored group containing pts, and an end stops just after
it, however old.

## Plan

Decided 2026-10-23 (maintainer, on
[moq-dev/moq#6262](https://github.com/moq-dev/moq/pull/6262)): this stays in
m2, required by [DVR rewind](/quest/m2/archive-dvr.md), rather than blocking
the archive consumer, which ships without time anchors.

The consumer creates each track live from its newest stored group and serves
older groups through `track::Fill`, so its cache holds almost nothing a time
anchor can resolve against: today a `Time(pts)` start older than the newest
group resolves to the oldest cached group instead. A `Fill` names group
positions only, so an anchor the cache can't resolve never reaches the
handler. [moq-dev/moq#6171](https://github.com/moq-dev/moq/pull/6171) deferred carrying the anchor to the consumer quest, which
landed without it.

- `moq-net` passes a time anchor the cache can't resolve to the
`track::Dynamic` handler, unresolved, in its fill.
- The consumer resolves it from its index: every object key carries its first
frame's pts, so the object holding pts is one lookup, and the group within
it comes from the stored group timestamps.

Test with the in-memory store: a start and an end older than the newest
group, an anchor before the oldest stored object, an anchor in a gap, and an
untimed track refusing it.

Public API: `track::Fill` gains the anchor. Wire: none.
41 changes: 41 additions & 0 deletions quest/m2/errored-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# [M] An errored track end keeps its history

## Goal

A track can end with an error and still serve the groups it holds or can fill:
a reader arriving later gets that history, then the error, instead of either
the error alone or a clean finish. `archive::Consumer` replays a stored
`{"error":code}` end this way, so a replayed recording ends the way its source
did.

## Plan

From [moq-dev/moq#6262](https://github.com/moq-dev/moq/pull/6262), decided by
the maintainer 2026-10-23: the consumer replays an errored end as a clean
finish after its newest stored group, plus a warn log, because
`track::Producer::abort` refuses every later reader the stored history. The
alternatives were an abort (loses history) and an abort only after the last
group (still refuses later readers). This quest replaces the clean finish.

- `moq-net`: an end that records an error alongside the final position.
Readers below the end read groups and fills as after `finish_at`; a reader
reaching the end gets the error instead of `None`. Unlike `abort`, the cache,
fills, and the listing stay as after a finish. Pick the API shape in the PR
with options and a recommendation, such as `finish_at` taking an end reason,
and no `finish_with_x` variant.
- Carry it across a hop if a relay must forward it: moq-lite resets the
subscription with the code today, which a later subscriber never sees. Any
wire change goes in the next unpublished lite version and its draft.
- `archive::Consumer` maps `End::Error(code)` to the new end and drops its
warn log.

Test a consumer replaying an errored track: a subscriber from the oldest group
reads every stored group and then the error; a later fetch still reads stored
groups.

Public API: a `track::Producer` end carrying an error, and what a reader
observes at it. Wire: possibly a lite track end carrying an error code.

## Related

- [DVR rewind](/quest/m2/archive-dvr.md) - seeks back through an archive whose source may have ended with an error
5 changes: 2 additions & 3 deletions quest/m2/performance-survey/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,8 @@ No duplicate quest or workflow edit belongs here.
`Unlimited`; their numbers cannot establish controller performance. Expand
reassembly flight/reordering coverage there before proposing another index.
[BBR study](/quest/m2/quic-bbr-study.md) owns controller behavior.
- Archive reader internals are being replaced by the
[archive consumer](/quest/m1/archive/consumer.md). Do not optimize the
outgoing timeline reader. Benchmark the landed replacement when revisiting it.
- The archive consumer replaced the timeline reader; benchmark it when
revisiting archive reads.

## Required

Expand Down
6 changes: 1 addition & 5 deletions quest/m3/archive-paced-replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ viewer who joins late joins mid-replay.

## Plan

Retargeted 2026-10-17 onto [Archive consumer](/quest/m1/archive/consumer.md),
Retargeted 2026-10-17 onto the archive consumer,
whose broadcast already includes the archived catalog as an ordinary track.

- Decided 2026-10-08: the catalog paces on the same clock as the media, each
Expand All @@ -36,7 +36,3 @@ whose broadcast already includes the archived catalog as an ordinary track.
current group, and that two tracks stay aligned.
- Deferred in the 2026-09-30 audit and moved to m3 in the 2026-10-05 audit: no
named consumer.

## Required

- [Archive consumer](/quest/m1/archive/consumer.md) - the stored broadcast this paces
Loading
Loading