Repository navigation
feat(net)!: announcements carry availability; a let-go is offline, not END - #8375
Conversation
Quest-Claim: 1dc886e941f195de
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…certification Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/availability # Conflicts: # rs/moq-cli/tests/integration/instances.rs # rs/moq-cli/tests/integration/rtsp.rs # rs/moq-gst/src/source/imp.rs # rs/moq-net/benches/announce.rs # rs/moq-net/src/auth.rs # rs/moq-net/src/ietf/publisher.rs # rs/moq-net/src/ietf/subscriber.rs # rs/moq-net/src/lite/publisher.rs # rs/moq-net/src/lite/replay.rs # rs/moq-net/src/model/announced.rs # rs/moq-net/src/model/follow.rs # rs/moq-net/src/model/origin.rs # rs/moq-net/src/route/node.rs # rs/moq-net/tests/integration/announce_covering_route.rs # rs/moq-net/tests/integration/announce_to_serve.rs # rs/moq-net/tests/integration/auth.rs # rs/moq-net/tests/integration/claim_continues.rs # rs/moq-net/tests/integration/dial_split_horizon.rs # rs/moq-net/tests/integration/extensions.rs # rs/moq-net/tests/integration/instance.rs # rs/moq-net/tests/integration/lazy_discovery.rs # rs/moq-net/tests/integration/mount.rs # rs/moq-net/tests/integration/multi_cdn.rs # rs/moq-net/tests/integration/session_panic.rs # rs/moq-net/tests/integration/support/mod.rs # rs/moq-net/tests/integration/synced.rs # rs/moq-relay/src/cluster.rs # rs/moq-relay/src/internal.rs # rs/moq-relay/tests/drills.rs # rs/moq-relay/tests/integration/auth_lifetime.rs # rs/moq-relay/tests/integration/cluster_idle.rs # rs/moq-relay/tests/integration/cluster_unknown.rs # rs/moq-relay/tests/integration/connection.rs # rs/moq-relay/tests/integration/embed.rs # rs/moq-relay/tests/integration/memory.rs # rs/moq-relay/tests/integration/runtime_uring.rs # rs/moq-relay/tests/integration/runtime_workers.rs # rs/moq-relay/tests/integration/smoke.rs # rs/moq-relay/tests/integration/tenant_matrix.rs # rs/moq-stats/src/aggregate.rs # rs/moq-stats/src/consume.rs # rs/moq-stats/src/produce.rs # rs/moq-tokio/tests/integration/broadcast.rs
A token-scoped session asked its peer for everything, which its scope did not cover, so the peer's floors certified nothing and a clean finish reached readers through a relay as offline. The subscriber now narrows each ANNOUNCE_REQUEST to the one region where its limit and subscribe grant meet, and reopens it when that region changes, keeping the old request until the new one is synced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uest decision Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t losses go offline A replacement request takes slots for the claims its predecessor still holds, so it keeps the predecessor only while the session has room for both, and lets it go first otherwise. The JS moq-transport subscriber ends a namespace only on NAMESPACE_DONE or the peer finishing a PUBLISH_NAMESPACE, as Rust does; a reflected namespace or a request that went away goes offline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wider replacement can need more slots than its predecessor held, so budgeting a duplicate of the old set was not enough. The predecessor now gives back its slots at the handover: the replacement gets the whole cap, and what the old one keeps stays bounded by the cap it took it under. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/availability # Conflicts: # dart/moq_ffi/lib/src/moq.dart # rs/moq-c/src/api.rs # rs/moq-c/src/origin.rs # rs/moq-c/src/test.rs # rs/moq-ffi/src/origin.rs # rs/moq-net/src/lite/subscriber/routed.rs
During a scoped lite-07 subscriber's ANNOUNCE_REQUEST handover, the old request moves what it holds onto a separate counter with the session's cap, so the session's count belongs to the replacement alone. A peer can hold at most twice the cap, during one handover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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 6 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head: c8902ab87491f923f442dbcb0c341447f4ac7c57
Three findings:
-
[P2] Share handover credit across the session’s predecessors — rs/moq-net/src/lite/subscriber/routed.rs:311–316. Each
hand_over()creates an independent full-cap counter. Discovery intentionally retains overlapping requests: watcha, then root, then change the permit region toa/b. Both requests reopen and retain predecessors. A peer can withhold replacement SYNCED and fill both old counters to C while replacements share another C, retaining 3C slots rather than the intended 2C session bound; more overlapping prefixes multiply it further. Preserve separate handover credit, but share one capped predecessor budget across the session. Add an overlapping-request regression, rather than testing one predecessor alone. -
[P2] Preserve Offline when a scoped cursor falls back to a covering route — rs/moq-net/src/model/origin.rs:1753–1757. With a scoped, covering cursor rooted at
room/alice, an exact forwarded route can haveSome([])captures while the broaderroomfallback hasNone. Losing the exact route’s session correctly passesGone::Offlineinto the update, but this capture-change branch replaces it withGone::End. Readers receive End/Absent even though the publisher never withdrew. Preserve the actual removal reason when replacing the captured identity; extendcapture_change_retracts_before_reannouncing_a_presented_prefixwith a lost forwarded route. -
[P2] Classify scoped JS teardown through the same terminal transition — js/net/src/origin.ts:1695–1698. When a session receives routes through a scoped Producer, closing that scoped handle while keeping the parent origin open invokes the raw callbacks registered by
#hold, bypassingclose(lost). Received entries retainlost = false, so the parent announcement reader emitsend/publicabsentinstead ofoffline. Later route closure cannot repair this becauseendedis already true. Put the reason in the shared release transition: received-route teardown defaults to offline, while explicit wire end selects withdrawal. Cover scoped-handle closure in a regression test.
Direction: keep the agreed narrowed-request handover, but enforce its bound at session scope and preserve terminal provenance across every release path.
Verification: GitHub-only static review of the changes, relevant callers, bindings, and tests. No builds, tests, or interoperability runs were executed.
(written by unknown OpenAI model)
|
This run was cancelled before it finished. |

Announcements now report availability. When this side lets go of a route, readers get a distinct offline event instead of the origin's END. This covers Rust,
js/net, and every binding. Finishesquest/m2/cluster-routing/routes/availability.mdand deletes it, building on the Listing API. The maintainer's decision moves to the routes epic as item 16.A token-scoped lite-07 subscriber now asks its peer for the one region its scope allows, rather than for everything. The peer's floors can then certify the request, so a clean finish still ENDs across a relay instead of going offline.
Decisions
; then/quest-iterateimplements it and runs review round 4 withquest review --new` if the approach changed.Decisions
Offline.) Maintainer, 2026-11-09, via/quest-unblock./quest-unblock. Recorded as item 17 in the routes epic README.auth::lite_07_a_scoped_withdrawal_endsfail without it.How the narrowing works
Permit::region()is the one prefix where the peer's grant and this side's limit meet, orNoneif there are several or none.AnnouncePrefixputs the discovery prefix on the wire, narrowed to that region when the region lies beneath it. On lite-07 only, since only lite-07 has floors.Routed::hand_overmoves its slots ontoSlots::split, a fresh counter with the session's cap.Public API impact (breaking, no wire change)
RouteKind::Offline;announce::Event::{Unavailable, Available, Offline}besidePresent/Absent;RouteInfo::available().unavailable | available | offline, route kindoffline,RouteInfo.available.MoqAnnounceEvent::{Unavailable, Available, Offline},MoqRouteInfo.available. C ABI:MOQ_ANNOUNCE_KIND_{OFFLINE,UNAVAILABLE,AVAILABLE},moq_route_info.available. Go, Python, Kotlin, Swift and Dart aliases and docs are mirrored.Impact / migration
An exhaustive match on
announce::EventorRouteKindneeds the new arms. To keep the old behavior, treatOfflinelikeAbsent/Endand ignoreAvailable/Unavailable.Choices made without asking
Absentand did not rename it toEnded.Update/RouteInfo. The listing turns flips intoAvailable/Unavailable.Offlineis now aRouteKindvariant besideStart/Update/Restart/End. Merged main to pick it up.a_lost_namespace_stream_closes_the_broadcast), goes offline. A PUBLISH_NAMESPACE ends when the peer ends its request. So a moqt-18 graceful close now reads as offline inconnection/close.test.ts, while lite-07 still ends.Checks
On head
c8902ab874(the handover budget):just check: pass,check cincluded. Earlier heads of this branch failedcheck cthe same way a cleanorigin/maindid: the C bindgen refusedMoqInstance's exportedDebug("the C backend does not support exported traits on objects yet"), which came in with moq-dev/moq#8366. That failure is pre-existing and nothing here changes it; it did not reproduce in this run.just test interop --all: pass. Main quarantines the finish scenario (moq-dev/moq#8407), so its 45 lanes are skipped, as are the existing JS lite-07 skips.routed::tests::a_handover_counts_the_replacement_alone(a handover near the cap keeps the session) androuted::tests::a_handover_caps_the_old_request(a peer flooding the old request is still capped) both fail with round 3's give-back in place of the private budget.On head
3320df4e1c, before that quarantine reached this branch,just test interop --allpassed with all 45finish X -> Ylanes, and with narrowing disabledfinish rust -> rustfailed again, so the narrowing is what fixes it.New tests:
auth::lite_07_a_scoped_withdrawal_ends,auth::lite_07_a_scope_change_reopens_without_letting_go,auth::lite_07_a_scope_change_at_the_announce_cap_keeps_the_session,auth::tests::the_region_is_where_grant_and_limit_meet, and the two handover tests above. Each fails with its fix removed.Suggested follow-ups
MoqInstanceabove. A separate quest is being planned for it; it is not this PR's change.Caused by
None: this is a feature.
Models
Size: L
Implementer: Claude Code, Opus 5.5, unknown effort
Implementer: Claude Code, claude-opus-5-5, high effort (iterate, L)
Implementer: Claude Code, claude-opus-5-5, high effort (iterate, L)
(Written by Opus 5.5 with high)
🤖 Generated with Claude Code
Review
Rewritten by
quest reviewafter each round.01a1c340-5a55-7111-87e2-2c56f4f1f33f, round 4The separate capped handover counter fixes duplicate charging for incoming claims, and the incremental changes introduce no remaining findings.rs/moq-net/src/lite/subscriber/routed.rs:308:Extend handover credit to claims arriving on the predecessor;Routed::hand_over moves existing slot guards and future acquisitions onto a separate Slots::split counter with the same cap, leaving replacement credit unaffected by predecessor updates.