From a6b8135c06469fe5de0b2c27089d58bef5b2bd4b Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 10:58:04 -0700 Subject: [PATCH 1/4] quest: claim quest/m1/wt-close-upstream From 972b94d5121e2cf8f1f266ccfaf3a3c0d1f4bc29 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 11:06:31 -0700 Subject: [PATCH 2/4] quest: wt-close-upstream waits on the web-transport-moq release The fix is open upstream as moq-dev/noq#23 (2.x) and #24 (1.3.x). The pin bump and the CLOSE_LINGER removal wait for a published release. Co-Authored-By: Claude Opus 5.5 --- quest/m1/wt-close-upstream.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/quest/m1/wt-close-upstream.md b/quest/m1/wt-close-upstream.md index a277c17cb0..4e2cb62a11 100644 --- a/quest/m1/wt-close-upstream.md +++ b/quest/m1/wt-close-upstream.md @@ -1,4 +1,4 @@ -# [S] WebTransport close delivers its capsule upstream +# [XS] WebTransport close delivers its capsule upstream ## Goal @@ -20,21 +20,28 @@ detached task until `closed()` resolves, capped at a 10s `CLOSE_LINGER`. Decided 2026-09-29: fix it at the source and remove the timeout workaround. -- In moq-dev/noq, the close path keeps whatever the capsule needs alive until - it is delivered, without the caller's help. Its regression test drops the - session right after `close()` and asserts the peer reads the capsule - before the H3 control stream ends; it must fail on 1.3.2. A Rust peer - alone proves nothing: `session_close_surfaces_a_rejection_code` in - `rs/moq-tokio/tests/broadcast.rs` passed on 1.3.2 without the linger, - because only Chromium treats the control stream's end as fatal. Add a - browser check too if the playwright harness makes it cheap. -- Release `web-transport-moq` 1.3.x and bump the pin on `main` (2.x on `dev` - if it has moved). +The upstream fix makes the close task own a clone of the whole session: +[moq-dev/noq#23](https://github.com/moq-dev/noq/pull/23) for 2.x (`dev`) and +its backport [moq-dev/noq#24](https://github.com/moq-dev/noq/pull/24) for +1.3.x (`main`). Their `tests/close_capsule.rs` drops the session right after +`close()` against a hand-rolled HTTP/3 peer and fails on 1.3.2 and 2.0.0. + +Remaining, once the releases carrying them are published: + +- Bump the `web-transport-moq` pin on `main` (and on `dev` if 2.0.x lands + first). - Delete `CLOSE_LINGER`, its task, and its mock-session tests from - moq-tokio. + moq-tokio. They are exactly what #4429 added, so reverting it is enough. + +A browser check was skipped: the playwright harness has no close-code +scenario, so it is not cheap. Public API: none. Wire: none. +## Required + +- A `web-transport-moq` 1.3.x release that carries moq-dev/noq#24 + ## Related - [Close codes on every transport](/quest/m1/close-codes.md) - the same symptom over qmux and raw QUIC From 42f984258f540eea8778fc5b2570eaf0f35ba3d4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Tue, 29 Sep 2026 13:02:30 -0700 Subject: [PATCH 3/4] quest(m1): plan the browser close-code check Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 + quest/m1/browser-close-code.md | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 quest/m1/browser-close-code.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 55b4f0a17f..3e6616cc1f 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -29,6 +29,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 - [WebTransport close upstream](/quest/m1/wt-close-upstream.md) - web-transport-moq delivers the close capsule itself, and moq-tokio's `CLOSE_LINGER` is deleted +- [Browser close code](/quest/m1/browser-close-code.md) - a playwright case proves the page reads a relay's close code and reason, on every web-transport backend - [IETF hidden default](/quest/m1/ietf-hidden-default.md) - a moq-transport peer without the MoQ Hidden option is advertised hidden namespaces; one with it filters per subscription - [Watch refusal](/quest/m1/watch-refusal.md) - `` shows an origin refusal as an error instead of sitting offline - [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 diff --git a/quest/m1/browser-close-code.md b/quest/m1/browser-close-code.md new file mode 100644 index 0000000000..c4c75c6339 --- /dev/null +++ b/quest/m1/browser-close-code.md @@ -0,0 +1,35 @@ +# [M] A browser sees the close code on every WebTransport backend + +## Goal + +A browser interop case proves a relay's rejection reaches the page: the relay +closes the session with an application code and reason, and the page reads +both from `WebTransport.closed`. Every `moq-dev/web-transport` backend that +speaks WebTransport (noq, quinn, iroh) delivers the close capsule, verified by +that case or an equivalent test, and any that doesn't is fixed upstream. + +## Plan + +[WebTransport close upstream](/quest/m1/wt-close-upstream.md) fixed +`web-transport-moq`'s close path with a Rust regression test, but only +Chromium treats the H3 control stream ending early as fatal, so no Rust peer +proves the browser path. The playwright interop harness in `test/interop/` +has no close-code scenario yet. + +- Add the browser case: a relay (or `Request::reject`) closes with a code, and + the page asserts the code and reason. It should fail with the capsule fix + reverted. +- Audit `web-transport-noq`, `-quinn`, and `-iroh` for the same bug: a close + capsule sent from a task that doesn't keep the H3 control streams alive. + `web-transport-iroh` 0.7 sends no capsule at all, only a QUIC close; decide + there whether iroh's WebTransport path should send one. +- Fix upstream, release, and bump the pins, as the wt-close quest did. + +Decided with the maintainer: the browser case and the backend audit ship +together, since the case is what proves each backend. + +Public API: none. Wire: none, unless iroh starts sending the capsule. + +## Required + +- [WebTransport close upstream](/quest/m1/wt-close-upstream.md) - the noq fix and pin bump the browser case needs From 82c23e958caa32006110f8477a485803de2696a4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 30 Sep 2026 06:24:01 -0700 Subject: [PATCH 4/4] quest(m1): the browser quest waits on wt-close, not the other way round Grok's review flagged the past tense in the last plan bullet: wt-close has not released yet, and browser-close-code Required's it. Say so. --- quest/m1/browser-close-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quest/m1/browser-close-code.md b/quest/m1/browser-close-code.md index c4c75c6339..95df47c767 100644 --- a/quest/m1/browser-close-code.md +++ b/quest/m1/browser-close-code.md @@ -23,7 +23,7 @@ has no close-code scenario yet. capsule sent from a task that doesn't keep the H3 control streams alive. `web-transport-iroh` 0.7 sends no capsule at all, only a QUIC close; decide there whether iroh's WebTransport path should send one. -- Fix upstream, release, and bump the pins, as the wt-close quest did. +- Fix upstream, release, and bump the pins, once wt-close finishes. Decided with the maintainer: the browser case and the backend audit ship together, since the case is what proves each backend.