diff --git a/quest/m0/README.md b/quest/m0/README.md index 8c3f4064763..102be0474b5 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -28,3 +28,4 @@ edge, which embeds moq-relay; it moved there on 2026-09-28. - [Backport the SETUP-path and iroh-key fixes](/quest/m0/backport-security-fixes.md) - `release` refuses a conflicting SETUP Path and keeps persisted iroh keys private, in one patch release - [CI cost and latency](/quest/m0/ci/README.md) - cloud CI runs only in a batched merge gate as one `just ci` pass per batch, expensive checks run nightly and gate `release`, caches move to R2, and runners are chosen last - [More tests under load](/quest/m0/test-flakes-2/README.md) - load-only and queue failures, one quest per flake, fixed at the cause, since each one fails a whole batch +- [Backport the dot-segment and auth-redirect refusals](/quest/m0/backport-release-hardening.md) - `release`'s relay refuses dot-segment paths and its auth client refuses redirects diff --git a/quest/m0/backport-release-hardening.md b/quest/m0/backport-release-hardening.md new file mode 100644 index 00000000000..979924a53a8 --- /dev/null +++ b/quest/m0/backport-release-hardening.md @@ -0,0 +1,24 @@ +# [S] Backport the dot-segment and auth-redirect refusals to release + +## Goal + +The `release` branch's relay refuses connection paths with `.` or `..` segments before asking the auth server, and `release`'s auth client refuses redirects from the auth server, matching main. The fixes ship in the next patch release a human cuts. + +## Plan + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). + +- **Dot segments:** [moq-dev/moq#7366](https://github.com/moq-dev/moq/pull/7366) makes `Auth::admit` in `rs/moq-relay/src/auth.rs` refuse any path segment that is `.` or `..`, percent-encoded or not. Before it, a raw-QUIC SETUP path like `/tenant-a/../tenant-b?jwt=b` reached the auth server unresolved. Port it once #7366 merges. +- **Auth redirects:** on `release`, `rs/moq-auth/src/client.rs:35` builds the client without a redirect policy, so reqwest follows up to 10 redirects and re-sends the credential's JSON body on a 307/308. Main refuses since [moq-dev/moq#6384](https://github.com/moq-dev/moq/pull/6384) (`redirect::Policy::none()`, plus an error naming the redirect). Found by the agent on [moq-dev/moq#7298](https://github.com/moq-dev/moq/pull/7298), which fixes the cluster client but not this one. +- Hand-port rather than cherry-pick where `release` lacks main's structure, as [moq-dev/moq#7389](https://github.com/moq-dev/moq/pull/7389) did, with a regression test for each on `release`. +- Version bumps stay with a human: several backports combine into one patch release, so this PR bumps nothing. + +Public API: none. Wire: none. + +## Required + +- [Tenant matrix](/quest/m2/tenant-matrix.md) - its PR, #7366, lands the dot-segment refusal on main + +## Related + +- [Backport the SETUP-path and iroh-key fixes](/quest/m0/backport-security-fixes.md) - the earlier backport in the same patch release diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index 67d0fd4d449..c3cd076801c 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -139,3 +139,6 @@ The historical samples locate candidates; they are not controlled benchmarks. - [Cargo aliases in nested checkouts](/quest/m0/ci/cargo-alias-strings.md) - `cargo build-ffi` and `cargo moq` work from a worktree nested inside another checkout - [Release credentials off main](/quest/m0/ci/publish-release-only.md) - `publish` admits only `release` and release tags once its `workflow_run` publishers run on their tag - [VM checks refuse a host without KVM](/quest/m0/ci/vm-kvm.md) - `check flake` fails fast with a clear message instead of an emulated-boot timeout +- [Biome checks every file it can parse](/quest/m0/ci/biome-whole-repo.md) - Biome runs over the whole repository and main is clean under it +- [workspace.test.py ignores the caller's gate filter](/quest/m0/ci/workspace-test-env.md) - the dispatcher test passes under any `just gate` module filter +- [PRs build the feature shapes they can break](/quest/m0/ci/pr-feature-builds.md) - a PR touching a crate's source runs that crate's isolated feature builds diff --git a/quest/m0/ci/biome-whole-repo.md b/quest/m0/ci/biome-whole-repo.md new file mode 100644 index 00000000000..3311f2b55eb --- /dev/null +++ b/quest/m0/ci/biome-whole-repo.md @@ -0,0 +1,17 @@ +# [S] Biome checks every file it can parse + +## Goal + +`just check` runs Biome over the whole repository, not only `js`, `bench/browser`, `test`, `doc`, and `demo`, and main is clean under it: the 16 problems Biome reports today in `.github/`, `cpp/`, `rs/`, and `sh/` are fixed. + +## Plan + +Found by the agent on [moq-dev/moq#7305](https://github.com/moq-dev/moq/pull/7305). Biome's paths are listed in `sh/js/workspace.py:201` and `js/justfile:45`, so these files are never checked: + +- JSON formatting: `.github/ci-selection-audit.json`, `cpp/obs/buildspec.json`, `rs/hang/fixtures/*.json`, `rs/moq-pattern/tests/pattern.json`. +- TS formatting: `sh/gh/cpp.test.ts`, `sh/size/bundles.ts`. +- `noTemplateCurlyInString` in `sh/gh/setup.test.ts:109-119`, whose plain strings hold GitHub's `${{ ... }}`; suppress it there with a reason rather than rewrite the fixtures. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Fix the files and widen the paths in one PR so the gate stays green. Exclude generated or vendored trees (fixtures written by tools, `node_modules`, build output) through `biome.json` rather than by listing directories in two scripts. The bigint `useMathMinMax` false positive in Biome 2.6 belongs to #7305; no upstream report was approved. + +Public API: none. Wire: none. diff --git a/quest/m0/ci/pr-feature-builds.md b/quest/m0/ci/pr-feature-builds.md new file mode 100644 index 00000000000..eff9176572e --- /dev/null +++ b/quest/m0/ci/pr-feature-builds.md @@ -0,0 +1,18 @@ +# [S] PRs build the feature shapes they can break + +## Goal + +A PR that narrows which features compile a function's callers fails the merge gate when it leaves that function unused in some isolated feature build, instead of breaking Nightly's `rs features` job after it lands. + +## Plan + +Found by the agent on [moq-dev/moq#7380](https://github.com/moq-dev/moq/pull/7380): only Nightly runs every isolated `moq-tokio` feature build (`just rs tokio-features --all`), plus PRs that change feature definitions. [moq-dev/moq#7120](https://github.com/moq-dev/moq/pull/7120) fixed one unused helper and missed `Addr::with_url`, because nothing on the PR built `--features webrtc` alone. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Select the isolated builds for a PR from the crates its diff touches, not only from changes to feature tables: a diff touching `rs/moq-tokio/src` runs moq-tokio's isolated shapes. Keep the gate's time budget from [the five-minute quest](/quest/m0/ci/five-minutes.md): if every shape is too slow, run them through `cargo check` (not tests), and only for crates whose source changed. Measure the added wall time on the queue runner and record it in the PR. + +Public API: none. Wire: none. + +## Related + +- [Gate each diff in under five minutes](/quest/m0/ci/five-minutes.md) - the time budget this must fit in +- [Unify dependency features](/quest/m0/ci/hakari.md) - shares the feature-shape plumbing diff --git a/quest/m0/ci/workspace-test-env.md b/quest/m0/ci/workspace-test-env.md new file mode 100644 index 00000000000..ef712a9d368 --- /dev/null +++ b/quest/m0/ci/workspace-test-env.md @@ -0,0 +1,13 @@ +# [XS] workspace.test.py ignores the caller's gate filter + +## Goal + +`sh/js/workspace.test.py` passes under any `just gate` module filter, because it no longer inherits `CHECK_MODULES` and the other gate and CI variables from the shell that runs it. + +## Plan + +Found by the agent on [moq-dev/moq#7356](https://github.com/moq-dev/moq/pull/7356). `test_dispatcher_passes_only_js_impact_paths` builds its environment as `dict(os.environ, ...)` (`sh/js/workspace.test.py:65`) and runs `sh/dispatch.sh ci` (`:74`). Under a filter that drops the `js` module, such as `just gate py`, the stub `just` never writes `calls` and `:75` fails. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Pop the same keys `sh/dispatch-query.test.py:33-37` already pops (`CHECK_MODULES`, `CHECK_JOBS`, `IN_MOQ_DEV_SHELL`, `GITHUB_ACTIONS`, `GITHUB_STEP_SUMMARY`, `GITHUB_BASE_REF`), ideally through one shared helper both tests use. + +Public API: none. Wire: none. diff --git a/quest/m0/nightly/README.md b/quest/m0/nightly/README.md index edab3d0a04c..f74e2ae04a0 100644 --- a/quest/m0/nightly/README.md +++ b/quest/m0/nightly/README.md @@ -42,3 +42,4 @@ Public API: none. Wire: none. - [Firefox's player closes every VideoFrame](/quest/m0/nightly/firefox-videoframe.md) - the Media job's Firefox player stops leaking an unclosed VideoFrame - [moq-tokio's isolated feature builds pass](/quest/m0/nightly/tokio-features.md) - `Incoming::refuse` stops failing `tokio-features --all` on dead_code, so the rest of `rs features` runs - [moq-uring's tests build on macOS and Windows](/quest/m0/nightly/kio-e0433.md) - the platform jobs stop failing with E0433 on `kio` in moq-uring's integration tests +- [@moq/hang's libav tests pass on main](/quest/m0/nightly/hang-libav-tests.md) - the two libav tests pass, or skip with a reason when the libav build is missing diff --git a/quest/m0/nightly/hang-libav-tests.md b/quest/m0/nightly/hang-libav-tests.md new file mode 100644 index 00000000000..52a663e07d5 --- /dev/null +++ b/quest/m0/nightly/hang-libav-tests.md @@ -0,0 +1,15 @@ +# [S] @moq/hang's libav tests pass on main + +## Goal + +The two tests in `js/hang/src/util/libav.test.ts` pass under `just ci` on main, or are skipped with a stated reason when the libav build they need isn't available, instead of failing. + +## Plan + +Reported by the agent on [moq-dev/moq#7061](https://github.com/moq-dev/moq/pull/7061): `just ci` passed except these two tests, which also failed on main's identical JS tree. Reproduce first; the cause isn't confirmed. + +The likely cause: both tests call `polyfill()` (`js/hang/src/util/libav.ts:16`), which imports `@moq/libav`. That package's entry point is `./dist/libav-opus-aac-af.mjs`, and `dist/` exists only after `js/libav/build.ts` runs `nix build .#moq-libav`. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). If that is the cause, make the test depend on the build the way other generated artifacts do: build it in the gate's JS setup, or skip with a message naming the missing build outside the dev shell. Don't commit the built WebAssembly. + +Public API: none. Wire: none. diff --git a/quest/m1/README.md b/quest/m1/README.md index a6a89d23d21..a1f1e015946 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -186,6 +186,12 @@ removed). - [moqsrc max-delay](/quest/m1/gst-max-delay.md) - GStreamer's `moqsrc` takes a `max-delay` property instead of a hard-coded 1 s budget - [moq-clock moves into kio::clock](/quest/m1/kio-clock.md) - moq-clock and its test macro become `kio::clock` and `#[kio::test]` before the cut publishes them - [Poll-first async](/quest/m1/poll-first/README.md) - library crates compile no coroutines by default: async wrappers are generated from `poll_*` and gated behind an `async` feature +- [HLS export follows a resolution change](/quest/m1/hls-reinit-same-codec.md) - a decoder-identity change under the same codec string starts a new init and segment tag +- [The last AAC segment advertises its full duration](/quest/m1/hls-final-aac-segment.md) - a TS import's final audio segment's EXTINF matches what decodes +- [A JS reader finishes after a clean close](/quest/m1/js-close-range-finish.md) - a range ending below the cache delivers what's held and then ends +- [fMP4 decode times keep increasing](/quest/m1/fmp4-bframe-decode-time.md) - fragments shorter than a GOP of B-frame video never step `tfdt` backwards +- [A JS relay floors an unresolved copy at its newest group](/quest/m1/js-early-latest.md) - lite-01..05 subscribes without a start don't replay the cache from group 0 +- [moq-quic deletes its remaining 0-RTT code](/quest/m1/quic-zero-rtt-leftovers.md) - only the code that drops a 0-RTT packet remains, with a test ## Related diff --git a/quest/m1/fmp4-bframe-decode-time.md b/quest/m1/fmp4-bframe-decode-time.md new file mode 100644 index 00000000000..30789138dea --- /dev/null +++ b/quest/m1/fmp4-bframe-decode-time.md @@ -0,0 +1,13 @@ +# [M] fMP4 fragments shorter than a GOP keep decode times increasing + +## Goal + +With B-frame video and a fragment duration shorter than a GOP, every fragment the fMP4 exporter writes has a `tfdt` at or after the previous fragment's end, so `moq export fmp4`, HLS packaging, and an unfinished `moq export mp4` file are valid fragmented MP4. + +## Plan + +Found by the agent on [moq-dev/moq#7357](https://github.com/moq-dev/moq/pull/7357): with `--fragment-duration` shorter than a GOP of B-frame video, the exporter writes fragment decode times that step backwards. The finished `export mp4` file is correct because it rebuilds its sample tables, but the fragmented output isn't. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). The fix is in how the exporter infers B-frame sample durations and decode times, which `export fmp4`, HLS packaging, and `export mp4` share; fix it there once rather than per consumer. Test with reordered video cut into fragments shorter than a GOP: decode times never decrease, presentation order and A/V timing are unchanged, and ffprobe reads every fragment without warnings. + +Public API: none. Wire: none. diff --git a/quest/m1/hls-conformance.md b/quest/m1/hls-conformance.md index dd4afbcf664..b0d21709e1a 100644 --- a/quest/m1/hls-conformance.md +++ b/quest/m1/hls-conformance.md @@ -29,6 +29,15 @@ coverage. Decided with the maintainer on 2026-10-24: Public API: none. Wire: none. +Decided 2026-10-30 (maintainer): fix first. The two bugs the test found +are their own quests, required below, and the test lands once it passes +with both checks intact. + +## Required + +- [HLS export follows a resolution change](/quest/m1/hls-reinit-same-codec.md) - the conformance test's same-codec switch fails until it lands +- [The last AAC segment advertises its full duration](/quest/m1/hls-final-aac-segment.md) - the conformance test's audio check fails until it lands + ## Related - [Export sync flags](/quest/m2/intra-refresh/export-sync-flags.md) - the sync flags a refresh rendition should advertise diff --git a/quest/m1/hls-final-aac-segment.md b/quest/m1/hls-final-aac-segment.md new file mode 100644 index 00000000000..43b3a4505a1 --- /dev/null +++ b/quest/m1/hls-final-aac-segment.md @@ -0,0 +1,17 @@ +# [S] The last AAC segment after a TS import advertises its full duration + +## Goal + +After an MPEG-TS import ends, the final AAC segment's `EXTINF` and DASH `S@d` match what decodes, as they already do for the plain producer path. + +## Plan + +Found by the conformance test in [moq-dev/moq#7387](https://github.com/moq-dev/moq/pull/7387): the last audio segment advertises 1.365 s but 1.387 s decodes, one AAC frame short. The plain producer path gets it right, so the gap is somewhere in the TS import's end handling (likely the last frame's duration, which the importer infers from the next timestamp and has none for at the end). + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Find the root cause before changing anything; fix it at the source rather than padding the advertised duration. The conformance test's audio check is the regression test. + +Public API: none. Wire: none. + +## Related + +- [HLS and DASH segment conformance](/quest/m1/hls-conformance.md) - the test that found it, which waits on this diff --git a/quest/m1/hls-reinit-same-codec.md b/quest/m1/hls-reinit-same-codec.md new file mode 100644 index 00000000000..d00d94ff06b --- /dev/null +++ b/quest/m1/hls-reinit-same-codec.md @@ -0,0 +1,17 @@ +# [M] HLS export follows a resolution change under the same codec string + +## Goal + +When a rendition changes resolution (or any other decoder-affecting field) while keeping its codec string, the HLS and DASH export starts a new init segment and segment tag for it, so every served segment decodes under the init it is listed with, the rendition's first segments are listed, and its recording cursor keeps up. + +## Plan + +Found by the conformance test in [moq-dev/moq#7387](https://github.com/moq-dev/moq/pull/7387): after `video1` switches from 320x180 to 480x270 with the same codec string, the export keeps the old segment tag and init. The 480x270 segments are served under the 320x180 init with no in-band parameter sets, so they don't decode, that rendition's first segments are never listed, and its recording cursor fails with `RecordingLagged`. When the codec string also changes, the importer makes a new rendition name and everything passes. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Treat a change of the rendition's decoder identity (the same fields hang's `same_identity` compares) as a new instance in the export, as a codec-string change already is. Reuse the existing instance-replacement path rather than adding a second one. The conformance test's `video1` case is the regression test; it must pass with the same-codec switch restored. + +Public API: none. Wire: none. + +## Related + +- [HLS and DASH segment conformance](/quest/m1/hls-conformance.md) - the test that found it, which waits on this diff --git a/quest/m1/js-close-range-finish.md b/quest/m1/js-close-range-finish.md new file mode 100644 index 00000000000..401705a72a0 --- /dev/null +++ b/quest/m1/js-close-range-finish.md @@ -0,0 +1,13 @@ +# [S] A JS reader whose range ends below the cache finishes after a clean close + +## Goal + +In `@moq/net`, after a track closes cleanly, a reader whose range ends below groups the cache still holds receives those groups and then finishes, as in Rust, instead of waiting forever for the range to widen. + +## Plan + +Found by the agent on [moq-dev/moq#7349](https://github.com/moq-dev/moq/pull/7349) while writing its tests; the bug is already on main. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Reproduce with a test in `js/net` first, then fix the reader's end condition: once the track is closed, a range that can no longer grow is complete when every group in it is delivered or known missing. Check Rust's equivalent reader with the same scenario and add the test there too if it lacks one. + +Public API: none. Wire: none. diff --git a/quest/m1/js-early-latest.md b/quest/m1/js-early-latest.md new file mode 100644 index 00000000000..b005bd2dece --- /dev/null +++ b/quest/m1/js-early-latest.md @@ -0,0 +1,13 @@ +# [S] A JS relay starts an unresolved copy's subscription at its newest group + +## Goal + +On lite-01..05, a SUBSCRIBE with no start group served by a JS publisher from a track copy that has no groups yet starts at the newest group once the copy resolves, instead of replaying the whole cache from group 0, matching the Rust fix in [moq-dev/moq#7328](https://github.com/moq-dev/moq/pull/7328). + +## Plan + +Found by the agent on [moq-dev/moq#7328](https://github.com/moq-dev/moq/pull/7328). `cursorFloor` (`js/net/src/lite/publisher.ts:419-423`) returns `latest ?? 0` and is called once right after subscribe (`:890`) with `hooks.latestSequence(track)` (`js/net/src/track.ts:2249`); a related fallback is at `:1290`. Rust's fix re-reads `latest_sequence()` once the track resolves. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Port the Rust rule rather than invent a JS one: an unresolved copy defers the floor until it resolves, then floors at the newest group. Test a JS relay serving a copy that resolves after the SUBSCRIBE arrives. + +Public API: none. Wire: none. diff --git a/quest/m1/quic-zero-rtt-leftovers.md b/quest/m1/quic-zero-rtt-leftovers.md new file mode 100644 index 00000000000..0cca73b79fa --- /dev/null +++ b/quest/m1/quic-zero-rtt-leftovers.md @@ -0,0 +1,20 @@ +# [XS] moq-quic deletes its remaining 0-RTT code + +## Goal + +moq-quic has no 0-RTT code left beyond what it needs to recognize and drop a 0-RTT packet: no Data-space mapping for 0-RTT, no early-data guards or tests, and a test proving a peer's 0-RTT packets are dropped unprocessed. + +## Plan + +The maintainer deleted 0-RTT entirely (see [quic-api](/quest/m1/quic-api/README.md)): MoQ needs the finished handshake for CONNECT and authorization. [moq-dev/moq#6837](https://github.com/moq-dev/moq/pull/6837) already drops 0-RTT before decryption (`rs/moq-quic/src/endpoint.rs:215-227`, `rs/moq-quic/src/connection/packet_crypto.rs:16-19`), so the "ACK in 0-RTT" report on [moq-dev/moq#7334](https://github.com/moq-dev/moq/pull/7334) isn't reachable on main. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run): a standalone m1 cleanup. Remove what's left: + +- `PartialDecode::space()` and `Header::space()` mapping 0-RTT to the Data space, and `is_0rtt()` (`rs/moq-quic/src/packet.rs:82-95`, `:391-393`), keeping only the `LongType::ZeroRtt` decode needed to skip the packet. +- The no-op `LongType::ZeroRtt` arm in `rs/moq-quic/src/connection/mod.rs:2812-2814`. +- The rustls early-data guards and their test (`rs/moq-quic/src/crypto/rustls.rs:295-296, 355-356, 420-421, 458-459, 605-681`) if rustls's defaults already leave early data off; otherwise keep the guard and its test. +- The characterize test `early_packets_leave_handshake_and_application_data_usable` (`rs/moq-quic/src/tests/characterize/frames.rs:38-110`) becomes a test that a 0-RTT packet, ACK included, is dropped; fix the comment at `rs/moq-uring/tests/integration/endpoint.rs:290`. + +`process_early_payload` handles Initial and Handshake packets, not 0-RTT; leave it. + +Public API: none. Wire: none. diff --git a/quest/m2/README.md b/quest/m2/README.md index 4f4f2452b0a..a6ec3f47689 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -272,3 +272,14 @@ lite-07; bitrate enforcement is not part of this release. - [Resume delivers late groups below its resume point](/quest/m2/resume-late-groups.md) - a bonus: groups that arrive late after a route change are delivered instead of written off - [The HLS server follows each path once](/quest/m2/hls-follow-once.md) - `Following` reports synced absence, so moq-hls resolves with one follow - [moq-bench pins the instances it measures](/quest/m2/bench-epoch-pin.md) - pooled requests pin their announced epoch, so a takeover is not counted as the same broadcast +- [We own our WebRTC stack](/quest/m2/webrtc/README.md) - moq-rtc becomes moq-webrtc with moq-ice, the data-channel transport, and a vendored, trimmed str0m/dimpl/is/sctp-proto +- [Certificate reload and qmux's tasks are supervised](/quest/m2/tls-reload-supervision.md) - a panic in either fails loudly instead of silently stopping +- [PUBLISH_DONE says why](/quest/m2/publish-done-codes.md) - GOING_AWAY, TOO_FAR_BEHIND, and EXPIRED round-trip as stream codes +- [An interrupted mp4 recording keeps its last fragment](/quest/m2/mp4-sigint-flush.md) - SIGINT flushes the buffering fragment before finishing +- [Repair a crashed mp4 recording](/quest/m2/mp4-repair.md) - a crashed recording's fragmented file becomes a regular MP4 +- [Windows receives notice truncated control data](/quest/m2/cmsg-windows-truncation.md) - Windows handles `MSG_CTRUNC` like the Unix paths +- [moq-quic's internal dev-dependencies are path-only](/quest/m2/quic-dev-dep-path.md) - moq-congestion is a path dev-dependency +- [moq-mux's AAC config reports SBR](/quest/m2/aac-sbr-config.md) - moq-audio reads backward-compatible SBR from the config +- [An ended HLS playlist has no trailing gap](/quest/m2/hls-trailing-gap.md) - no zero-duration `EXT-X-GAP` before `EXT-X-ENDLIST` +- [moq-video's Apple Sink encodes on its own thread](/quest/m2/apple-sink-thread.md) - macOS and iOS ladders never encode on a tokio worker +- [The test inventory sees JS bench scripts](/quest/m2/js-bench-inventory.md) - JS bench entry points are resolved by the inventory gate diff --git a/quest/m2/aac-sbr-config.md b/quest/m2/aac-sbr-config.md new file mode 100644 index 00000000000..a90ea5606ec --- /dev/null +++ b/quest/m2/aac-sbr-config.md @@ -0,0 +1,17 @@ +# [S] moq-mux's AAC config reports backward-compatible SBR + +## Goal + +`moq_mux::codec::aac::Config` reports the backward-compatible SBR extension of an AudioSpecificConfig, so moq-audio reads it from there and stops walking the AudioSpecificConfig itself. + +## Plan + +Follow-up of [moq-dev/moq#7398](https://github.com/moq-dev/moq/pull/7398), which moved moq-audio's SBR check onto `bitstream-io` rather than add moq-mux API. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Add the field to moq-mux's public config and delete moq-audio's own parse; test an implicit and an explicit SBR config. + +Public API: an additive field on `moq_mux::codec::aac::Config`. Wire: none. + +## Required + +- [Parse H.264 and codec config bits with maintained crates](/quest/m2/h264-reader.md) - its PR, #7398, rewrites both readers diff --git a/quest/m2/apple-sink-thread.md b/quest/m2/apple-sink-thread.md new file mode 100644 index 00000000000..a48e3a7861e --- /dev/null +++ b/quest/m2/apple-sink-thread.md @@ -0,0 +1,17 @@ +# [M] moq-video's Apple Sink encodes on its own thread + +## Goal + +On macOS and iOS, a `moq_video::encode::Sink` resizes and encodes on its own thread, as on other platforms, so a forced-CPU or software-decoded transcode ladder never blocks a tokio worker. + +## Plan + +Follow-up of [moq-dev/moq#7375](https://github.com/moq-dev/moq/pull/7375), which moved rung resizing into the encoder's thread everywhere except Apple, where `Sink` runs inline. The comment in `sink.rs` saying `CVPixelBuffer` is `!Send` is stale: `PixelBuffer` is `Send`. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Give Apple the same threaded Sink as the other backends and delete the inline path and the stale comment. Needs a macOS host to verify: run the moq-video encoder tests and a transcode ladder with a forced CPU path, and record that the tokio worker stays free. + +Public API: none. Wire: none. + +## Required + +- [Transcode rungs](/quest/m2/transcode-rungs.md) - its PR, #7375, adds the encoder-side resize diff --git a/quest/m2/cmsg-windows-truncation.md b/quest/m2/cmsg-windows-truncation.md new file mode 100644 index 00000000000..87a082cbaa5 --- /dev/null +++ b/quest/m2/cmsg-windows-truncation.md @@ -0,0 +1,17 @@ +# [XS] Windows receives notice truncated control data + +## Goal + +moq-sock's Windows receive path detects truncated control data (`MSG_CTRUNC`) and keeps the packet with whatever metadata fit, logging once, as the Unix paths do. + +## Plan + +Follow-up of [moq-dev/moq#7394](https://github.com/moq-dev/moq/pull/7394), which did this for `recvmmsg`, `recvmsg`, and Apple's `recvmsg_x`. Windows's 128-byte buffer already fits what it uses, so this is defensive. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Match #7394's handling; verify with a cross-target `cargo check` and the platform workflow, since no Windows host runs locally. + +Public API: none. Wire: none. + +## Required + +- [Bound cmsg payload reads](/quest/m2/cmsg-decode-bound.md) - its PR, #7394, adds the Unix handling this mirrors diff --git a/quest/m2/fill-wait-cancel-test.md b/quest/m2/fill-wait-cancel-test.md index 024533d9a27..5cd3cc04464 100644 --- a/quest/m2/fill-wait-cancel-test.md +++ b/quest/m2/fill-wait-cancel-test.md @@ -23,3 +23,7 @@ that group is the documented contract and a late FETCH risks the duplicate group #7135 fixed. Public API: none. Wire: none. + +## Required + +- [The browser backend baseline completes on IETF22](/quest/m0/nightly/browser-baseline.md) - its PR, #7135, adds the wait this tests diff --git a/quest/m2/hls-trailing-gap.md b/quest/m2/hls-trailing-gap.md new file mode 100644 index 00000000000..19058e514f5 --- /dev/null +++ b/quest/m2/hls-trailing-gap.md @@ -0,0 +1,15 @@ +# [XS] An ended HLS playlist has no trailing gap + +## Goal + +A media playlist that ends with `EXT-X-ENDLIST` no longer lists a zero-duration `EXT-X-GAP` segment after its last real segment. + +## Plan + +Found by the conformance test in [moq-dev/moq#7387](https://github.com/moq-dev/moq/pull/7387), which allows the gap because the export writes it by design: a track's end writes a zero-duration stop record. Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Keep the stop record; omit it from the rendered playlist when `EXT-X-ENDLIST` follows, and drop the conformance test's allowance. + +Public API: none. Wire: none. + +## Related + +- [HLS and DASH segment conformance](/quest/m1/hls-conformance.md) - the test that allows the gap today diff --git a/quest/m2/js-bench-inventory.md b/quest/m2/js-bench-inventory.md new file mode 100644 index 00000000000..2f7b2220d98 --- /dev/null +++ b/quest/m2/js-bench-inventory.md @@ -0,0 +1,17 @@ +# [XS] The test inventory sees JS bench scripts + +## Goal + +The test-inventory gate resolves the JS bench scripts Nightly runs, so a JS bench entry point no workflow runs is flagged like any other opt-in target. + +## Plan + +Found by the agent on [moq-dev/moq#7316](https://github.com/moq-dev/moq/pull/7316): Nightly runs every JS bench through the `javascript` helper as `"$@"` (`sh/gh/nightly.sh:66-72`, the loop at `:138-155`), which the checker can't resolve, and `test-inventory.py` skips scripts under `/bench/`. Every JS bench entry point has a caller today, so this closes a coverage gap in the checker, not in CI. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Make the bench list something the checker can read (a static list Nightly loops over, or a recipe per bench) rather than teaching the checker shell indirection, and stop skipping `/bench/`. + +Public API: none. Wire: none. + +## Required + +- [Every opt-in test target has a CI caller](/quest/m2/test-inventory.md) - its PR, #7316, adds the checker diff --git a/quest/m2/mp4-repair.md b/quest/m2/mp4-repair.md new file mode 100644 index 00000000000..555ddfb351b --- /dev/null +++ b/quest/m2/mp4-repair.md @@ -0,0 +1,17 @@ +# [M] Repair a crashed mp4 recording + +## Goal + +`moq` can turn the fragmented file a crashed or twice-interrupted `moq export mp4` left behind into a regular MP4, with the same layout a clean finish produces, including renditions that joined mid-recording. + +## Plan + +Follow-up of [moq-dev/moq#7357](https://github.com/moq-dev/moq/pull/7357). Its finish step already rebuilds the sample tables by parsing the written moofs back; reuse that indexer rather than writing a second one. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Late renditions are written inside `free` boxes that only the final `moov` indexes, so they're unrecoverable today: the recorder must also write each late rendition's track description into the file at the time it joins. Test a file cut at several points (mid-fragment, after a late rendition joins) and check the repaired file decodes like a clean finish of the same data. + +Public API: a CLI subcommand or flag (pick the shape in the PR and list it). Wire: none. + +## Required + +- [moq export mp4](/quest/m2/mp4-export.md) - its PR, #7357, adds the recorder and the moof indexer diff --git a/quest/m2/mp4-sigint-flush.md b/quest/m2/mp4-sigint-flush.md new file mode 100644 index 00000000000..37818429f44 --- /dev/null +++ b/quest/m2/mp4-sigint-flush.md @@ -0,0 +1,17 @@ +# [S] An interrupted mp4 recording keeps its last fragment + +## Goal + +On the first SIGINT or SIGTERM, `moq export mp4 --output` writes the fragment it is still buffering before finishing the file, so an interrupted recording loses nothing it had received. + +## Plan + +Follow-up of [moq-dev/moq#7357](https://github.com/moq-dev/moq/pull/7357), which drops up to one buffering fragment on SIGINT. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Flush through the fMP4 exporter's normal fragment path so the flushed fragment is indexed by the final `moov` like any other; extend #7357's nightly SIGINT test to check the last frames received before the signal are in the file. + +Public API: none. Wire: none. + +## Required + +- [moq export mp4](/quest/m2/mp4-export.md) - its PR, #7357, adds the recorder diff --git a/quest/m2/p2p/native-quic.md b/quest/m2/p2p/native-quic.md index 4d05a99735e..6c17e237e7c 100644 --- a/quest/m2/p2p/native-quic.md +++ b/quest/m2/p2p/native-quic.md @@ -21,7 +21,9 @@ Decided with the maintainer on 2026-10-13: candidates with `moq_tokio::webrtc`'s host gathering, `.local` resolution, and the STUN Binding client in `moq-ice`; don't copy them. The first two are private to the data channel transport today, so move them into - `moq-ice` rather than export them from `moq-tokio`. + `moq-ice` rather than export them from `moq-tokio`. Decided 2026-10-30 (maintainer): `moq-ice`, + the data-channel transport, and a vendored `is` all move into + `moq-webrtc` ([rename](/quest/m2/webrtc/rename.md)); take them from there. - QUIC is `moq-quic`, fed datagrams from the nominated pair. - Authentication: each side generates a self-signed certificate and sends its SHA-256 fingerprint in its offer or answer, as DTLS fingerprints ride diff --git a/quest/m2/publish-done-codes.md b/quest/m2/publish-done-codes.md new file mode 100644 index 00000000000..78efe9abe11 --- /dev/null +++ b/quest/m2/publish-done-codes.md @@ -0,0 +1,17 @@ +# [S] PUBLISH_DONE says why a subscription ended + +## Goal + +A moq-transport PUBLISH_DONE carries GOING_AWAY (0x4), TOO_FAR_BEHIND (0x5), and EXPIRED (0x6) when those are the reason, and a subscriber reads each back as its stream code, in Rust and JS, so the bindings report why instead of an unspecified remote error. + +## Plan + +Follow-up of [moq-dev/moq#7396](https://github.com/moq-dev/moq/pull/7396), which did this for MALFORMED_TRACK (0x12). The stream-reset codes already map both ways in `rs/moq-net/src/ietf/error.rs:30-75`; only PUBLISH_DONE doesn't use them. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Reuse #7396's path: the publisher picks the PUBLISH_DONE code from the same error-to-code mapping that resets the track's streams, and the subscriber reads it back as the peer's stream code. Cover drafts 18 and 22, both bindings, and JS, each test failing without its fix. + +Public API: none. Wire: existing registered codes only. + +## Required + +- [A malformed track says so downstream](/quest/m2/malformed-track-status.md) - its PR, #7396, adds the PUBLISH_DONE code path this extends diff --git a/quest/m2/quic-dev-dep-path.md b/quest/m2/quic-dev-dep-path.md new file mode 100644 index 00000000000..cffa75c5034 --- /dev/null +++ b/quest/m2/quic-dev-dep-path.md @@ -0,0 +1,11 @@ +# [XS] moq-quic's internal dev-dependencies are path-only + +## Goal + +moq-quic's dev-dependency on moq-congestion uses a path, as every internal dev-dependency should, so publishing moq-quic never needs a matching moq-congestion release. + +## Plan + +Found by the agent on [moq-dev/moq#7377](https://github.com/moq-dev/moq/pull/7377): moq-quic's `[dev-dependencies]` take moq-congestion with `workspace = true`. Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). Switch it to a path and check no other crate has the same pattern (`cargo metadata`, or grep the `[dev-dependencies]` tables). + +Public API: none. Wire: none. diff --git a/quest/m2/quic-egress-profile.md b/quest/m2/quic-egress-profile.md index 54c16ff7cf5..1d35c388071 100644 --- a/quest/m2/quic-egress-profile.md +++ b/quest/m2/quic-egress-profile.md @@ -13,6 +13,11 @@ Decided in the 2026-09-30 audit: send batching, kernel pacing, and send buffer pools merged into this one measure-first quest, since all three are questions about the same egress path and share one profile. +Decided 2026-10-30 (maintainer): the profile also covers moq-quic's +ingress and egress (`just bench-profile cpu` on `io-uring-workers`), since +[quic-io-boundary](/quest/m2/quic-io-boundary.md) waits on it to rank its +candidates. + Measure first, on the chat and fanout shapes with many connections, tokio workers versus io_uring workers, with the existing profiling captures: diff --git a/quest/m2/quic-io-boundary.md b/quest/m2/quic-io-boundary.md index 7f19f0b1051..87de27fc0b2 100644 --- a/quest/m2/quic-io-boundary.md +++ b/quest/m2/quic-io-boundary.md @@ -58,6 +58,6 @@ settled once; registering buffers stays with #3204, which measures whether it pays. Report relay CPU per Gbps before and after on the same workloads. -## Related +## Required -- [Relay egress profile](/quest/m2/quic-egress-profile.md) - its send-buffer pool lever touches the same `SendBuffer` seam +- [Relay egress profile](/quest/m2/quic-egress-profile.md) - its moq-quic profile ranks this quest's candidates, and its send-buffer pool shares the `SendBuffer` seam diff --git a/quest/m2/rind/rtc.md b/quest/m2/rind/rtc.md index 76a9e6cda06..eac425c11f7 100644 --- a/quest/m2/rind/rtc.md +++ b/quest/m2/rind/rtc.md @@ -3,7 +3,7 @@ ## Goal moq-rtc and moq-ice run str0m and dimpl on `rind-str0m` and `rind-dimpl`: -DTLS 1.2 and 1.3, SRTP (AES-CM-HMAC-SHA1-80 and AEAD AES-128/256-GCM), STUN +DTLS 1.2, SRTP (AES-CM-HMAC-SHA1-80 and AEAD AES-128/256-GCM), STUN MESSAGE-INTEGRITY, and DTLS certificates through `rind-rcgen`. Their graphs hold no aws-lc-rs or str0m-aws-lc-rs, and a browser still connects. @@ -15,9 +15,17 @@ Decided with the maintainer on 2026-10-26; see the and reqwest switches to its no-provider feature. The existing browser interop tests prove the switch. +Decided 2026-10-30 (maintainer): the stack is vendored into moq-webrtc +([epic](/quest/m2/webrtc/README.md)) with DTLS 1.2 only, so this quest +changes the vendored crypto provider directly and offers no DTLS 1.3. + Public API: none. Wire: unchanged; the same SRTP profiles and DTLS suites are offered. ## Required - [rind covers MoQ](/quest/m2/rind/covered.md) - the rind release this switches to + +## Related + +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - where str0m and dimpl live once vendored diff --git a/quest/m2/rtc-pacing.md b/quest/m2/rtc-pacing.md index 037e3fd6327..ac5c7dc0fdc 100644 --- a/quest/m2/rtc-pacing.md +++ b/quest/m2/rtc-pacing.md @@ -30,8 +30,11 @@ WHEP enforcement needs it and nobody owned it. and a resend with no credit left is skipped, so faking loss only spends the credit the viewer's next group would have used. str0m fulfils NACKs from its video send buffer (`rs/moq-rtc/src/session.rs`), so charging needs a - hook on that path: find one in str0m 0.24's API, or propose one upstream - to str0m with the maintainer's approval. + hook on that path. Decided 2026-10-30 (maintainer): add it to the vendored str0m + ([WebRTC epic](/quest/m2/webrtc/README.md)) rather than upstream. +- Decided 2026-10-30 (maintainer): the pacer is moq-congestion's, made public and + extended with payload credit here, so moq-quic and moq-webrtc share one + pacer; the vendored str0m's leaky pacer is deleted. - A per-session rate handle sets the cap and accepts live updates. The pacer counts media; the docs advise headroom for RTP overhead. - Tests: a capped viewer never receives more than cap plus burst, including @@ -42,6 +45,10 @@ WHEP enforcement needs it and nobody owned it. Public API: a rate handle on moq-rtc's WHEP server. Wire: none. +## Required + +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - the vendored str0m this adds the NACK hook to + ## Related - [moq-rtc REMB and TIAS](/quest/m2/rtc-remb-tias.md) - the WHIP half of the same caps diff --git a/quest/m2/tls-reload-supervision.md b/quest/m2/tls-reload-supervision.md new file mode 100644 index 00000000000..82e49ec5e9a --- /dev/null +++ b/quest/m2/tls-reload-supervision.md @@ -0,0 +1,20 @@ +# [S] Certificate reload and qmux's tasks are supervised + +## Goal + +A panic in moq-tokio's certificate reload watcher fails the listener loudly instead of silently stopping certificate rotation, and a panic in one of qmux's own session or WebSocket tasks is logged and counted like moq-tokio's per-connection tasks. + +## Plan + +Left over from [moq-dev/moq#7360](https://github.com/moq-dev/moq/pull/7360), which supervises the other long-lived tasks. + +Decided 2026-10-30 (maintainer, follow-up planning after the /quest-afk run). + +- **Reload watcher** (`rs/moq-tokio/src/tls.rs`, `ReloadState`/`Reloading`): run it under the same supervision #7360 gives a dead QUIC endpoint, so its panic surfaces as an error from `Listener::accept`. +- **qmux** spawns with plain `tokio::spawn` (`rs/qmux/src/session.rs:486-487`, `rs/qmux/src/transport.rs:188,985,1195,1223,1244`) and can't reach `moq_tokio::panic`. Give qmux a small isolation hook of its own (or take one from its caller) so a panic is caught, logged, and counted, and the session closes with an error rather than hanging. + +Public API: none expected; if qmux needs a hook in its public API, bring the shape to the maintainer. Wire: none. + +## Required + +- [Task supervision](/quest/m2/task-supervision.md) - its PR, #7360, adds the supervisor and `panic::isolate` this extends diff --git a/quest/m2/webrtc/README.md b/quest/m2/webrtc/README.md new file mode 100644 index 00000000000..bb1a64a81f9 --- /dev/null +++ b/quest/m2/webrtc/README.md @@ -0,0 +1,33 @@ +# We own our WebRTC stack + +## Goal + +All of MoQ's WebRTC lives in one published crate, `moq-webrtc` (today's `moq-rtc` renamed), holding the WHIP/WHEP gateway, the ICE driver from `moq-ice`, the native data-channel transport from `moq-tokio`, and vendored copies of str0m, dimpl, `is`, and sctp-proto. Fixes to the stack land in normal MoQ PRs instead of waiting on upstream, and code MoQ doesn't use is gone. + +Non-goals: TURN; DTLS 1.3; replacing a vendored layer before there's a reason to. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview), after the str0m workarounds piled up: one `b=` line per SDP media section ([moq-dev/moq#7373](https://github.com/moq-dev/moq/pull/7373)), DTLS retransmits running out before ICE connects ([moq-dev/moq#7379](https://github.com/moq-dev/moq/pull/7379)), no hook to charge or serve NACK resends ([rtc-pacing](/quest/m2/rtc-pacing.md)), and `std::time::Instant` in a moq-clock workspace. No upstream reports were approved; vendoring replaces them. + +- **Vendor, then replace.** Copy the upstream crates in, trim them, and fix them in-tree. Replacing a layer with our own (SDP, RTP/RTCP, SRTP, SCTP) is a later child once a concrete need appears; none is planned yet. +- **One crate.** `moq-ice` merges into it, since its only users are `moq-rtc` and moq-tokio's data-channel transport. moq-tokio's `webrtc` feature moves into `moq-webrtc` as well, which breaks the cycle (`moq-webrtc` depends on moq-tokio; moq-tokio would otherwise need ICE and SCTP). The vendored crates become private modules, not separate published crates, keeping their MIT/Apache-2.0 notices (str0m's `packet/` carries webrtc-rs's Apache notice). +- **What goes** (from the 2026-10-30 inventory: we use frame mode, full ICE, and DTLS 1.2, and never enable str0m's BWE, pacer, or stats): GCC bandwidth estimation (8k lines; GCC is being ported into [moq-congestion](/quest/m2/quic-realtime/gcc.md)), the leaky pacer (replaced by moq-congestion's pacer, made public by [rtc-pacing](/quest/m2/rtc-pacing.md)), H.266, RED, DTMF, G.7xx and comfort noise, VLA and simulcast layers, RTCP XR and app feedback, stats, internal test exports, and dimpl's DTLS 1.3 and version auto-selection. Roughly 17k of str0m's 72k lines and 11k of dimpl's 33k. +- **DTLS 1.2 only.** Every browser negotiates 1.2 today. [moq-rtc on rind](/quest/m2/rind/rtc.md) narrows to 1.2 to match. +- **SCTP stays**, for browser data channels in [P2P](/quest/m2/p2p/README.md), but compiles only with the data-channel feature, so WHIP/WHEP builds carry none of it. Vendored, it can later be simplified or put on moq-congestion. +- **Upstream fixes:** after vendoring, nobody merges upstream automatically. Record the vendored upstream versions in the crate docs; the release-cut audit checks the upstream changelogs for security fixes (malformed SRTP/RTCP input, for example) and ports them. +- Docs a change makes stale (crate names, `doc/lib/rs`, `doc/setup/upgrade.md`, other quests naming `moq-rtc` or `moq-ice`) change in the PR that makes them stale. + +## Required + +- [Rename moq-rtc to moq-webrtc](/quest/m2/webrtc/rename.md) - one crate holds the gateway, the ICE driver, and the data-channel transport +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - str0m, dimpl, `is`, and sctp-proto live in moq-webrtc with unused code removed +- [Several bandwidth lines per media section](/quest/m2/webrtc/sdp-bandwidth.md) - answers carry both `b=TIAS` and `b=AS` without splicing +- [DTLS waits for ICE](/quest/m2/webrtc/dtls-after-ice.md) - DTLS flights start once ICE nominates a pair, so a late ICE path can't exhaust them +- [The stack takes moq-clock time](/quest/m2/webrtc/clock.md) - the vendored code takes `moq_clock::Instant`, so sim tests drive it directly + +## Related + +- [moq-rtc paces WHEP viewers](/quest/m2/rtc-pacing.md) - adds the NACK hook and the shared pacer on the vendored stack +- [moq-rtc on rind](/quest/m2/rind/rtc.md) - the vendored DTLS and SRTP take rind's crypto +- [GCC candidate](/quest/m2/quic-realtime/gcc.md) - where bandwidth estimation lives instead of str0m diff --git a/quest/m2/webrtc/clock.md b/quest/m2/webrtc/clock.md new file mode 100644 index 00000000000..cc37d57fc3b --- /dev/null +++ b/quest/m2/webrtc/clock.md @@ -0,0 +1,15 @@ +# [XS] The WebRTC stack takes moq-clock time + +## Goal + +The vendored str0m and dimpl take `moq_clock::Instant` instead of `std::time::Instant`, so moq-webrtc drops its `into_std()` conversions and sim tests drive the stack's timers directly. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview). Every call into str0m converts with `into_std()` today (see the third-party clocks note in [time-test-sweep](/quest/m2/time-test-sweep.md)). Change the vendored modules' time type, delete the conversions, and move any moq-webrtc test that pauses tokio time onto the sim. + +Public API: none. Wire: none. + +## Required + +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - the modules whose time type changes diff --git a/quest/m2/webrtc/dtls-after-ice.md b/quest/m2/webrtc/dtls-after-ice.md new file mode 100644 index 00000000000..099861228f4 --- /dev/null +++ b/quest/m2/webrtc/dtls-after-ice.md @@ -0,0 +1,15 @@ +# [S] DTLS waits for ICE + +## Goal + +DTLS sends its first flight only once ICE has nominated a pair, so a peer whose ICE connects late (near the 30 s ICE deadline) still completes the DTLS handshake, and a session that never connects ends with `IceTimeout`. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview). Today DTLS starts before ICE connects and dimpl gives up after 1+2+4+8+16 s with jitter (`dimpl/src/config.rs:90-92`), racing moq-ice's 30 s deadline; [moq-dev/moq#7379](https://github.com/moq-dev/moq/pull/7379) papered over the race by reporting an early DTLS failure as a timeout. Hold DTLS activation (around str0m's `lib.rs:1493`) until nomination, then remove #7379's remapping if nothing else needs it. Test on the paused clock: ICE nominating at 29 s still completes DTLS. + +Public API: none. Wire: none. + +## Required + +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - the DTLS activation this moves diff --git a/quest/m2/webrtc/rename.md b/quest/m2/webrtc/rename.md new file mode 100644 index 00000000000..ceb61ee99f4 --- /dev/null +++ b/quest/m2/webrtc/rename.md @@ -0,0 +1,20 @@ +# [M] Rename moq-rtc to moq-webrtc + +## Goal + +The published `moq-webrtc` crate holds what `moq-rtc`, `moq-ice`, and moq-tokio's `webrtc` feature hold today: the WHIP/WHEP gateway, the ICE driver, and the native data-channel connect/accept. `moq-rtc` and `moq-ice` are gone from the workspace, and moq-tokio has no `webrtc` feature. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview); see the [epic](/quest/m2/webrtc/README.md). + +- Move `rs/moq-rtc` to `rs/moq-webrtc` and merge `rs/moq-ice` in as a module. Keep the gateway's `server` feature; add a feature for the data-channel transport so WHIP/WHEP-only builds skip SCTP. +- Move moq-tokio's `webrtc` module (`rs/moq-tokio/src/webrtc.rs`, host gathering, `.local` resolution) into moq-webrtc, built on moq-tokio's public session types. If the move needs something moq-tokio keeps private, bring the shape to the maintainer. +- Update moq-cli's `rtc` subcommand, the bindings or examples that use the transport, `test/p2p`, and every doc and quest naming the old crates. +- Publishing: the last `moq-rtc`/`moq-ice` releases stay on crates.io; `doc/setup/upgrade.md` says where things moved. Version numbers are the maintainer's. + +Public API: breaking (crate rename; `moq_tokio::webrtc` moves). Wire: none. + +## Related + +- [moq-rtc REMB and TIAS](/quest/m2/rtc-remb-tias.md) - its open PR, #7373, edits moq-rtc; whichever lands second resolves the move diff --git a/quest/m2/webrtc/sdp-bandwidth.md b/quest/m2/webrtc/sdp-bandwidth.md new file mode 100644 index 00000000000..56f1261099d --- /dev/null +++ b/quest/m2/webrtc/sdp-bandwidth.md @@ -0,0 +1,16 @@ +# [S] Several bandwidth lines per media section + +## Goal + +The vendored SDP parser and writer accept and emit several `b=` lines per media section, so a capped WHIP answer carries both `b=TIAS` and `b=AS` natively and moq-webrtc stops string-splicing `b=TIAS` into the answer. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview). str0m 0.24 reads `optional(bandwidth_line())` (`sdp/parser.rs:357`) into `bw: Option` (`sdp/data.rs:137,251`), so a second line made the whole answer unparseable and [moq-dev/moq#7373](https://github.com/moq-dev/moq/pull/7373) shipped TIAS only through `sdp::cap_answer`. Make it a `Vec`, emit both lines on a capped answer as the original Goal asked, delete the splice, and test that our own WHIP client parses the answer. + +Public API: none. Wire: capped answers gain `b=AS`. + +## Required + +- [Vendor and trim the stack](/quest/m2/webrtc/vendor.md) - the parser this changes +- [moq-rtc REMB and TIAS](/quest/m2/rtc-remb-tias.md) - its PR, #7373, adds the capped answer diff --git a/quest/m2/webrtc/vendor.md b/quest/m2/webrtc/vendor.md new file mode 100644 index 00000000000..fa6eeccfef6 --- /dev/null +++ b/quest/m2/webrtc/vendor.md @@ -0,0 +1,20 @@ +# [L] Vendor and trim the WebRTC stack + +## Goal + +moq-webrtc builds str0m, dimpl, `is`, and sctp-proto from private in-tree modules instead of crates.io, with the code MoQ doesn't use removed, DTLS 1.2 only, and every existing WHIP, WHEP, data-channel, and browser interop test still passing. + +## Plan + +Decided 2026-10-30 with the maintainer (planning interview); see the [epic](/quest/m2/webrtc/README.md) for what goes and why. + +- Vendor the exact versions in the lockfile (str0m 0.24.1, str0m-proto, `is` 0.11.1, dimpl 0.7.4, sctp-proto 0.10.5), first unchanged in one commit so later diffs show only our changes, then trim. +- Remove: str0m's `bwe/`, leaky pacer, H.266, RED, DTMF, G.7xx and comfort noise, VLA and simulcast layers, RTCP XR and app feedback, stats, `_internal_test_exports`, the crypto backends other than the one rind will replace (keep the provider trait until [moq-rtc on rind](/quest/m2/rind/rtc.md)); dimpl's `dtls13/` and `auto.rs`. Keep upstream's tests for what remains. +- Keep the license files and record each upstream version in the module docs. +- Drop the crates.io dependencies on str0m and friends from the workspace once nothing uses them. + +Public API: none beyond what [rename](/quest/m2/webrtc/rename.md) changed. Wire: unchanged; the same DTLS 1.2 suites and SRTP profiles are offered. + +## Required + +- [Rename moq-rtc to moq-webrtc](/quest/m2/webrtc/rename.md) - the crate the stack is vendored into