From 5a75972f7185d2d1d946e7ad7f6cd7071a8e2cbb Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 07:41:41 -0800 Subject: [PATCH 1/3] quest: plan follow-ups from the 2026-11-05 quest-complete run New quests: PulseAudio D-Bus isolation and the open-GOP fixture (m0 test-flakes), the macOS Nightly job (m0 nightly), native comfort noise (m1 push-to-talk), the moq-json table/deltas bench and same-track copy recovery (m1). Smoke-bindings-rename also covers #7678's optional result. Co-Authored-By: Claude Opus 5.5 --- quest/m0/nightly/README.md | 1 + quest/m0/nightly/macos.md | 34 +++++++++++++++++ quest/m0/test-flakes-2/README.md | 2 + quest/m0/test-flakes-2/open-gop-fixture.md | 20 ++++++++++ quest/m0/test-flakes-2/pulse-dbus.md | 23 ++++++++++++ quest/m1/README.md | 2 + quest/m1/bench-json-deltas.md | 24 ++++++++++++ quest/m1/copy-recover.md | 31 ++++++++++++++++ quest/m1/push-to-talk/README.md | 1 + quest/m1/push-to-talk/native-comfort-noise.md | 37 +++++++++++++++++++ quest/m2/release/smoke-bindings-rename.md | 17 +++++++-- 11 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 quest/m0/nightly/macos.md create mode 100644 quest/m0/test-flakes-2/open-gop-fixture.md create mode 100644 quest/m0/test-flakes-2/pulse-dbus.md create mode 100644 quest/m1/bench-json-deltas.md create mode 100644 quest/m1/copy-recover.md create mode 100644 quest/m1/push-to-talk/native-comfort-noise.md diff --git a/quest/m0/nightly/README.md b/quest/m0/nightly/README.md index c18fabc416c..eb79b121e0d 100644 --- a/quest/m0/nightly/README.md +++ b/quest/m0/nightly/README.md @@ -44,3 +44,4 @@ Public API: none. Wire: none. - [moq-net's ingest fuzz target finds no stall](/quest/m0/nightly/ingest-fuzz.md) - the 13-byte stream that stalls ingest is fixed at its cause and kept as a regression test - [Nightly's shipping-size check passes](/quest/m0/nightly/shipping-size.md) - js/net, js/watch, and moq-relay growth over 5% is traced to its commits, then shrunk or accepted - [Nightly's Miri job stays inside its time budget](/quest/m0/nightly/miri.md) - both Miri passes go green, with ARM timings inside the 60-minute job and 15-minute-per-test budgets +- [Nightly's macOS platform job passes](/quest/m0/nightly/macos.md) - `moq-audio`'s tests compile on macOS again and the websocket version test's timeout is fixed if it recurs diff --git a/quest/m0/nightly/macos.md b/quest/m0/nightly/macos.md new file mode 100644 index 00000000000..19577268b65 --- /dev/null +++ b/quest/m0/nightly/macos.md @@ -0,0 +1,34 @@ +# [S] Nightly's macOS platform job passes + +## Goal + +The scheduled Nightly's `platform / macOS` job on `main` passes: `moq-audio`'s +tests compile on macOS again, and +`moq-tokio::integration broadcast::broadcast_websocket_uses_newest_version`, +which timed out at 120 s in an earlier scheduled run, is fixed at its cause if +it still fails once the job reaches it. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from +[moq-dev/moq#7653](https://github.com/moq-dev/moq/pull/7653), which saw the +timeout. Milestone: ✅ m0. + +Since 2026-11-04 the job fails before any test runs +([run 41979277343](https://github.com/moq-dev/moq/actions/runs/41979277343), +`just rs macos-test`): the test `an_empty_gap_buffer_marks_the_next_samples` +(`rs/moq-audio/src/capture.rs:859`) hands `tokio::spawn` a +`capture::Producer::run()` future that is not `Send` on macOS, because +`Producer` holds the ScreenCaptureKit `SCStream` across an await. It came with +[moq-dev/moq#7706](https://github.com/moq-dev/moq/pull/7706). Decide from the +type whether `Producer` should be `Send` (fix the field) or the test should +drive it without spawning; don't paper over it in the test if real callers +spawn it too. Check why the PR gate didn't catch a macOS-only test build +error, and close that gap if it is cheap. + +Then read the job's next run for the websocket timeout. Reproduce it on macOS +before fixing; if it never recurs across a week of scheduled runs, record that +and finish. + +Public API: none. Wire: none. diff --git a/quest/m0/test-flakes-2/README.md b/quest/m0/test-flakes-2/README.md index 6c21678a2fd..4bb385ce55d 100644 --- a/quest/m0/test-flakes-2/README.md +++ b/quest/m0/test-flakes-2/README.md @@ -102,3 +102,5 @@ Public API: none. Wire: none. - [dispatch-query's terminate test survives load](/quest/m0/test-flakes-2/dispatch-query-terminate.md) - `test_terminating_the_run_stops_the_running_phase` passes reliably under host load, re-enabled once fixed - [release.test.ts never reaches the network](/quest/m0/test-flakes-2/release-test-offline.md) - the JSR release test fakes its registry lookup and passes offline - [moq-ffi's datagram tests don't hinge on one datagram](/quest/m0/test-flakes-2/ffi-datagram-roundtrip.md) - the default-connection datagram tests pass under CI load +- [Test PulseAudio servers stay off the desktop's D-Bus](/quest/m0/test-flakes-2/pulse-dbus.md) - concurrent media runs on one desktop never collide on `org.PulseAudio1` +- [The open-GOP browser fixture fails loudly](/quest/m0/test-flakes-2/open-gop-fixture.md) - its broadcast stub matches current stats dependencies and an effect exception fails the test diff --git a/quest/m0/test-flakes-2/open-gop-fixture.md b/quest/m0/test-flakes-2/open-gop-fixture.md new file mode 100644 index 00000000000..216b79ee669 --- /dev/null +++ b/quest/m0/test-flakes-2/open-gop-fixture.md @@ -0,0 +1,20 @@ +# [XS] The open-GOP browser fixture fails loudly + +## Goal + +The open-GOP browser test (`test/interop/clients/js/open-gop.browser.ts`) +exercises current `@moq/watch` code: its broadcast stub satisfies the stats +dependencies the player now reads, and an exception inside the fixture's +effect fails the test directly instead of being swallowed. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from +[moq-dev/moq#7716](https://github.com/moq-dev/moq/pull/7716). Milestone: ✅ m0. + +The stub lives in `test/interop/clients/js/open-gop/main.ts`. Prefer building +the stub from the real types so the next dependency change fails to compile +rather than at runtime. + +Public API: none. Wire: none. diff --git a/quest/m0/test-flakes-2/pulse-dbus.md b/quest/m0/test-flakes-2/pulse-dbus.md new file mode 100644 index 00000000000..8c66fe3e416 --- /dev/null +++ b/quest/m0/test-flakes-2/pulse-dbus.md @@ -0,0 +1,23 @@ +# [S] Test PulseAudio servers stay off the desktop's D-Bus + +## Goal + +Every private PulseAudio server the test suites start stays off the session +bus, so two media runs on one desktop never collide on the +`org.PulseAudio1` name. Concurrent `just test media` runs on a desktop host +pass where today one can fail with `D-Bus name org.PulseAudio1 already taken`. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from +[moq-dev/moq#7716](https://github.com/moq-dev/moq/pull/7716). Milestone: ✅ m0. + +`test/lib/pulse.py` already drops `DBUS_SESSION_BUS_ADDRESS` from its own +daemon's environment, so another launch path still reaches the bus: find it +(the Playwright media runner, `test/native-play`, `test/audio-quality`, or a +module PulseAudio loads by default) and close it at the source, for example by +starting the daemon without its D-Bus modules. Reproduce with two overlapping +media runs first. + +Public API: none. Wire: none. diff --git a/quest/m1/README.md b/quest/m1/README.md index 19d7bfe6fa3..adac63d8b25 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -149,6 +149,7 @@ their end report, so an embedder's drain alert pages on real cuts. - [A job's exact route continues its claim's readers](/quest/m1/announce-takeover.md) - a job's route inherits its claim's instance, replacing the ANNOUNCE_TAKEOVER predecessor design - [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - a lite-07 SUBSCRIBE asks for past and live ranges with absolute or relative ends, in either order, and replaces FETCH; merged ranges never starve a subscriber; relays fill misses by range, including over moq-transport - [Drop set](/quest/m1/drop-set.md) - each cursor judges missing groups against its own budget, so a narrow reader drops promptly and a widening re-offers groups already reported dropped on lite-07 +- [A reader on a failed copy continues on the held re-offer](/quest/m1/copy-recover.md) - a live reader keeps a group whose copy fails by continuing on the same track's re-offer at the same frame - [Group errors are cached by kind](/quest/m1/group-error-cache.md) - permanent group errors bind every reader, retryable ones are kept only for a bounded time, and a live SUBSCRIBE_DROP or FETCH answered `Old` no longer caches it for good - [A group a subscriber gives up on is always reported](/quest/m1/group-drops.md) - the wire names every abandoned group and the drop signal is public, so readers count losses instead of stalling - [The overcommitted receive-window cells run again](/quest/m1/receive-windows-evicted.md) - `benchmark_receive_windows` measures its 256-stream and 4 MiB-frame cells again, reporting delivered frames and evictions instead of stalling @@ -172,6 +173,7 @@ their end report, so an embedder's drain alert pages on real cuts. - [Rust releases publish through OIDC](/quest/m1/release-rs-oidc.md) - release-plz publishes with trusted publishing and `CARGO_REGISTRY_TOKEN` is revoked - [Main is no slower than release](/quest/m1/release-perf/README.md) - every benchmark where main is slower than release is fixed or accepted with a reason, checked by a final paired run before the cut - [Calibrate the paired nightly benchmarks](/quest/m1/bench-ci.md) - A/A and deliberately slowed Nightly runs set the paired comparison's threshold, so its regression alerts can be trusted +- [moq-json's table/deltas bench measures the same work every iteration](/quest/m1/bench-json-deltas.md) - the `table/deltas/1%` cases stop flipping between snapshot and delta timings, so their trend history is usable - [moq-net's benches run to completion](/quest/m1/bench-test-mode.md) - the `origin`, `session` and `memory` benches stop panicking in test mode and Nightly's measured run, and the maintainer picks where CI catches the next break - [Catalog contributions become includes on the root's timeline](/quest/m1/catalog-include.md) - rename the unreleased `contributions` map before the cut and state that included broadcasts share the root's timeline - [Unbounded groups](/quest/m1/unbounded-groups/README.md) - groups and frames lose their size caps; a group keeps a 32 MB tail, and readers behind it replay through paced flights or refresh diff --git a/quest/m1/bench-json-deltas.md b/quest/m1/bench-json-deltas.md new file mode 100644 index 00000000000..9f9340f375e --- /dev/null +++ b/quest/m1/bench-json-deltas.md @@ -0,0 +1,24 @@ +# [XS] moq-json's table/deltas bench measures the same work every iteration + +## Goal + +`moq-json`'s `table/deltas/1%/{10000,20000}` cases report a stable time, so +their Nightly trend history is usable. Today each iteration ticks one +long-lived encoder, so a short run either catches a forced snapshot or misses +it: 4.9 vs 35.7 ms, and 71 vs 10 ms, in +[moq-dev/moq#7928](https://github.com/moq-dev/moq/pull/7928)'s quick screen. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from #7928. Milestone: ✅ m1. + +Make each iteration do the same work: measure deltas and forced snapshots as +separate cases, or give every iteration the same snapshot cadence. Check the +fix with repeated quick screens of both cases on one host. + +Public API: none. Wire: none. + +## Related + +- [Calibrate the paired nightly benchmarks](/quest/m1/bench-ci.md) - its rounds rejected these cases as outliers diff --git a/quest/m1/copy-recover.md b/quest/m1/copy-recover.md new file mode 100644 index 00000000000..8d37bc202d8 --- /dev/null +++ b/quest/m1/copy-recover.md @@ -0,0 +1,31 @@ +# [M] A reader on a failed copy continues on the held re-offer + +## Goal + +A live reader already handed a copy of a group that then fails keeps the +group: its `group::Consumer` continues on the successor copy the held +re-offer carries in the same track, at the same frame, instead of surfacing +the failure. Rust and `js/net` behave the same. + +## Waiting + +- moq-dev/moq: [moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) + merges. It adds the held re-offer (its decision 24) this builds on. Check + that it is merged into `main`. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from #7670. Milestone: ✅ m1. + +`with_recover` already continues a reader across a front's routes; reuse it +or its mechanism for the same-track successor rather than adding a parallel +path. A successor that diverges from what the reader already consumed fails +the reader, as a route switch does. Test on mock time: a reader mid-group on a +copy that fails continues on the re-offer with no duplicate or missing frame. + +Public API: none expected. Wire: none. + +## Related + +- [Drop set](/quest/m1/drop-set.md) - reworks the re-offer path this reads from diff --git a/quest/m1/push-to-talk/README.md b/quest/m1/push-to-talk/README.md index d5eb11bf00e..e92815b1bd2 100644 --- a/quest/m1/push-to-talk/README.md +++ b/quest/m1/push-to-talk/README.md @@ -95,6 +95,7 @@ catches this. A complete set of runs finishes this epic. ## Required - [Push-to-talk suppresses silence instead of toggling the catalog](/quest/m1/push-to-talk/dtx.md) - replaces hold with DTX +- [Native playback fills declared silence with comfort noise](/quest/m1/push-to-talk/native-comfort-noise.md) - `moq play` plays comfort noise over declared silence and counts no underrun, matching the browser ## Closes diff --git a/quest/m1/push-to-talk/native-comfort-noise.md b/quest/m1/push-to-talk/native-comfort-noise.md new file mode 100644 index 00000000000..31d72a23a36 --- /dev/null +++ b/quest/m1/push-to-talk/native-comfort-noise.md @@ -0,0 +1,37 @@ +# [M] Native playback fills declared silence with comfort noise + +## Goal + +`rs/moq-audio` playback, as used by `moq play`, treats a publisher's declared +silence the way `@moq/watch` does: when the ring runs dry at a declared end it +plays level-matched comfort noise instead of plain silence, counts no underrun +or stall, and keeps that silence through the next utterance's refill. A +push-to-talk listener on `moq play` then matches the browser. + +## Waiting + +- moq-dev/moq: [moq-dev/moq#7942](https://github.com/moq-dev/moq/pull/7942) + merges. It defines how a suppressed span is declared and already gives + `moq-audio`'s jitter estimator the declared-silence exception. Check that + it is merged and `quest/m1/push-to-talk/dtx.md` is gone from `main`. + +## Plan + +Planned 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run), from #7942, whose agent left native playback +with the estimator exception but no comfort noise because its ring has no +notion of a declared end. Milestone: ✅ m1. + +Follow the browser's choices in #7942 unless measurement argues otherwise: the +decoder hands the ring each declared end less the Opus pre-skip, and comfort +noise is white noise matched to the quietest 20 ms block played, capped at +-60 dBFS (`js/watch/src/audio/comfort.ts`). Share the level rule with the +browser through `doc/concept/audio-jitter.md` and a corpus case rather than +two descriptions. Cover it in `test/audio-quality`'s native rows or +`just test push-to-talk` if that can drive `moq play`. + +Coordinate with [moq play reaches zero steady-state underruns by +time-stretching](/quest/m1/audio-jitter-target/native-stretch.md), which edits +the same playback ring: whichever lands second rebases onto the other. + +Public API: none expected. Wire: none. diff --git a/quest/m2/release/smoke-bindings-rename.md b/quest/m2/release/smoke-bindings-rename.md index 0f787d2194e..ded82927bb2 100644 --- a/quest/m2/release/smoke-bindings-rename.md +++ b/quest/m2/release/smoke-bindings-rename.md @@ -3,14 +3,18 @@ ## Goal `test/smoke/clients/{go,python,kotlin,swift}` call `request` and `follow` -instead of `request_broadcast` and `announced_broadcast`. +instead of `request_broadcast` and `announced_broadcast`, and handle the +optional result a followed broadcast now gives once it goes offline, instead +of `announcedBroadcast(...).available()`. ## Waiting - moq-dev/moq: a binding release ships the `request` and `follow` names from - [moq-dev/moq#7193](https://github.com/moq-dev/moq/pull/7193). Check each + [moq-dev/moq#7193](https://github.com/moq-dev/moq/pull/7193) and the path + listing and offline-reporting `follow` from + [moq-dev/moq#7678](https://github.com/moq-dev/moq/pull/7678). Check each registry's latest moq-ffi-based package (Go, Python, Kotlin, Swift) for - `request`/`follow`. + `request`/`follow` and #7678's optional result. ## Plan @@ -20,6 +24,13 @@ Decided 2026-10-29 (maintainer). The smoke clients install published packages smoke client the same way. If a client must run against both old and new releases for a while, detect the method, as the JS compat client does. +Decided 2026-11-05 (maintainer, /quest-plan of the follow-ups from the +2026-11-05 /quest-complete run): #7678's optional result joins this quest +rather than a separate one, since both changes reach the same clients through +the same release. Today's call sites: `test/smoke/clients/swift/Sources/smoke/main.swift`, +`test/smoke/clients/kotlin/src/main/kotlin/Main.kt`, and +`test/smoke/clients/python/smoke.py`. + The C smoke client is out of scope: it uses the hand-written `moq-c`, which keeps `moq_origin_announced_broadcast`, and [Published generated C](/quest/m2/release/c-publish.md) moves it onto the From ac26e16814d7de2b3f70965cf85d9f26cb59059b Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 07:49:56 -0800 Subject: [PATCH 2/3] quest: flakes join m0 without asking; record the early concurrency trial Co-Authored-By: Claude Opus 5.5 --- quest/README.md | 6 ++++++ quest/m0/ci/queue-concurrency.md | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/quest/README.md b/quest/README.md index 0962dc51d88..e5e084f463b 100644 --- a/quest/README.md +++ b/quest/README.md @@ -27,6 +27,12 @@ The [release execution](/quest/m2/release/README.md) starts after m1 is ready. It publishes artifacts before downstream adoption and rollout can complete. Critical fixes continue to reach the existing release through backports. +Test flakes join m0 without asking (decided 2026-11-05, maintainer): a test +that fails intermittently in CI, Nightly, or the merge queue becomes a child +of m0's [test flakes](/quest/m0/test-flakes-2/README.md) epic. Skills that plan +follow-ups plan a reported flake there without asking whether or where; the +planning PR's review is the check. + ## Required - [m0: immediate priorities](/quest/m0/README.md) - urgent correctness fixes and maintenance of the current release diff --git a/quest/m0/ci/queue-concurrency.md b/quest/m0/ci/queue-concurrency.md index b31532832de..001261c99fc 100644 --- a/quest/m0/ci/queue-concurrency.md +++ b/quest/m0/ci/queue-concurrency.md @@ -31,3 +31,12 @@ maintainer's actions (ruleset 2420853); the quest names the setting and reports the numbers. The host runner must handle two builds at once without the shared mbx cache or `quest gate` admission erasing the gain, so record host load during the trial. + +The trial started early (decided 2026-11-05, maintainer, /quest-plan): the +second queue runner `nixos-moq-ci2` landed in +[kixelated/nixos#24](https://github.com/kixelated/nixos/pull/24), and ruleset +2420853 went to `max_entries_to_build: 2` at 2026-11-05T15:49:30Z. A scheduled +task sets it back to 1 at 2026-11-07T08:00Z. Measure the setting-2 window +against a comparable setting-1 window before it, such as +2026-11-03 06:47Z to 2026-11-04 06:47Z, and check the ruleset reads 1 again +before recording the result. From e2be631f865222534d30bb65dbe3f2ea2a34b4d1 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 5 Nov 2026 09:42:40 -0800 Subject: [PATCH 3/3] quest: settle the declined quests, split make-room, record the userns flip Co-Authored-By: Claude Opus 5.5 --- quest/m0/ci/nix-builder-userns.md | 11 ++++- quest/m1/fuzz-findings/README.md | 7 +++ quest/m1/lite07-finalize.md | 2 +- quest/m1/stream-credit/README.md | 21 +++++---- quest/m1/stream-credit/make-room.md | 46 ------------------- quest/m1/stream-credit/open-streams.md | 30 ++++++++++++ quest/m1/stream-credit/reset-fallback.md | 32 +++++++++++++ quest/m1/stream-credit/takeover.md | 38 +++++++++++++++ quest/m1/subscribe-ranges/js-fills.md | 10 ++++ .../m1/subscribe-ranges/start-bookkeeping.md | 30 +++++++++--- 10 files changed, 165 insertions(+), 62 deletions(-) delete mode 100644 quest/m1/stream-credit/make-room.md create mode 100644 quest/m1/stream-credit/open-streams.md create mode 100644 quest/m1/stream-credit/reset-fallback.md create mode 100644 quest/m1/stream-credit/takeover.md diff --git a/quest/m0/ci/nix-builder-userns.md b/quest/m0/ci/nix-builder-userns.md index ffb22d075a6..adb268aca9e 100644 --- a/quest/m0/ci/nix-builder-userns.md +++ b/quest/m0/ci/nix-builder-userns.md @@ -1,4 +1,4 @@ -# [S] The desktop host's Nix builders may map user namespaces +# [XS] The desktop host's Nix builders may map user namespaces ## Goal @@ -28,3 +28,12 @@ As of 2026-11-05 the probe fails on the desktop host (Ubuntu 26.04, where AppArmor's restriction for Nix builds there, persistently and as narrowly as works, apply it when no gate is running, and check that the probe succeeds and `just ci` passes `check flake`. + +Decided 2026-11-05 (maintainer, /quest-plan): flip the sysctl host-wide rather than a narrow AppArmor +profile for the Nix builders. Not chosen: a narrow profile; declining and +deleting the quest. Applied the same day: +`/etc/sysctl.d/99-userns.conf` sets +`kernel.apparmor_restrict_unprivileged_userns = 0` (it loads after Ubuntu's +`/usr/lib/sysctl.d/10-apparmor.conf`), `unshare --map-root-user true` +succeeds, and `nix/runner/userns-probe.nix` builds. What remains: run +`just ci` on the desktop and confirm `check flake` passes. diff --git a/quest/m1/fuzz-findings/README.md b/quest/m1/fuzz-findings/README.md index d20a1a4f31c..f80f6e21003 100644 --- a/quest/m1/fuzz-findings/README.md +++ b/quest/m1/fuzz-findings/README.md @@ -20,6 +20,13 @@ input committed before the fix would fail the merge gate. Every known finding is now fixed: run every target nightly for a few sessions; a new finding becomes a child here. +Decided 2026-11-05 (maintainer, /quest-plan): keep this epic as an `[S]` watcher after +[moq-dev/moq#7690](https://github.com/moq-dev/moq/pull/7690) removed its last +child, since Fuzz (annexb), (fmp4), (ts), and (ingest) were still red on +2026-11-05. An agent triages each red target into a child quest (an +intermittent failure joins m0's test flakes instead), and the epic is deleted +once every target runs green. Not chosen: deleting it now. + For the crates outside the `kixelated` and `moq-dev` orgs, moq-mux fixes the bug on its own side and the bug is reported upstream (decided 2026-10-30, maintainer): [austinleroy/webm-iterable#11](https://github.com/austinleroy/webm-iterable/issues/11), diff --git a/quest/m1/lite07-finalize.md b/quest/m1/lite07-finalize.md index 961d41858c9..c9c7cd2b2e4 100644 --- a/quest/m1/lite07-finalize.md +++ b/quest/m1/lite07-finalize.md @@ -116,7 +116,7 @@ timestamp bound in [#6168](https://github.com/moq-dev/moq/pull/6168). - [Lite-07 by start](/quest/m1/time-addressing/lite07.md) - lite-07 carries group starts, previous-start links, GROUP_END, and time bounds instead of sequence numbers - [@moq/net by start](/quest/m1/time-addressing/js.md) - JS speaks the same lite-07 wire - [A job's exact route continues its claim's readers](/quest/m1/announce-takeover.md) - the job's route inherits the claim's instance, with no new wire message -- [A higher-priority group makes room](/quest/m1/stream-credit/make-room.md) - lite-07 lets a group stream take over for another group +- [Takeover](/quest/m1/stream-credit/takeover.md) - lite-07 lets a group stream take over for another group - [Datagram tracks](/quest/m1/datagram-tracks/README.md) - lite-07 subscribes to datagram tracks through their own request, and datagrams carry their own sequence ## Related diff --git a/quest/m1/stream-credit/README.md b/quest/m1/stream-credit/README.md index d162b732ed5..90e86b12904 100644 --- a/quest/m1/stream-credit/README.md +++ b/quest/m1/stream-credit/README.md @@ -25,13 +25,16 @@ which JS cannot raise; Chrome stalled at 103, Firefox at 100): - ✅ Make room by taking over, else cancelling. Rejected: cancelling only (each reset frees credit only after the peer's MAX_STREAMS, a round trip later), and ordering waiting opens without making room. -- ✅ Transports report the peer's stream credit and blocked state - (decided 2026-11-04, maintainer): moq-quic, Rust qmux and `@moq/qmux` - expose it, and the Rust entry point reads it through - `poll::Session::stream_credit`. Rejected: treating any waiting open as the - signal, since an open also waits on things other than credit. Browsers - report nothing and keep `createUnidirectionalStream()` pending without - credit, so there a waiting open stays the only signal. +- ~~Transports report the peer's stream credit and blocked state~~, + reversed the same day (maintainer, on + [moq-dev/moq#7894](https://github.com/moq-dev/moq/pull/7894)): the signal + is the open line's waiting state (the head of the line is pending), with + no transport credit or blocked API. +- Decided 2026-11-05 (maintainer, /quest-plan): "make room" splits into open + streams (ready now), reset fallback, and takeover (after + [moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670)), and the + browser test stays this README's own work. Not chosen: holding the whole + quest until #7670 merges. This epic owns the end-to-end test: a browser reader and a Rust relay over WebTransport, 300 live tracks of mixed priority, the highest-priority ones @@ -40,4 +43,6 @@ receiving every group, and nothing hanging. ## Required - [A departed subscription releases its streams](/quest/m1/stream-credit/release-on-leave.md) - on lite-07 a subscription whose reader left frees its upstream streams, so 3 slots serve the next one -- [A higher-priority group makes room](/quest/m1/stream-credit/make-room.md) - it takes over a lower-priority stream, or one is reset, without over-cancelling +- [Open streams](/quest/m1/stream-credit/open-streams.md) - each session lists its open streams and, where the transport can tell, which are fully acknowledged +- [Reset fallback](/quest/m1/stream-credit/reset-fallback.md) - a waiting higher-priority open resets the lowest-priority stream, one reset in flight per waiting open +- [Takeover](/quest/m1/stream-credit/takeover.md) - on lite-07 a higher-priority group takes over an acknowledged stream, and a group that lost its stream resumes diff --git a/quest/m1/stream-credit/make-room.md b/quest/m1/stream-credit/make-room.md deleted file mode 100644 index a6413e5d5df..00000000000 --- a/quest/m1/stream-credit/make-room.md +++ /dev/null @@ -1,46 +0,0 @@ -# [M] A higher-priority group makes room - -## Goal - -When an open waits for stream credit, the session makes room for it from a -lower-priority stream: it hands that stream over to the new group at once if -all the stream's data is acknowledged, and otherwise resets the -lowest-priority stream, with at most one reset in flight per waiting open, so -a slow MAX_STREAMS never cuts more streams than needed. A group that lost its -stream resumes from where it stopped when it is next served. - -## Plan - -See the [epic](/quest/m1/stream-credit/README.md) for the decisions. -Decided 2026-11-04 (maintainer): - -- **Takeover first.** A takeover needs no credit, so the new group starts at - once. Only a stream whose sent data is all acknowledged qualifies, since a - QUIC stream is ordered and the new group's bytes would otherwise queue - behind the old group's retransmissions. -- **Cancel only as the fallback**, and the lowest-priority stream, not just - idle ones. One reset in flight per waiting open, because credit returns - only when the peer's MAX_STREAMS arrives, at least a round trip later. -- **When.** Rust acts as soon as the transport reports it is blocked: - `StreamCredit::blocked` from `poll::Session::stream_credit(dir)`, which - the open line in `rs/moq-net/src/transport/open.rs` already reads where it - starts waiting. moq-quic (raw QUIC and WebTransport), qmux and the memory - transport report it; a pending open alone does not say, since a - WebTransport open also waits on its stream header. JS over `@moq/qmux` acts - as soon as `Session.streamCredit(dir).blocked` says so. A browser's - WebTransport reports no credit, so there JS acts once an open has waited - about one round trip, not a fixed time: a fixed 100 ms is too long on a - LAN and too short on mobile. The RTT comes - from WebTransport's `getStats()` when it reports one, else moq's own probe - (`Probe.rtt`, as `js/net/src/connection/stats.ts` already falls back), and - before any sample exists the maintainer's original 100 ms. -- **Wire.** Takeover needs lite-07 to let a group stream end one group and - start another. Add it to `drafts/draft-lcurley-moq-lite.md` before lite-07 - finalizes; moq-transport and older lite versions only cancel. - -Tests: a browser over WebTransport and a Rust relay, more live groups than -the cap, the higher-priority group starting without waiting a round trip; -a cap with no acknowledged stream resets exactly one stream per waiting open; -a group that lost its stream resumes without loss. - -Public API: none. Wire: lite-07 group stream takeover. diff --git a/quest/m1/stream-credit/open-streams.md b/quest/m1/stream-credit/open-streams.md new file mode 100644 index 00000000000..02af3563340 --- /dev/null +++ b/quest/m1/stream-credit/open-streams.md @@ -0,0 +1,30 @@ +# [M] Each session knows its open streams and which are acknowledged + +## Goal + +Rust and `js/net` sessions keep a per-session list of their open outgoing +group streams with each stream's priority, and the transports that can tell +report when all of a stream's sent data is acknowledged: moq-quic (raw QUIC +and WebTransport), Rust qmux, `@moq/qmux`, and the memory transport. A +browser's WebTransport cannot report it, and the session knows that at setup +rather than guessing. This is what [reset fallback](/quest/m1/stream-credit/reset-fallback.md) +and [takeover](/quest/m1/stream-credit/takeover.md) choose a stream from. + +## Plan + +Decided 2026-11-05 (maintainer, /quest-plan): split from the [M] "A higher-priority group makes room" quest, which +the agent that declined it on 2026-11-05 found was really XL and had gaps: +the transport could not report when a stream's data is all acknowledged, +nothing defined where a reset group resumes, and no session listed its open +streams. This part starts now; it touches no code +[moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) rewrites. + +The signal that room is needed is the open line's waiting state (the head of +the line is pending), per the maintainer's 2026-11-04 decision on +[moq-dev/moq#7894](https://github.com/moq-dev/moq/pull/7894), not a transport +credit or blocked API. Acknowledgement reporting is a different fact (has the +peer received this stream's bytes), so it is new transport surface; keep it +to what takeover needs. + +Public API: transport traits gain an acknowledged signal where supported. +Wire: none. diff --git a/quest/m1/stream-credit/reset-fallback.md b/quest/m1/stream-credit/reset-fallback.md new file mode 100644 index 00000000000..fd38c62ccad --- /dev/null +++ b/quest/m1/stream-credit/reset-fallback.md @@ -0,0 +1,32 @@ +# [S] A waiting higher-priority open resets the lowest-priority stream + +## Goal + +When the head of a session's open line waits for stream credit and a +lower-priority group stream is open, the session resets the lowest-priority +one, with at most one reset in flight per waiting open, so a slow +MAX_STREAMS never cuts more streams than needed. The reset group is reported +dropped on that subscription until [takeover](/quest/m1/stream-credit/takeover.md) +defines where it resumes. Every version and transport does this, browsers +included. + +## Plan + +Decided 2026-11-05 (maintainer, /quest-plan): split from "A higher-priority group makes room". Cancel is the +fallback the 2026-11-04 epic decision kept, and it needs no wire change, so +it lands before takeover. The trigger is the open line's waiting state +([moq-dev/moq#7894](https://github.com/moq-dev/moq/pull/7894)). A browser's +WebTransport reports no credit and keeps `createUnidirectionalStream()` +pending, so there JS acts once an open has waited about one round trip: the +RTT from WebTransport's `getStats()` when it reports one, else `Probe.rtt` +(as `js/net/src/connection/stats.ts` already falls back), and before any +sample the maintainer's original 100 ms. + +Tests: a cap with no acknowledged stream resets exactly one stream per +waiting open, and the higher-priority group starts once credit returns. + +Public API: none. Wire: none. + +## Required + +- [Open streams](/quest/m1/stream-credit/open-streams.md) - the session's open-stream list to choose the lowest-priority stream from diff --git a/quest/m1/stream-credit/takeover.md b/quest/m1/stream-credit/takeover.md new file mode 100644 index 00000000000..df2d6be2e45 --- /dev/null +++ b/quest/m1/stream-credit/takeover.md @@ -0,0 +1,38 @@ +# [M] A higher-priority group takes over an acknowledged stream on lite-07 + +## Goal + +On lite-07, when an open waits for credit and a lower-priority group stream's +sent data is all acknowledged, the new group takes that stream over at once: +the group stream ends one group and starts another, needing no credit. A +group that lost its stream, by takeover or by +[reset](/quest/m1/stream-credit/reset-fallback.md), resumes from where it +stopped when it is next served, under the duplicate rule of +[moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670). moq-transport +and older lite versions keep reset only. + +## Waiting + +- moq-dev/moq: [moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) + merges. Resuming a reset group sends a second stream for the same group, + which #7670's hold-the-duplicate rule rewrites. Check that it is merged. + +## Plan + +Decided 2026-11-05 (maintainer, /quest-plan): split from "A higher-priority group makes room". Takeover first, +as decided 2026-11-04: only a stream whose sent data is all acknowledged +qualifies, since a QUIC stream is ordered and the new group's bytes would +otherwise queue behind the old group's retransmissions. Add the takeover +wire to `drafts/draft-lcurley-moq-lite.md` before lite-07 finalizes, and +define where a reset or taken-over group resumes. + +Tests: a browser over WebTransport and a Rust relay, more live groups than +the cap, the higher-priority group starting without waiting a round trip; +a group that lost its stream resumes without loss. + +Public API: none. Wire: lite-07 group stream takeover. + +## Required + +- [Open streams](/quest/m1/stream-credit/open-streams.md) - which open streams are fully acknowledged +- [Reset fallback](/quest/m1/stream-credit/reset-fallback.md) - the fallback a takeover is tried before diff --git a/quest/m1/subscribe-ranges/js-fills.md b/quest/m1/subscribe-ranges/js-fills.md index cf78d65e7d2..13df15ec38d 100644 --- a/quest/m1/subscribe-ranges/js-fills.md +++ b/quest/m1/subscribe-ranges/js-fills.md @@ -60,3 +60,13 @@ through a JS relay between Rust peers (`just test relay-fill`). Public API: `Fill` gains `time` and a frame start, matching Rust's `Fill::time` and `Fill::start`. Wire: none. + +Decided 2026-11-05 (maintainer, /quest-plan): this waits for [Drop set](/quest/m1/drop-set.md), since its head-fill +half rewrites the same JS fill and miss code as +[moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) and Drop set, +which also redefine the stale-drop judgment it relies on. Not chosen: +splitting the `Fill.time` lookup off to start now. + +## Required + +- [Drop set](/quest/m1/drop-set.md) - redefines the JS stale-drop judgment and fill code this rewrites diff --git a/quest/m1/subscribe-ranges/start-bookkeeping.md b/quest/m1/subscribe-ranges/start-bookkeeping.md index 599ecb645ef..6d154e6d3d8 100644 --- a/quest/m1/subscribe-ranges/start-bookkeeping.md +++ b/quest/m1/subscribe-ranges/start-bookkeeping.md @@ -1,15 +1,33 @@ -# [M] Delete the old start bookkeeping +# [S] Delete the old start bookkeeping ## Goal -What each lite cursor is owed and what was dropped lives in the model, and -lite `TrackRun` only translates it into SUBSCRIBE_START, SUBSCRIBE_DROP, and -SUBSCRIBE_END per version. `skipped`, `served`, `first`, and the pre-06 -branches are deleted rather than layering lite-07 ranges on top, and on -lite-07 a subscriber's end is "owed range covered and no stream active". +Where a lite subscription starts lives in one place per version: lite +`TrackRun`'s `start`, `first`, `start_frame`, the pre-06 re-pin branch, and +the per-version start branches collapse into one start state that only +translates into SUBSCRIBE_START per version, and on lite-07 the +`requested == start` gate is gone, since a fresh START per update makes it +unnecessary. What a cursor is owed or was served is out of scope: it stays +with [Drop set](/quest/m1/drop-set.md). + +## Waiting + +- moq-dev/moq: [moq-dev/moq#7670](https://github.com/moq-dev/moq/pull/7670) + merges. It rewrites the same lite publisher state (about 1,250 lines of + `lite/publisher.rs`). Check that it is merged into `main`. ## Plan +Decided 2026-11-05 (maintainer, /quest-plan): narrow this quest to the start state and wait for #7670, as the +agent that declined it on 2026-11-05 recommended. #7670 rewrites the same +publisher state, Drop set already promises to delete the served-groups +record (`skipped`, `served`, `Delivered`), and +[moq-dev/moq#7872](https://github.com/moq-dev/moq/pull/7872)'s +SUBSCRIBE_STARTED makes the lite-07 start gate removable. Not chosen: +folding it into Drop set, or doing it now on main. The bullets below +predate the narrowing; the owed-range ones are Drop set's. + + Moved out of the [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) README's own work, where the 2026-10-24 pre-cut audit put it. Decided 2026-10-27 (maintainer): a child quest of its own, since