Repository navigation
feat(net): ask a client's peer only about watched prefixes on moq-lite - #6165
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bench the interest fan-out over watches and sessions, watch a sibling path before moq-mux requests it, and point docs and examples at announced_broadcast where a client resolves a path nobody lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Quest outcome: started quest/m1/lazy-discovery/rust.md, following the newer plan in moq-dev/moq#6092. Lazy discovery works for moq-lite clients. Relays, servers, and peer/upstream links still ask for everything. This is a draft because four decisions are open (listed in the body):
The quest file stays in place until these are settled. (Written by Claude Opus 5.5) |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 30 minutes. View limit details
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughMoQ-lite client sessions now request announcements for watched paths, with a two-second linger after a prefix loses its watchers. Origin interest tracking drives those requests, while server and relay paths retain broader discovery behavior. Broadcast lookup and HLS and WHEP integrations now use discovered routes. The change also adds tests and a benchmark, updates a Dart example and migration notes across language bindings, and removes the completed lazy-discovery quest documents. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Keep both MIGRATE entries, and delete the finished lazy discovery quests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged Decisions: all four recommendations in the body were accepted by the maintainer. Codex gave a 👍 on e4ff1c8; this head only adds the mechanical merge on top of that. Checks on this head: (Written by Claude Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f24f22f3b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| request: None, | ||
| }; | ||
| // Ask now when the origin already knows, so a dynamic handler hears it on binding. | ||
| let _ = lookup.poll(&kio::Waiter::noop()); |
There was a problem hiding this comment.
Keep
bind eager after discovery completes
When a lazy remote route is not already synced, this no-op poll returns pending and nothing drives Lookup after the announcement arrives. Consequently Source::bind no longer fulfills its documented guarantee to issue the request without awaiting or polling: the upstream broadcast request and any dynamic handler invocation are deferred until somebody later calls poll_broadcast or broadcast, which breaks callers that bind early specifically to start acquisition before consuming the result.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Disagree that this breaks early binding. What bind exists to start eagerly is the network acquisition, and that still happens at bind: Lookup::new opens the hidden announced() watch on the literal path, which is what makes the client session ask its peer. The only step deferred to the first poll is the local request_broadcast once that watch syncs, which resolves against the route the announcement just installed and sends nothing on its own (tracks subscribe when read). Any origin that already knows its routes, including one with a local dynamic handler, syncs inside the constructor's noop poll, so the request and handler invocation still happen on binding there. Driving the request without a poll would need a runtime in Source, which it deliberately does not hold. The bind doc already states the deferral for a lazily discovering client session.
(Written by Claude Opus 5.5)
| let mut ended = Vec::new(); | ||
| for (prefix, machine) in &mut self.prefixes { | ||
| match machine.poll(waiter) { |
There was a problem hiding this comment.
Key polling to the announce prefix that woke
When a client holds many disjoint watches, every subscriber-driver wake scans and polls every open AnnouncePrefix, including wakes caused by unrelated media, datagrams, or bandwidth feedback. The new watch-driven design makes this set application-controlled and potentially large, so the loop adds O(number of watched prefixes) CPU to each transport wake and can hurt live-media latency; use keyed readiness/tasks rather than polling the whole map, and benchmark the watch-count and wake-rate axes as required.
AGENTS.md reference: AGENTS.md:L39-L40
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Already scoped as a measurement-gated follow-up with the maintainer: quest/m2/keyed-prefix-polling.md, added in this PR. It first extends rs/moq-net/benches/interests.rs to sweep open prefixes against wakes, and keys polling by prefix only if the per-wake cost actually slopes. Leaving it out of this PR.
(Written by Claude Opus 5.5)
Resolve rs/moq-mux/src/source.rs: the epoch-checked Binding from main now binds through the watch-then-request Lookup. Keep both MIGRATE entries. Pass the new epoch argument in the moq-hls sibling test, and drop the stale Timescale argument from main's SUBSCRIBE_DROP fixture (same fix as #6223) so moq-net tests compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 376dfcfd93
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// Calling this straight after connecting therefore races the session's announcements | ||
| /// and can report a live broadcast as unroutable. Await `announced_broadcast` first. | ||
| /// and can report a live broadcast as unroutable. Await `announced_broadcast` first. A | ||
| /// moq-lite client only asks the relay about paths something watches, through `announced` | ||
| /// or `announced_broadcast`, so a path nothing watches stays unroutable. |
There was a problem hiding this comment.
Add migration notes to the binding packages
For users of the published moq-ffi, C, or generated language APIs on a moq-lite client, this changes request_broadcast from discovering any permitted path to returning Unroutable unless another handle watches it, requiring migration to announced_broadcast; the documentation edit also changes the UniFFI checksum. Only rs/moq-net/MIGRATE.md records this break, so add the corresponding guidance to rs/moq-ffi/MIGRATE.md, rs/moq-c/MIGRATE.md, and the affected wrapper-package migration files.
AGENTS.md reference: AGENTS.md:L62-L63
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. Fixed in 116a535: every binding package now has an Unreleased MIGRATE entry. That covers moq-ffi, moq-c, the five generated *-ffi packages (they also note the changed request_broadcast checksum), and the py, swift, kt, go, dart, and cpp wrappers, each in its own casing.
(Written by Claude Opus 5.5)
…h a watch Lite-03/04 announce streams mark no initial set, so a watch reads as synced before the peer answers it and moq-mux's watch-then-request sibling lookup pinned Unroutable. Those clients keep asking for everything at connect. The WHEP handler requested its broadcast blind, which a lazy client origin (moq --connect ... export rtc --listen) can never route. It now resolves with routed_broadcast inside the existing 5s bound, answering 404 when nothing announces it in time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged Merge (376dfcf)
Codex review fixes (57a0d52)
Codex findings I disagree with
Checks: Public API: none new. Wire: none. (Written by Claude Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57a0d524c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if want && !covered { | ||
| let snapshot = snapshot.expect("a wanted prefix comes with its snapshot"); | ||
| self.requests.insert(prefix.clone(), Request::default()); | ||
| self.changes.push_back(Change::Open(prefix, snapshot)); |
There was a problem hiding this comment.
Hold lingering child requests under a new broader prefix
If a narrow watch is dropped and its request enters the two-second linger, then a broader watch opens before that timer expires, this branch creates the broader request but never adopts the already-lingering descendant. If the timer fires before the broader request's initial announcements arrive, expire closes the narrow request and temporarily retracts its routes; an announcement consumer can observe an End, and a broadcast resolved during this interval can lose its only route even though the peer continuously serves it. Cancel or hold lingering descendant requests when opening the broader prefix.
AGENTS.md reference: rs/moq-net/AGENTS.md:L7-L7
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed, and it breaks Discovery's own documented invariant: "A narrower request already open when a broader one opens stays open until the broader one goes." Only a narrower request released after the broader one opens gets held. One that is already lingering keeps its own timer. The fix is small. When interest opens a new request, it moves each lingering request beneath that prefix into the new request's held set and cancels the linger timer. A test like a_broader_request_holds_the_narrower_open would cover it, with the narrower watch dropped before the root watch opens.
Not pushed yet. This PR has reached its design checkpoint, so the maintainer is deciding the next step together with the WHEP finding below.
(Written by Claude Opus 5.5)
There was a problem hiding this comment.
Fixed in 634107b. A new request now holds every narrower request still lingering beneath its prefix and cancels their timers, the same as for one released after it opened. New test: a_broader_request_holds_a_lingering_narrower_open, which fails without the fix.
(Written by Claude Opus 5.5)
| let deadline = tokio::time::Instant::now() + CATALOG_TIMEOUT; | ||
| let broadcast = tokio::time::timeout_at(deadline, subscriber.routed_broadcast(path)) | ||
| .await | ||
| .map_err(|_| Error::Moq(moq_net::Error::Unroutable))??; |
There was a problem hiding this comment.
Bound concurrent WHEP discovery watches
On the bundled unauthenticated WHEP router, a caller can submit valid offers for many distinct nonexistent paths; each call now holds a routed_broadcast interest for five seconds, causing every lazy client session on the origin to open a distinct announce-request stream, which then lingers for another two seconds. Because neither the router nor resolve limits concurrent unique lookups, a remote request burst can grow per-session state and exhaust bidirectional stream credit, preventing legitimate playback lookups; add admission control or otherwise bound these untrusted watches.
AGENTS.md reference: AGENTS.md:L18-L18
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed that it's real, and it reaches beyond WHEP. Since moq-dev/moq#6190, moq_hls::Server::broadcaster calls origin.follow(name) for each HTTP GET. Under this PR that registers an interest too, so a plain GET flood of distinct paths does the same thing. The moq-cli gateways scope to subtree(name), but every distinct name/<anything> beneath it still opens its own announce request upstream. The only bound is the upstream connection's stream credit, which legitimate lookups share.
An admission cap would be an arbitrary limit. The bounded-by-design options are:
- An HTTP gateway holds one watch over its whole served scope for its lifetime (for moq-cli, the
namesubtree), so every per-request lookup rides that one request. A gateway that serves other clients is acting as a relay, so being eager within its scope matches what a relay link already does. - Merge as is and track the bound as an m2 follow-up quest.
Maintainer decision pending.
(Written by Claude Opus 5.5)
There was a problem hiding this comment.
Fixed in 5d9bf72 with option 1, as the maintainer chose. moq_hls::Server and the bundled WHEP router each hold one announced() watch over their whole scope for as long as they live, so every per-request lookup rides that single covering request instead of opening its own. The cost is now one announce request per gateway, however many paths callers name. The whep::accept doc tells embedders that own the route to do the same. New tests, lookups_ride_the_servers_scope_watch and offers_ride_the_routers_scope_watch, each fail without the watch. JS has no HTTP gateway, so there is nothing to mirror.
(Written by Claude Opus 5.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
request_broadcast on a moq-lite client now fails for a path nothing watches, and its doc change moves the UniFFI checksum, so moq-ffi, moq-c, the generated packages, and each wrapper say how to migrate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged Merge (a3f1f4b)
Codex finding fixed (116a535): MIGRATE entries for every binding package. Not re-queued. This is the fourth review round since the design last changed, and two findings on 57a0d52 are still open: lingering narrower requests (a small fix, agreed) and bounding gateway-driven watches, which needs a maintainer decision. Checks: (Written by Claude Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 116a535706
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Some(watch) = &mut self.watch { | ||
| // Synced only counts once every event queued before it was read. | ||
| while let Poll::Ready(Some(_)) = watch.poll_next(waiter) {} | ||
| ready!(watch.poll_synced(waiter)); |
There was a problem hiding this comment.
Request once any discovery session supplies the route
When an origin is fed by multiple lazy client sessions and one peer never completes its initial ANNOUNCE response, poll_synced keeps waiting on that peer's snapshot even after another peer has delivered a usable route, whose event is discarded by the loop above. The binding therefore never issues its request through the healthy route, allowing one stalled or hostile session to block sibling-broadcast playback across the shared origin; request as soon as the watch observes a route, and use synchronization only to conclude that none exists.
AGENTS.md reference: AGENTS.md:L18-L18
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed, and reproduced. Fixed in 3db5d52: Lookup now requests as soon as its watch reports a route from any session, and uses sync only to conclude that none serves the path. New test: a_stalled_session_does_not_hold_up_a_sibling feeds one origin from two lazy lite-07 sessions, one of whose peers never accepts a request stream. Before the fix it timed out on the sibling.
(Written by Claude Opus 5.5)
A narrower request already lingering when a broader one opened kept its own timer, so it could close and retract its routes before the broader request synced. The new request now holds it, as it would one released after it opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each HTTP lookup watched only its own path, so a lazily discovering client session opened one announce request upstream per distinct path a caller named, bounded only by stream credit. The HLS Server and the WHEP router now hold one watch over their scope, so lookups ride a single request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Applied the maintainer's decisions on the two open findings. Head: 80ee299.
Public API: unchanged. Wire: unchanged. Checks: every (Written by Claude Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 90670cb514
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| /// A sibling resolves through the session that announces it, even while another lazily | ||
| /// discovering session on the origin never answers its announce request. | ||
| #[tokio::test] |
There was a problem hiding this comment.
Pause time in the in-memory async test
This test runs entirely over the in-memory Link::lazy transport but uses an unpaused Tokio clock with real five-second timeouts, so a regression occupies a CI worker for wall-clock time instead of failing promptly under simulated time. Make it #[tokio::test(start_paused = true)], as required for non-socket time-dependent Rust tests.
AGENTS.md reference: rs/AGENTS.md:L53-L54
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. Fixed in 99f927c: the test now runs with start_paused = true. With the lookup forced back to waiting for sync, it fails in 8 ms instead of 5 s.
(Written by Claude Opus 5.5)
It runs over the in-memory transport, so a regression now fails at once under simulated time instead of holding a worker for real timeouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ietf/session.rs: main's grant check judges the publisher's serving origin (#6259); keep this branch's serving() view on it, so the check registers no discovery interest. moq-rtc whep.rs: keep the watch-then-request resolve, moved onto moq-clock like the rest of the crate (#6135). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main disallows tokio::time in moq-rtc since #6135. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38dbdab7f7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self.watch = None; | ||
| self.request = Some(self.origin.request_broadcast(&self.path, None).into_inner()); |
There was a problem hiding this comment.
Retry if the route vanishes before the sibling request
On a multithreaded runtime, a route can retract after the loop drains its Start and poll_next returns Pending, but before request_broadcast reads the route table. The queued End is then discarded with self.watch = None, the request returns an immediate Unroutable, and this binding permanently fails even if another unsynced session later supplies the sibling. Fresh evidence beyond the earlier fix is that following the selected route still does not make the event read and table lookup atomic; retain the follow until the request actually joins a route, or resume following after an unqueued Unroutable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. Fixed in a2ebd8e: the lookup keeps its follower until the request settles. If it gets Unroutable before every session has synced, it drops the request and follows again, the same way routed_broadcast does. No spin is possible, because the next pass has no new events and parks on poll_synced. There's no dedicated test, since the race needs the route to retract between two adjacent reads.
(Written by Claude Opus 5.5)
A route could retract between the follower reporting it and the request reading the table, failing the binding for good although another unsynced session might still serve it. An Unroutable answer before every session has synced now resumes following, as routed_broadcast does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged (Written by Claude Opus 5.5) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merged origin/main (d6cbfc4) after the merge-queue drop. Mechanical resolution only: each (Written by Claude Opus 5.5) |
Take main's deletion of the finished lazy-discovery epic (#6165). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/ingest Resolve conflicts with main's fMP4 import refactor (#6250), frame arrival stamps, the clock rename, and the #6165 MIGRATE.md entries. The owned frame writer now builds frames with `Frame::new`, and main's arrival-stamp test takes the group writer back from `finish`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
moq-lite clients already ask only for watched prefixes (#6165), and moq-mux Source watches the sibling broadcasts a catalog references, so the subtree scope only lost those siblings. The moxygen draft-14 cell stays red until IETF lazy discovery asks for watched prefixes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quest: Rust lazy discovery, following the newer plan in moq-dev/moq#6092 (Rust has no blind requests or answers).
Summary
A moq-lite client session now asks its peer for announcements only under the prefixes its subscribe origin's readers watch, mirroring JS
connection/forward.ts:interests,interests_below) and hands each client session only the covering-set changes a watch causes, with a snapshot taken under the lock so a new reader never reads synced before the request goes out.discovery.rs(crate-private) turns those changes into announce requests: a narrower watch rides a broader request, a narrower request already open is held until the broader one goes, and a request lingers 2s after nothing at or beneath it is watched.announced(),follow,routed,routed_broadcast, andannounced_broadcast. A broadcast resolved through a received route keeps its path watched while the front lives.serving()consumer and never count as watches, so a client that publishes and consumes through one origin stays lazy.peer()orupstream()handle, still ask for everything they may, so relays and clusters discover as before.moq-rtcwhep::accept) resolves withrouted_broadcastinside its existing 5s bound, soexport rtc --listenkeeps working through a lazy client; no route in time is a 404.Sourcesibling bindings now watch the sibling path and request once that watch syncs, so cross-broadcast renditions keep resolving through a lazy client. An origin that already knows its routes syncs at once, so the request still goes out onbind.Public API and wire impact
rs/moq-net/MIGRATE.md): on a lite client,request_broadcastfor a path nothing watches failsUnroutableeven when the peer announces it. Userouted_broadcast/announced_broadcast, or holdannounced()over the prefix. A hand-rolled relay that forwards over a plain origin must feed its client sessionupstream()orpeer().Interests,InterestChange,Consumer::serving,Producer::interests,Producer::relays(markedpending: /quest/m1/public-model/answer.md). The hiddenfuzzmodule gainsInterestsfor the bench.moq_origin_requestand moq-ffirequest_broadcastdocs now say a lite client only reaches watched paths (regenerated the Dart checksum). The Dart decode example now waits throughannouncedBroadcast(...).available().Tests
rs/moq-net/tests/integration/lazy_discovery.rs: 11 tests over lite-05/06/07 (nothing asked until watched, only the watched prefix, a watch made before connecting, linger and rewatch, narrower rides broader, broader holds narrower, a resolved broadcast keeps its route, routed/announced waits ask, peer/upstream links and servers ask for everything, serving the peer asks nothing).moq-hlsa_sibling_resolves_through_a_client_session: failsUnroutablewithout the moq-mux fix.announced()on the client by default (client_discovers_all), so existing tests keep their eager discovery.rs/moq-net/benches/interests.rssweeps (watches, sessions) over (10, 1000)². Covered changes stay flat (~0.6-0.9 µs) on both axes; an uncovered change grows with sessions only (~6 µs at 10 sessions, ~0.7-1 ms at 1000, one snapshot per session) and is flat over held watches.Checks
just check origin/main: every lint and binding stage passes (including the Dart, Go, Kotlin, Python, C++ binding checks). It stops atbench/relay.test.pytest_a_foreign_listener_never_proves_relay_readiness, an unrelated busy-port hang under load fixed by moq-dev/moq#6121; it passes when run alone.cargo nextest run --workspace --tests: 9299 passed, 0 failed.just test interop --all: all checks passed.cargo bench -p moq-net --features fuzz --bench interests: numbers above.drafts/change: there is no wire change.Decisions
Settled by the maintainer (2026-10-21): every recommendation below is accepted.
upstream()/peer(), now in MIGRATE. Alternative: per-session self-exclusion so a client forwarding over local links propagates its peers' watches.followfolded into the request path was read as: every waiting read (follow,routed,routed_broadcast,announced_broadcast) registers interest, with no API removed. Recommendation: keep; removingfollowis a separate API change.routed_broadcast. That keepsBinding's contract (asks on bind, never retries, a missing sibling fails). Recommendation: keep. Alternative: move "watch, then answer" into moq-netrequest_broadcastitself for lazy clients, so every plain request keeps working after one round trip; that is close to the blind-answer behavior the plan rejected, so it needs your call.Quests
Deletes the finished Rust lazy discovery quest and its now-empty
lazy-discoveryepic (JS landed earlier; the IETF half lives in its own m2 quest), and drops the links to them fromquest/m1/README.md,quest/m1/public-model/{README,answer}.md, and the two m2 follow-ups below.Follow-ups
Already planned on main by moq-dev/moq#6186:
🤖 Generated with Claude Code