From 4a323dca4bf1d6455115827b95478a95d97eda2c Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 25 Sep 2026 12:11:30 -0700 Subject: [PATCH 1/2] quest: JS lite fetch waits for the publisher's answer Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 + quest/m1/js-fetch-answer.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 quest/m1/js-fetch-answer.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 93cb6d3458..9bd6d05171 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -19,6 +19,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Play harness](/quest/m1/play-harness.md) - moq play's tune-in, rendition-switch, and drain logic runs in per-PR CI without a device - [Missing fetch group](/quest/m1/fetch-missing-group.md) - HTTP /fetch answers 404 and `moq fetch` fails cleanly for a group the track lacks +- [JS fetch answer](/quest/m1/js-fetch-answer.md) - js/net's lite fetch settles on the publisher's answer, and a JS publisher's miss resets with NotFound - [libmoq hidden opt-in](/quest/m1/libmoq-hidden.md) - `moq_origin_announced` takes a `hidden` flag so C callers can list `.`-named broadcasts - [lite-07 stream count](/quest/m1/lite-stream-count.md) - moq-lite-07 replaces SUBSCRIBE_DROP with a group-stream count in SUBSCRIBE_END, like moq-transport - [Announce compression](/quest/m1/announce-compression.md) - a lite-07 announce reuses the path head and hop-chain tail of a live announcement on its stream instead of resending them diff --git a/quest/m1/js-fetch-answer.md b/quest/m1/js-fetch-answer.md new file mode 100644 index 0000000000..938eb62a16 --- /dev/null +++ b/quest/m1/js-fetch-answer.md @@ -0,0 +1,29 @@ +# [S] JS lite fetch waits for the publisher's answer + +## Goal + +`js/net`'s lite `fetchGroup` resolves only once the publisher has answered: +the first response byte, or a FIN for an empty group. A missing group rejects +the fetch itself, and every coalesced caller sees the same rejection, instead +of receiving a group whose first `readFrame()` fails. A JS publisher that +cannot serve a group resets the stream with `NotFound`, not a generic error, +so a Rust or JS subscriber can tell a miss from a failure. + +## Plan + +- Rust already behaves this way since #4164, which waits in the lite + subscriber before accepting and rejects on reset. Mirror it: the lite + subscriber's fetch path returns its mirror before the response arrives, and + the stream reader can already block until data or FIN and throw on reset. +- The publisher side throws a plain error for a local miss, which reaches the + wire as a generic reset code. Give it the `NotFound` code the Rust side uses. +- The IETF JS path refuses `fetchGroup` outright and is out of scope. +- Tests in the lite integration suite: a missing group rejects the fetch, a + coalesced second caller rejects too, an existing group is unchanged, and a + JS publisher's miss reaches a subscriber as `NotFound`. + +Public API: none; a behavior change in when `fetchGroup` settles. Wire: none. + +## Related + +- [#4164](https://github.com/moq-dev/moq/pull/4164) - the same fix in Rust From ef705dd78162cc83ee7cdcc5672372765cbb63ef Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 25 Sep 2026 15:35:18 -0700 Subject: [PATCH 2/2] quest: state the fetch wait and the NotFound reset The plan said to mirror Rust, then described today's early return. Resolve after the first byte or an empty-group FIN. A miss uses the existing NotFound code; that is not a wire format change. --- quest/m1/js-fetch-answer.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/quest/m1/js-fetch-answer.md b/quest/m1/js-fetch-answer.md index 938eb62a16..0e9bc28b3b 100644 --- a/quest/m1/js-fetch-answer.md +++ b/quest/m1/js-fetch-answer.md @@ -12,9 +12,10 @@ so a Rust or JS subscriber can tell a miss from a failure. ## Plan - Rust already behaves this way since #4164, which waits in the lite - subscriber before accepting and rejects on reset. Mirror it: the lite - subscriber's fetch path returns its mirror before the response arrives, and - the stream reader can already block until data or FIN and throw on reset. + subscriber before accepting and rejects on reset. Mirror it: resolve after + the first response byte or an empty-group FIN, and reject on reset. Today + the fetch path returns its mirror before the response arrives; the stream + reader can already block until data or FIN and throw on reset. - The publisher side throws a plain error for a local miss, which reaches the wire as a generic reset code. Give it the `NotFound` code the Rust side uses. - The IETF JS path refuses `fetchGroup` outright and is out of scope. @@ -22,7 +23,9 @@ so a Rust or JS subscriber can tell a miss from a failure. coalesced second caller rejects too, an existing group is unchanged, and a JS publisher's miss reaches a subscriber as `NotFound`. -Public API: none; a behavior change in when `fetchGroup` settles. Wire: none. +Public API: none; a behavior change in when `fetchGroup` settles. Wire: no +format change; a miss resets with the existing `NotFound` code instead of a +generic one. ## Related