diff --git a/quest/m1/README.md b/quest/m1/README.md index 93cdfc7aad..d0d397df16 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -31,6 +31,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Close codes](/quest/m1/close-codes.md) - a client sees the peer's application close code over WebSocket and raw QUIC, like WebTransport - [Raw stream codes](/quest/m1/raw-stream-codes.md) - raw QUIC stream resets and stops carry the application's code, not an HTTP/3-mapped one - [Live in apps](/quest/m1/announce-live-apps.md) - the demo and `@moq/room` show "no broadcasts" from the `live` marker, which waits for the first session on page load +- [Watch refusal](/quest/m1/watch-refusal.md) - `` shows an origin refusal as an error instead of sitting offline - [kio waiter overflow](/quest/m1/kio-waiter-lost.md) - a retained `Waiter` past 8 lists stops adding a duplicate entry to lists it already recorded - [Capture re-anchor](/quest/m1/capture-reanchor.md) - a repeating or restarting device clock never rewinds native capture during a fast backlog drain - [Splice edge cases](/quest/m1/splice-edges.md) - an unstamped successor, a pruned segment's boundary group, and a warm head during a takeover are each handled correctly @@ -38,6 +39,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Session death parity](/quest/m1/session-death.md) - a local close ends tracks cleanly in both languages, and JS group readers see the session's error on session death - [moqsrc stop](/quest/m1/moqsrc-stop.md) - moqsrc's stop blocks until its session ends, without deadlocking on a blocked pad push - [More tests under load](/quest/m1/test-flakes-2.md) - the second round of load-only failures, fixed at the cause +- [Auth outage clock](/quest/m1/auth-outage-clock.md) - the relay and moq-auth outage tests run on a paused clock again and assert both bounds of `expires` - [Interop contention](/quest/m1/interop-contention.md) - two `just test interop --all` matrices pass side by side, and `just test harness` runs from a clean checkout - [UnknownSession log flood](/quest/m1/unknown-session-logs.md) - streams reset before their WebTransport header stop being reported as UnknownSession at WARN - [Merge queue](/quest/m1/merge-queue.md) - the required checks run on `merge_group`, so a stale green check can no longer break main @@ -54,6 +56,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Capture control](/quest/m1/capture-control.md) - on dev, `encode::Capture` replaces `CaptureOptions` without a `clock` field (it reads the catalog's), an unsupported `cut()` errors, and dropping the last `Control` cancels in-flight opens - [Video surface](/quest/m1/video-surface.md) - on dev, moq-ffi's `native` becomes `surface`, refused on platforms with no surface - [HLS discontinuity sequence](/quest/m1/hls-discontinuity-sequence.md) - on dev, `Segment::discontinuity` is the absolute sequence, so every cursor agrees +- [Auth client CA](/quest/m1/relay-auth-client-ca.md) - on dev, `auth::Config::validate` and `init` take the client-CA flag, so no caller can skip the check - [RTMP TLS only](/quest/m1/rtmp-tls-only.md) - an RTMP listener configured for TLS can refuse plaintext instead of sniffing and serving it - [HLS linger](/quest/m1/hls-linger.md) - `moq_hls::Server` serves an ended broadcast for its playlist window plus grace, so the moq.pro edge drops its own pool - [Remove live()](/quest/m1/remove-live.md) - on dev, importers publish stream timestamps verbatim, the catalog clock maps them to wall time, and an encoder restart becomes a new epoch diff --git a/quest/m1/auth-outage-clock.md b/quest/m1/auth-outage-clock.md new file mode 100644 index 0000000000..43c923a8b3 --- /dev/null +++ b/quest/m1/auth-outage-clock.md @@ -0,0 +1,63 @@ +# [M] Auth outage tests on a paused clock + +## Goal + +The moq-relay and moq-auth outage tests run on tokio's paused clock again and +assert both bounds: a session (or grant) survives an auth outage until its +`expires`, and closes at `expires`, not later. No wall-clock sleeps, no +widened timeouts, and no dependence on how fast the OS delivers loopback. + +## Plan + +- The tests: `an_outage_keeps_the_session_until_expires` in + `rs/moq-relay/tests/auth_lifetime.rs` + ([#4244](https://github.com/moq-dev/moq/pull/4244)) and + `an_outage_keeps_the_grant_until_expires` in `rs/moq-auth/src/client.rs` + ([#4291](https://github.com/moq-dev/moq/pull/4291)). Both moved to the real + clock because a paused clock auto-advances while the runtime waits on a + real socket, so a virtual timer fired before macOS delivered loopback. That + swapped one violation of "unit tests mock time" for another, and #4244 + dropped the upper bound. Read both PR descriptions: they list what was + tried and why it failed (restoring a listener probe, pausing after setup, + waiting on the log). +- The race is real sockets under virtual time, so fix it by taking the + sockets out of these tests. Look at what the codebase already offers + before building anything: `rs/moq-net/tests/support/mock.rs` (an in-memory + session pair), `moq_relay::auth::Auth::embedded` with its `Admissions` + (decides leases in-process), and the lease driver in moq-auth, which could + be exercised against an in-process answer source instead of HTTP. If the + relay's `Connection` or moq-auth's `Client` cannot take such a transport, + prefer the small seam that lets them over a test-only shim. +- Decide where each assertion belongs. The outage semantics (a 503 keeps the + grant until `expires`) are moq-auth's; the relay test may only need to show + that a lease reaching `expires` closes the session as `Expired` and reports + `end`. Don't keep two tests proving the same thing. +- Measure against tokio's clock, not `SystemTime`: the grant still carries a + wall-clock `expires`, so pin how it maps onto the paused clock. +- Other paused-clock tests touch real sockets and would share the hazard + once a timeout lands on their path. #4291's audit named + `a_grant_within_clock_skew_stays_live` (moq-auth) and + `fixed_addresses_keep_tls_name_and_request_host` (moq-tokio websocket); + moq-auth's `clock_server` helper exists only to keep axum on the paused + clock. Move those onto the same seam if it is cheap. +- Prove it: loop the tests with every core loaded, on macOS if available, + and mutate the deadline both ways (close early, close late) to see each + bound fail. + +Public API: none unless a transport seam is needed; report it if so. Wire: +none. + +## Related + +- [More tests under load](/quest/m1/test-flakes-2.md) - the same rule + applied to other load-only failures +- [moq-shaper virtual time](/quest/m1/shaper-virtual-time.md) - the same + paused-clock-versus-real-socket fight in moq-shaper +- [#4280](https://github.com/moq-dev/moq/pull/4280) - moq-archive and + moq-hls tests poll with real-clock sleeps, on the archive track-timeline + line +- [#4281](https://github.com/moq-dev/moq/pull/4281) - OBS `WaitFor` polling, + on the C++ line +- [Nightly 2026-09-26](https://github.com/moq-dev/moq/actions/runs/36240326747/job/108399481809) - + the macOS relay tarball job failed this test with "publisher connect + timeout", before #4244 landed diff --git a/quest/m1/relay-auth-client-ca.md b/quest/m1/relay-auth-client-ca.md new file mode 100644 index 0000000000..5393c341df --- /dev/null +++ b/quest/m1/relay-auth-client-ca.md @@ -0,0 +1,37 @@ +# [S] moq-relay auth validate takes the client-CA flag + +## Goal + +On dev, no caller of `moq_relay::auth::Config` can start with `--auth-public` +rules alongside a listener TLS client CA by forgetting a check. +`Config::validate` takes the client-CA flag, `init` requires it too, and +`validate_client_ca` is gone. The `moq` CLI fails loud on an invalid auth +config instead of quietly refusing every session. + +## Plan + +- [#4364](https://github.com/moq-dev/moq/pull/4364) adds an additive + `Config::validate_client_ca(&self, client_ca: bool)` that `Relay::load` and + the CLI must each remember to call; the CLI missing the original check is + the bug it fixes. Fold it into `validate(&self, client_ca: bool)` so every + caller has to answer, and have `init` take the same answer so a caller that + skips `validate` still cannot start. `init` today only receives the + outbound auth TLS, so the listener's client-CA answer is a new input; its + shape (a bool, or the listener TLS config) is open. Prefer whatever makes + the wrong call unrepresentable. +- `spawn_server` in `rs/moq-cli/src/main.rs` maps any `auth.validate()` error + to `Auth::refuse`. That fallback is only right for a LAN-only mesh with no + auth configured, which `MoqSide::validate` permits and whose peers admit + through the cluster. Make that case explicit and let any other error stop + startup. +- Update every caller, the tests #4364 added in both `moq-cli` and + `moq-relay`, and `doc/bin/relay/auth.md` or `doc/lib/rs` wherever they name + the methods. + +Public API: breaks `moq_relay::auth::Config::validate` and `init`, removes +`validate_client_ca`, so this targets `dev`. Wire: none. + +## Required + +- #4364 merged to `main` +- `dev` has merged `main` after #4364 lands diff --git a/quest/m1/watch-refusal.md b/quest/m1/watch-refusal.md new file mode 100644 index 0000000000..4412cf6113 --- /dev/null +++ b/quest/m1/watch-refusal.md @@ -0,0 +1,38 @@ +# [S] Watch shows a refusal + +## Goal + +When the origin refuses the broadcast `` asks for, the player shows +that refusal as an error instead of sitting offline as if nothing was +published yet. Refusal stays terminal, as +[#4230](https://github.com/moq-dev/moq/pull/4230) made it in `@moq/net` to +match Rust moq-net: the player never re-asks a handler that already said no. + +## Plan + +- The gap is Codex's P1 on #4230 + ([r4109909244](https://github.com/moq-dev/moq/pull/4230#discussion_r4109909244)): + `js/watch/src/broadcast.ts` only watches `request.active`, so after a + `dynamic()` handler refuses, the request closes with an error that nobody + reads and `active` stays `undefined` forever. +- Observe `Requesting.closed` and carry the error into the broadcast's + state. Whether that is a new `"error"` status, a separate error signal, or + both is open; mirror how the element already surfaces other terminal + states, such as the unsupported indicator. Keep the error's message so the + UI can say why. `unroutable` is also true for a path nothing serves yet, so + it cannot tell a refusal from offline. +- What clears the error is part of the design: a fresh request (a new + `name` or origin, or re-enabling) should be the only way back. No retry + loop. +- Cover both the announced and unannounced paths in `#runBroadcast`. +- Show it in the UI, and update `demo/web` if it consumes the status. Add a + test in `js/watch` where a `dynamic()` handler refuses and the broadcast + reports the error. +- Update `doc/` wherever the watch status values are documented. + +Public API: likely additive (a new status value or error signal on the watch +broadcast and element). Wire: none. + +## Related + +- [#4230](https://github.com/moq-dev/moq/pull/4230) - made JS refusals terminal