Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ edge, which embeds moq-relay; it moved there on 2026-09-28.
- [CI cost and latency](/quest/m0/ci/README.md) - cloud CI runs only in GitHub's merge queue, one tested entry at a time, 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 drops a queued PR
- [Agent build output can't fill the desktop's disk](/quest/m0/agent-disk-budget.md) - finished agent checkouts reclaim their mbx targets automatically, so parallel agent builds stop filling the root disk
- [GOAWAY redirects to loopback are refused in every spelling](/quest/m0/goaway-loopback.md) - Rust's `is_local` decodes `moqt://127.1`-style hosts as the dial does, and both clients' fixes reach `release`
39 changes: 39 additions & 0 deletions quest/m0/goaway-loopback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# [S] GOAWAY redirects to loopback are refused in every spelling, on main and release

## Goal

A GOAWAY from a non-local relay cannot steer a `redirect: "follow"` client,
and the tokens it carries, to a loopback, private, or link-local address,
whatever literal spelling the URI uses. A hostname that resolves to such
an address is out of scope: classifying DNS results would race the dial's
own lookup. Rust's `is_local` judges a non-special
scheme's host the way the native dial resolves it, as js/net's typed endpoint
does since [moq-dev/moq#8222](https://github.com/moq-dev/moq/pull/8222), and
both fixes reach the `release` line in its next patch.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8222](https://github.com/moq-dev/moq/pull/8222)). Milestone:
m0, since it is a hostile-input fix in shipped code plus a `release`
backport.

- Rust on `main`: `is_local` in `rs/moq-net/src/reconnect/redirect.rs`
parses a `Host::Domain` with `str::parse::<IpAddr>`, which refuses
shorthand and numeric IPv4 (`127.1`, `0x7f000001`, `2130706433`), so
`moqt://127.1` counts as non-local while the native dial may still reach
127.0.0.1. Classify the host the dial will use: decode the IPv4 spellings
WHATWG's host parser accepts (as js/net reads a non-special host back
through https), and treat a trailing-dot `localhost.` as `localhost`, as
`js/net/src/connection/endpoint.ts` does.
- Test both sides from one fixture of endpoint spellings and expected
verdicts, replayed by `rs/moq-net` and `js/net`, so the two classifiers
cannot drift again. Put it beside the other shared corpora (see
[one fixture layout](/quest/m4/corpus-fixtures.md)).
- Backport to `release`: js/net's GOAWAY classification
(`js/net/src/connection/goaway.ts` `isLocal` on the opaque `moql:`/`moqt:`
host) and Rust's (`rs/moq-tokio/src/connection.rs` `is_local` there) both
have the hole. Port the smallest fix to each as PRs into `release`; the
maintainer cuts the patch.

Public API: none. Wire: none. Behavior: such redirects are refused.
1 change: 1 addition & 0 deletions quest/m1/audio-jitter-target/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ native playback against its budgets.
- [Native stretch](/quest/m1/audio-jitter-target/native-stretch.md) - `moq play` copies NetEq's accelerate and preemptive expand, sharing the browser's algorithm and vectors, so its native rows reach zero steady-state underruns
- [Buffered converge](/quest/m1/audio-jitter-target/buffered-converge.md) - a buffered `<moq-watch>` time-stretches its audio back onto the shared clock after a stall or drift, so lip sync returns and no audio group is dropped
- [Capped budgets](/quest/m1/audio-jitter-target/capped-budgets.md) - the AAC browser budgets are re-measured from nightlies after the TS importer groups each PES
- [Native rendition delay](/quest/m1/audio-jitter-target/native-rendition-delay.md) - `moq play` holds each subscribed rendition's catalog `delay` plus its target, as the browser does

## Closes

Expand Down
26 changes: 26 additions & 0 deletions quest/m1/audio-jitter-target/native-rendition-delay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# [S] moq play holds each rendition's catalog delay

## Goal

`moq play` sets its playout from the catalog as the browser does:
`max over subscribed renditions of (delay + target)`
(`doc/concept/audio-jitter.md`), so a rendition whose publisher states a
`delay`, such as a slow video encoder, plays in sync with audio instead of
behind it.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8272](https://github.com/moq-dev/moq/pull/8272), which gives
moq-mux's playout sync per-track targets and the largest-target delay). Milestone:
m1, an additive fix to shipped playback; start after #8272 merges.

On `main`, nothing under `rs/moq-cli/src/play/` reads a rendition's
`delay`; the browser does (`js/watch/src/video/playhead.ts`,
`js/watch/src/audio/decoder.ts`). With #8272, each track's target is what it
holds through `Track::set_target`: add the rendition's catalog `delay` to it
(video holds `delay` alone, since it has no estimator), recompute when a
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
rendition is subscribed, dropped, or its catalog entry rises, and test that a
200 ms video `delay` delays audio to match. A fixed `--delay` stays fixed.

Public API: none. Wire: none.
1 change: 1 addition & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,5 @@ quests that require them.
- [Cut readiness](/quest/m2/cut-gate/README.md) - the checks the cut needs that break nothing: main no slower than release, vendored fixes ported, and moq-c at parity
- [Probe](/quest/m2/probe/README.md) - a session probes up to the bitrate it wants to send or receive, so a stopped simulcast rendition resumes and a viewer up-switches; browser padding is parked in m3 (held in m2 by open claims; moves to m3)
- [Finish the milestone re-cut](/quest/m2/recut-held.md) - claim-held epics move to the milestone their kind and priority give them once their claims merge
- [moq-tokio's resolved config reports "off" as None](/quest/m2/tokio-resolved-options.md) - resolved timeouts and backoff accessors use `Option` while the text knobs keep `0` = off
- [Nix clusters authenticate leaves with mTLS](/quest/m2/nix-cluster-mtls.md) - a clustered NixOS relay authenticates leaves with cluster-CA client certificates and `--mtls-link peer`, and the module stops minting an unbounded cluster JWT
1 change: 1 addition & 0 deletions quest/m2/cluster-routing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ setup after the capabilities land.

- [Outbound federation](/quest/m2/cluster-routing/federation.md) - an outbound-only local relay exchanges authorized broadcasts with the CDN and survives partition/healing
- [Routes and announces](/quest/m2/cluster-routing/routes/README.md) - implement the proven route layer with Rust/JS parity and tiered-cluster acceptance
- [A relay refuses a lite-07 session with itself](/quest/m2/cluster-routing/lite07-self-dial.md) - the self-dial refusal lite-06 has, on lite-07, whose SETUP carries no Hop ID

## Related

Expand Down
27 changes: 27 additions & 0 deletions quest/m2/cluster-routing/lite07-self-dial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# [S] A relay refuses a lite-07 session with itself

## Goal

A relay whose cluster peer URL reaches itself under a spelling `PeerKey`
misses, such as an IP for its hostname, refuses that session on lite-07 as it
already does on lite-05, lite-06, and moq-transport.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8225](https://github.com/moq-dev/moq/pull/8225)). Milestone:
m2, since the likely fix changes lite-07-wip's SETUP, which the cut
publishes.

`rs/moq-relay/src/connection.rs` refuses a session whose `peer_hop()` is
this relay's own Hop ID, but lite-07 SETUP carries no Hop ID
(`lite::Version::has_routes`), so the check never fires there. The route key
cannot stand in: relays may share one on purpose.

- Decide, with the maintainer: a per-process node identity in lite-07 SETUP
(a wire change, lands while lite-07 is wip), or a local check that this
relay's own route came back over the session (no wire change; would move
this to m1).
- Test a relay dialing itself by IP on lite-07.

Public API: none expected. Wire: possibly a lite-07-wip SETUP field.
33 changes: 33 additions & 0 deletions quest/m2/tokio-resolved-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# [S] moq-tokio's resolved config reports "off" as None

## Goal

moq-tokio's resolved values and accessors say "off" with `Option`, not zero,
finishing what
[moq-dev/moq#8265](https://github.com/moq-dev/moq/pull/8265) does for moq-net:
`connect::Resolved.timeout`, `connection::Backoff::{max, timeout}()`, the
GOAWAY handover, and `quic::Resolved.idle_timeout`. The text knobs (CLI,
TOML, env, FFI, C) keep `0` = off, since they need three states and absence
already means "use the default"; the conversion happens once, where they
resolve.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8265](https://github.com/moq-dev/moq/pull/8265), which makes
moq-net's reconnect policy take `Option` and keeps moq-tokio's config layer
on zero). Milestone: m2, since it breaks moq-tokio's public API; start after
#8265 merges.

On `main`: `Backoff::max()` documents "zero leaves the delay uncapped" and
`timeout()` "zero meaning unlimited" (`rs/moq-tokio/src/connection.rs`);
`connect::Resolved.timeout` is a `Duration` whose `--connect-timeout` says
"zero disables this duration" (`rs/moq-tokio/src/connect.rs`); the handover
knob says the same; and `quic::Resolved.idle_timeout` is a `Duration` passed to the transport as
`Some(resolved.idle_timeout)` (`Some(0)` is QUIC's "no idle timeout"), with
zero turned into `None` only in `Resolved::keep_alive_interval`
(`rs/moq-tokio/src/quic.rs`); making the field `Option` replaces both. Audit the
other `Resolved` structs for the same pattern while there.

Public API: moq-tokio's resolved fields and accessors become `Option`.
Migrate: compare against `None` instead of `Duration::ZERO`. Wire: none.
4 changes: 4 additions & 0 deletions quest/m4/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ deleted.
- [P2P](/quest/m4/p2p/README.md) - application-selected peer sessions use shared priorities, routing, and same-epoch handover
- [Challenge proof for certificate credentials](/quest/m4/cert-challenge.md) - a relay can require a signed nonce, so a certificate credential replayed within its time window admits nothing
- [Benchmark sources](/quest/m4/bench-sources.md) - every measuring nightly benchmark feeds the regression table, and none is a smoke run
- [The fan-out benchmark's session scenario builds again](/quest/m4/bench-fanout-session.md) - `just bench-fanout` measures sessions, not only the model
- [A browser round-trips media through moq-webrtc](/quest/m4/whip-whep-browser.md) - Chromium and Firefox publish over WHIP and play over WHEP nightly
- [Shared corpora live in each crate's fixtures/](/quest/m4/corpus-fixtures.md) - moq-auth's corpus moves beside moq-net's
- [An MV-HEVC stream with access unit delimiters stays one frame per access unit](/quest/m4/h265-layered-aud.md) - x265's `--aud` layered output imports, or is refused by name
- [HLS and DASH browser playback](/quest/m4/hls-player-test.md) - hls.js and dash.js play, reload, switch, and survive reconfiguration nightly
- [Cache fair share](/quest/m4/cache-fairness.md) - one tenant cannot crowd another out of the relay's shared group cache
- [Peer grant bound](/quest/m4/peer-grant-bound.md) - a peer's AUTH_OK costs near-linear time in its bytes on every input shape, in Rust and in a browser
Expand Down
24 changes: 24 additions & 0 deletions quest/m4/bench-fanout-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [XS] The fan-out benchmark's session scenario builds again

## Goal

`just bench-fanout` measures its `session` scenario against current `main`
and a base revision, as it does `model`, so a fan-out cost change in the
session layer shows up in its numbers.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8243](https://github.com/moq-dev/moq/pull/8243), whose
measurement could not run the scenario). Milestone: m4, a benchmark nobody
waits on; no Nightly job runs it.

`bench/fanout/session/src/main.rs` imports `moq_net::Hop`, `pub(crate)` since
[moq-dev/moq#8023](https://github.com/moq-dev/moq/pull/8023), and
`origin::Config::new` now takes a `NonZeroU64`. Build the config through the
public constructor (`NonZeroU64::new(hop)`), not a new export. The harness
builds against each revision's own crates, so a base older than #8023 still
needs the old spelling: either pick the constructor by what the revision
exposes in `bench/run.sh`, or state the earliest base it measures.

Public API: none. Wire: none.
23 changes: 23 additions & 0 deletions quest/m4/corpus-fixtures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# [XS] Shared corpora live in each crate's fixtures/

## Goal

Every JSON corpus a Rust crate and a JS package both replay, except the
audio jitter corpora under `doc/concept/`, lives in that crate's `fixtures/`
directory, so a contributor finds them in one place.
`rs/moq-auth/tests/corpus.json` moves to `rs/moq-auth/fixtures/`, matching
`rs/moq-net/fixtures/lite.json`, `rs/hang/fixtures/`, and
`rs/moq-msf/fixtures/`.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up). Milestone: m4, tooling
nobody waits on.

- Move the file and update its readers: `rs/moq-auth/src/corpus.rs`
(`include_str!` and the module doc) and `js/auth/src/corpus.test.ts`.
- `tests/` keeps only integration test targets (`released_token.rs`).
- The audio jitter corpora under `doc/concept/audio-jitter/` stay: the spec
document cites them as its own conformance data, not one crate's.

Public API: none. Wire: none.
28 changes: 28 additions & 0 deletions quest/m4/h265-layered-aud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# [S] An MV-HEVC stream with access unit delimiters stays one frame per access unit

## Goal

moq-mux imports an x265 MV-HEVC stream encoded with `--aud` as one frame per
access unit, as it already does without `--aud`, or refuses it with an error
that names the cause, rather than splitting each view into its own frame.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8199](https://github.com/moq-dev/moq/pull/8199)). Milestone:
m4, a feature with no named consumer yet.

x265 4.2 with `--num-views 2 --aud` writes a layer-0 AUD between the base
and enhancement views. `rs/moq-mux/src/codec/h265/split.rs` closes the open
access unit on any `NalType::AUD`, which H.265 F.7.4.2.4.4 requires for a
layer-0 AUD, so each view becomes its own frame.

- First find out whether that stream conforms. If x265 is wrong, report it
(with the [upstream reports](/quest/m4/upstream-reports.md)) and make the
splitter refuse that specific violation with an error naming it, instead
of adding a heuristic. Annex F allows an access unit with no layer-0
picture, so the refusal never rejects those by itself.
- If it conforms, frame by what the spec says starts an access unit and add
the x265 `--aud` fixture beside #8199's.

Public API: none. Wire: none.
8 changes: 8 additions & 0 deletions quest/m4/qmux-02-subprotocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ Decided 2026-11-03 (maintainer, /quest-plan of the follow-ups from
need qmux-01 or later), and extend it to allow 02.
- Move the version list into one place that the relay and moq-tokio share,
as the comment beside `QMUX01_ONLY_ALPNS` asks.
- Added 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8224](https://github.com/moq-dev/moq/pull/8224)): the relay
now derives its subprotocols from `moq_net::Versions`, keeping every
moq-transport version off qmux-00 (`supported_subprotocols` in
`rs/moq-relay/src/websocket.rs`), but moq-tokio's dial side still keeps the
hand-written `QMUX01_ONLY_ALPNS = ["moqt-18", "moqt-20", "moqt-22"]`
(`rs/moq-tokio/src/websocket.rs`). Derive it from the version the same
way, so a new draft needs one edit.
- Test: a JS and a Rust client each negotiate qmux-02 against the relay and
a moq-tokio server. A client that offers only qmux-01 still connects.

Expand Down
7 changes: 7 additions & 0 deletions quest/m4/vaapi-render-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ which covers only NVIDIA.
10-bit stream, which is refused, since moq-vaapi has no P010 output
([moq-dev/vaapi#11](https://github.com/moq-dev/vaapi/pull/11)).
- Not chosen: dropping either gap.
- Added 2026-11-08 (maintainer, approved follow-up): moq-vaapi's own
tests return early too. `rs/moq-vaapi/src/encode.rs`, `decode.rs`, and
`vpp.rs` print "skipping: ..." and pass when there is no VA-API display,
no encoder or decoder for the codec, no VPP entrypoint, or no
ffmpeg/ffprobe, so a host without libva or a driver reports them green. Give
them the same `#[ignore = "requires ..."]` treatment and run them in
`just rs gpu` per render node.

## Related

Expand Down
24 changes: 24 additions & 0 deletions quest/m4/whip-whep-browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [S] A browser round-trips media through moq-webrtc

## Goal

A `test/` recipe publishes from Chromium and Firefox over WHIP to a relay with
moq-webrtc and plays the broadcast back over WHEP, with fake media, and
asserts from `getStats()` that DTLS and SRTP connected and the viewer decoded
frames. It guards the vendored WebRTC stack's crypto and negotiation against
real browsers.

## Plan

Planned 2026-11-08 (maintainer, approved follow-up of
[moq-dev/moq#8229](https://github.com/moq-dev/moq/pull/8229), which moved the
stack onto rind assuming browser interop tests existed). None drives a
browser against moq-webrtc today: no recipe in `test/` or workflow mentions
WHIP or WHEP. Milestone: m4, a test nobody waits on.

- Reuse the Playwright setup the interop and audio-quality harnesses use
(`test/interop/clients/js/`), with the browsers' fake capture devices.
- Run it where the other browser tests run; it is slow, so nightly, not the
merge gate.

Public API: none. Wire: none.
Loading