Skip to content

feat!: announcements are a path listing; follow reports offline - #7678

Merged
kixelated merged 29 commits into
mainfrom
quest/m1/api-audit/route/events
Nov 5, 2026
Merged

kixelated merged 29 commits into
mainfrom
quest/m1/api-audit/route/events

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Implements Listing and follow and deletes the quest.

Applications now read announcements as a listing: each path is present or absent. A restart, takeover, or route re-price no longer surfaces as an announce event; readers that need instance changes hold a follow. The five-variant stream survives only as a hidden, relay-level routes() that the sessions, moq-relay, and moq-stats use.

Changes

Rust (moq-net)

  • origin::Consumer::announced() returns announce::Consumer, yielding announce::Event::{Present, Absent} (new model/announced.rs, a thin filter over the route stream).
  • origin::Consumer::routes() (#[doc(hidden)]) returns origin::Routes of origin::RouteEvent (the old Start/Update/End/Restart/Takeover). Every session, relay and stats caller moved to it. The old crate-private routes(path) is now path_routes(path).
  • Following::available() returns Result<Option<broadcast::Consumer>>: None once the path goes offline, or, when pinned, once another instance takes it over (the pinned instance is handed out again when it returns). The first call never returns None. After a resolution failure, such as a handler refusing the path, the next call waits for another instance instead of asking the same handler again, so callers keep their follower.
  • moq_mux::follow::Instances folds into Following: Instances::new and follow::Event are gone. Source::follow keeps a thin Instances for its one-shot hand-out of the catalog's own broadcast, which moq-gst needs, and for a named sibling instance: that is followed unpinned and checked with check_instance, since a pin never matches an instance an older hop minted.
  • Callers that relied on Restart move to a follower per listed path: moq-boy viewers, moq-room (Room holds a follower per path, as room-bench expects), moq export echo, the archive recorder, and the hang subscribe example. The rest only merged variants and now read Present/Absent.

JS (@moq/net)

  • Announce.Event is { kind: "present" | "absent" } & Announce. Connections and origins keep an @internal routes() (stripped from the API report) that connection/forward.ts and the pool pump use. Announce.Producer is @internal too, since it was never exported.
  • @moq/watch, @moq/room, @moq/boy, the demo dashboard (which now follows each stats node) and carla's web player move to present/absent.

Bindings

  • moq-ffi: MoqAnnounceEvent is Present { announce } / Absent { announce }; Takeover's predecessor is gone. MoqFollowing::available() returns an optional broadcast: none once the path goes offline (or, when pinned, once another instance takes it over), mirroring Rust and JS. Python returns None, Go (nil, nil) (as NextGroup does at track end), Kotlin and Dart null, Swift nil, and C++ a null shared_ptr (uniffi-bindgen-cpp maps Option<Arc<T>> that way).
  • C ABI: moq_announce_kind is MOQ_ANNOUNCE_KIND_PRESENT (0) / _ABSENT (1); moq_announce_event loses predecessor/predecessor_len. moq_origin_follow takes a new moq_follow_callback(void *user_data, moq_follow_event event, int32_t code): MOQ_FOLLOW_EVENT_ONLINE with the broadcast handle, MOQ_FOLLOW_EVENT_OFFLINE with 0, and one terminal MOQ_FOLLOW_EVENT_END with 0 or a negative error that moq_error() explains inside the callback, which is moq-c's existing terminal-code convention. Internally, follow tasks get their own slab and a Terminal trait delivers each callback type's end.
  • The OBS source uses the moq-ffi C++ binding, not moq_origin_follow: a null broadcast logs the offline and waits for the next one, holding the last frame as before.
  • Python, Go, Kotlin, Dart (regenerated moq_ffi/lib/src/moq.dart) and Swift wrappers, their READMEs, tests and doc/lib/* samples. Restart tests become "a follower gets the new instance while the path stays listed".
  • Interop clients (Rust, JS, Go, Python, C) and carla's publish.py use the listing plus follow. The Go client is gofmt-clean; the Python one already passed ruff format.

Docs: doc/concept/identity.md (followers), doc/setup/redundancy.md, MIGRATE.md entries for moq-net, moq-mux, moq-ffi, moq-c, the C header, @moq/net and every wrapper.

Impact

  • Public API: breaking in every language.
    • Rust: announce::Event is Present/Absent. Map Start to Present, End to Absent, drop Update, and replace "request again on Restart" with consumer.follow(path, None). Following::available() returns an Option. moq_mux::follow::Instances::new and follow::Event are removed. Relays use the hidden consumer.routes().
    • JS: Announce.Event.kind is "present" | "absent"; follow origin.follow(path).active for instance changes.
    • moq-ffi and wrappers: MoqAnnounceEvent::{Present, Absent}; hold follow(path) for restarts. available() may return none: handle it as offline. Go keeps its signature but can return (nil, nil).
    • C ABI: MOQ_ANNOUNCE_KIND_PRESENT/_ABSENT, predecessor fields removed. moq_origin_follow takes a moq_follow_callback: read the handle on MOQ_FOLLOW_EVENT_ONLINE (where a positive code was), and release user_data on MOQ_FOLLOW_EVENT_END (where a code <= 0 was).
  • Wire: none. ANNOUNCE, ANNOUNCE_RESTART and ANNOUNCE_TAKEOVER are unchanged.

Decisions

Answered by the maintainer on this PR, 2026-11-03 (the chosen option is marked ✅):

  • Should the bindings report the follow end that Rust's Following now reports? Keep skipping ends; ✅ report it: MoqFollowing.available() returns an optional broadcast in moq-ffi and every wrapper, and C's moq_origin_follow callback gets a distinct offline code.
  • Listing names: ✅ Present/Absent (JS "present"/"absent"); Active/Ended; Announced/Unannounced.
  • Follow end shape: ✅ Following::available() -> Result<Option<broadcast::Consumer>>; a follow::Event { Start, End } enum.

Answered after the maintainer's API review, 2026-11-03:

  • C follow offline signal: ✅ explicit event kind: moq_origin_follow's callback takes a moq_follow_event (ONLINE, OFFLINE, END) beside its code; keep the MOQ_FOLLOW_OFFLINE sentinel; rethink more. MOQ_FOLLOW_OFFLINE and the ID allocator cap are removed, and END carries errors through moq-c's negative-code convention.

Choices made without asking

  • Moved to routes() rather than to the listing: moq-net's own tests and benches, moq-relay, moq-stats, and the moq-cli, moq-gst, moq-rtmp, moq-tokio and moq-net-tests tests that assert restarts, updates or hop changes.
  • The C follow callback's third argument is named code, not handle, since END carries the terminal status there.
  • moq export archive fails when a handler refuses a listed path, as the recording request it replaced did. moq-room and carla instead keep following past a refusal.
  • The demo stats page resets the cluster aggregate when a node's instance changes, not only that node's history.
  • Carla keys its driver lease on a local run counter, since the Python BroadcastConsumer exposes no instance; a follow's None now ends the driver's run.
  • Dropped two tests that only asserted re-price Update events reached apps (Python test_route_update_observes_restart, carla's "keeps the lease across route updates"). Apps no longer see re-prices.
  • Interop finish clients (Go, Python) no longer check for a restart, matching the Rust and JS finish clients; a restart still fails them through the media read.
  • Merged origin/main several times, taking main's poll-native moq-ffi (moq-dev/moq#7497) and reapplying this PR's changes on top. Later merges joined MIGRATE entries, kept a JS integration test main deleted deleted, and moved main's new relay-level tests (route_dies_across_instances, the lite subscriber sync test, cluster_fresh_port) to routes().
  • The 2026-11-05 merges of origin/main (after the merge queue dropped this PR on a stale announced() call in main's new mesh_withdraw.rs):
    • Kept main's Following refusal rule from moq-dev/moq#7836: after a refusal, any change to the serving route asks again, even the same instance over another route. This replaces this PR's narrower "wait for another instance". This PR already forgets the broadcast handed out last on a refusal, so main's extra !refused guard on the closed-broadcast check was redundant and is dropped. Main's announced_waits_after_refusing_a_closed_instances_successor is dropped too: this PR's announced_does_not_retry_a_refusal_after_a_takeover covers the same case and also asserts the owed end.
    • Ported main's new callers to this API: mesh_withdraw.rs and other relay-level tests to routes(); the socket endpoint step (model/socket.rs, whose only call is the first, which is never None), the HLS contributions follower (follow::Event to Option), the moq-gst, moq-audio, moq-ffi, moq-relay, moq-tokio and moq-net tests, and the fan-out session bench harness to the optional available(). That harness builds against each base revision, so a base from before this PR now reports it unsupported, as bench/run.sh already does for an API a revision lacks.
    • Took main's self-subscribing snapshot consumers (moq-dev/moq#7736) in moq export echo, the demo stats page, and carla. carla's publish.py still used the old SnapshotConsumer(track) on main as well, which fails its pyright check; it and its test fake now pass (broadcast, "control").
    • Rewrote a prose link in follow-refused-retry that pointed at this PR's deleted quest.

Checks

On the final head, a18da85:

  • just ci origin/main (the landing gate): every module passes, including 10857 Rust tests, JS, Python, carla, C, C++, OBS compile, Go, Kotlin, Dart and Swift selection, markdown and quest check.
  • cargo check of the out-of-workspace bench/fanout/session harness: clean.

Earlier on this branch, and unaffected by the later merges: just py check, just py test, just go check, just kt check, just cpp check, just obs test, and just test interop --all (185 checks). Swift was edited but not compiled: there is no Swift toolchain on this Linux host.

Suggested follow-ups

  • MoqBroadcastConsumer exposes no instance in moq-ffi, so carla's Python lease uses a local counter. An instance() accessor would let it key on the real instance.
  • rs/moq-c test::raw_track_subscription_options_and_update failed once (-27 from a close) and passed four reruns: a flake unrelated to announcements.
  • cpp/obs test/moq-output-test.cpp:909 ("stop drains the session") failed once under a load average near 33 and passed on rerun; it does not touch the follow path.
  • test/smoke clients (Kotlin, Swift, Python) install published packages and still call the pre-rename announcedBroadcast(...).available(); after this ships they need the optional result. Nothing builds them from this tree.
  • rs/moq-pattern/benches/matching.rs fails cargo clippy --all-targets on main, with default features too: overlapping match ranges (..64, ..16384) from moq-dev/moq#7536. Main's Check does not catch it because its clippy gate rides the test build, which compiles no bench targets. Out of scope here.

Review

Codex gpt-6.1-sol, medium effort, via quest review --base origin/main, a new session on this branch:

  • Round 1, head 25c2526: two findings, both fixed in 9c8adbe, each with a test that fails without the fix:
    • rs/moq-boy/src/input.rs:105: a viewer whose handler refused it stopped being read, and a publisher taking over the still-listed path was never picked up. follow_viewer now ends only on Error::Closed. Test: input::tests::a_refused_viewer_is_read_once_a_publisher_takes_over.
    • rs/moq-cli/src/report.rs:577: the same for moq export echo. Test: report::tests::a_refused_feedback_path_is_read_once_a_publisher_takes_over.
  • Round 2, head 9c8adbe: no findings.
  • Round 3, head f5c5ee3 (after the second main merge and the carla port): no findings.

Head a18da85 adds a conflict-free merge of origin/main (cfed1ec) and one mechanical line in bench/fanout/session/src/main.rs (.expect on the now-optional available()), skipped as too trivial for another round.

Earlier sessions (6 rounds, ending with no findings on 3582fd9) fixed three findings, each with a test: a refused moq-room participant dropped (reports_the_absence_of_a_refused_participant), the archive swallowing a refusal (a_refused_path_fails_the_export), and a refusal re-armed by a closed broadcast's movement (announced_does_not_retry_a_refusal_after_a_takeover). The automated GitHub review and pullfrog's two inline threads are fixed and answered (comment).

Overlap

moq-dev/moq#7846 (open, split JavaScript datagram tracks) touches 10 of the same js/net files: MIGRATE.md, api/index.api.md, ietf/connection.ts, ietf/subscriber.ts and its test, integration.test.ts, lite/compression.test.ts, lite/connection.ts, lite/subscriber.ts and its test. Whichever lands second merges the other.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits November 2, 2026 22:47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 5 commits November 2, 2026 23:33
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Listing and follow feat!: announcements are a path listing; follow reports offline Nov 3, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 3, 2026 10:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 18:29

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)
Reviewed commit: 73bac21

  • [P2] Preserve compatible instance matching when following a rendition (rs/moq-mux/src/source.rs:392). Passing the catalog's instance directly to the network follower requires an exact route ID. Lite versions before 07 and moq-transport mint route IDs because the wire carries none, so a valid pinned sibling rendition can wait indefinitely instead of playing; the previous unpinned follow plus check_instance deliberately accepted minted IDs. Direct pinning also lets a late follower resolve an older, still-routable instance after another has won, whereas the previous code waited for the named instance to serve again. Follow the winning route and check the returned instance with the existing compatibility rules, or provide equivalent behavior in the network follower.
  • [P2] Keep watching a listed participant after a resolution refusal (rs/moq-room/src/room.rs:137). If a handler rejects the initial request, this permanently clears the follower. An actual publisher can then replace that claim without the path becoming absent; the listing suppresses its restart and the room never emits the recovered camera. Previously the restart caused another request. Retain or re-establish a watcher with bounded retry while the path is present. The same stop-on-error pattern also affects rs/moq-cli/src/archive.rs:139 and demo/carla/sim/moq_carla_sim/publish.py:362.
  • [P3] Reset the aggregate baseline on a node restart (demo/web/src/stats.ts:197). The follower clears only that node's history, while the sampler identifies the cluster solely by its unchanged node names. A restarted node's counters therefore splice into the old aggregate history, producing false zeroes or spikes in cluster rates. Invalidate the aggregate history or membership baseline along with the node's history.

Verification: static inspection through GitHub only; I did not run tests or builds.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This reworks origin announcements into a path listing (each path present or absent) and moves instance/restart/offline observation to follow, breaking in every language.

  • moq-net listing model: origin::Consumer::announced() yields new announce::Event::{Present, Absent} (model/announced.rs), a filter over the route stream. The old five variants survive only as a hidden origin::Routes/RouteEvent reached via Consumer::routes(); the crate-private routes(path) became path_routes(path).
  • Following reports offline: available() is now Result<Option<broadcast::Consumer>>, with None when the path goes offline (or a pinned instance is taken over). New live/ended/end() state machine.
  • moq-mux fold: follow::Instances/follow::Event fold into Following; Source::follow keeps a thin Instances for the catalog's one-shot hand-out.
  • JS + bindings + C ABI: Announce.Event.kind is "present" | "absent", internal routes()/list(); MoqAnnounceEvent::{Present, Absent}; optional available() across all wrappers; MOQ_FOLLOW_OFFLINE (INT32_MAX) plus an ID allocator capped at INT32_MAX - 1.
  • Callers/docs: relay, stats, CLI, gst, room, boy, demo and interop clients migrated; MIGRATE notes and concept docs updated; quest/m1/api-audit/route/events.md deleted.

I read the full diff and verified the three highest-risk surfaces: the Following end state machine (no missing/double end, no None on first call; the lone Ok(None)-after-still-served case is the deliberate Follow gap end), the AnnounceConsumer::list filter (a faithful per-prefix projection with no same-prefix duplicate or spurious absence), and offline-None handling across every wrapper (offline is consistently distinguished from error and from "still waiting"), including the C sentinel and the allocator bound.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog

pullfrog Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Fixed the failing Check job. The PR changed Announce.Event.kind to "present" | "absent", but test/max-age/client.ts:42 still compared it against "start", so tsc failed with TS2367. Updated the comparison to "present" and verified bunx tsc --noEmit --project test/tsconfig.json now exits 0. Pushed as 92ec3cf00.

Task list (5/5 completed)

Pullfrog  | View workflow run | via Pullfrog | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

kixelated and others added 3 commits November 3, 2026 12:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ute/events

# Conflicts:
#	cpp/moq/MIGRATE.md
#	dart/moq/MIGRATE.md
#	go/wrapper/MIGRATE.md
#	kt/moq/MIGRATE.md
#	py/moq-rs/MIGRATE.md
#	rs/moq-c/MIGRATE.md
#	rs/moq-ffi/MIGRATE.md
#	swift/MIGRATE.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head 6f3ad66 against base 151ed38.

The substantive follow-up replaces the C callback's overloaded status/sentinel with explicit online, offline and terminal events, moves follows into a separate cancellation slab, and restores the full positive handle range. I found no new actionable issue in that change or its merge integration.

The three findings in the earlier review remain open: pinned sibling renditions still pass the instance directly to the network follower, a refused participant follower still stops while its listing remains present, and node restart still clears only per-node history rather than the aggregate baseline.

Static GitHub-only review of the C callback lifecycle, cancellation, affected callers and prior findings. I did not run builds, tests or interop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one minor test cleanup inline.

Reviewed changes

This increment replaces the C follow sentinel with an explicit event stream and integrates main.

  • Explicit C follow events: moq_origin_follow now takes moq_follow_callback over moq_follow_event (ONLINE/OFFLINE/END) instead of overloading a status code with MOQ_FOLLOW_OFFLINE; follows moved to their own Task<OnFollow> slab with a generic terminal callback.
  • Full handle range restored: id.rs no longer reserves INT32_MAX, so the C ID allocator wraps the whole positive i32 range again.
  • Callers and docs: the C interop subscriber, rs/moq-c/c/moq MIGRATE notes, doc/lib/c, and doc/concept/identity.md moved to the event API; the merge re-applied main's #7697 Delay docs and fixed test/max-age/client.ts to wait on present.

Verified locally: cargo check and clippy -p moq-c pass, cbindgen emits the moq_follow_event enum and callback typedef, and test/interop/clients/c/subscribe.c compiles against the generated header. The follow tests could not run in this environment because libpipewire-0.3 is missing.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Comment thread rs/moq-c/src/test.rs Resolved

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Archive export fails on a refused listed path: follow now returns anyhow::Result and export propagates a follower error from JoinSet::join_next, ignoring only the abort that follows an Absent; a handler refusing a listed path now fails the export instead of silently dropping that follower. The new a_refused_path_fails_the_export test covers it.

Error::Closed still ends the follower cleanly, and path-gone aborts are filtered via JoinError::is_cancelled. No new issues in this increment; the earlier inline test cleanup is still open, so this stays a comment review.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

…ter stats on restart

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 4, 2026 22:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's #7836 retries a refused follower on any serving-route change. Drop
its test that duplicated this branch's refusal-after-takeover test, and
say the follower waits for a route change, not another instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 4, 2026 23:17
The listing reports nothing new when a publisher takes over a path that
stays present, so a reader that quit on a refusal never saw the
takeover. Only an origin close ends them now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 3 commits November 4, 2026 23:41
…nsumer

Main's #7736 has moq.json.SnapshotConsumer take the broadcast and track
name; publish.py and its test fake still used the subscribed track.

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>
@kixelated
kixelated added this pull request to the merge queue Nov 5, 2026

@pullfrog pullfrog Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run covers d2b04d0..a18da85: two merges of main, the author's refusal alignment and caller fixes, and the adaptations main's new code needed.

  • Kept main's Following refusal rule and dropped its duplicate test: the refused flag now re-asks on any serving-route change (Update/Takeover, not just another instance), and announced_waits_after_refusing_a_closed_instances_successor is gone because announced_does_not_retry_a_refusal_after_a_takeover covers the same closed-instance path and also asserts the owed None. Dropping main's !refused guard on the closed-broadcast check is correct — a refusal already clears last.
  • Refused followers stay alive in moq-boy and moq export echo: both now select on following.available() with a boxed reader, returning only on Error::Closed and logging other refusals while the reader keeps running. New tests cover a publisher taking over after a refusal.
  • Carla reads driver control through the self-subscribing snapshot consumer: SnapshotConsumer(broadcast, "control") plus matching test fakes, following main's #7736.
  • Ported main's new callers to the listing and optional result: main's datagram-track, range-fill, stream-credit, warmup and JSON-snapshot work now uses routes() or the Option from available() across the model, relay, tokio, HLS and bench tests; socket::Step's expect is safe because Resolving is only the first state.
  • Fixed the fan-out bench harness to read the optional available().

MIGRATE ordering is intact (#7678 stays on top, including the hand-written C ABI section). I could not run the Rust suite here (libpipewire-0.3 is missing), and the head's Check was still in progress.

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: a18da85

The two findings from my earlier review remain open, rather than new regressions:

  • Carla still continues on a replacement refusal before cancelling the active driver (demo/carla/sim/moq_carla_sim/publish.py:356–360). Existing subscriptions survive replacement, so the old driver can retain its lease and continue commands.
  • Archive still treats a handler's Error::Closed refusal as origin termination (rs/moq-cli/src/archive.rs:149–153), although a handler can reject with that error while the listing remains. The new refusal-recovery paths in rs/moq-boy/src/input.rs:114 and rs/moq-cli/src/report.rs:574 have the same closed-refusal hole and can miss a later takeover. Distinguish refusal from actual origin termination.

The new retry-on-serving-route-change policy otherwise fits the follower model, and the earlier sibling-instance and room-retention fixes remain present.

Verification: focused GitHub-only static review of the incremental lifecycle paths. I did not run builds, tests or interop.

Merged via the queue into main with commit 354ee16 Nov 5, 2026
10 of 11 checks passed
@kixelated
kixelated deleted the quest/m1/api-audit/route/events branch November 5, 2026 08:33
kixelated added a commit that referenced this pull request Nov 5, 2026
Ports #7678's route listing and Following changes into the IETF
namespace machines and moq-hls's follow machines, takes its Activity::poll_close,
and links Following::poll_available so poll-only docs resolve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 5, 2026
Ports main's announcement listing and follow (#7678), split JS datagram
tracks (#7846) and generated async wrappers (#7607) onto the Route handle:
the ietf session layer takes main's poll machines with this branch's route
renames, main's new tests publish through routes, and re-price checks read a
fresh listing now that apps no longer see updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant