Skip to main content

moq_net/model/
origin.rs

1use crate::{broadcast, cache, stats, track};
2use kio::Pollable;
3use std::{
4	cmp::Reverse,
5	collections::{BTreeMap, HashMap, HashSet},
6	fmt,
7	sync::Arc,
8	sync::atomic::{AtomicU64, Ordering},
9	task::{Poll, ready},
10	time::Duration,
11};
12
13use rand::RngExt;
14use web_async::Lock;
15
16use super::{Requests, WeakCache};
17use crate::{
18	AsPath, Error, Path, PathOwned, PathPrefixes,
19	coding::{BoundsExceeded, Decode, DecodeError, Encode, EncodeError},
20};
21
22/// A relay origin, identified by a 62-bit varint on the wire.
23///
24/// Local origins are built with [`Origin::new`] or [`Origin::random`], both of
25/// which guarantee a non-zero id so loop detection can work. Remote peers may
26/// still send `0`; it is legal on the wire but cannot be used for loop detection.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct Origin {
29	/// 62-bit identifier. Encoded as a QUIC varint on the wire.
30	id: u64,
31}
32
33/// Returned when a local origin id is zero or outside the 62-bit wire range.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub struct InvalidOrigin;
37
38impl fmt::Display for InvalidOrigin {
39	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40		write!(f, "local origin id must be non-zero and below 2^62")
41	}
42}
43
44impl std::error::Error for InvalidOrigin {}
45
46impl Origin {
47	/// Placeholder for hop entries whose actual id is not on the wire (Lite03).
48	/// Also used for remote peers that choose the legal but loop-blind id 0.
49	pub(crate) const UNKNOWN: Self = Self { id: 0 };
50
51	/// Build an origin from a stable id.
52	///
53	/// The id must be non-zero and fit in the 62-bit QUIC varint range. Wire
54	/// decode accepts remote id 0, but local origins should not use it because
55	/// downstream peers cannot exclude it for loop detection.
56	pub fn new(id: u64) -> Result<Self, InvalidOrigin> {
57		if id == 0 || id >= 1u64 << 62 {
58			return Err(InvalidOrigin);
59		}
60		Ok(Self { id })
61	}
62
63	/// Generate a fresh origin with a random non-zero id. Use this for any
64	/// origin that does not need a stable identity across restarts.
65	///
66	/// TEMPORARY: the wire format allows 62 bits, but older `@moq/lite` JS
67	/// clients decode `AnnounceInterest.exclude_hop` as a u53 (number) and
68	/// throw on anything > 2^53-1. To keep those clients alive against
69	/// fresh relays, we cap the random id at 53 bits. Restore to 62 bits
70	/// once the JS u62 fix has propagated to deployed bundles.
71	pub fn random() -> Self {
72		let mut rng = rand::rng();
73		let id = rng.random_range(1..(1u64 << 53));
74		Self { id }
75	}
76
77	/// Return the origin's wire id.
78	pub fn id(self) -> u64 {
79		self.id
80	}
81
82	/// Consume this [Origin] to create a producer that carries its id, with an
83	/// unbounded cache pool. Use [`Info::produce`] to configure the pool.
84	pub fn produce(self) -> Producer {
85		Info::new(self).produce()
86	}
87}
88
89/// An origin's identity plus the cache pool its broadcasts inherit.
90///
91/// Doubles as the construction config for an [origin `Producer`](Producer) and as the
92/// parent handle every broadcast carries ([`broadcast::Info::origin`]): the origin owns
93/// the [`cache::Pool`] every group in the tree charges into, so a relay configures one
94/// bounded pool here and every broadcast, track, and group beneath it reaches that single
95/// budget by walking up the ownership chain. Defaults to an unbounded pool
96/// ([`Origin::produce`] is the shorthand for that). Cheap to clone (a `Copy` id plus an
97/// `Arc`-handle bump), so it's stored by value rather than behind another `Arc`.
98#[derive(Clone, Debug)]
99#[non_exhaustive]
100pub struct Info {
101	/// The origin's wire identity, appended to broadcast hop chains for loop
102	/// detection and shortest-path routing.
103	pub id: Origin,
104
105	/// The cache pool broadcasts under this origin charge their groups into. It flows
106	/// down the ownership chain (origin -> broadcast -> track -> group): a track opens
107	/// an account against it, and its groups charge through that. Unbounded by
108	/// default; a relay sets a bounded one (via [`Self::with_pool`]) so cached groups
109	/// across the whole process share one memory budget.
110	pub pool: cache::Pool,
111
112	/// Ceiling on how long any non-latest group under this origin is retained. Each
113	/// track's own [`latency_max`](track::Info::latency_max) window is clamped down to
114	/// this when the track binds, so a group is never held longer than this regardless
115	/// of what a publisher advertises. The age budget alongside [`Self::pool`]'s byte
116	/// budget: a relay bounds memory by both. [`Duration::MAX`] (the default) imposes no
117	/// ceiling, leaving each track's own window in force.
118	pub cache_duration: Duration,
119
120	/// How long a broadcast under this origin outlives the *ungraceful* loss of its
121	/// last source before closing. Within the window the path stays announced and a
122	/// source re-attaching at it (a session reconnecting, a publisher re-announcing)
123	/// splices in seamlessly, so consumers never observe the gap. A source that ends
124	/// deliberately ([`broadcast::Producer::finish`], a clean unannounce from a peer)
125	/// closes the broadcast immediately regardless. Zero (the default) closes
126	/// immediately either way; a duration too large to represent as a deadline
127	/// (e.g. [`Duration::MAX`]) lingers indefinitely.
128	pub linger: Duration,
129}
130
131impl Default for Info {
132	/// An unknown origin (id `0`, no loop detection) with an unbounded pool. This is
133	/// what a standalone broadcast (no relay origin) inherits.
134	fn default() -> Self {
135		Self {
136			id: Origin::UNKNOWN,
137			pool: cache::Pool::default(),
138			cache_duration: Duration::MAX,
139			linger: Duration::ZERO,
140		}
141	}
142}
143
144impl Info {
145	/// Config for the given origin id with an unbounded cache pool.
146	pub fn new(id: Origin) -> Self {
147		Self { id, ..Self::default() }
148	}
149
150	/// Set the cache pool this origin's broadcasts inherit, returning `self` for chaining.
151	pub fn with_pool(mut self, pool: cache::Pool) -> Self {
152		self.pool = pool;
153		self
154	}
155
156	/// Set the retention ceiling (see [`Self::cache_duration`]) applied to every track
157	/// under this origin, returning `self` for chaining.
158	pub fn with_cache_duration(mut self, cache_duration: Duration) -> Self {
159		self.cache_duration = cache_duration;
160		self
161	}
162
163	/// Set how long a broadcast survives ungracefully losing its last source (see
164	/// [`Self::linger`]), returning `self` for chaining.
165	pub fn with_linger(mut self, linger: Duration) -> Self {
166		self.linger = linger;
167		self
168	}
169
170	/// Consume this config to create an origin [`Producer`].
171	pub fn produce(self) -> Producer {
172		Producer::new(self)
173	}
174}
175
176impl TryFrom<u64> for Origin {
177	type Error = InvalidOrigin;
178
179	fn try_from(id: u64) -> Result<Self, Self::Error> {
180		Self::new(id)
181	}
182}
183
184impl fmt::Display for Origin {
185	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186		self.id.fmt(f)
187	}
188}
189
190impl<V: Copy> Encode<V> for Origin
191where
192	u64: Encode<V>,
193{
194	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
195		self.id.encode(w, version)
196	}
197}
198
199impl<V: Copy> Decode<V> for Origin
200where
201	u64: Decode<V>,
202{
203	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
204		let id = u64::decode(r, version)?;
205		if id >= 1u64 << 62 {
206			return Err(DecodeError::InvalidValue);
207		}
208		Ok(Self { id })
209	}
210}
211
212/// Maximum number of origins (hops) an [`OriginList`] can hold.
213///
214/// Caps pathological or loop-induced announcements at a reasonable cluster
215/// diameter; appending past this limit returns [`TooManyOrigins`] rather than
216/// silently truncating.
217pub(crate) const MAX_HOPS: usize = 32;
218
219/// Bounded list of [`Origin`] entries, typically the hop chain of a broadcast.
220///
221/// Guarantees `len() <= MAX_HOPS`. Construct via [`OriginList::new`] +
222/// [`OriginList::push`], or fall back to the fallible [`TryFrom<Vec<Origin>>`].
223#[derive(Debug, Clone, Default, PartialEq, Eq)]
224pub struct OriginList(Vec<Origin>);
225
226/// Returned when an operation would grow an [`OriginList`] past its hop-count cap.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228#[non_exhaustive]
229pub struct TooManyOrigins;
230
231impl fmt::Display for TooManyOrigins {
232	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233		write!(f, "too many origins (max {MAX_HOPS})")
234	}
235}
236
237impl std::error::Error for TooManyOrigins {}
238
239impl From<TooManyOrigins> for DecodeError {
240	fn from(_: TooManyOrigins) -> Self {
241		DecodeError::BoundsExceeded
242	}
243}
244
245impl OriginList {
246	/// Create an empty list.
247	pub fn new() -> Self {
248		Self(Vec::new())
249	}
250
251	/// Append an [`Origin`]. Returns [`TooManyOrigins`] if the list is full.
252	pub fn push(&mut self, origin: Origin) -> Result<(), TooManyOrigins> {
253		if self.0.len() >= MAX_HOPS {
254			return Err(TooManyOrigins);
255		}
256		self.0.push(origin);
257		Ok(())
258	}
259
260	/// Replace the first entry equal to `target` with `replacement`, returning
261	/// true if a match was found. The length is unchanged.
262	pub fn replace_first(&mut self, target: Origin, replacement: Origin) -> bool {
263		for entry in &mut self.0 {
264			if *entry == target {
265				*entry = replacement;
266				return true;
267			}
268		}
269		false
270	}
271
272	/// Returns true if any entry matches `origin`.
273	pub fn contains(&self, origin: &Origin) -> bool {
274		self.0.contains(origin)
275	}
276
277	/// Number of entries currently in the list (always `<= MAX_HOPS`).
278	pub fn len(&self) -> usize {
279		self.0.len()
280	}
281
282	/// Whether the list contains no entries.
283	pub fn is_empty(&self) -> bool {
284		self.0.is_empty()
285	}
286
287	/// Iterate over the entries in hop order (oldest first).
288	pub fn iter(&self) -> std::slice::Iter<'_, Origin> {
289		self.0.iter()
290	}
291
292	/// Borrow the entries as a slice.
293	pub fn as_slice(&self) -> &[Origin] {
294		&self.0
295	}
296}
297
298impl TryFrom<Vec<Origin>> for OriginList {
299	type Error = TooManyOrigins;
300
301	fn try_from(v: Vec<Origin>) -> Result<Self, Self::Error> {
302		if v.len() > MAX_HOPS {
303			return Err(TooManyOrigins);
304		}
305		Ok(Self(v))
306	}
307}
308
309impl<'a> IntoIterator for &'a OriginList {
310	type Item = &'a Origin;
311	type IntoIter = std::slice::Iter<'a, Origin>;
312
313	fn into_iter(self) -> Self::IntoIter {
314		self.iter()
315	}
316}
317
318impl<V: Copy> Encode<V> for OriginList
319where
320	u64: Encode<V>,
321	Origin: Encode<V>,
322{
323	fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
324		(self.0.len() as u64).encode(w, version)?;
325		for origin in &self.0 {
326			origin.encode(w, version)?;
327		}
328		Ok(())
329	}
330}
331
332impl<V: Copy> Decode<V> for OriginList
333where
334	u64: Decode<V>,
335	Origin: Decode<V>,
336{
337	fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
338		let count = u64::decode(r, version)? as usize;
339		if count > MAX_HOPS {
340			return Err(DecodeError::BoundsExceeded);
341		}
342		let mut list = Vec::with_capacity(count);
343		for _ in 0..count {
344			list.push(Origin::decode(r, version)?);
345		}
346		Ok(Self(list))
347	}
348}
349
350static NEXT_CONSUMER_ID: AtomicU64 = AtomicU64::new(0);
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
353struct ConsumerId(u64);
354
355impl ConsumerId {
356	fn new() -> Self {
357		Self(NEXT_CONSUMER_ID.fetch_add(1, Ordering::Relaxed))
358	}
359}
360
361// The origin-owned broadcast at a leaf: the spliced broadcast consumers see,
362// the table of sources feeding it, and whether the path is currently announced.
363// `announced` is gated on the best source's `live` flag; a non-announced entry
364// is still returned by lookups, so an offline broadcast stays reachable for
365// subscribes and fetches.
366struct OriginBroadcast {
367	path: PathOwned,
368	/// The shared, spliced broadcast; its `consume()` is what consumers get.
369	broadcast: broadcast::Producer,
370	/// The source table, shared with every source watcher and the front task.
371	/// Also the broadcast's identity for stale-teardown checks.
372	state: kio::Producer<FrontState>,
373	announced: bool,
374}
375
376/// Ordering key used to pick the active route among broadcasts at the same path.
377///
378/// Lower wins. Shorter hop chains sort first (routing prefers the shortest path);
379/// remaining ties break on a deterministic hash of the broadcast name and hop
380/// chain. Every node in the cluster, given the same candidate routes, converges
381/// on the same winner: the hops are forwarded unchanged, and the hash is
382/// build-stable. Mixing the name in spreads equal routes across different
383/// upstreams rather than funneling onto one.
384fn route_key(name: &Path, hops: &OriginList) -> (usize, u64) {
385	(hops.len(), fnv_key(name, hops.iter().copied()))
386}
387
388/// FNV-1a over the broadcast name and a sequence of origin ids.
389///
390/// FNV-1a, not the std hasher: its output is fixed across Rust versions and
391/// builds, which matters when nodes run mismatched binaries during a rolling
392/// deploy and still need to agree on the same route. SEED is a custom basis
393/// (any nonzero u64 works, the textbook one is just as arbitrary); FNV_PRIME is
394/// the standard FNV-64 prime and should stay put.
395///
396/// Two callers, two different id sequences: [`route_key`] hashes a route's hop
397/// chain to pick among *routes*, and [`FrontState::handover_allowed`] hashes a
398/// single relay's origin to pick among *relays*. Mixing the name in spreads
399/// equal candidates across different winners rather than funneling onto one.
400fn fnv_key(name: &Path, origins: impl IntoIterator<Item = Origin>) -> u64 {
401	const SEED: u64 = 0x420C0DECB00B; // 420 C0DEC B00B
402	const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
403
404	let mut hash = SEED;
405	for &byte in name.as_str().as_bytes() {
406		hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
407	}
408	for origin in origins {
409		for &byte in &origin.id().to_le_bytes() {
410			hash = (hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME);
411		}
412	}
413
414	hash
415}
416
417/// Full ordering key for an attached route: announced routes first (an actively
418/// published source beats an offline one), then the marginal cost of pulling via
419/// the route, then the [`route_key`] hop ordering, and finally the newest source
420/// to attach. Lower wins.
421///
422/// Hop length stays the tie-break below cost, so peers that never carry a cost
423/// (pre-lite-06, or a plain local publish) rank exactly as they did before route
424/// cost existed, and equal-cost warm copies resolve to the closest one, which
425/// bounds same-datacenter chains to a single hop.
426///
427/// Recency is the last word, so it only separates routes that are identical in
428/// every advertised respect: same hop chain, same cost. That is a publisher
429/// reconnecting over a fresh session while its old one is still being kept alive
430/// by the transport, and the new session is the one actually carrying frames, so
431/// it wins the moment it attaches instead of after the QUIC idle timeout finally
432/// retires the corpse. Local attach order never leaks into cluster convergence:
433/// routes it can reorder are indistinguishable downstream, since what is
434/// forwarded is the chain and cost, which are equal by construction here.
435fn route_order(name: &Path, route: &FrontRoute) -> (bool, u64, usize, u64, Reverse<u64>) {
436	let (len, hash) = route_key(name, &route.route.hops);
437	(!route.route.announce, route.route.cost, len, hash, Reverse(route.id))
438}
439
440/// One coalesced update queued for an `AnnounceConsumer`.
441///
442/// At most one entry exists per path, so a slow consumer's pending set is bounded
443/// by the number of distinct paths. `UnannounceAnnounce` preserves the signal
444/// that a broadcast genuinely went away and a different one took its place (the
445/// consumer must see the `None` before the `Some`), while a stale `Announce`
446/// cancels with a subsequent `unannounce` because the consumer has not yet
447/// observed it.
448enum PendingUpdate {
449	Announce(broadcast::Consumer),
450	Unannounce,
451	UnannounceAnnounce(broadcast::Consumer),
452}
453
454/// Pending updates keyed by path. `BTreeMap` keeps memory strictly bounded by
455/// the number of distinct paths with outstanding work (collapsed pairs are
456/// fully erased) and gives a deterministic lexicographic delivery order so
457/// tests can predict it.
458#[derive(Default)]
459struct OriginConsumerState {
460	pending: BTreeMap<PathOwned, PendingUpdate>,
461}
462
463impl OriginConsumerState {
464	fn apply_announce(&mut self, path: PathOwned, broadcast: broadcast::Consumer) {
465		let new = match self.pending.remove(&path) {
466			// First announce, or a stale announce being replaced.
467			None | Some(PendingUpdate::Announce(_)) => PendingUpdate::Announce(broadcast),
468			// Consumer needs to observe the unannounce before this announce.
469			Some(PendingUpdate::Unannounce | PendingUpdate::UnannounceAnnounce(_)) => {
470				PendingUpdate::UnannounceAnnounce(broadcast)
471			}
472		};
473		self.pending.insert(path, new);
474	}
475
476	fn apply_unannounce(&mut self, path: PathOwned) {
477		match self.pending.remove(&path) {
478			// Consumer has not seen the pending announce; drop both entirely.
479			Some(PendingUpdate::Announce(_)) => {}
480			None | Some(PendingUpdate::Unannounce) => {
481				self.pending.insert(path, PendingUpdate::Unannounce);
482			}
483			// The embedded announce cancels with this unannounce; the consumer still
484			// needs the leading unannounce.
485			Some(PendingUpdate::UnannounceAnnounce(_)) => {
486				self.pending.insert(path, PendingUpdate::Unannounce);
487			}
488		}
489	}
490
491	/// Take one update to deliver to the consumer, if any.
492	fn take(&mut self) -> Option<OriginAnnounce> {
493		let path = self.pending.keys().next()?.clone();
494		let broadcast = match self.pending.remove(&path).unwrap() {
495			PendingUpdate::Announce(broadcast) => Some(broadcast),
496			PendingUpdate::Unannounce => None,
497			PendingUpdate::UnannounceAnnounce(broadcast) => {
498				// Deliver the unannounce now; leave the trailing announce pending so
499				// the next take returns it for the same path.
500				self.pending.insert(path.clone(), PendingUpdate::Announce(broadcast));
501				None
502			}
503		};
504		Some(OriginAnnounce { path, broadcast })
505	}
506}
507
508#[derive(Clone)]
509struct AnnounceConsumerNotify {
510	root: PathOwned,
511	state: kio::Producer<OriginConsumerState>,
512}
513
514impl AnnounceConsumerNotify {
515	fn announce(&self, path: impl AsPath, broadcast: broadcast::Consumer) {
516		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
517		self.state
518			.write()
519			.ok()
520			.expect("consumer closed")
521			.apply_announce(path, broadcast);
522	}
523
524	fn unannounce(&self, path: impl AsPath) {
525		let path = path.as_path().strip_prefix(&self.root).unwrap().to_owned();
526		self.state.write().ok().expect("consumer closed").apply_unannounce(path);
527	}
528}
529
530struct NotifyNode {
531	parent: Option<Lock<NotifyNode>>,
532
533	// Consumers that are subscribed to this node.
534	// We store a consumer ID so we can remove it easily when it closes.
535	consumers: HashMap<ConsumerId, AnnounceConsumerNotify>,
536}
537
538impl NotifyNode {
539	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
540		Self {
541			parent,
542			consumers: HashMap::new(),
543		}
544	}
545
546	fn announce(&mut self, path: impl AsPath, broadcast: &broadcast::Consumer) {
547		for consumer in self.consumers.values() {
548			consumer.announce(path.as_path(), broadcast.clone());
549		}
550
551		if let Some(parent) = &self.parent {
552			parent.lock().announce(path, broadcast);
553		}
554	}
555
556	fn unannounce(&mut self, path: impl AsPath) {
557		for consumer in self.consumers.values() {
558			consumer.unannounce(path.as_path());
559		}
560
561		if let Some(parent) = &self.parent {
562			parent.lock().unannounce(path);
563		}
564	}
565}
566
567/// Keeps a peer registered in [`FrontState::excluded`] for as long as it holds the
568/// shared front, so the front stays off routes that flow back through it.
569///
570/// Carried by the [`broadcast::Consumer`] handed to that peer and shared by its
571/// clones, so the registration ends when the last of them drops. A guard whose
572/// front has already closed is inert.
573pub(crate) struct ExclusionGuard {
574	state: kio::Producer<FrontState>,
575	peer: Origin,
576}
577
578impl ExclusionGuard {
579	/// Register `peer` and return the guard that releases it, or `None` if the
580	/// front is closing (nothing left to keep off a route).
581	fn new(state: &kio::Producer<FrontState>, peer: Origin) -> Option<Arc<Self>> {
582		let mut s = state.write().ok()?;
583		if s.closed {
584			return None;
585		}
586		*s.excluded.entry(peer).or_default() += 1;
587		drop(s);
588		Some(Arc::new(Self {
589			state: state.clone(),
590			peer,
591		}))
592	}
593}
594
595impl Drop for ExclusionGuard {
596	fn drop(&mut self) {
597		let Ok(mut state) = self.state.write() else { return };
598		if let std::collections::hash_map::Entry::Occupied(mut entry) = state.excluded.entry(self.peer) {
599			match entry.get() {
600				1 => drop(entry.remove()),
601				n => *entry.get_mut() = n - 1,
602			}
603		}
604	}
605}
606
607/// How a path resolves against the announce tree for one consumer.
608///
609/// `Excluded` is deliberately not folded into `Missing`: they mean opposite
610/// things to a caller holding a dynamic handler. Missing is "nobody here, go
611/// ask"; excluded is "here, but not for you", and asking a handler to route it
612/// anyway is how a split-horizon violation gets in through the back door.
613enum Resolved {
614	/// A broadcast this consumer may read: the shared front, or a single source
615	/// pinned because the front holds a route back through the requester.
616	Found(broadcast::Consumer),
617	/// The path is live, but every route to it flows through the requester.
618	Excluded,
619	/// Nothing is published at the path, or it is outside the consumer's scope.
620	Missing,
621}
622
623struct OriginNode {
624	// The origin-owned broadcast published at this node, if any (see
625	// [`Producer::create_broadcast`]).
626	broadcast: Option<OriginBroadcast>,
627
628	// Nested nodes, one level down the tree.
629	nested: HashMap<String, Lock<OriginNode>>,
630
631	// Unfortunately, to notify consumers we need to traverse back up the tree.
632	notify: Lock<NotifyNode>,
633}
634
635impl OriginNode {
636	fn new(parent: Option<Lock<NotifyNode>>) -> Self {
637		Self {
638			broadcast: None,
639			nested: HashMap::new(),
640			notify: Lock::new(NotifyNode::new(parent)),
641		}
642	}
643
644	fn leaf(&mut self, path: &Path) -> Lock<OriginNode> {
645		let (dir, rest) = path.next_part().expect("leaf called with empty path");
646
647		let next = self.entry(dir);
648		if rest.is_empty() { next } else { next.lock().leaf(&rest) }
649	}
650
651	fn entry(&mut self, dir: &str) -> Lock<OriginNode> {
652		match self.nested.get(dir) {
653			Some(next) => next.clone(),
654			None => {
655				let next = Lock::new(OriginNode::new(Some(self.notify.clone())));
656				self.nested.insert(dir.to_string(), next.clone());
657				next
658			}
659		}
660	}
661
662	/// Toggle the announce state of this leaf's broadcast, notifying consumers on
663	/// a change. The identity check keeps a stale front from toggling its
664	/// successor.
665	fn set_announced(&mut self, expect: &kio::Producer<FrontState>, announce: bool) {
666		let Some(existing) = &mut self.broadcast else { return };
667		if !existing.state.same_channel(expect) || existing.announced == announce {
668			return;
669		}
670		existing.announced = announce;
671		let path = existing.path.clone();
672		let consumer = existing.broadcast.consume();
673		let mut notify = self.notify.lock();
674		if announce {
675			notify.announce(&path, &consumer);
676		} else {
677			notify.unannounce(&path);
678		}
679	}
680
681	fn consume(&mut self, id: ConsumerId, mut notify: AnnounceConsumerNotify) {
682		self.consume_initial(&mut notify);
683		self.notify.lock().consumers.insert(id, notify);
684	}
685
686	fn consume_initial(&mut self, notify: &mut AnnounceConsumerNotify) {
687		// Only announced (live) broadcasts replay; offline ones are reachable by
688		// exact path but never advertised.
689		if let Some(broadcast) = &self.broadcast
690			&& broadcast.announced
691		{
692			notify.announce(&broadcast.path, broadcast.broadcast.consume());
693		}
694
695		// Recursively subscribe to all nested nodes.
696		for nested in self.nested.values() {
697			nested.lock().consume_initial(notify);
698		}
699	}
700
701	fn resolve_broadcast(&self, rest: impl AsPath, exclude: Option<Origin>) -> Resolved {
702		let rest = rest.as_path();
703
704		if let Some((dir, rest)) = rest.next_part() {
705			let Some(node) = self.nested.get(dir) else {
706				return Resolved::Missing;
707			};
708			let node = node.lock();
709			return node.resolve_broadcast(&rest, exclude);
710		}
711
712		let Some(broadcast) = self.broadcast.as_ref() else {
713			return Resolved::Missing;
714		};
715		let Some(origin) = exclude else {
716			return Resolved::Found(broadcast.broadcast.consume());
717		};
718
719		// Data-plane split horizon: never serve a requester from a source whose
720		// chain flows through them (they'd receive their own bytes back, or worse,
721		// a subscription cycle).
722		//
723		// The shared spliced front is safe only while *no* attached route is
724		// tainted for the requester: the front picks per track and re-picks on
725		// failover, so a tainted route anywhere in the table is one the front may
726		// serve them from. Checking the whole table rather than just the active
727		// route is what keeps that honest at this instant; the guard registered
728		// below keeps it honest afterwards, holding the front off a route through
729		// this peer for as long as they are reading it. Otherwise pin them to the
730		// best clean source. A pinned broadcast skips the front's re-splicing,
731		// which is fine: the requester is itself a relay (only a forwarder's origin
732		// can appear in a chain) and re-splices via its own front when the pinned
733		// source dies.
734		let state = broadcast.state.read();
735		if !state.routes.iter().any(|r| r.route.hops.contains(&origin)) {
736			drop(state);
737			let shared = broadcast.broadcast.consume();
738			return match ExclusionGuard::new(&broadcast.state, origin) {
739				Some(guard) => Resolved::Found(shared.with_exclusion(guard)),
740				// The front closed between the lookup and the registration; it
741				// serves nothing now, so there is nothing to keep off a route.
742				None => Resolved::Found(shared),
743			};
744		}
745		match state
746			.dispatch(Some(origin))
747			.and_then(|clean| state.routes.iter().find(|r| r.id == clean))
748		{
749			Some(route) => Resolved::Found(route.source.clone()),
750			None => Resolved::Excluded,
751		}
752	}
753
754	fn unconsume(&mut self, id: ConsumerId) {
755		self.notify.lock().consumers.remove(&id).expect("consumer not found");
756		if self.is_empty() {
757			//tracing::warn!("TODO: empty node; memory leak");
758			// This happens when consuming a path that is not being broadcasted.
759		}
760	}
761
762	/// Remove the broadcast at `relative` if it is `expect`, unannouncing it if
763	/// needed and pruning empty nodes on the way back up. The identity check
764	/// keeps a stale teardown from clobbering a replacement.
765	fn remove(&mut self, expect: &kio::Producer<FrontState>, relative: impl AsPath) {
766		let relative = relative.as_path();
767
768		if let Some((dir, relative)) = relative.next_part() {
769			let Some(nested) = self.nested.get(dir) else { return };
770			let nested = nested.clone();
771			let mut locked = nested.lock();
772			locked.remove(expect, &relative);
773
774			if locked.is_empty() {
775				drop(locked);
776				self.nested.remove(dir);
777			}
778		} else if let Some(existing) = &self.broadcast
779			&& existing.state.same_channel(expect)
780		{
781			let existing = self.broadcast.take().expect("checked above");
782			if existing.announced {
783				self.notify.lock().unannounce(&existing.path);
784			}
785		}
786	}
787
788	fn is_empty(&self) -> bool {
789		self.broadcast.is_none() && self.nested.is_empty() && self.notify.lock().consumers.is_empty()
790	}
791}
792
793#[derive(Clone)]
794struct OriginNodes {
795	nodes: Vec<(PathOwned, Lock<OriginNode>)>,
796}
797
798impl OriginNodes {
799	// Returns nested roots that match the prefixes.
800	// PathPrefixes guarantees no duplicates or overlapping prefixes.
801	pub fn select(&self, prefixes: &PathPrefixes) -> Option<Self> {
802		let mut roots = Vec::new();
803
804		for (root, state) in &self.nodes {
805			for prefix in prefixes {
806				if root.has_prefix(prefix) {
807					// Keep the existing node if we're allowed to access it.
808					roots.push((root.to_owned(), state.clone()));
809					continue;
810				}
811
812				if let Some(suffix) = prefix.strip_prefix(root) {
813					// If the requested prefix is larger than the allowed prefix, then we further scope it.
814					let nested = state.lock().leaf(&suffix);
815					roots.push((prefix.to_owned(), nested));
816				}
817			}
818		}
819
820		if roots.is_empty() {
821			None
822		} else {
823			Some(Self { nodes: roots })
824		}
825	}
826
827	pub fn root(&self, new_root: impl AsPath) -> Option<Self> {
828		let new_root = new_root.as_path();
829		let mut roots = Vec::new();
830
831		if new_root.is_empty() {
832			return Some(self.clone());
833		}
834
835		for (root, state) in &self.nodes {
836			if let Some(suffix) = root.strip_prefix(&new_root) {
837				// If the old root is longer than the new root, shorten the keys.
838				roots.push((suffix.to_owned(), state.clone()));
839			} else if let Some(suffix) = new_root.strip_prefix(root) {
840				// If the new root is longer than the old root, add a new root.
841				// NOTE: suffix can't be empty
842				let nested = state.lock().leaf(&suffix);
843				roots.push(("".into(), nested));
844			}
845		}
846
847		if roots.is_empty() {
848			None
849		} else {
850			Some(Self { nodes: roots })
851		}
852	}
853
854	// Returns the root that has this prefix.
855	pub fn get(&self, path: impl AsPath) -> Option<(Lock<OriginNode>, PathOwned)> {
856		let path = path.as_path();
857
858		for (root, state) in &self.nodes {
859			if let Some(suffix) = path.strip_prefix(root) {
860				return Some((state.clone(), suffix.to_owned()));
861			}
862		}
863
864		None
865	}
866}
867
868impl Default for OriginNodes {
869	fn default() -> Self {
870		Self {
871			nodes: vec![("".into(), Lock::new(OriginNode::new(None)))],
872		}
873	}
874}
875
876/// A path and the broadcast now available there, delivered by [`AnnounceConsumer`].
877#[derive(Clone)]
878pub struct OriginAnnounce {
879	/// The path of the broadcast, relative to the consuming cursor's root.
880	pub path: PathOwned,
881	/// The broadcast now available at that path, or `None` if it is no longer available.
882	///
883	/// A replacement (a relay failover, or a shorter hop path arriving) is delivered as a
884	/// `None` followed by a `Some`, never as a swap in place. A route change alone is invisible here (the handles stay
885	/// valid); observe it via [`broadcast::Consumer::route_changed`].
886	pub broadcast: Option<broadcast::Consumer>,
887}
888
889/// Announces broadcasts to consumers over the network.
890#[derive(Clone)]
891pub struct Producer {
892	// Identity for this origin. Appended to broadcast hops when
893	// re-announcing so downstream relays can detect loops and prefer the
894	// shortest path.
895	info: Origin,
896
897	// The roots of the tree that we are allowed to publish.
898	// A path of "" means we can publish anything.
899	nodes: OriginNodes,
900
901	// The prefix that is automatically stripped from all paths.
902	root: PathOwned,
903
904	// Fallback request queue, shared with every derived consumer. Separate from
905	// `nodes` because dynamic broadcasts are never announced: they only resolve a
906	// consumer's `request_broadcast` when no live announcement exists.
907	dynamic: kio::Shared<OriginDynamicState>,
908
909	// The cache pool inherited by broadcasts created under this origin (sessions
910	// mint their remote broadcasts with it). Unbounded by default.
911	pool: cache::Pool,
912
913	// Retention ceiling inherited by broadcasts created under this origin (see
914	// [`Info::cache_duration`]). `Duration::MAX` (no ceiling) by default.
915	cache_duration: Duration,
916
917	// How long a broadcast outlives ungracefully losing its last source (see
918	// [`Info::linger`]). Zero by default.
919	linger: Duration,
920
921	// Ingress stats context. Broadcasts created through this producer are attributed
922	// to it (writes counted on the subscriber/ingress side). Empty (no-op) unless a
923	// session tagged this handle via [`Self::with_stats`].
924	stats: stats::Session,
925}
926
927impl std::ops::Deref for Producer {
928	type Target = Origin;
929
930	fn deref(&self) -> &Self::Target {
931		&self.info
932	}
933}
934
935impl Producer {
936	/// Build a producer from an [`Info`] (identity + cache pool) with no scoped
937	/// prefix and no pre-existing broadcasts. Prefer [`Info::produce`] /
938	/// [`Origin::produce`].
939	pub fn new(info: Info) -> Self {
940		Self {
941			info: info.id,
942			nodes: OriginNodes::default(),
943			root: PathOwned::default(),
944			dynamic: kio::Shared::default(),
945			pool: info.pool,
946			cache_duration: info.cache_duration,
947			linger: info.linger,
948			stats: stats::Session::default(),
949		}
950	}
951
952	/// Attach an ingress stats context: broadcasts created through this handle (and
953	/// any handle derived from it) are attributed to `session` on the subscriber
954	/// (ingress) side. Pass [`stats::Session::default`] to opt out.
955	pub fn with_stats(mut self, session: stats::Session) -> Self {
956		self.stats = session;
957		self
958	}
959
960	/// Set the linger (see [`Info::linger`]) for broadcasts created through this
961	/// handle and any handle derived from it.
962	///
963	/// A broadcast adopts the window of the handle whose source *created* it (the
964	/// first source at the path), so set this before handing the producer to
965	/// whatever attaches sources through it (e.g. a session). Lets one supplier
966	/// declare its own recovery promise, like a reconnecting client lingering for
967	/// as long as its retry loop keeps trying, without reconfiguring the origin.
968	pub fn with_linger(mut self, linger: Duration) -> Self {
969		self.linger = linger;
970		self
971	}
972
973	/// This origin's [`Info`] (identity + cache pool), the parent handle a broadcast
974	/// created under this origin carries (see [`broadcast::Info::origin`]).
975	pub fn info(&self) -> Info {
976		Info {
977			id: self.info,
978			pool: self.pool.clone(),
979			cache_duration: self.cache_duration,
980			linger: self.linger,
981		}
982	}
983
984	/// A producer with *no* allowed prefixes: it can't publish anything and
985	/// advertises no subscribe interest (its `allowed()` is empty, so the
986	/// subscriber issues no ANNOUNCE_PLEASE). Used to fill an unset session half
987	/// so both the publisher and subscriber loops still run.
988	pub(crate) fn empty(info: Origin) -> Self {
989		Self {
990			info,
991			nodes: OriginNodes { nodes: Vec::new() },
992			root: PathOwned::default(),
993			dynamic: kio::Shared::default(),
994			pool: cache::Pool::default(),
995			cache_duration: Duration::MAX,
996			linger: Duration::ZERO,
997			stats: stats::Session::default(),
998		}
999	}
1000
1001	/// Create a broadcast at `path`, fed through the returned producer.
1002	///
1003	/// This is the sole way content enters an origin. The returned
1004	/// [`broadcast::Producer`] is a route source: the origin owns the broadcast
1005	/// consumers actually see, and splices its tracks across every source created
1006	/// at the same path (other local publishers, or sessions attaching announces
1007	/// from the network), always serving from the best [`broadcast::Route`] (live
1008	/// first, then lowest cost, then shortest hops with a deterministic
1009	/// tie-break, and the newest source among otherwise equal routes). When the
1010	/// best source changes, tracks resume from the replacement at the first
1011	/// missing group; consumers never observe the swap.
1012	///
1013	/// Splicing requires the same content identity: every source at a path must
1014	/// share the first hop of its route, which is a promise that they produce
1015	/// interchangeable tracks. An *announced* source arriving with a different
1016	/// first hop is a replacement instead: it takes the path immediately and
1017	/// consumers see an unannounce followed by an announce, rather than unrelated
1018	/// content spliced into a live subscription. So a publisher reconnecting
1019	/// under a fresh identity displaces the session it replaced right away,
1020	/// without waiting for the transport to notice the old one is gone. An
1021	/// offline source never displaces anything: it ranks below every announced
1022	/// route, so it waits invisibly for the incumbent to end.
1023	///
1024	/// `route` is the source's initial metadata; update it with
1025	/// [`broadcast::Producer::set_route`]. The [`broadcast::Route::announce`] flag
1026	/// controls whether the path is announced: a non-live broadcast is invisible
1027	/// to [`Consumer::announced`] but stays reachable by exact path for
1028	/// subscribes and fetches (e.g. serving cached or on-demand content), so
1029	/// toggling `live` announces or unannounces without touching the broadcast.
1030	///
1031	/// The broadcast becomes visible to consumers asynchronously, shortly after
1032	/// this returns. Create tracks and register a
1033	/// [`broadcast::Producer::dynamic`] handler before awaiting, so the first
1034	/// consumer finds them.
1035	///
1036	/// End the broadcast with [`broadcast::Producer::finish`]; dropping it
1037	/// without finishing also works, but logs a warning. A finish closes and
1038	/// unannounces the path immediately once it was the last source. An unfinished
1039	/// drop is treated as an outage: the path survives for the origin's
1040	/// [`Info::linger`] (zero by default), so a replacement source attaching within
1041	/// that window splices in without consumers noticing.
1042	///
1043	/// Fails with [`Error::Unauthorized`] if `path` is outside the prefixes this
1044	/// producer may publish under (after [`scope`](Self::scope) /
1045	/// [`with_root`](Self::with_root)), or [`Error::BoundsExceeded`] if the full
1046	/// rooted path exceeds [`Path::MAX_PARTS`]. Must be called with a runtime
1047	/// available (it spawns the broadcast's lifecycle task). Callers must not use
1048	/// a route whose hop chain contains this origin's id (it would form a routing
1049	/// loop); relays filter such reflections before they reach here, checked by a
1050	/// `debug_assert`.
1051	pub fn create_broadcast(&self, path: impl AsPath, route: broadcast::Route) -> Result<broadcast::Producer, Error> {
1052		let path = path.as_path();
1053
1054		debug_assert!(
1055			!route.hops.contains(&self.info),
1056			"create_broadcast called with a looping hop chain",
1057		);
1058
1059		let (node, rest) = self.nodes.get(&path).ok_or(Error::Unauthorized)?;
1060		let full = self.root.join(&path).to_owned();
1061
1062		// A decoded announce prefix and suffix are each within the wire limit, but their
1063		// join might not be. Enforcing here bounds the tree depth and guarantees the path
1064		// can be re-encoded when forwarded.
1065		if full.parts().count() > Path::MAX_PARTS {
1066			return Err(BoundsExceeded.into());
1067		}
1068
1069		// Resolve the ingress counters once, keyed by the absolute broadcast path.
1070		// The source producer tags its tracks; run_source drives the announce guard
1071		// off route transitions.
1072		let ingress = self.stats.ingress(&full);
1073
1074		let mut source = broadcast::Info { origin: self.info() }
1075			.produce()
1076			.with_stats(ingress.clone());
1077		source.set_route(route).expect("fresh producer");
1078
1079		web_async::spawn(run_source(self.info(), node, full, rest, source.consume(), ingress));
1080
1081		Ok(source)
1082	}
1083
1084	/// Returns a new Producer restricted to publishing under one of `prefixes`.
1085	///
1086	/// Returns None if there are no legal prefixes (the requested prefixes are
1087	/// disjoint from this producer's current scope).
1088	// TODO accept PathPrefixes instead of &[Path]
1089	pub fn scope(&self, prefixes: &[Path]) -> Option<Producer> {
1090		let prefixes = PathPrefixes::new(prefixes);
1091		Some(Producer {
1092			info: self.info,
1093			nodes: self.nodes.select(&prefixes)?,
1094			root: self.root.clone(),
1095			dynamic: self.dynamic.clone(),
1096			pool: self.pool.clone(),
1097			cache_duration: self.cache_duration,
1098			linger: self.linger,
1099			stats: self.stats.clone(),
1100		})
1101	}
1102
1103	/// Create a dynamic handler that picks up [`Consumer::request_broadcast`]
1104	/// calls for paths that are not announced.
1105	///
1106	/// This is the origin-level analogue of [`broadcast::Producer::dynamic`]: it serves
1107	/// broadcasts on demand rather than tracks. Crucially the served broadcasts are
1108	/// *not* announced, so [`Consumer::announced`] never sees them; they exist
1109	/// only as a fallback for a consumer that asks for an exact path with no live
1110	/// announcement. Drop the handler (and every clone) to reject pending requests.
1111	pub fn dynamic(&self) -> Dynamic {
1112		Dynamic::new(self.info, self.root.clone(), self.dynamic.clone())
1113	}
1114
1115	/// Cheap read handle over this origin's broadcast tree.
1116	///
1117	/// Use [`Consumer::announced`] to register interest and start receiving
1118	/// announcement events; the consumer itself does not allocate any channels.
1119	pub fn consume(&self) -> Consumer {
1120		// Untagged: a session tags the egress consumer separately via
1121		// `origin::Consumer::with_stats` (ingress and egress are distinct sides).
1122		Consumer::new(
1123			self.info,
1124			self.root.clone(),
1125			self.nodes.clone(),
1126			self.dynamic.clone(),
1127			stats::Session::default(),
1128		)
1129	}
1130
1131	/// Handle to the announcement stream for this producer's subtree.
1132	///
1133	/// Symmetric counterpart to [`Self::consume`]; call
1134	/// [`AnnounceProducer::consume`] to get an [`AnnounceConsumer`] that
1135	/// receives announce / unannounce events.
1136	pub fn announces(&self) -> AnnounceProducer {
1137		AnnounceProducer::new(self.root.clone(), self.nodes.clone())
1138	}
1139
1140	/// Returns a new Producer that automatically strips out the provided prefix.
1141	///
1142	/// Returns None if the provided root is not authorized; when [`Self::scope`]
1143	/// was already used without a wildcard.
1144	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
1145		let prefix = prefix.as_path();
1146
1147		Some(Self {
1148			info: self.info,
1149			root: self.root.join(&prefix).to_owned(),
1150			nodes: self.nodes.root(&prefix)?,
1151			dynamic: self.dynamic.clone(),
1152			pool: self.pool.clone(),
1153			cache_duration: self.cache_duration,
1154			linger: self.linger,
1155			stats: self.stats.clone(),
1156		})
1157	}
1158
1159	/// Returns the root that is automatically stripped from all paths.
1160	pub fn root(&self) -> &Path<'_> {
1161		&self.root
1162	}
1163
1164	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
1165	// TODO return PathPrefixes
1166	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
1167		self.nodes.nodes.iter().map(|(root, _)| root)
1168	}
1169
1170	/// Converts a relative path to an absolute path.
1171	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
1172		self.root.join(path)
1173	}
1174}
1175
1176/// How long a spliced track stays warm after its last reader leaves.
1177///
1178/// Within the window a returning viewer, or the next of a run of back-to-back
1179/// fetches, reuses the source's copy: no new track request, and no second round
1180/// trip for its `TRACK_INFO`. After it, the copy is released so an idle track
1181/// costs nothing upstream.
1182///
1183/// Sized above the fetch cadence of a segmented consumer: HLS polls every
1184/// `TARGETDURATION` seconds, commonly 6 or 10, so a shorter window would drop the
1185/// copy between every segment and re-request the track each time. A warm copy
1186/// holds no upstream subscription (that is canceled as soon as demand ends), so
1187/// waiting longer costs cached state, not a viewer.
1188const TRACK_IDLE_LINGER: Duration = Duration::from_secs(30);
1189
1190/// One attached source in a [`FrontState`] table.
1191struct FrontRoute {
1192	id: u64,
1193	/// The source's latest [`broadcast::Route`], mirrored from its
1194	/// `route_changed` stream; picks the active source and gates the announce.
1195	route: broadcast::Route,
1196	/// The source broadcast tracks are served from.
1197	source: broadcast::Consumer,
1198}
1199
1200/// Shared state behind a [`Front`]: the attached sources and which one is active.
1201struct FrontState {
1202	/// Absolute path of the broadcast, mixed into the route tie-break hash.
1203	path: PathOwned,
1204	/// The local origin's identity, the other half of the handover key gate.
1205	self_origin: Origin,
1206	/// Content identity: the original publisher (first hop) shared by every
1207	/// attached source, or `None` for a broadcast produced locally (no hops).
1208	/// Fixed for the front's lifetime; a source with a different first hop is new
1209	/// content, not an alternate route, so it replaces this front (or waits for
1210	/// it, when offline) rather than joining it (see [`attach_source`]). This is
1211	/// the same rule the session layer applies to a restart whose first hop
1212	/// changed.
1213	publisher: Option<Origin>,
1214	/// Attach counter, handed to each [`FrontRoute`] so [`route_order`] can break
1215	/// an exact tie toward the newest source.
1216	next_route: u64,
1217	routes: Vec<FrontRoute>,
1218	/// Peers currently reading this front through the shared broadcast, refcounted
1219	/// by live [`ExclusionGuard`]s. The resolve-time check only proves the table is
1220	/// clean for a requester at that instant; the front picks per track and re-picks
1221	/// on failover, so without this a route tainted for an attached peer could be
1222	/// adopted underneath them and hand them back their own bytes. Routes through
1223	/// these origins are avoided while any clean alternative exists.
1224	excluded: HashMap<Origin, usize>,
1225	/// The source tracks are dispatched to. Backups park until promoted.
1226	active: Option<u64>,
1227	/// How long the front outlives ungracefully losing its last source (see
1228	/// [`Info::linger`]). [`run_front`] arms the countdown when the table empties.
1229	linger: Duration,
1230	/// Terminal: no more sources may attach and every poller stops. Set
1231	/// synchronously by the detach that empties the table or by an
1232	/// [`attach_source`] takeover, and by [`run_front`] when the linger window
1233	/// expires without a replacement.
1234	closed: bool,
1235}
1236
1237impl FrontState {
1238	/// The one selection primitive every picker goes through: the best route by
1239	/// [`route_order`] among those surviving `keep`. With `untainted`, the pick
1240	/// also steers away from routes that flow through a peer currently reading
1241	/// the shared front, unless that leaves nothing (see
1242	/// [`Self::prefer_untainted`]); it is off for [`Self::dispatch`], which pins
1243	/// one requester to one source rather than serving the shared front.
1244	fn pick(&self, keep: impl Fn(&FrontRoute) -> bool, untainted: bool) -> Option<u64> {
1245		let candidates: Vec<&FrontRoute> = self.routes.iter().filter(|r| keep(r)).collect();
1246		let candidates = match untainted {
1247			true => self.prefer_untainted(&candidates),
1248			false => candidates,
1249		};
1250		candidates
1251			.into_iter()
1252			.min_by_key(|r| route_order(&self.path.as_path(), r))
1253			.map(|r| r.id)
1254	}
1255
1256	/// The source new track requests should dispatch to: live first, then lowest
1257	/// cost, then shortest hop chain with a deterministic hash tie-break and the
1258	/// newest source last, skipping routes that flow through a peer currently
1259	/// reading the front while any other route remains.
1260	fn best_route(&self) -> Option<u64> {
1261		self.pick(|_| true, true)
1262	}
1263
1264	/// The source a subscription from `exclude` should dispatch to: the best
1265	/// route whose hop chain does not contain the requester. This is the same
1266	/// selection a session uses to pick what it announces to that peer, and the
1267	/// two being one computation is the loop-freedom invariant: chains stay
1268	/// truthful, so any would-be cycle surfaces the requester's own origin in
1269	/// the candidate chain and is filtered here, at any cycle length.
1270	fn dispatch(&self, exclude: Option<Origin>) -> Option<u64> {
1271		self.pick(|r| exclude.is_none_or(|origin| !r.route.hops.contains(&origin)), false)
1272	}
1273
1274	/// Whether serving from `route` would hand an attached peer its own bytes back.
1275	fn taints_a_reader(&self, route: &broadcast::Route) -> bool {
1276		route.hops.iter().any(|hop| self.excluded.contains_key(hop))
1277	}
1278
1279	/// Narrow `candidates` to the routes clean for every peer currently reading the
1280	/// shared front, unless that would leave nothing.
1281	///
1282	/// Keeping the front off a tainted route is what makes the resolve-time
1283	/// split-horizon check hold for the life of a subscription rather than just at
1284	/// request time. Falling back when every route is tainted is deliberate: the
1285	/// alternative is starving readers the route is perfectly good for, and a peer
1286	/// whose only path runs back through itself has nothing to be served from
1287	/// anyway. It re-resolves to [`Error::Unroutable`] on its next request.
1288	fn prefer_untainted<'a>(&self, candidates: &[&'a FrontRoute]) -> Vec<&'a FrontRoute> {
1289		if self.excluded.is_empty() {
1290			return candidates.to_vec();
1291		}
1292		let clean: Vec<&FrontRoute> = candidates
1293			.iter()
1294			.copied()
1295			.filter(|r| !self.taints_a_reader(&r.route))
1296			.collect();
1297		match clean.is_empty() {
1298			true => candidates.to_vec(),
1299			false => clean,
1300		}
1301	}
1302
1303	/// The source one track should be served from: the front's active source
1304	/// unless `skip` rules it out, then the next-best route that survives.
1305	///
1306	/// Whether a source carries a given track is a per-track property (a standby
1307	/// that has not created it yet, a publisher whose encoder is still starting),
1308	/// so a source refusing one track is ruled out of that track only, never out
1309	/// of the front. Preferring `active` keeps a servable track on exactly the
1310	/// route [`Self::reselect`] chose, handover gate included.
1311	fn serve_route(&self, skip: impl Fn(u64) -> bool) -> Option<u64> {
1312		if let Some(active) = self.active
1313			&& !skip(active)
1314			&& let Some(route) = self.routes.iter().find(|r| r.id == active)
1315			&& !self.taints_a_reader(&route.route)
1316		{
1317			return Some(active);
1318		}
1319		self.pick(|r| !skip(r.id), true)
1320	}
1321
1322	/// Re-pick the active source after the table changed. Serve tasks watch
1323	/// `active` and re-splice on their own, so a cheaper route takes over
1324	/// seamlessly at a group boundary.
1325	///
1326	/// The one exception is the simultaneous-activation race: two nodes that
1327	/// each pulled the broadcast before seeing the other both advertise zero
1328	/// cost, so each sees the other as cheaper than its own source, and
1329	/// re-parenting onto each other at once leaves the broadcast with no
1330	/// upstream at all. That hazard only exists when both sides are actively
1331	/// carrying, so the gate is scoped to exactly that: while `carrying` (the
1332	/// front has live demand), a cheaper route whose announcing relay is itself
1333	/// carrying (it advertised zero from a chain of two or more hops; a chain of
1334	/// one is the original publisher, which can never adopt a route to its own
1335	/// broadcast) displaces an announced incumbent only when
1336	/// [`Self::handover_allowed`] says so. Every other cheaper route, e.g. a
1337	/// forwarder path or an upstream that repriced itself down, is taken
1338	/// immediately.
1339	fn reselect(&mut self, carrying: bool) {
1340		let best = self.best_route();
1341		if carrying
1342			&& let (Some(best_id), Some(cur_id)) = (best, self.active)
1343			&& best_id != cur_id
1344			&& let Some(candidate) = self.routes.iter().find(|r| r.id == best_id)
1345			&& let Some(incumbent) = self.routes.iter().find(|r| r.id == cur_id)
1346			&& incumbent.route.announce
1347			&& candidate.route.cost < incumbent.route.cost
1348			&& candidate.route.advertised == 0
1349			&& candidate.route.hops.len() >= 2
1350			&& !self.handover_allowed(&candidate.route)
1351		{
1352			// We won the key comparison: keep our source and let the peer come to us.
1353			return;
1354		}
1355		self.active = best;
1356	}
1357
1358	/// Whether re-parenting onto `route` is allowed while actively carrying: the
1359	/// announcing peer (the chain's last hop) must hash strictly below our own
1360	/// origin for this broadcast name.
1361	///
1362	/// Both sides compute the same two keys (the hash is build-stable and the
1363	/// inputs are shared), so the comparison resolves the same way everywhere:
1364	/// the lower-keyed node keeps its source, the higher-keyed one re-parents.
1365	/// A strict total order has no cycles, so mutual pulls cannot happen. Mixing
1366	/// the broadcast name in spreads ownership across a region's relays instead
1367	/// of funneling every broadcast onto the lowest-keyed one. A route with no
1368	/// hops is a local publish and always allowed.
1369	fn handover_allowed(&self, route: &broadcast::Route) -> bool {
1370		let name = self.path.as_path();
1371		match route.hops.iter().last() {
1372			Some(peer) => fnv_key(&name, [*peer]) < fnv_key(&name, [self.self_origin]),
1373			None => true,
1374		}
1375	}
1376
1377	/// Every attached route in preference order with the active one first,
1378	/// mirrored onto the front's broadcast so sessions can advertise (and be
1379	/// served) a different route per peer. The active route leads even when the
1380	/// handover gate kept it over a lower-ordered candidate, so `routes[0]` is
1381	/// always what this node is actually serving from.
1382	fn routes_snapshot(&self) -> Vec<broadcast::Route> {
1383		let mut routes: Vec<&FrontRoute> = self.routes.iter().collect();
1384		routes.sort_by_key(|r| route_order(&self.path.as_path(), r));
1385		routes.sort_by_key(|r| Some(r.id) != self.active);
1386		routes.into_iter().map(|r| r.route.clone()).collect()
1387	}
1388}
1389
1390/// Refresh the front's public face after a table change: advertise the best
1391/// source's route on the spliced broadcast and gate the path's announcement on
1392/// its `live` flag.
1393///
1394/// Re-reads the table at apply time (rather than applying a value computed under
1395/// an earlier lock) so concurrent attach/detach/update calls converge on the
1396/// latest winner regardless of the order their applies land in. An empty table
1397/// leaves the advert and announce state alone; the front task is closing and
1398/// unannounces on its way out.
1399fn sync_front(state: &kio::Producer<FrontState>, broadcast: &broadcast::Producer, leaf: &Lock<OriginNode>) {
1400	// Snapshot and apply under the leaf lock: two concurrent syncs would
1401	// otherwise race their applies, letting a stale snapshot land last and
1402	// leave the announce flag (or advert) contradicting the current table.
1403	// Lock order (leaf, then table, then broadcast) matches attach_source.
1404	let mut leaf_guard = leaf.lock();
1405	let routes = state.read().routes_snapshot();
1406	if let Some(advert) = routes.first() {
1407		let announce = advert.announce;
1408		broadcast.clone().set_routes(routes);
1409		leaf_guard.set_announced(state, announce);
1410	}
1411}
1412
1413/// Detach source `id`, promoting the next-best source; the tracks it was serving
1414/// re-splice on their own. Idempotent.
1415///
1416/// `graceful` says how the source ended: a deliberate finish (a publisher done
1417/// publishing, a peer's clean unannounce) or an abrupt loss (a session dying).
1418/// Detaching the last source *gracefully* closes the broadcast synchronously,
1419/// which guarantees a following create at the path is a *new* broadcast rather
1420/// than splicing new content into this one. An abrupt last detach instead leaves
1421/// the front open for the configured linger ([`Info::linger`]), so a source
1422/// re-attaching within the window (a reconnect) splices in seamlessly;
1423/// [`run_front`] closes it if the window expires empty. A zero linger closes
1424/// abrupt detaches synchronously too.
1425fn detach_source(
1426	state: &kio::Producer<FrontState>,
1427	broadcast: &broadcast::Producer,
1428	leaf: &Lock<OriginNode>,
1429	id: u64,
1430	graceful: bool,
1431) {
1432	let close = {
1433		// Snapshotted before the state lock (lock order: broadcast, then front).
1434		// A demand flip in between only stales this reselect's handover gate,
1435		// which self-corrects on the next table change.
1436		let carrying = broadcast.demand().is_used();
1437		let Ok(mut s) = state.write() else { return };
1438		let Some(pos) = s.routes.iter().position(|r| r.id == id) else {
1439			return;
1440		};
1441		s.routes.remove(pos);
1442		s.reselect(carrying);
1443		if s.routes.is_empty() && !s.closed && (graceful || s.linger.is_zero()) {
1444			// Last one out: close now. The front task observes `closed` and
1445			// finishes the teardown (unpublish).
1446			s.closed = true;
1447			true
1448		} else {
1449			false
1450		}
1451	};
1452	if close {
1453		broadcast.abort_spliced(Error::Dropped);
1454	}
1455	sync_front(state, broadcast, leaf);
1456}
1457
1458/// Match the ingress announce guard to a route's announce flag: opening bumps
1459/// the announced counters, dropping bumps the closed ones. See [`run_source`].
1460fn sync_announce(guard: &mut Option<stats::Announce>, announced: bool, ingress: &stats::Scope) {
1461	match (announced, guard.is_some()) {
1462		(true, false) => *guard = Some(ingress.announce()),
1463		(false, true) => *guard = None,
1464		_ => {}
1465	}
1466}
1467
1468/// Owns one source's lifecycle: attaches it to the front at its path on the first
1469/// route observation, forwards route updates, and detaches it when the source
1470/// closes. Spawned by [`Producer::create_broadcast`].
1471///
1472/// An announced source whose original publisher (first hop) differs from the
1473/// live front's takes the path over as a fresh broadcast rather than joining; an
1474/// offline one parks until the front closes. A route update that changes the
1475/// source's own first hop likewise detaches it and re-runs the attach, so a
1476/// publisher swap is always a replacement, never a silent splice.
1477async fn run_source(
1478	origin: Info,
1479	node: Lock<OriginNode>,
1480	full: PathOwned,
1481	rest: PathOwned,
1482	mut source: broadcast::Consumer,
1483	ingress: stats::Scope,
1484) {
1485	let ctx = AttachContext {
1486		origin: &origin,
1487		node: &node,
1488		full: &full,
1489		rest: &rest,
1490	};
1491
1492	// The first `route_changed` yields the current route immediately; nothing is
1493	// visible to consumers until this attach, giving the creator a window to set
1494	// up tracks and dynamic handlers.
1495	let Ok(mut route) = source.route_changed().await else {
1496		// Closed before ever attaching; nothing became visible.
1497		return;
1498	};
1499
1500	// Ingress announce guard: held while this source's route is announced. Opening
1501	// bumps `announced` + `announced_bytes`; dropping (route offline, or the source
1502	// closing below) bumps `announced_closed` + `announced_bytes`. Empty scope =
1503	// no-op.
1504	let mut announce = route.announce.then(|| ingress.announce());
1505
1506	'attach: loop {
1507		// Re-resolved every attempt: between attaches the previous front's
1508		// teardown may have pruned the (then-empty) leaf from the tree, and
1509		// attaching to the stale lock would publish into an orphan that lookups
1510		// can no longer reach.
1511		let leaf = if rest.is_empty() {
1512			node.clone()
1513		} else {
1514			node.lock().leaf(&rest)
1515		};
1516
1517		let (state, broadcast, id) = match attach_source(&ctx, &leaf, &source, route.clone()) {
1518			Attach::Ready(state, broadcast, id) => (state, broadcast, id),
1519			Attach::Parked(incumbent) => {
1520				tracing::warn!(
1521					broadcast = %full,
1522					"path already live with a different publisher; parking an offline source until it ends",
1523				);
1524				// Wait for the incumbent front to close, or for our own route to
1525				// change: announcing ourselves is what earns the takeover, and our
1526				// source closing means giving up.
1527				let update = kio::wait(|waiter| {
1528					if let Poll::Ready(update) = source.poll_route_changed(waiter) {
1529						return Poll::Ready(Some(update));
1530					}
1531					// Ready on either the closed flag or the channel itself dying;
1532					// both mean the incumbent is gone.
1533					match incumbent.poll(waiter, |s| if s.closed { Poll::Ready(()) } else { Poll::Pending }) {
1534						Poll::Ready(_) => Poll::Ready(None),
1535						Poll::Pending => Poll::Pending,
1536					}
1537				})
1538				.await;
1539				match update {
1540					// Our route moved; recompute the guard and retry with it.
1541					Some(Ok(update)) => {
1542						sync_announce(&mut announce, update.announce, &ingress);
1543						route = update;
1544					}
1545					// The source closed while parked; it was never visible.
1546					Some(Err(_)) => return,
1547					// The incumbent is gone; retry, creating a fresh front.
1548					None => {}
1549				}
1550				continue 'attach;
1551			}
1552		};
1553		let publisher = route.hops.iter().next().copied();
1554
1555		loop {
1556			match source.route_changed().await {
1557				Ok(update) => {
1558					let announced = update.announce;
1559					// A different first hop is new content: this source can no
1560					// longer feed the front it attached to. Detach deliberately
1561					// (a linger would only stall the replacement) and re-attach.
1562					//
1563					// Plain equality, not `same_publisher`: within one source
1564					// handle the session layer already guarantees continuity (it
1565					// replaces the handle when identity breaks, UNKNOWN restarts
1566					// included), so this only catches a caller moving a live
1567					// handle to a new publisher. An UNKNOWN-to-UNKNOWN metadata
1568					// update (a legacy peer repricing) must not detach.
1569					if update.hops.iter().next().copied() != publisher {
1570						detach_source(&state, &broadcast, &leaf, id, true);
1571						sync_announce(&mut announce, announced, &ingress);
1572						route = update;
1573						continue 'attach;
1574					}
1575					{
1576						let carrying = broadcast.demand().is_used();
1577						let Ok(mut s) = state.write() else { return };
1578						let Some(entry) = s.routes.iter_mut().find(|r| r.id == id) else {
1579							return;
1580						};
1581						if entry.route == update {
1582							continue;
1583						}
1584						entry.route = update;
1585						s.reselect(carrying);
1586					}
1587					// Toggle the ingress announce guard on a live/offline transition.
1588					sync_announce(&mut announce, announced, &ingress);
1589					sync_front(&state, &broadcast, &leaf);
1590				}
1591				Err(_) => {
1592					// A deliberate finish closes the front immediately; an abrupt loss
1593					// (dropped producer, dead session) may linger for a replacement.
1594					detach_source(&state, &broadcast, &leaf, id, source.is_finished());
1595					return;
1596				}
1597			}
1598		}
1599	}
1600}
1601
1602/// The outcome of [`attach_source`].
1603enum Attach {
1604	/// The source joined (or created) the front at its path, yielding the shared
1605	/// source table, the spliced broadcast, and the source's table id.
1606	Ready(kio::Producer<FrontState>, broadcast::Producer, u64),
1607	/// The path's live front belongs to a different original publisher and this
1608	/// source is offline, so it would rank below every route the front holds and
1609	/// must not take the path from it. The caller parks on the returned table
1610	/// until the front closes.
1611	Parked(kio::Producer<FrontState>),
1612}
1613
1614/// Everything about a source's attach that does not change between attempts.
1615struct AttachContext<'a> {
1616	origin: &'a Info,
1617	node: &'a Lock<OriginNode>,
1618	/// Absolute path, for the front's identity and log lines.
1619	full: &'a PathOwned,
1620	/// Path relative to `node`, for locating (and later pruning) the leaf.
1621	rest: &'a PathOwned,
1622}
1623
1624/// Attach a source to the broadcast at `leaf`, creating (and publishing) the
1625/// broadcast if none is live. One lock acquisition covers the whole
1626/// join-or-create decision, so concurrent attaches cannot race each other.
1627///
1628/// Joining requires the same content identity (first hop). A source whose
1629/// original publisher differs takes the path over instead: the incumbent front
1630/// closes and a fresh one is created below, so consumers observe an unannounce
1631/// followed by an announce. That mirrors the session layer's rule that a restart
1632/// with a different first hop is a replacement, never a standby, and it is what
1633/// keeps a reconnect from waiting on the transport to retire the session it
1634/// replaced.
1635///
1636/// Taking over requires announcing, which keeps the rule consistent with
1637/// [`route_order`]: an offline source ranks below every announced route, so it
1638/// waits ([`Attach::Parked`]) rather than unannouncing a live broadcast and
1639/// cutting its subscribers for content nobody has advertised.
1640/// Whether two sources carry the same content and may therefore splice.
1641///
1642/// [`Origin::UNKNOWN`] identifies nothing: it is what a peer that declared no
1643/// identity, or that does not speak the hops extension at all, contributes as a
1644/// first hop. Two such sources are not interchangeable even though their first
1645/// hops compare equal, so splicing them would cut one publisher's subscribers
1646/// over to an unrelated publisher's content. Every other id compares normally,
1647/// including `None` for a locally produced broadcast with no hops.
1648fn same_publisher(a: Option<Origin>, b: Option<Origin>) -> bool {
1649	if a == Some(Origin::UNKNOWN) || b == Some(Origin::UNKNOWN) {
1650		return false;
1651	}
1652	a == b
1653}
1654
1655fn attach_source(
1656	ctx: &AttachContext,
1657	leaf: &Lock<OriginNode>,
1658	source: &broadcast::Consumer,
1659	route: broadcast::Route,
1660) -> Attach {
1661	let publisher = route.hops.iter().next().copied();
1662	let mut leaf_guard = leaf.lock();
1663
1664	// Join the live broadcast if the leaf already has one. A closed one (torn
1665	// down, awaiting teardown, or evicted just below) is replaced instead.
1666	if let Some(existing) = &leaf_guard.broadcast {
1667		let mut joined = None;
1668		let carrying = existing.broadcast.demand().is_used();
1669		if let Ok(mut s) = existing.state.write()
1670			&& !s.closed
1671		{
1672			if same_publisher(s.publisher, publisher) {
1673				let id = s.next_route;
1674				s.next_route += 1;
1675				s.routes.push(FrontRoute {
1676					id,
1677					route: route.clone(),
1678					source: source.clone(),
1679				});
1680				s.reselect(carrying);
1681				joined = Some(id);
1682			} else if !route.announce {
1683				return Attach::Parked(existing.state.clone());
1684			} else {
1685				// New content at a live path: the newest publisher wins it. Closing
1686				// the incumbent here (rather than letting the newcomer wait it out)
1687				// is what makes a reconnect immediate, and routing the takeover
1688				// through the replacement path below keeps the guarantee that
1689				// unrelated content is never spliced into live subscribers. The
1690				// incumbent's own task observes the flag and finishes its teardown,
1691				// finding the leaf slot already taken.
1692				s.closed = true;
1693				tracing::warn!(broadcast = %ctx.full, "replacing a live broadcast from a different publisher");
1694			}
1695		}
1696		if let Some(id) = joined {
1697			let state = existing.state.clone();
1698			let broadcast = existing.broadcast.clone();
1699			drop(leaf_guard);
1700			sync_front(&state, &broadcast, leaf);
1701			return Attach::Ready(state, broadcast, id);
1702		}
1703	}
1704
1705	// First source: create the broadcast and publish it into the tree.
1706	let announce = route.announce;
1707	let broadcast = broadcast::Producer::new_spliced(broadcast::Info {
1708		origin: ctx.origin.clone(),
1709	});
1710	let _ = broadcast.clone().set_route(route.clone());
1711	let state = kio::Producer::new(FrontState {
1712		path: ctx.full.clone(),
1713		self_origin: ctx.origin.id,
1714		publisher,
1715		next_route: 1,
1716		excluded: HashMap::new(),
1717		routes: vec![FrontRoute {
1718			id: 0,
1719			route,
1720			source: source.clone(),
1721		}],
1722		active: Some(0),
1723		linger: ctx.origin.linger,
1724		closed: false,
1725	});
1726
1727	// Replacing a stale (closed) entry counts as an unannounce, so consumers
1728	// observe the replacement rather than a silent swap; its own teardown task
1729	// then finds the slot already taken and leaves it alone.
1730	if let Some(stale) = leaf_guard.broadcast.take()
1731		&& stale.announced
1732	{
1733		leaf_guard.notify.lock().unannounce(&stale.path);
1734	}
1735	let entry = OriginBroadcast {
1736		path: ctx.full.clone(),
1737		broadcast: broadcast.clone(),
1738		state: state.clone(),
1739		announced: announce,
1740	};
1741	if entry.announced {
1742		leaf_guard.notify.lock().announce(ctx.full, &broadcast.consume());
1743	}
1744	leaf_guard.broadcast = Some(entry);
1745	drop(leaf_guard);
1746
1747	web_async::spawn(run_front(
1748		state.clone(),
1749		broadcast.clone(),
1750		ctx.node.clone(),
1751		ctx.rest.clone(),
1752	));
1753
1754	Attach::Ready(state, broadcast, 0)
1755}
1756
1757/// Owns a front's lifecycle: dispatches each requested track to a serve task
1758/// until the last source detaches, then unpublishes the broadcast.
1759async fn run_front(
1760	state: kio::Producer<FrontState>,
1761	mut broadcast: broadcast::Producer,
1762	node: Lock<OriginNode>,
1763	rest: PathOwned,
1764) {
1765	enum Step {
1766		Serve(Arc<str>, super::resume::Producer),
1767		/// The source table emptied or refilled: re-arm the linger countdown.
1768		Changed,
1769		/// The linger window expired: close if the table is still empty.
1770		Expired,
1771		Closed,
1772	}
1773
1774	let linger = state.read().linger;
1775	// Armed while the table is ungracefully empty: the instant the front gives up
1776	// waiting for a replacement source. A graceful close never gets here (the
1777	// detach sets `closed` synchronously), so a running countdown always means a
1778	// reconnect is welcome.
1779	let mut deadline = kio::time::Deadline::new();
1780
1781	loop {
1782		let empty = {
1783			let s = state.read();
1784			!s.closed && s.routes.is_empty()
1785		};
1786		deadline.set(match (empty, deadline.deadline()) {
1787			// An unrepresentable deadline (e.g. `Duration::MAX`) lingers forever:
1788			// no timer, only a re-attach or teardown moves the front on.
1789			(true, None) => web_async::time::Instant::now().checked_add(linger),
1790			(true, at) => at,
1791			(false, _) => None,
1792		});
1793
1794		let step = {
1795			kio::wait(|waiter| {
1796				if let Poll::Ready((name, resume)) = broadcast.poll_spliced_assigned(waiter) {
1797					return Poll::Ready(Step::Serve(name, resume));
1798				}
1799				// Watch for the close, and for the table changing shape so the
1800				// countdown re-arms (a detach emptying it, a reconnect refilling it).
1801				match state.poll(waiter, |s| {
1802					if s.closed || s.routes.is_empty() != empty {
1803						Poll::Ready(())
1804					} else {
1805						Poll::Pending
1806					}
1807				}) {
1808					Poll::Ready(Ok(guard)) => {
1809						return Poll::Ready(if guard.closed { Step::Closed } else { Step::Changed });
1810					}
1811					Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1812					Poll::Pending => {}
1813				}
1814				deadline.poll(waiter).map(|_| Step::Expired)
1815			})
1816			.await
1817		};
1818
1819		match step {
1820			Step::Serve(name, resume) => {
1821				// Serve tasks self-terminate when the track completes or the
1822				// front closes.
1823				web_async::spawn(serve_track(state.clone(), name, resume));
1824			}
1825			Step::Changed => {}
1826			Step::Expired => {
1827				// Close only if the table is still empty: a source that re-attached
1828				// as the window expired wins the write-lock race and keeps the
1829				// broadcast alive.
1830				let close = {
1831					let Ok(mut s) = state.write() else { break };
1832					if !s.closed && s.routes.is_empty() {
1833						s.closed = true;
1834						true
1835					} else {
1836						false
1837					}
1838				};
1839				if close {
1840					break;
1841				}
1842			}
1843			Step::Closed => break,
1844		}
1845	}
1846
1847	// Abort the logical tracks (releasing their subscribers) and unpublish.
1848	broadcast.abort_spliced(Error::Dropped);
1849
1850	// Deliberate end; suppresses the dropped-without-finish warning.
1851	broadcast.finish();
1852
1853	// Remove the broadcast from the tree (identity-checked, so a replacement is
1854	// untouched) and prune empty nodes.
1855	node.lock().remove(&state, &rest);
1856}
1857
1858/// Serves one spliced logical track: splices in the best source's copy of the
1859/// track, re-splicing on handover or failure, until the track completes or the
1860/// front closes. A refusal (a source rejecting the track, or its copy dying
1861/// before delivering anything) is authoritative and never retried: the refuser
1862/// is skipped for this track so a joining standby cannot kill a subscription
1863/// the incumbent is serving, and once every attached source has refused, the
1864/// track aborts with the last refusal's error. The verdict belongs to this
1865/// request; a later consumer request asks afresh (see `track_inner`). Failures
1866/// after delivered progress (a serving session dying mid-stream) are normal
1867/// failover and re-splice from the next source at the first missing group; a
1868/// source that fails while *closing* is a corpse to fail over past (its watcher
1869/// is about to detach it), not a verdict on the track.
1870async fn serve_track(state: kio::Producer<FrontState>, name: Arc<str>, mut resume: super::resume::Producer) {
1871	enum Step {
1872		Closed,
1873		Splice(u64, broadcast::Consumer),
1874		Complete,
1875		Failed(Error),
1876		/// The route we were serving from left the table with nothing servable to
1877		/// replace it: drop our handle; the verdict block at the top of the loop
1878		/// decides between aborting (all refused) and parking (corpses detaching,
1879		/// or an empty table awaiting a reconnect).
1880		NoRoute,
1881		/// The linger expired with the track still unread: release the segment.
1882		Idle,
1883		/// A reader arrived or the last one left: recompute the demand gate.
1884		Demand,
1885	}
1886
1887	// The source whose copy is currently spliced in, and that copy.
1888	let mut serving: Option<(u64, track::Consumer)> = None;
1889	// The delivered edge when that copy spliced in. A copy that dies without
1890	// advancing it never delivered anything, which is what [`Step::Failed`]
1891	// uses to tell a refusal from a mid-stream failover. Snapshotted per splice,
1892	// not per wake: an unrelated wake between the copy's last group and its
1893	// death must not launder its delivered progress away.
1894	let mut spliced_edge: Option<u64> = None;
1895	// Sources that refused this track, and the most recent refusal's error. A
1896	// standby joining a live front wins dispatch the moment it attaches, which is
1897	// before a real publisher has created every track, so its refusal must cost
1898	// the incumbent nothing: we keep serving from a route that has the track.
1899	let mut refused: HashSet<u64> = HashSet::new();
1900	let mut refusal: Option<Error> = None;
1901	// Sources whose splice failed because they had already closed. Their watchers
1902	// are about to detach them, so wait for the table to move on rather than
1903	// treating a corpse's error as a refusal (ids are never reused, so this
1904	// cannot wedge).
1905	let mut dead: HashSet<u64> = HashSet::new();
1906	// When the spliced segment stopped being read, starting the release countdown.
1907	let mut idle_since: Option<web_async::time::Instant> = None;
1908	let mut deadline = kio::time::Deadline::new();
1909
1910	loop {
1911		let serving_id = serving.as_ref().map(|(id, _)| *id);
1912
1913		// The table's verdict: once every attached source has refused, nothing
1914		// will ever serve the track (refusals are never retried) and it aborts
1915		// with the last refusal's error. A detached refuser leaves the set, so a
1916		// source that reattaches (under a fresh id) is asked anew; a table blocked
1917		// only by corpses awaiting detach parks instead, since their replacement
1918		// (a reconnect) deserves the seamless splice.
1919		{
1920			let s = state.read();
1921			refused.retain(|id| s.routes.iter().any(|r| r.id == *id));
1922			dead.retain(|id| s.routes.iter().any(|r| r.id == *id));
1923			let exhausted = !s.routes.is_empty()
1924				&& s.serve_route(|id| refused.contains(&id) || dead.contains(&id))
1925					.is_none();
1926			if exhausted && dead.is_empty() {
1927				drop(s);
1928				let err = refusal.take().unwrap_or(Error::NotFound);
1929				tracing::debug!(name = %name, %err, "every source refused track; aborting");
1930				let _ = resume.abort(err);
1931				return;
1932			}
1933		}
1934
1935		// Demand gates both directions: an unread track never splices a source in,
1936		// and a spliced one is released once the linger expires. Both sides use the
1937		// same signal, so a release can't immediately re-splice and spin.
1938		//
1939		// The countdown keys off the segment, not our handle on the route that
1940		// produced it: a route that leaves (or a copy that dies) drops the handle
1941		// while the segment stays spliced, and that segment is exactly what the
1942		// release exists to reclaim. Keying off the handle strands it until the front
1943		// closes, which pins the departed source's cached groups for a linger that
1944		// may never expire and leaves a dead segment's edge behind for the next
1945		// takeover to splice above.
1946		let used = resume.is_used();
1947		idle_since = match (resume.is_spliced(), used) {
1948			(true, false) => idle_since.or_else(|| Some(web_async::time::Instant::now())),
1949			_ => None,
1950		};
1951		deadline.set(idle_since.and_then(|at| at.checked_add(TRACK_IDLE_LINGER)));
1952
1953		let step = {
1954			let skip = |id: u64| refused.contains(&id) || dead.contains(&id);
1955			kio::wait(|waiter| {
1956				// Watch the source table: the front closing, a better servable
1957				// source than the one spliced in (skipping any we already know
1958				// can't serve this track), or the served route leaving the table,
1959				// which retires the refusals collected against it. Splicing waits
1960				// for a reader.
1961				match state.poll(waiter, |s| {
1962					let gone = serving_id.is_some_and(|id| !s.routes.iter().any(|r| r.id == id));
1963					if s.closed
1964						|| (used && (gone || matches!(s.serve_route(skip), Some(next) if Some(next) != serving_id)))
1965					{
1966						Poll::Ready(())
1967					} else {
1968						Poll::Pending
1969					}
1970				}) {
1971					Poll::Ready(Ok(guard)) => {
1972						if guard.closed {
1973							return Poll::Ready(Step::Closed);
1974						}
1975						let Some(next) = guard.serve_route(skip) else {
1976							return Poll::Ready(Step::NoRoute);
1977						};
1978						let source = guard
1979							.routes
1980							.iter()
1981							.find(|r| r.id == next)
1982							.expect("servable source in table")
1983							.source
1984							.clone();
1985						return Poll::Ready(Step::Splice(next, source));
1986					}
1987					Poll::Ready(Err(_)) => return Poll::Ready(Step::Closed),
1988					Poll::Pending => {}
1989				}
1990
1991				// Watch the demand edge in whichever direction is unmet. This has to end
1992				// the wait, not just wake it: `used` and the countdown are computed by
1993				// the outer loop, so a wake that stayed inside would re-poll with the
1994				// stale value and never arm (or cancel) the linger.
1995				let edge = match used {
1996					true => resume.poll_unused(waiter),
1997					false => resume.poll_used(waiter),
1998				};
1999				if edge.is_ready() {
2000					return Poll::Ready(Step::Demand);
2001				}
2002
2003				// Watch the spliced copy for its end: complete means the logical
2004				// track is over; anything else means the serving copy died.
2005				if let Some((_, track)) = &serving
2006					&& let Poll::Ready(result) = track.poll_complete(waiter)
2007				{
2008					return Poll::Ready(match result {
2009						Ok(()) => Step::Complete,
2010						Err(err) => Step::Failed(err),
2011					});
2012				}
2013
2014				deadline.poll(waiter).map(|_| Step::Idle)
2015			})
2016			.await
2017		};
2018
2019		match step {
2020			// The front's teardown aborts the logical track.
2021			Step::Closed => return,
2022			Step::Complete => {
2023				let _ = resume.finish();
2024				return;
2025			}
2026			Step::Failed(err) => {
2027				// The spliced copy died mid-serve. With delivered progress since
2028				// its splice it's a normal failover: re-splice from the (possibly
2029				// same) active source. A copy that died before producing anything
2030				// is a refusal (a source whose track keeps dying right after
2031				// acceptance must not re-splice forever), unless the source
2032				// itself is closing: that corpse parks for its detach so a
2033				// reconnect gets the seamless splice.
2034				if resume.latest() == spliced_edge
2035					&& let Some(id) = serving_id
2036				{
2037					let closing = state
2038						.read()
2039						.routes
2040						.iter()
2041						.find(|r| r.id == id)
2042						.is_some_and(|r| r.source.is_closing());
2043					if closing {
2044						dead.insert(id);
2045					} else {
2046						refused.insert(id);
2047						refusal = Some(err);
2048					}
2049				}
2050				serving = None;
2051			}
2052			// The outer loop recomputes `used` and the countdown on the next pass.
2053			Step::Demand => {}
2054			// Forget which route we were serving from, or the `gone` edge that woke
2055			// us keeps firing: the id stays absent from the table, the wait returns
2056			// Ready at once, and the loop spins on a full core without ever parking.
2057			// The segment itself stays spliced into `resume` (readers keep whatever
2058			// it delivered) until a replacement is proven servable.
2059			Step::NoRoute => serving = None,
2060			Step::Idle => {
2061				// Nobody has read the track for the linger: drop the source's copy so
2062				// its session can release the track (and the cached `track::Info` that
2063				// came with it). The logical track stays alive and re-splices on the
2064				// next reader, so a returning viewer or a follow-up fetch resumes.
2065				if resume.release().is_err() {
2066					// Finished or aborted meanwhile; the track is over either way.
2067					return;
2068				}
2069				serving = None;
2070			}
2071			Step::Splice(id, source) => {
2072				// Ask the source for its copy and wait for the info to resolve,
2073				// proving it servable, before splicing it in. Bail out early if
2074				// the table moves on while waiting.
2075				let attempt = match source.track(&name) {
2076					Ok(track) => {
2077						// `into_inner` sheds the `Pending` future wrapper so only
2078						// the pollable (which is `Sync`) is held across the await.
2079						let query = track.info().into_inner();
2080						let skip = |id: u64| refused.contains(&id) || dead.contains(&id);
2081						let info = kio::wait(|waiter| {
2082							if let Poll::Ready(result) = query.poll(waiter) {
2083								return Poll::Ready(Some(result));
2084							}
2085							match state.poll(waiter, |s| {
2086								if s.closed || s.serve_route(skip) != Some(id) {
2087									Poll::Ready(())
2088								} else {
2089									Poll::Pending
2090								}
2091							}) {
2092								Poll::Ready(_) => Poll::Ready(None),
2093								Poll::Pending => Poll::Pending,
2094							}
2095						})
2096						.await;
2097						match info {
2098							// The table changed under us; re-pick from the top.
2099							None => continue,
2100							// A copy that is already aborted can't be spliced;
2101							// its error is the source's answer for the track.
2102							Some(Ok(_)) => match track.poll_complete(&kio::Waiter::noop()) {
2103								Poll::Ready(Err(err)) => Err(err),
2104								_ => Ok(track),
2105							},
2106							Some(Err(err)) => Err(err),
2107						}
2108					}
2109					Err(err) => Err(err),
2110				};
2111
2112				match attempt {
2113					Ok(track) => {
2114						if let Err(err) = resume.takeover(&track) {
2115							// Closed means the logical track already ended
2116							// (finished or aborted). Anything else is a boundary
2117							// bug; abort rather than strand subscribers on a
2118							// track no task serves (a no-op after a clean end).
2119							let _ = resume.abort(err);
2120							return;
2121						}
2122						dead.clear();
2123						// The new segment has produced nothing yet, so this is
2124						// the edge the copy is asked to advance.
2125						spliced_edge = resume.latest();
2126						serving = Some((id, track));
2127					}
2128					// The source itself closed or deliberately ended: not a
2129					// verdict on the track. Park until its watcher detaches
2130					// it and the table promotes a replacement.
2131					Err(_) if source.is_closing() => {
2132						dead.insert(id);
2133						serving = None;
2134					}
2135					// The dispatched source does not carry the track: a publisher
2136					// announces a broadcast only once its tracks exist, so the
2137					// answer is authoritative and never re-asked. Skip the source
2138					// for this track; the verdict block aborts once every source
2139					// has refused.
2140					Err(err) => {
2141						tracing::debug!(name = %name, source = id, %err, "source refused track");
2142						refused.insert(id);
2143						refusal = Some(err);
2144						serving = None;
2145					}
2146				}
2147			}
2148		}
2149	}
2150}
2151
2152/// Shared fallback request queue for an origin.
2153///
2154/// Lives off to the side of the announce tree because dynamically served broadcasts
2155/// are never announced. Carried in a [`kio::Shared`], so consumers enqueue and handlers
2156/// drain under one lock. Mirrors the fetch state of the track model.
2157#[derive(Default)]
2158struct OriginDynamicState {
2159	// Result channels for pending requests, keyed by absolute path so concurrent
2160	// `request_broadcast` calls for the same path coalesce onto one channel.
2161	requests: Requests<PathOwned, kio::Producer<PendingBroadcast>>,
2162
2163	// Broadcasts a handler has already served, kept weakly so a repeat request for the
2164	// same path resolves to a shared clone instead of re-invoking the handler (which would
2165	// open a duplicate upstream subscription). Weak so a served broadcast still closes once
2166	// its real consumers drop. The cache reclaims closed entries incrementally on insert, so a
2167	// long-lived origin serving many distinct one-shot paths stays bounded by the live count.
2168	served: WeakCache<PathOwned, broadcast::WeakConsumer>,
2169}
2170
2171/// One-shot result of a dynamic broadcast request.
2172///
2173/// Stays `None` until a handler [`accept`](Request::accept)s (yielding the served
2174/// broadcast) or [`reject`](Request::reject)s (yielding an error). The producer is
2175/// dropped right after writing, closing the channel; kio checks the value before the closed
2176/// flag, so an awaiting requester still observes the final result.
2177#[derive(Default)]
2178struct PendingBroadcast {
2179	resolved: Option<Result<broadcast::Consumer, Error>>,
2180}
2181
2182/// Picks up [`Consumer::request_broadcast`] calls for paths that are not announced.
2183///
2184/// The origin-level analogue of [`broadcast::Dynamic`]: where that serves tracks on
2185/// demand within a broadcast, this serves whole broadcasts on demand within an origin. A
2186/// relay uses it as a fallback router, fetching a broadcast from upstream only when a
2187/// downstream consumer asks for an exact path that nobody announced.
2188///
2189/// Served broadcasts are deliberately *not* announced, so they never appear in
2190/// [`Consumer::announced`]. Drop this handle (and every clone) to reject the
2191/// requests still waiting to be served.
2192pub struct Dynamic {
2193	info: Origin,
2194	root: PathOwned,
2195	state: kio::Shared<OriginDynamicState>,
2196}
2197
2198impl Clone for Dynamic {
2199	fn clone(&self) -> Self {
2200		// Mirror `new`: count each live handle. Without this, dropping a clone would
2201		// decrement past `new`'s increment and prematurely flip the handler count to
2202		// zero, making future `request_broadcast` calls return `Unroutable`.
2203		self.state.lock().requests.add_handler();
2204
2205		Self {
2206			info: self.info,
2207			root: self.root.clone(),
2208			state: self.state.clone(),
2209		}
2210	}
2211}
2212
2213impl Dynamic {
2214	fn new(info: Origin, root: PathOwned, state: kio::Shared<OriginDynamicState>) -> Self {
2215		state.lock().requests.add_handler();
2216
2217		Self { info, root, state }
2218	}
2219
2220	/// The origin this handler belongs to.
2221	pub fn info(&self) -> &Origin {
2222		&self.info
2223	}
2224
2225	/// Poll for the next requested broadcast, without blocking.
2226	pub fn poll_requested_broadcast(&mut self, waiter: &kio::Waiter) -> Poll<Result<Request, Error>> {
2227		let mut state = ready!(self.state.poll(waiter, |state| {
2228			if state.requests.has_queued() {
2229				Poll::Ready(())
2230			} else {
2231				Poll::Pending
2232			}
2233		}));
2234
2235		let path = state.requests.pop().expect("predicate guaranteed a request");
2236		// The popped request stays pending, so a repeat request in the window between
2237		// hand-off and accept coalesces onto it instead of re-invoking the handler. The
2238		// producer is a shared clone; `Request::{accept, reject, drop}` removes the
2239		// entry. This mirrors how `poll_requested_track` keeps a served track
2240		// discoverable via the weak cache across the same window.
2241		let producer = state.requests.get(&path).expect("popped key must be pending").clone();
2242		Poll::Ready(Ok(Request {
2243			path,
2244			producer,
2245			state: self.state.clone(),
2246		}))
2247	}
2248
2249	/// Block until a consumer requests an unannounced broadcast, returning a
2250	/// [`Request`] to serve.
2251	pub async fn requested_broadcast(&mut self) -> Result<Request, Error> {
2252		kio::wait(|waiter| self.poll_requested_broadcast(waiter)).await
2253	}
2254
2255	/// Returns the prefix that is automatically stripped from requested paths.
2256	pub fn root(&self) -> &Path<'_> {
2257		&self.root
2258	}
2259}
2260
2261impl Drop for Dynamic {
2262	fn drop(&mut self) {
2263		// Decrement and reject under one lock, so a `request_broadcast` that saw a
2264		// live handler through the same lock can't slip a request past the rejection.
2265		let mut state = self.state.lock();
2266		if state.requests.remove_handler() {
2267			// No handlers left to pop queued requests; drop them, closing their result
2268			// channels so awaiting requesters resolve to `Unroutable`. A request already
2269			// handed to a handler stays, resolved by its `Request` instead.
2270			state.requests.drain_queued();
2271		}
2272	}
2273}
2274
2275/// A pending request for a broadcast that was not announced.
2276///
2277/// Yielded by [`Dynamic::requested_broadcast`]. The requester is awaiting inside
2278/// [`Consumer::request_broadcast`]; [`accept`](Self::accept) resolves it with a live
2279/// broadcast (which the handler keeps producing into) and [`reject`](Self::reject) resolves
2280/// it with an error. Dropping the request without either rejects it.
2281pub struct Request {
2282	// Absolute path that was requested.
2283	path: PathOwned,
2284
2285	// Result channel back to the awaiting requester(s). Writing `resolved` and dropping
2286	// this wakes them with the outcome.
2287	producer: kio::Producer<PendingBroadcast>,
2288
2289	// Shared dynamic state, so `accept` can cache the served broadcast for repeat requests.
2290	state: kio::Shared<OriginDynamicState>,
2291}
2292
2293impl Request {
2294	/// The absolute path that was requested.
2295	pub fn path(&self) -> &Path<'_> {
2296		&self.path
2297	}
2298
2299	/// Accept the request, resolving every awaiting requester with `broadcast`.
2300	///
2301	/// The caller keeps producing into `broadcast` (e.g. a relay proxying tracks from
2302	/// upstream); the requesters receive a consumer for it. The broadcast is *not*
2303	/// announced.
2304	pub fn accept(self, broadcast: impl Consume<broadcast::Consumer>) {
2305		let broadcast = broadcast.consume();
2306
2307		// Move the entry out of the in-flight queue and into the weak `served` cache, so repeat
2308		// requests for this path share the same broadcast instead of asking the handler to serve
2309		// (and subscribe upstream) again. Re-check under the lock: if a live broadcast was already
2310		// served for this path while we were fetching upstream, dedup onto it and drop ours rather
2311		// than replace a good entry with a duplicate subscription.
2312		let resolved = {
2313			let mut state = self.state.lock();
2314			let existing = state.served.insert(self.path.clone(), broadcast.weak());
2315			state
2316				.requests
2317				.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
2318			existing.map(|weak| weak.consume()).unwrap_or(broadcast)
2319		};
2320
2321		if let Ok(mut pending) = self.producer.write() {
2322			pending.resolved = Some(Ok(resolved));
2323		}
2324		// `self.producer` drops here, closing the channel; the value is still observable.
2325	}
2326
2327	/// Reject the request, resolving every awaiting requester with `err`.
2328	pub fn reject(self, err: Error) {
2329		self.state
2330			.lock()
2331			.requests
2332			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
2333		if let Ok(mut state) = self.producer.write() {
2334			state.resolved = Some(Err(err));
2335		}
2336	}
2337}
2338
2339impl Drop for Request {
2340	fn drop(&mut self) {
2341		// Handed off but neither accepted nor rejected: drop the still-pending entry so its
2342		// producer clone (plus this one) closes the channel, resolving coalesced requesters to
2343		// `Unroutable` rather than hanging.
2344		//
2345		// The identity guard matters: `accept`/`reject` already removed our entry and released
2346		// the lock before we run, so a concurrent request for the same path may have registered
2347		// a *new* one here. Removing unconditionally would clobber it, stranding its requesters.
2348		self.state
2349			.lock()
2350			.requests
2351			.remove_if(&self.path, |producer| producer.same_channel(&self.producer));
2352	}
2353}
2354
2355/// The pollable result of [`Consumer::request_broadcast`].
2356///
2357/// Awaited via the [`kio::Pending`] wrapper; resolves to the [`broadcast::Consumer`]
2358/// immediately when the broadcast was already announced, or once an [`Dynamic`]
2359/// handler serves the request. Resolves to an error if the request is rejected or every
2360/// handler drops before serving it.
2361pub struct Requesting {
2362	inner: RequestState,
2363	// Egress scope applied to the resolved broadcast, so its reads are attributed.
2364	// Empty (no-op) for an untagged consumer.
2365	stats: stats::Scope,
2366}
2367
2368enum RequestState {
2369	// Already announced: resolves immediately with a clone of this broadcast.
2370	Ready(broadcast::Consumer),
2371	// Unroutable at request time: resolves immediately with this error. Baked in so
2372	// `request_broadcast` itself stays infallible.
2373	Failed(Error),
2374	// Awaiting a handler: resolves when the request's result channel is written.
2375	Pending(kio::Consumer<PendingBroadcast>),
2376}
2377
2378impl Requesting {
2379	fn ready(broadcast: broadcast::Consumer) -> Self {
2380		Self {
2381			inner: RequestState::Ready(broadcast),
2382			stats: stats::Scope::default(),
2383		}
2384	}
2385
2386	fn failed(error: Error) -> Self {
2387		Self {
2388			inner: RequestState::Failed(error),
2389			stats: stats::Scope::default(),
2390		}
2391	}
2392
2393	fn pending(consumer: kio::Consumer<PendingBroadcast>) -> Self {
2394		Self {
2395			inner: RequestState::Pending(consumer),
2396			stats: stats::Scope::default(),
2397		}
2398	}
2399
2400	fn with_stats(mut self, scope: stats::Scope) -> Self {
2401		self.stats = scope;
2402		self
2403	}
2404
2405	/// Poll for the requested broadcast without blocking.
2406	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<broadcast::Consumer, Error>> {
2407		match &self.inner {
2408			RequestState::Ready(broadcast) => Poll::Ready(Ok(broadcast.clone().with_stats(self.stats.clone()))),
2409			RequestState::Failed(error) => Poll::Ready(Err(error.clone())),
2410			RequestState::Pending(consumer) => Poll::Ready(
2411				match ready!(consumer.poll(waiter, |state| match &state.resolved {
2412					Some(result) => Poll::Ready(result.clone()),
2413					None => Poll::Pending,
2414				})) {
2415					Ok(result) => result.map(|broadcast| broadcast.with_stats(self.stats.clone())),
2416					// Every handler dropped without resolving: nobody could route it.
2417					Err(_closed) => Err(Error::Unroutable),
2418				},
2419			),
2420		}
2421	}
2422}
2423
2424impl kio::Pollable for Requesting {
2425	type Output = Result<broadcast::Consumer, Error>;
2426
2427	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2428		self.poll_ok(waiter)
2429	}
2430}
2431
2432/// Derive a read view from a handle.
2433///
2434/// Lets APIs accept either a producer or a consumer (e.g.
2435/// [`Client::with_publisher`](crate::Client::with_publisher),
2436/// [`Request::accept`]). The blanket `&T` impl means you can
2437/// pass by value (`foo(x)`) to hand off ownership, or by reference (`foo(&x)`)
2438/// to keep it, without spelling out `.consume()`.
2439pub trait Consume<T> {
2440	/// Derive a read view (a consumer) from this handle.
2441	fn consume(&self) -> T;
2442}
2443
2444impl<T, U: Consume<T>> Consume<T> for &U {
2445	fn consume(&self) -> T {
2446		(**self).consume()
2447	}
2448}
2449
2450impl Consume<Consumer> for Producer {
2451	fn consume(&self) -> Consumer {
2452		// Mirrors the inherent `Producer::consume`; inlined to avoid the
2453		// inherent-vs-trait `consume` ambiguity. Untagged: egress is tagged
2454		// separately from ingress.
2455		Consumer::new(
2456			self.info,
2457			self.root.clone(),
2458			self.nodes.clone(),
2459			self.dynamic.clone(),
2460			stats::Session::default(),
2461		)
2462	}
2463}
2464
2465impl Consume<Consumer> for Consumer {
2466	fn consume(&self) -> Consumer {
2467		self.clone()
2468	}
2469}
2470
2471impl Consume<broadcast::Consumer> for broadcast::Producer {
2472	fn consume(&self) -> broadcast::Consumer {
2473		// The inherent `consume` shadows this trait method, so this delegates.
2474		self.consume()
2475	}
2476}
2477
2478impl Consume<broadcast::Consumer> for broadcast::Consumer {
2479	fn consume(&self) -> broadcast::Consumer {
2480		self.clone()
2481	}
2482}
2483
2484impl Consume<track::Consumer> for track::Producer {
2485	fn consume(&self) -> track::Consumer {
2486		self.consume()
2487	}
2488}
2489
2490impl Consume<track::Consumer> for track::Consumer {
2491	fn consume(&self) -> track::Consumer {
2492		self.clone()
2493	}
2494}
2495
2496/// Cheap read handle over an origin's broadcast tree.
2497///
2498/// Clones share the underlying tree state without allocating any per-cursor
2499/// resources. To actually receive announce / unannounce events, call
2500/// [`Self::announced`] to obtain an [`AnnounceConsumer`].
2501#[derive(Clone)]
2502pub struct Consumer {
2503	// Identity of the origin this consumer was derived from.
2504	info: Origin,
2505	nodes: OriginNodes,
2506
2507	// A prefix that is automatically stripped from all paths.
2508	root: PathOwned,
2509
2510	// Shared fallback request queue, fed to any `Dynamic` handler on the
2511	// producer side. Used only by `request_broadcast`; announced lookups ignore it.
2512	dynamic: kio::Shared<OriginDynamicState>,
2513
2514	// Egress stats context. Broadcasts handed out through this consumer (and any
2515	// handle derived from them) are attributed to it (reads counted on the
2516	// publisher/egress side). Empty (no-op) unless a session tagged this handle.
2517	stats: stats::Session,
2518
2519	// Data-plane split horizon: broadcasts resolved through this handle are
2520	// served from a source whose hop chain excludes this origin (the requesting
2521	// peer). `None` (the default) serves from the active source as usual.
2522	exclude: Option<Origin>,
2523}
2524
2525impl std::ops::Deref for Consumer {
2526	type Target = Origin;
2527
2528	fn deref(&self) -> &Self::Target {
2529		&self.info
2530	}
2531}
2532
2533impl Consumer {
2534	fn new(
2535		info: Origin,
2536		root: PathOwned,
2537		nodes: OriginNodes,
2538		dynamic: kio::Shared<OriginDynamicState>,
2539		stats: stats::Session,
2540	) -> Self {
2541		Self {
2542			info,
2543			nodes,
2544			root,
2545			dynamic,
2546			stats,
2547			exclude: None,
2548		}
2549	}
2550
2551	/// A clone that never serves the given peer its own data: broadcasts resolve
2552	/// to a source whose hop chain excludes `peer`, matching what the announce
2553	/// loop advertises to them. Sessions apply this once they learn the peer's
2554	/// origin id.
2555	pub(crate) fn excluding(mut self, peer: Origin) -> Self {
2556		self.exclude = Some(peer);
2557		self
2558	}
2559
2560	/// Attach an egress stats context: broadcasts handed out through this handle (and
2561	/// any handle derived from it) are attributed to `session` on the publisher
2562	/// (egress) side. Pass [`stats::Session::default`] to opt out.
2563	pub fn with_stats(mut self, session: stats::Session) -> Self {
2564		self.stats = session;
2565		self
2566	}
2567
2568	/// A clone of this consumer with its stats context cleared, so an internal
2569	/// lookup stream (e.g. [`Self::announced_broadcast`]) doesn't drive the egress
2570	/// announce guards; the caller re-attributes the result itself.
2571	fn untagged(&self) -> Self {
2572		Self {
2573			stats: stats::Session::default(),
2574			..self.clone()
2575		}
2576	}
2577
2578	/// A view with this consumer's identity and root but no broadcasts:
2579	/// [`announced`](Self::announced) yields nothing. Used to answer a peer's
2580	/// announce-interest for a prefix outside our scope by announcing nothing,
2581	/// rather than tearing the stream down.
2582	pub(crate) fn empty(&self) -> Self {
2583		Self {
2584			info: self.info,
2585			nodes: OriginNodes { nodes: Vec::new() },
2586			root: self.root.clone(),
2587			dynamic: self.dynamic.clone(),
2588			stats: self.stats.clone(),
2589			exclude: self.exclude,
2590		}
2591	}
2592
2593	/// Subscribe to announce / unannounce events for this consumer's subtree.
2594	///
2595	/// Allocates a per-cursor coalescing buffer, registers it with each root
2596	/// in this consumer's scope, and replays the currently active broadcast
2597	/// set as initial announcements. Drop the returned [`AnnounceConsumer`]
2598	/// to unregister.
2599	pub fn announced(&self) -> AnnounceConsumer {
2600		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), self.stats.clone())
2601	}
2602
2603	/// Returns a cheap duplicate of this read handle.
2604	pub fn consume(&self) -> Self {
2605		self.clone()
2606	}
2607
2608	/// Internal synchronous lookup: how the broadcast at `path` resolves for this
2609	/// consumer, telling "announced but every route loops back through you" apart
2610	/// from "nothing here".
2611	///
2612	/// Races announcement gossip (a freshly-connected consumer sees `Missing` even when
2613	/// the broadcast is about to arrive), so it is not public. [`Self::request_broadcast`]
2614	/// is the public lookup: it builds on this for the announced case, then falls back to
2615	/// a dynamic handler. [`Self::announced_broadcast`] waits for a future announcement.
2616	fn resolve(&self, path: impl AsPath) -> Resolved {
2617		let path = path.as_path();
2618		let Some((root, rest)) = self.nodes.get(&path) else {
2619			return Resolved::Missing;
2620		};
2621		let state = root.lock();
2622		state.resolve_broadcast(&rest, self.exclude)
2623	}
2624
2625	/// [`Self::resolve`] reduced to "can I read it": the peek the tests assert on.
2626	#[cfg(test)]
2627	pub(crate) fn get_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2628		match self.resolve(path) {
2629			Resolved::Found(broadcast) => Some(broadcast),
2630			Resolved::Excluded | Resolved::Missing => None,
2631		}
2632	}
2633
2634	/// Block until a broadcast with the given path is announced and return it.
2635	///
2636	/// Returns `None` if the path is outside this consumer's allowed prefixes or if the consumer
2637	/// is closed before the broadcast is announced. The returned broadcast may itself be closed
2638	/// later. Subscribers should watch [`broadcast::Consumer::closed`] to react to that.
2639	///
2640	/// Prefer this over [`Self::request_broadcast`] when you know the exact path you want but
2641	/// cannot guarantee the announcement has already been received. With moq-lite-05 (and
2642	/// the older Lite01/02) `connect()` already blocks until the initial announce set lands,
2643	/// so [`Self::request_broadcast`] is race-free for broadcasts that were live at connect time;
2644	/// this method is still needed to wait for a broadcast that comes online *after* connect.
2645	pub async fn announced_broadcast(&self, path: impl AsPath) -> Option<broadcast::Consumer> {
2646		let path = path.as_path();
2647
2648		// Scope a fresh consumer down to this path so we only wake up for relevant announcements.
2649		let consumer = self.scope(std::slice::from_ref(&path))?;
2650
2651		// `scope` keeps narrower permissions intact: if we ask for `foo` on a consumer limited
2652		// to `foo/specific`, `scope` returns a consumer scoped to `foo/specific`. No
2653		// announcement at the exact path `foo` can ever arrive. Bail rather than loop forever.
2654		if !consumer.allowed().any(|allowed| path.has_prefix(allowed)) {
2655			return None;
2656		}
2657
2658		// Use an untagged stream: this is a lookup, not egress announce forwarding, so
2659		// it must not drive the announce guards. The matched result is attributed
2660		// with the egress scope instead.
2661		let mut announced = consumer.untagged().announced();
2662		let scope = self.stats.egress(self.root.join(&path).to_owned());
2663		loop {
2664			let OriginAnnounce {
2665				path: announced_path,
2666				broadcast,
2667			} = announced.next().await?;
2668			// `scope` narrows by prefix, but we only want an exact-path match.
2669			if announced_path.as_path() == path
2670				&& let Some(broadcast) = broadcast
2671			{
2672				return Some(broadcast.with_stats(scope));
2673			}
2674		}
2675	}
2676
2677	/// Returns a new Consumer restricted to broadcasts under one of `prefixes`.
2678	///
2679	/// Returns None if there are no legal prefixes (the requested prefixes are
2680	/// disjoint from this consumer's current scope, so it would always return None).
2681	// TODO accept PathPrefixes instead of &[Path]
2682	pub fn scope(&self, prefixes: &[Path]) -> Option<Consumer> {
2683		let prefixes = PathPrefixes::new(prefixes);
2684		Some(Consumer {
2685			info: self.info,
2686			root: self.root.clone(),
2687			nodes: self.nodes.select(&prefixes)?,
2688			dynamic: self.dynamic.clone(),
2689			stats: self.stats.clone(),
2690			exclude: self.exclude,
2691		})
2692	}
2693
2694	/// Get a broadcast by path, falling back to a dynamic request when it is not announced.
2695	///
2696	/// Returns a [`kio::Pending`] future (resolved synchronously for an announced broadcast,
2697	/// otherwise once a handler serves it), mirroring [`track::Consumer::fetch_group`](track::Consumer::fetch_group).
2698	/// The lookup order is: an already-announced broadcast resolves
2699	/// immediately; otherwise, if an [`Dynamic`] handler is live (see
2700	/// [`Producer::dynamic`]), a fallback request is registered and the future resolves
2701	/// when the handler [`accept`](Request::accept)s it (or errors if it
2702	/// [`reject`](Request::reject)s or every handler drops). Concurrent requests for
2703	/// the same unannounced path coalesce onto one handler request, and once served the
2704	/// broadcast is cached weakly so *later* requests for that path also share it (rather
2705	/// than re-invoking the handler and opening a duplicate upstream subscription) for as
2706	/// long as it stays live; a closed one is re-served on the next request.
2707	///
2708	/// The returned future resolves to [`Error::Unroutable`] when the path is not announced and no
2709	/// dynamic handler exists. A request that is registered while a handler is live but then loses
2710	/// every handler before being served also resolves to [`Error::Unroutable`]. Unlike an announced
2711	/// broadcast, a dynamically served one is never visible to [`Self::announced`].
2712	pub fn request_broadcast(&self, path: impl AsPath) -> kio::Pending<Requesting> {
2713		let path = path.as_path();
2714
2715		// Key requests by absolute path so a scoped/rooted consumer and the handler
2716		// (which may have a different root) agree on the same entry, and so the egress
2717		// counters resolve against the same broadcast the ingress side wrote.
2718		let absolute = self.root.join(&path).to_owned();
2719		let scope = self.stats.egress(&absolute);
2720
2721		// Prefer a live announcement when one is present; the dynamic queue is only a
2722		// fallback for a path we hold nothing for. A broadcast we do hold but cannot
2723		// serve this requester is unroutable, not missing: a handler resolves paths
2724		// with no route chain to check, so falling through would let it route around
2725		// the split horizon and rebuild the loop.
2726		match self.resolve(&path) {
2727			Resolved::Found(broadcast) => return kio::Pending::new(Requesting::ready(broadcast).with_stats(scope)),
2728			Resolved::Excluded => return kio::Pending::new(Requesting::failed(Error::Unroutable)),
2729			Resolved::Missing => {}
2730		}
2731
2732		let mut state = self.dynamic.lock();
2733
2734		// Reuse a still-live broadcast a handler already served for this path, so repeat
2735		// requests share one upstream subscription. A closed entry is stale; `get` drops it
2736		// and returns `None`, so we fall through and re-serve below.
2737		if let Some(weak) = state.served.get(&absolute) {
2738			return kio::Pending::new(Requesting::ready(weak.consume()).with_stats(scope));
2739		}
2740
2741		// Coalesce onto a pending request for the same path; otherwise register a new
2742		// one, unless there is no handler alive to serve it.
2743		let consumer = if let Some(producer) = state.requests.join(&absolute) {
2744			producer.consume()
2745		} else {
2746			let producer = kio::Producer::<PendingBroadcast>::default();
2747			let consumer = producer.consume();
2748			if state.requests.insert(absolute, producer).is_err() {
2749				return kio::Pending::new(Requesting::failed(Error::Unroutable));
2750			}
2751			consumer
2752		};
2753
2754		kio::Pending::new(Requesting::pending(consumer).with_stats(scope))
2755	}
2756
2757	/// Returns a new Consumer that automatically strips out the provided prefix.
2758	///
2759	/// Returns None if the provided root is not authorized; when [`Self::scope`] was
2760	/// already used without a wildcard.
2761	pub fn with_root(&self, prefix: impl AsPath) -> Option<Self> {
2762		let prefix = prefix.as_path();
2763
2764		Some(Self {
2765			info: self.info,
2766			root: self.root.join(&prefix).to_owned(),
2767			nodes: self.nodes.root(&prefix)?,
2768			dynamic: self.dynamic.clone(),
2769			stats: self.stats.clone(),
2770			exclude: self.exclude,
2771		})
2772	}
2773
2774	/// Returns the prefix that is automatically stripped from all paths.
2775	pub fn root(&self) -> &Path<'_> {
2776		&self.root
2777	}
2778
2779	/// Iterate over the path prefixes this handle is permitted to publish or subscribe under.
2780	// TODO return PathPrefixes
2781	pub fn allowed(&self) -> impl Iterator<Item = &Path<'_>> {
2782		self.nodes.nodes.iter().map(|(root, _)| root)
2783	}
2784
2785	/// Converts a relative path to an absolute path.
2786	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2787		self.root.join(path)
2788	}
2789}
2790
2791/// Handle to the announcement stream for a subtree.
2792///
2793/// Symmetric counterpart of [`AnnounceConsumer`]. Cheap to clone; call
2794/// [`Self::consume`] to obtain an [`AnnounceConsumer`] that receives events.
2795#[derive(Clone)]
2796pub struct AnnounceProducer {
2797	nodes: OriginNodes,
2798	root: PathOwned,
2799}
2800
2801impl AnnounceProducer {
2802	fn new(root: PathOwned, nodes: OriginNodes) -> Self {
2803		Self { nodes, root }
2804	}
2805
2806	/// Subscribe to announce / unannounce events for this subtree.
2807	///
2808	/// Allocates a per-cursor coalescing buffer and replays the currently active broadcast set
2809	/// as initial announcements. Drop the returned [`AnnounceConsumer`] to
2810	/// unregister.
2811	pub fn consume(&self) -> AnnounceConsumer {
2812		// Untagged: `AnnounceProducer` is used for internal announce plumbing, not
2813		// egress attribution (which flows through `origin::Consumer::announced`).
2814		AnnounceConsumer::new(self.root.clone(), self.nodes.clone(), stats::Session::default())
2815	}
2816
2817	/// Returns the prefix that is automatically stripped from announced paths.
2818	pub fn root(&self) -> &Path<'_> {
2819		&self.root
2820	}
2821}
2822
2823/// Receives announce / unannounce events for a subtree.
2824///
2825/// Created by [`Consumer::announced`] or [`AnnounceProducer::consume`].
2826/// Drop to unregister.
2827pub struct AnnounceConsumer {
2828	id: ConsumerId,
2829	nodes: OriginNodes,
2830	root: PathOwned,
2831
2832	// Pending updates queued for this cursor. Coalesced so a slow consumer
2833	// can't accumulate redundant announce/unannounce pairs.
2834	state: kio::Producer<OriginConsumerState>,
2835
2836	// Egress stats context (empty for an untagged stream). Announce events drive the
2837	// per-broadcast announce guards below and tag the broadcasts handed out.
2838	stats: stats::Session,
2839
2840	// Live egress announce guards, keyed by absolute broadcast path. An announce
2841	// opens one (bumping `announced` + `announced_bytes`); the matching unannounce
2842	// drops it (bumping `announced_closed` + `announced_bytes`).
2843	guards: HashMap<PathOwned, stats::Announce>,
2844}
2845
2846impl AnnounceConsumer {
2847	fn new(root: PathOwned, nodes: OriginNodes, stats: stats::Session) -> Self {
2848		let state = kio::Producer::<OriginConsumerState>::default();
2849		let id = ConsumerId::new();
2850
2851		for (_, node) in &nodes.nodes {
2852			let notify = AnnounceConsumerNotify {
2853				root: root.clone(),
2854				state: state.clone(),
2855			};
2856			node.lock().consume(id, notify);
2857		}
2858
2859		Self {
2860			id,
2861			nodes,
2862			root,
2863			state,
2864			stats,
2865			guards: HashMap::new(),
2866		}
2867	}
2868
2869	/// Drive the egress announce guards and tag the broadcast for one update.
2870	///
2871	/// An announce opens a guard (keyed by absolute path) and tags the yielded
2872	/// broadcast with the egress scope; an unannounce drops the guard. A no-op for
2873	/// an untagged stream.
2874	fn attribute(&mut self, update: OriginAnnounce) -> OriginAnnounce {
2875		let OriginAnnounce { path, broadcast } = update;
2876		let absolute = self.root.join(&path).to_owned();
2877		match broadcast {
2878			Some(broadcast) => {
2879				let scope = self.stats.egress(&absolute);
2880				self.guards.entry(absolute).or_insert_with(|| scope.announce());
2881				OriginAnnounce {
2882					path,
2883					broadcast: Some(broadcast.with_stats(scope)),
2884				}
2885			}
2886			None => {
2887				self.guards.remove(&absolute);
2888				OriginAnnounce { path, broadcast: None }
2889			}
2890		}
2891	}
2892
2893	/// Returns the next (un)announced broadcast and its path relative to this
2894	/// cursor's root.
2895	///
2896	/// The broadcast will only be announced if it was previously unannounced.
2897	/// The same path won't be announced/unannounced twice in a row; instead it
2898	/// toggles. Returns None if the cursor is closed.
2899	pub async fn next(&mut self) -> Option<OriginAnnounce> {
2900		kio::wait(|waiter| self.poll_next(waiter)).await
2901	}
2902
2903	/// Poll for the next (un)announced broadcast, without blocking.
2904	///
2905	/// Returns `Poll::Ready(Some(_))` for an update, `Poll::Ready(None)` if the
2906	/// cursor is closed, or `Poll::Pending` after registering `waiter` to be
2907	/// notified when the next update arrives.
2908	pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Option<OriginAnnounce>> {
2909		let update = {
2910			let mut state = match ready!(self.state.poll(waiter, |state| {
2911				if state.pending.is_empty() {
2912					Poll::Pending
2913				} else {
2914					Poll::Ready(())
2915				}
2916			})) {
2917				Ok(state) => state,
2918				// Closed: discard the Ref so its MutexGuard doesn't escape this call.
2919				Err(_) => return Poll::Ready(None),
2920			};
2921			state.take().expect("predicate guaranteed an update")
2922		};
2923		Poll::Ready(Some(self.attribute(update)))
2924	}
2925
2926	/// Returns the next (un)announced broadcast without blocking.
2927	///
2928	/// Returns None if there is no update available; NOT because the cursor is closed.
2929	/// Use [`Self::is_closed`] to check if the cursor is closed.
2930	pub fn try_next(&mut self) -> Option<OriginAnnounce> {
2931		let update = self.state.write().ok()?.take()?;
2932		Some(self.attribute(update))
2933	}
2934
2935	/// Returns true if the cursor is closed (no more updates will arrive).
2936	pub fn is_closed(&self) -> bool {
2937		self.state.write().is_err()
2938	}
2939
2940	/// Returns the prefix that is automatically stripped from emitted paths.
2941	pub fn root(&self) -> &Path<'_> {
2942		&self.root
2943	}
2944
2945	/// Converts a relative path to an absolute path.
2946	pub fn absolute(&self, path: impl AsPath) -> Path<'_> {
2947		self.root.join(path)
2948	}
2949}
2950
2951impl Drop for AnnounceConsumer {
2952	fn drop(&mut self) {
2953		for (_, root) in &self.nodes.nodes {
2954			root.lock().unconsume(self.id);
2955		}
2956	}
2957}
2958
2959#[cfg(test)]
2960use futures::FutureExt;
2961
2962#[cfg(test)]
2963#[allow(missing_docs)] // test-only assertion helpers
2964impl AnnounceConsumer {
2965	pub fn assert_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2966		let expected = expected.as_path();
2967		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2968		assert_eq!(announce.path, expected, "wrong path");
2969		let announced = announce.broadcast.expect("should be an active announce");
2970		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2971	}
2972
2973	/// An announce for `expected`, without asserting which broadcast backs it
2974	/// (the origin owns the announced broadcast, not the publisher). Returns the
2975	/// announced consumer.
2976	pub fn assert_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2977		let expected = expected.as_path();
2978		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
2979		assert_eq!(announce.path, expected, "wrong path");
2980		announce.broadcast.expect("should be an active announce")
2981	}
2982
2983	pub fn assert_try_next(&mut self, expected: impl AsPath, broadcast: &broadcast::Consumer) {
2984		let expected = expected.as_path();
2985		let announce = self.try_next().expect("no next");
2986		assert_eq!(announce.path, expected, "wrong path");
2987		let announced = announce.broadcast.expect("should be an active announce");
2988		assert!(announced.is_clone(broadcast), "should be the same broadcast");
2989	}
2990
2991	/// The `try_next` counterpart of [`Self::assert_next_some`].
2992	pub fn assert_try_next_some(&mut self, expected: impl AsPath) -> broadcast::Consumer {
2993		let expected = expected.as_path();
2994		let announce = self.try_next().expect("no next");
2995		assert_eq!(announce.path, expected, "wrong path");
2996		announce.broadcast.expect("should be an active announce")
2997	}
2998
2999	pub fn assert_next_none(&mut self, expected: impl AsPath) {
3000		let expected = expected.as_path();
3001		let announce = self.next().now_or_never().expect("next blocked").expect("no next");
3002		assert_eq!(announce.path, expected, "wrong path");
3003		assert!(announce.broadcast.is_none(), "should be unannounced");
3004	}
3005
3006	pub fn assert_next_wait(&mut self) {
3007		if let Some(res) = self.next().now_or_never() {
3008			panic!("next should block: got {:?}", res.map(|a| a.path));
3009		}
3010	}
3011
3012	/*
3013	pub fn assert_next_closed(&mut self) {
3014		assert!(
3015			self.next().now_or_never().expect("next blocked").is_none(),
3016			"next should be closed"
3017		);
3018	}
3019	*/
3020}
3021
3022#[cfg(test)]
3023mod tests {
3024	use crate::coding::Decode;
3025	use crate::group;
3026
3027	use super::*;
3028
3029	/// An announced direct route.
3030	fn announce() -> broadcast::Route {
3031		broadcast::Route::new().with_announce(true)
3032	}
3033
3034	/// The first origin whose handover key for `name` sits above (`true`) or below
3035	/// (`false`) the peer's, so tests exercising the carrying gate are
3036	/// deterministic instead of hinging on a random id winning a hash comparison.
3037	/// Starts searching above the small ids the tests use in hop chains, so the
3038	/// result never collides with a hop (a looping chain trips a debug_assert).
3039	fn origin_keyed(name: &str, peer: Origin, above: bool) -> Origin {
3040		let name = Path::new(name);
3041		let peer_key = fnv_key(&name, [peer]);
3042		(100u64..)
3043			.map(|id| Origin::new(id).unwrap())
3044			.find(|origin| (fnv_key(&name, [*origin]) > peer_key) == above)
3045			.unwrap()
3046	}
3047
3048	/// A front table for reselect tests: routes get ids in order, the first is
3049	/// the incumbent.
3050	fn front_state(self_origin: Origin, routes: Vec<broadcast::Route>) -> FrontState {
3051		let source = broadcast::Info::new().produce().consume();
3052		FrontState {
3053			path: Path::new("test").to_owned(),
3054			self_origin,
3055			publisher: routes.first().and_then(|r| r.hops.iter().next().copied()),
3056			next_route: routes.len() as u64,
3057			excluded: HashMap::new(),
3058			routes: routes
3059				.into_iter()
3060				.enumerate()
3061				.map(|(id, route)| FrontRoute {
3062					id: id as u64,
3063					route,
3064					source: source.clone(),
3065				})
3066				.collect(),
3067			active: Some(0),
3068			linger: Duration::ZERO,
3069			closed: false,
3070		}
3071	}
3072
3073	/// A route as a warm sibling would announce it: zero cost, chain ending at
3074	/// the announcing peer.
3075	fn sibling_route(peer: Origin) -> broadcast::Route {
3076		let hops = OriginList::try_from(vec![Origin::new(90).unwrap(), peer]).unwrap();
3077		announce().with_hops(hops)
3078	}
3079
3080	/// A route as the upstream announces it: priced, one hop.
3081	fn upstream_route(cost: u64) -> broadcast::Route {
3082		let hops = OriginList::try_from(vec![Origin::new(90).unwrap()]).unwrap();
3083		announce().with_hops(hops).with_cost(cost)
3084	}
3085
3086	/// While carrying, a strictly cheaper route from a peer that hashes above us
3087	/// must not displace the incumbent; the same table re-parents freely once
3088	/// idle, or when the peer hashes below us.
3089	#[test]
3090	fn test_carrying_gate_keys() {
3091		let peer = Origin::new(3).unwrap();
3092
3093		// We lose the key comparison: stay put while carrying, migrate when idle.
3094		let mut lost = front_state(
3095			origin_keyed("test", peer, false),
3096			vec![upstream_route(10), sibling_route(peer)],
3097		);
3098		lost.reselect(true);
3099		assert_eq!(
3100			lost.active,
3101			Some(0),
3102			"carrying front re-parented onto a higher-keyed peer"
3103		);
3104		lost.reselect(false);
3105		assert_eq!(lost.active, Some(1), "idle front must take the cheaper route");
3106
3107		// We win the key comparison: re-parent even while carrying.
3108		let mut won = front_state(
3109			origin_keyed("test", peer, true),
3110			vec![upstream_route(10), sibling_route(peer)],
3111		);
3112		won.reselect(true);
3113		assert_eq!(won.active, Some(1), "carrying front must follow a lower-keyed peer");
3114	}
3115
3116	/// The simultaneous-activation race: two relays that each pulled the same
3117	/// broadcast independently see each other's zero-cost route. Exactly one of
3118	/// them re-parents; the other keeps its upstream, so the broadcast is never
3119	/// left without a source.
3120	#[test]
3121	fn test_carrying_gate_symmetric_race() {
3122		let a = Origin::new(1).unwrap();
3123		let b = Origin::new(2).unwrap();
3124
3125		let mut a_view = front_state(a, vec![upstream_route(10), sibling_route(b)]);
3126		let mut b_view = front_state(b, vec![upstream_route(10), sibling_route(a)]);
3127		a_view.reselect(true);
3128		b_view.reselect(true);
3129
3130		let a_moved = a_view.active == Some(1);
3131		let b_moved = b_view.active == Some(1);
3132		assert!(
3133			a_moved != b_moved,
3134			"exactly one side must re-parent (a: {a_moved}, b: {b_moved})"
3135		);
3136	}
3137
3138	/// The gate is scoped to warm siblings: a cheaper route via a relay that is
3139	/// not itself carrying (advertised nonzero), or directly from the original
3140	/// publisher (single-hop chain), is taken immediately even while carrying
3141	/// and even when we would lose the key comparison.
3142	#[test]
3143	fn test_carrying_switches_to_benign_routes() {
3144		let peer = Origin::new(3).unwrap();
3145		let lost = origin_keyed("test", peer, false);
3146
3147		// A cheaper forwarder path: the relay advertised its accumulated cost.
3148		let mut forwarder = sibling_route(peer).with_cost(4);
3149		forwarder.advertised = 4;
3150		let mut state = front_state(lost, vec![upstream_route(10), forwarder]);
3151		state.reselect(true);
3152		assert_eq!(
3153			state.active,
3154			Some(1),
3155			"a cheaper forwarder path must win while carrying"
3156		);
3157
3158		// Directly from the original publisher: single-hop chain, advertised zero.
3159		let direct = announce().with_hops(OriginList::try_from(vec![peer]).unwrap());
3160		let mut state = front_state(lost, vec![upstream_route(10), direct]);
3161		state.reselect(true);
3162		assert_eq!(
3163			state.active,
3164			Some(1),
3165			"a direct publisher route must win while carrying"
3166		);
3167
3168		// The peer's session reconnecting: same chain, same cost, so the gate's
3169		// strictly-cheaper test does not apply and recency decides. Loosening that
3170		// test to `<=` would hold a carrying front on the dead session until the
3171		// transport timed it out, which is the whole point of the recency order.
3172		let mut state = front_state(lost, vec![sibling_route(peer), sibling_route(peer)]);
3173		state.reselect(true);
3174		assert_eq!(
3175			state.active,
3176			Some(1),
3177			"a reconnect on an identical chain must win while carrying"
3178		);
3179	}
3180
3181	/// The gate only protects an announced incumbent: one that lost its announce
3182	/// (the upstream retracted) is displaced regardless of the key comparison.
3183	#[test]
3184	fn test_carrying_gate_ignores_unannounced_incumbent() {
3185		let peer = Origin::new(3).unwrap();
3186		let unannounced = upstream_route(10).with_announce(false);
3187		let mut state = front_state(
3188			origin_keyed("test", peer, false),
3189			vec![unannounced, sibling_route(peer)],
3190		);
3191		state.reselect(true);
3192		assert_eq!(
3193			state.active,
3194			Some(1),
3195			"an unannounced incumbent must always be displaced"
3196		);
3197	}
3198
3199	/// Let the spawned origin tasks (source watchers, front dispatch) run. The
3200	/// tests pause tokio time, so this advances the clock instantly.
3201	async fn settle() {
3202		tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
3203	}
3204
3205	/// Serve one requested track from a source like a session would: wait for the
3206	/// origin to dispatch it, then accept with default info.
3207	async fn accept_track(dynamic: &mut broadcast::Dynamic, name: &str) -> track::Producer {
3208		let request = tokio::time::timeout(std::time::Duration::from_secs(1), dynamic.requested_track())
3209			.await
3210			.expect("timed out waiting for a track request")
3211			.expect("source closed");
3212		assert_eq!(request.name(), name, "unexpected track dispatched");
3213		request.accept(None)
3214	}
3215
3216	/// Tagging both origin handles with one context attributes the full model path:
3217	/// ingress writes on the subscriber side, egress reads on the publisher side,
3218	/// each counter landing exactly once (the model-layer silent-zero guard).
3219	#[tokio::test]
3220	async fn test_stats_tagged_end_to_end() {
3221		use crate::Timestamp;
3222		use crate::stats::{Config, Registry, Tier};
3223		use bytes::Bytes;
3224
3225		tokio::time::pause();
3226
3227		let registry = Registry::new(Config::new());
3228		let ctx = registry.tier(Tier::default()).session("acme");
3229
3230		let origin = Origin::random().produce();
3231		let ingress = origin.clone().with_stats(ctx.clone());
3232		let egress = origin.consume().with_stats(ctx.clone());
3233
3234		// Egress announce stream: this is the tagged stream that drives the egress
3235		// announce guard.
3236		let mut announced = egress.announced();
3237
3238		// Ingress publishes an announced broadcast.
3239		let source = ingress.create_broadcast("demo", announce()).unwrap();
3240		let mut dynamic = source.dynamic();
3241		settle().await;
3242		settle().await;
3243
3244		// Egress observes the announce and gets the tagged broadcast.
3245		let update = announced.next().await.unwrap();
3246		assert_eq!(update.path.as_str(), "demo");
3247		let broadcast = update.broadcast.unwrap();
3248
3249		// Egress subscribes; the ingress side serves the track on demand.
3250		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3251		let mut producer = accept_track(&mut dynamic, "video").await;
3252		settle().await;
3253		let mut sub = subscribing.await.unwrap();
3254
3255		// Ingress writes one group with two 5-byte frames.
3256		let mut group = producer.append_group().unwrap();
3257		group
3258			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
3259			.unwrap();
3260		group
3261			.write_frame(Timestamp::ZERO, Bytes::from_static(b"world"))
3262			.unwrap();
3263		group.finish().unwrap();
3264
3265		// Egress reads the group and both frames.
3266		let mut group_c = sub.recv_group().await.unwrap().unwrap();
3267		let mut frames = 0;
3268		while let Some(frame) = group_c.read_frame().await.unwrap() {
3269			assert_eq!(frame.payload.len(), 5);
3270			frames += 1;
3271		}
3272		assert_eq!(frames, 2);
3273		settle().await;
3274
3275		let report = registry.report();
3276		let entry = report
3277			.traffic
3278			.iter()
3279			.find(|e| e.path.as_str() == "demo")
3280			.expect("demo tracked");
3281		let path_len = "demo".len() as u64;
3282
3283		// Egress (publisher side): reads out of the model.
3284		let egress = &entry.publisher;
3285		assert_eq!(egress.announced, 1, "one egress announce");
3286		assert_eq!(egress.announced_bytes, path_len);
3287		assert_eq!(egress.subscriptions, 1, "one egress subscription");
3288		assert_eq!(egress.broadcasts, 1, "one viewer");
3289		assert_eq!(egress.groups, 1);
3290		assert_eq!(egress.frames, 2);
3291		assert_eq!(egress.bytes, 10);
3292		assert_eq!(egress.fetches, 0);
3293
3294		// Ingress (subscriber side): writes into the model.
3295		let ingress = &entry.subscriber;
3296		assert_eq!(ingress.announced, 1, "one ingress announce");
3297		assert_eq!(ingress.announced_bytes, path_len);
3298		assert_eq!(ingress.subscriptions, 1, "one ingress track");
3299		assert_eq!(ingress.broadcasts, 0, "ingress has no viewer refcount");
3300		assert_eq!(ingress.groups, 1);
3301		assert_eq!(ingress.frames, 2);
3302		assert_eq!(ingress.bytes, 10);
3303
3304		// A fetch bumps only `fetches` on the egress side, plus the delivered group.
3305		let fetched = broadcast.track("video").unwrap().fetch_group(0, None).await.unwrap();
3306		let _ = fetched;
3307		settle().await;
3308		let report = registry.report();
3309		let entry = report.traffic.iter().find(|e| e.path.as_str() == "demo").unwrap();
3310		assert_eq!(entry.publisher.fetches, 1, "one fetch");
3311		assert_eq!(entry.publisher.subscriptions, 1, "fetch does not bump subscriptions");
3312		assert_eq!(entry.publisher.broadcasts, 1, "fetch does not bump the viewer refcount");
3313		// `fetches` is egress-only for the same structural reason as `broadcasts`:
3314		// only a `track::Consumer` can fetch, and the ingress scope never reaches one
3315		// (`broadcast::Producer::consume` hands out an untagged consumer).
3316		assert_eq!(entry.subscriber.fetches, 0, "ingress cannot fetch");
3317	}
3318
3319	/// `Subscriber::read_frame` collapses a group to its first frame. The paths it
3320	/// delegates to (plain and spliced) build their own *unmetered* group consumers,
3321	/// so the wrapper is the only place that can attribute the read: exactly one
3322	/// group, one frame, and the payload bytes, counted once each.
3323	#[tokio::test]
3324	async fn test_stats_read_frame_counts_once() {
3325		use crate::Timestamp;
3326		use crate::stats::{Config, Registry, Tier};
3327		use bytes::Bytes;
3328
3329		tokio::time::pause();
3330
3331		let registry = Registry::new(Config::new());
3332		let ctx = registry.tier(Tier::default()).session("acme");
3333
3334		let origin = Origin::random().produce();
3335		let ingress = origin.clone().with_stats(ctx.clone());
3336		let egress = origin.consume().with_stats(ctx.clone());
3337
3338		let mut announced = egress.announced();
3339		let source = ingress.create_broadcast("demo", announce()).unwrap();
3340		let mut dynamic = source.dynamic();
3341		settle().await;
3342		settle().await;
3343
3344		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
3345		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3346		let mut producer = accept_track(&mut dynamic, "video").await;
3347		settle().await;
3348		let mut sub = subscribing.await.unwrap();
3349
3350		// A single-frame group, read back through the collapsing helper.
3351		producer
3352			.write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
3353			.unwrap();
3354
3355		let frame = sub.read_frame().await.unwrap().expect("frame");
3356		assert_eq!(frame.payload.len(), 5);
3357		settle().await;
3358
3359		let report = registry.report();
3360		let entry = report
3361			.traffic
3362			.iter()
3363			.find(|e| e.path.as_str() == "demo")
3364			.expect("demo tracked");
3365		assert_eq!(entry.publisher.groups, 1, "one group, counted once");
3366		assert_eq!(entry.publisher.frames, 1, "one frame, counted once");
3367		assert_eq!(
3368			entry.publisher.bytes, 5,
3369			"payload counted once, not zero and not doubled"
3370		);
3371	}
3372
3373	/// Datagrams bypass the group/frame handles entirely, so they're metered at the
3374	/// producer (ingress write) and the subscriber (egress read). Each one counts as
3375	/// the single-frame group it stands in for, plus the `datagrams` breakout.
3376	#[tokio::test]
3377	async fn test_stats_datagrams_counted_both_sides() {
3378		use crate::Timestamp;
3379		use crate::stats::{Config, Registry, Tier};
3380
3381		tokio::time::pause();
3382
3383		let registry = Registry::new(Config::new());
3384		let ctx = registry.tier(Tier::default()).session("acme");
3385
3386		let origin = Origin::random().produce();
3387		let ingress = origin.clone().with_stats(ctx.clone());
3388		let egress = origin.consume().with_stats(ctx.clone());
3389
3390		let mut announced = egress.announced();
3391		let source = ingress.create_broadcast("demo", announce()).unwrap();
3392		let mut dynamic = source.dynamic();
3393		settle().await;
3394		settle().await;
3395
3396		let broadcast = announced.next().await.unwrap().broadcast.unwrap();
3397		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3398		let mut producer = accept_track(&mut dynamic, "video").await;
3399		settle().await;
3400		let mut sub = subscribing.await.unwrap();
3401
3402		producer.append_datagram(Timestamp::ZERO, &b"hello"[..]).unwrap();
3403		let datagram = sub.recv_datagram().await.unwrap().expect("datagram");
3404		assert_eq!(&datagram.payload[..], b"hello");
3405		settle().await;
3406
3407		let report = registry.report();
3408		let entry = report
3409			.traffic
3410			.iter()
3411			.find(|e| e.path.as_str() == "demo")
3412			.expect("demo tracked");
3413
3414		for (side, traffic) in [("egress", &entry.publisher), ("ingress", &entry.subscriber)] {
3415			assert_eq!(traffic.datagrams, 1, "{side}: one datagram");
3416			assert_eq!(traffic.groups, 1, "{side}: counted as its single-frame group");
3417			assert_eq!(traffic.frames, 1, "{side}: one frame");
3418			assert_eq!(traffic.bytes, 5, "{side}: payload counted once");
3419		}
3420	}
3421
3422	#[test]
3423	fn origin_rejects_reserved_ids() {
3424		assert!(Origin::new(0).is_err());
3425		assert!(Origin::new(1u64 << 62).is_err());
3426		assert_eq!(Origin::new(1).unwrap().id(), 1);
3427
3428		let mut zero = [0u8].as_slice();
3429		assert_eq!(
3430			Origin::decode(&mut zero, crate::lite::Version::Lite05).unwrap(),
3431			Origin::UNKNOWN
3432		);
3433	}
3434
3435	#[test]
3436	fn origin_list_push_fails_at_limit() {
3437		let mut list = OriginList::new();
3438		for _ in 0..MAX_HOPS {
3439			list.push(Origin::random()).unwrap();
3440		}
3441		assert_eq!(list.len(), MAX_HOPS);
3442		assert_eq!(list.push(Origin::random()), Err(TooManyOrigins));
3443	}
3444
3445	#[test]
3446	fn origin_list_replace_first() {
3447		let mut list = OriginList::new();
3448		for _ in 0..3 {
3449			list.push(Origin::UNKNOWN).unwrap();
3450		}
3451
3452		// Rewrites only the first placeholder, keeping the length the same.
3453		assert!(list.replace_first(Origin::UNKNOWN, Origin::new(7).unwrap()));
3454		assert_eq!(
3455			list.as_slice(),
3456			&[Origin::new(7).unwrap(), Origin::UNKNOWN, Origin::UNKNOWN]
3457		);
3458
3459		// No match leaves the list untouched.
3460		assert!(!list.replace_first(Origin::new(99).unwrap(), Origin::new(8).unwrap()));
3461		assert_eq!(list.len(), 3);
3462	}
3463
3464	#[test]
3465	fn origin_list_try_from_vec_enforces_limit() {
3466		let under: Vec<Origin> = (0..MAX_HOPS).map(|_| Origin::random()).collect();
3467		assert!(OriginList::try_from(under).is_ok());
3468
3469		let over: Vec<Origin> = (0..MAX_HOPS + 1).map(|_| Origin::random()).collect();
3470		assert_eq!(OriginList::try_from(over), Err(TooManyOrigins));
3471	}
3472
3473	#[tokio::test]
3474	async fn test_announce() {
3475		tokio::time::pause();
3476
3477		let origin = Origin::random().produce();
3478
3479		let mut consumer1 = origin.consume().announced();
3480		consumer1.assert_next_wait();
3481
3482		// Publish the first broadcast; it becomes visible asynchronously.
3483		let mut broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
3484		settle().await;
3485
3486		consumer1.assert_next_some("test1");
3487		consumer1.assert_next_wait();
3488
3489		// Make a new consumer that should get the existing broadcast.
3490		// But we don't consume it yet.
3491		let mut consumer2 = origin.consume().announced();
3492
3493		// Publish the second broadcast.
3494		let mut broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
3495		settle().await;
3496
3497		consumer1.assert_next_some("test2");
3498		consumer1.assert_next_wait();
3499
3500		consumer2.assert_next_some("test1");
3501		consumer2.assert_next_some("test2");
3502		consumer2.assert_next_wait();
3503
3504		// Finish the first broadcast: a graceful end unannounces immediately.
3505		broadcast1.finish();
3506		settle().await;
3507
3508		// All consumers should get a None now.
3509		consumer1.assert_next_none("test1");
3510		consumer2.assert_next_none("test1");
3511		consumer1.assert_next_wait();
3512		consumer2.assert_next_wait();
3513
3514		// And a new consumer only gets the last broadcast.
3515		let mut consumer3 = origin.consume().announced();
3516		consumer3.assert_next_some("test2");
3517		consumer3.assert_next_wait();
3518
3519		broadcast2.finish();
3520		settle().await;
3521
3522		consumer1.assert_next_none("test2");
3523		consumer2.assert_next_none("test2");
3524		consumer3.assert_next_none("test2");
3525	}
3526
3527	/// Multiple sources created at one path feed a single origin-owned broadcast:
3528	/// one announce, no churn as sources come and go, and an unannounce only when
3529	/// the last source leaves.
3530	#[tokio::test]
3531	async fn test_duplicate() {
3532		tokio::time::pause();
3533
3534		let origin = Origin::random().produce();
3535		let consumer = origin.consume();
3536		let mut announced = consumer.announced();
3537
3538		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
3539		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
3540		let mut broadcast3 = origin.create_broadcast("test", announce()).unwrap();
3541		settle().await;
3542		assert!(consumer.get_broadcast("test").is_some());
3543
3544		announced.assert_next_some("test");
3545		announced.assert_next_wait();
3546
3547		// A standby source finishing changes nothing.
3548		broadcast2.finish();
3549		settle().await;
3550		assert!(consumer.get_broadcast("test").is_some());
3551		announced.assert_next_wait();
3552
3553		// The active source finishing hands over to a survivor, invisibly.
3554		broadcast1.finish();
3555		settle().await;
3556		assert!(consumer.get_broadcast("test").is_some());
3557		announced.assert_next_wait();
3558
3559		// The last source finishing unannounces and removes the broadcast.
3560		broadcast3.finish();
3561		settle().await;
3562		assert!(consumer.get_broadcast("test").is_none());
3563
3564		announced.assert_next_none("test");
3565		announced.assert_next_wait();
3566	}
3567
3568	/// A source dying mid-serve fails over: the track re-splices from the standby
3569	/// source and resumes exactly at the first missing group.
3570	#[tokio::test]
3571	async fn test_route_failover() {
3572		tokio::time::pause();
3573
3574		let origin = Origin::random().produce();
3575		let consumer = origin.consume();
3576		let mut announced = consumer.announced();
3577
3578		// Both routes share the first hop (the original publisher): only
3579		// interchangeable content may join as a standby.
3580		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3581		let hops_b = OriginList::try_from(vec![Origin::new(1).unwrap(), Origin::new(3).unwrap()]).unwrap();
3582
3583		// The first source announces the broadcast.
3584		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3585		let mut dynamic_a = source_a.dynamic();
3586		settle().await;
3587		settle().await;
3588		let broadcast = consumer.request_broadcast("test").await.unwrap();
3589		announced.assert_next_some("test");
3590
3591		// A second (longer) source joins silently as a standby.
3592		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3593		let mut dynamic_b = source_b.dynamic();
3594		settle().await;
3595		settle().await;
3596		announced.assert_next_wait();
3597
3598		// Subscribing dispatches the track to the best source (A).
3599		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3600		let mut producer = accept_track(&mut dynamic_a, "video").await;
3601		settle().await;
3602		dynamic_b.assert_no_request();
3603
3604		let mut sub = subscribing.await.unwrap();
3605		// Demand registers as the subscriber polls; a fresh segment carries no
3606		// boundary, so the demand is the subscriber's own.
3607		sub.assert_no_group();
3608		assert_eq!(producer.subscription().unwrap().group_start, None);
3609
3610		producer.append_group().unwrap();
3611		producer.append_group().unwrap();
3612		assert_eq!(sub.assert_group().sequence, 0);
3613		assert_eq!(sub.assert_group().sequence, 1);
3614
3615		// Source A dies (session loss): the track re-splices from B and nothing
3616		// is announced.
3617		// abort() consumes the producer, so this both aborts and drops it.
3618		producer.abort(Error::Dropped).unwrap();
3619		source_a.abort(Error::Dropped).unwrap();
3620		drop(dynamic_a);
3621		settle().await;
3622		announced.assert_next_wait();
3623
3624		// The new copy resumes one past the spliced groups: its demand starts at
3625		// the boundary, and groups the old source already delivered are filtered.
3626		let mut producer = accept_track(&mut dynamic_b, "video").await;
3627		settle().await;
3628		sub.assert_no_group();
3629		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
3630		producer.create_group(group::Info { sequence: 1 }).unwrap();
3631		producer.create_group(group::Info { sequence: 2 }).unwrap();
3632		assert_eq!(sub.assert_group().sequence, 2, "groups below the boundary are filtered");
3633		sub.assert_not_closed();
3634	}
3635
3636	/// `route_changed` yields the current route first, then each change; equal
3637	/// updates coalesce, and the watch errors once every producer is gone.
3638	#[tokio::test]
3639	async fn test_broadcast_route_watch() {
3640		let mut producer = broadcast::Info::new().produce();
3641		let mut consumer = producer.consume();
3642
3643		// Initial value: the default route.
3644		assert_eq!(consumer.route_changed().await.unwrap(), broadcast::Route::default());
3645
3646		// An equal update is a no-op.
3647		producer.set_route(broadcast::Route::default()).unwrap();
3648		assert!(consumer.route_changed().now_or_never().is_none());
3649
3650		let mut hops = OriginList::new();
3651		hops.push(Origin::new(7).unwrap()).unwrap();
3652		let route = broadcast::Route::new().with_hops(hops).with_cost(3);
3653		producer.set_route(route.clone()).unwrap();
3654		assert_eq!(consumer.route_changed().await.unwrap(), route);
3655
3656		// A fresh consumer sees the current value immediately.
3657		let mut fresh = producer.consume();
3658		assert_eq!(fresh.route_changed().await.unwrap(), route);
3659
3660		drop(producer);
3661		assert!(matches!(consumer.route_changed().await.unwrap_err(), Error::Dropped));
3662	}
3663
3664	/// A cost update that flips the winning source hands live tracks over at a
3665	/// group boundary and re-advertises the broadcast's route, without announce
3666	/// churn.
3667	#[tokio::test]
3668	async fn test_route_cost_update() {
3669		tokio::time::pause();
3670
3671		// The takeover happens while a subscriber is live (carrying), so the local
3672		// origin must win the handover key comparison against B's announcing hop
3673		// (origin 3); a random id would flake on the hash.
3674		let origin = Info::new(origin_keyed("test", Origin::new(3).unwrap(), true)).produce();
3675		let consumer = origin.consume();
3676		let mut announced = consumer.announced();
3677
3678		// Both routes share the first hop (the original publisher): only
3679		// interchangeable content may join as a standby.
3680		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3681		let hops_b = OriginList::try_from(vec![Origin::new(1).unwrap(), Origin::new(3).unwrap()]).unwrap();
3682
3683		// A (shorter chain) wins at equal cost.
3684		let mut source_a = origin
3685			.create_broadcast("test", announce().with_hops(hops_a.clone()))
3686			.unwrap();
3687		let mut dynamic_a = source_a.dynamic();
3688		settle().await;
3689		let broadcast = consumer.request_broadcast("test").await.unwrap();
3690		announced.assert_next_some("test");
3691
3692		let mut watch = broadcast.clone();
3693		assert_eq!(watch.route_changed().await.unwrap().hops, hops_a);
3694
3695		let mut source_b = origin
3696			.create_broadcast("test", announce().with_hops(hops_b.clone()))
3697			.unwrap();
3698		let mut dynamic_b = source_b.dynamic();
3699		settle().await;
3700		assert!(
3701			watch.route_changed().now_or_never().is_none(),
3702			"a losing standby must not change the advertised route"
3703		);
3704
3705		// Dispatch the track to A and deliver a group.
3706		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3707		let mut producer = accept_track(&mut dynamic_a, "video").await;
3708		settle().await;
3709		let mut sub = subscribing.await.unwrap();
3710		producer.append_group().unwrap();
3711		assert_eq!(sub.assert_group().sequence, 0);
3712
3713		// A's cost rises above B's: B takes over at the boundary and the
3714		// broadcast re-advertises B's route. No announce events.
3715		source_a
3716			.set_route(announce().with_hops(hops_a.clone()).with_cost(10))
3717			.unwrap();
3718		settle().await;
3719		assert_eq!(watch.route_changed().await.unwrap().hops, hops_b);
3720		announced.assert_next_wait();
3721
3722		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3723		settle().await;
3724		// Demand registers as the subscriber polls; the new segment starts at the
3725		// splice boundary.
3726		sub.assert_no_group();
3727		assert_eq!(producer_b.subscription().unwrap().group_start, Some(1));
3728		producer_b.create_group(group::Info { sequence: 1 }).unwrap();
3729		assert_eq!(sub.assert_group().sequence, 1);
3730		sub.assert_not_closed();
3731
3732		// The active source updating its own metadata re-advertises in place.
3733		source_b
3734			.set_route(announce().with_hops(hops_b.clone()).with_cost(5))
3735			.unwrap();
3736		settle().await;
3737		let advertised = watch.route_changed().await.unwrap();
3738		assert_eq!(advertised.hops, hops_b);
3739		assert_eq!(advertised.cost, 5);
3740		announced.assert_next_wait();
3741	}
3742
3743	/// A track completed for good must survive later source churn: it is never
3744	/// re-dispatched, and late subscribers still see a clean end.
3745	#[tokio::test]
3746	async fn test_completed_track_survives_route_churn() {
3747		tokio::time::pause();
3748
3749		let origin = Origin::random().produce();
3750		let consumer = origin.consume();
3751
3752		// Shared first hop, so B is a standby rather than a parked replacement.
3753		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3754		let hops_b = OriginList::try_from(vec![Origin::new(1).unwrap(), Origin::new(3).unwrap()]).unwrap();
3755
3756		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
3757		let mut dynamic_a = source_a.dynamic();
3758		settle().await;
3759		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
3760		let mut dynamic_b = source_b.dynamic();
3761		settle().await;
3762		settle().await;
3763		let broadcast = consumer.request_broadcast("test").await.unwrap();
3764
3765		// Serve the track via A and end it for good.
3766		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3767		let mut producer = accept_track(&mut dynamic_a, "video").await;
3768		settle().await;
3769		let mut sub = subscribing.await.unwrap();
3770		producer.append_group().unwrap();
3771		assert_eq!(sub.assert_group().sequence, 0);
3772		producer.finish().unwrap();
3773		drop(producer);
3774		settle().await;
3775		sub.assert_closed();
3776
3777		// A detaching must not re-dispatch the finished track to B.
3778		source_a.abort(Error::Dropped).unwrap();
3779		drop(dynamic_a);
3780		settle().await;
3781		dynamic_b.assert_no_request();
3782
3783		// A late subscriber sees the same clean end, not an abort.
3784		let mut late = broadcast.track("video").unwrap().subscribe(None).await.unwrap();
3785		late.assert_closed();
3786	}
3787
3788	/// A source rejecting a track ends the logical track immediately, with the
3789	/// source's own error: which tracks a broadcast carries is the publisher's
3790	/// contract, so there is no sweep of other routes and no retry budget.
3791	#[tokio::test]
3792	async fn test_refused_track_aborts_instantly() {
3793		tokio::time::pause();
3794
3795		let origin = Origin::random().produce();
3796		let consumer = origin.consume();
3797
3798		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3799		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3800		let mut dynamic = source.dynamic();
3801		settle().await;
3802		settle().await;
3803		let broadcast = consumer.request_broadcast("test").await.unwrap();
3804
3805		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3806		let request = dynamic.requested_track().await.unwrap();
3807		request.reject(Error::NotFound);
3808		settle().await;
3809
3810		// One refusal is the verdict; the source is never re-asked.
3811		assert!(matches!(subscribing.await, Err(Error::NotFound)));
3812		dynamic.assert_no_request();
3813	}
3814
3815	/// A rejection only rules its source out of the track, so a better route
3816	/// taking over while the original source's info request is pending (and the
3817	/// stale source rejecting at the same moment) rides the handover instead of
3818	/// aborting the subscription.
3819	#[tokio::test]
3820	async fn test_stale_rejection_does_not_abort_a_handover() {
3821		tokio::time::pause();
3822
3823		let origin = Origin::random().produce();
3824		let consumer = origin.consume();
3825
3826		let publisher = Origin::new(1).unwrap();
3827		let peer = Origin::new(5).unwrap();
3828		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
3829		let local = OriginList::try_from(vec![publisher]).unwrap();
3830
3831		// The only route: dispatch parks on its pending info request.
3832		let source_remote = origin
3833			.create_broadcast("test", announce().with_hops(via_peer).with_cost(2))
3834			.unwrap();
3835		let mut dynamic_remote = source_remote.dynamic();
3836		settle().await;
3837		settle().await;
3838		let broadcast = consumer.request_broadcast("test").await.unwrap();
3839		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3840		let request_remote = dynamic_remote.requested_track().await.unwrap();
3841
3842		// A better route attaches (taking dispatch) while the old source's
3843		// request is still pending; the old source rejects in the same window.
3844		let source_local = origin.create_broadcast("test", announce().with_hops(local)).unwrap();
3845		let mut dynamic_local = source_local.dynamic();
3846		request_remote.reject(Error::NotFound);
3847		settle().await;
3848
3849		// The subscription rides the handover onto the new route.
3850		let mut producer_local = accept_track(&mut dynamic_local, "video").await;
3851		settle().await;
3852		let mut sub = subscribing
3853			.await
3854			.expect("the handover must win over the stale rejection");
3855		producer_local.append_group().unwrap();
3856		assert_eq!(sub.assert_group().sequence, 0);
3857		sub.assert_not_closed();
3858	}
3859
3860	/// A better source attaching mid-subscription takes the track over at an
3861	/// explicit group boundary: the old copy's demand is capped, the new copy
3862	/// starts at the boundary, and the subscriber reads a seamless sequence.
3863	#[tokio::test]
3864	async fn test_route_handover() {
3865		tokio::time::pause();
3866
3867		let origin = Origin::random().produce();
3868		let consumer = origin.consume();
3869		let mut announced = consumer.announced();
3870
3871		// Shared first hop, so the short route joins as an interchangeable source.
3872		let hops_long = OriginList::try_from(vec![Origin::new(1).unwrap(), Origin::new(3).unwrap()]).unwrap();
3873		let hops_short = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3874
3875		let source_a = origin
3876			.create_broadcast("test", announce().with_hops(hops_long))
3877			.unwrap();
3878		let mut dynamic_a = source_a.dynamic();
3879		settle().await;
3880		settle().await;
3881		let broadcast = consumer.request_broadcast("test").await.unwrap();
3882		announced.assert_next_some("test");
3883
3884		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3885		let mut producer_a = accept_track(&mut dynamic_a, "video").await;
3886		settle().await;
3887		let mut sub = subscribing.await.unwrap();
3888		producer_a.append_group().unwrap();
3889		producer_a.append_group().unwrap();
3890		assert_eq!(sub.assert_group().sequence, 0);
3891		assert_eq!(sub.assert_group().sequence, 1);
3892
3893		// A strictly shorter source attaches: the live track is handed over with
3894		// no announce churn.
3895		let source_b = origin
3896			.create_broadcast("test", announce().with_hops(hops_short))
3897			.unwrap();
3898		let mut dynamic_b = source_b.dynamic();
3899		settle().await;
3900		settle().await;
3901		announced.assert_next_wait();
3902
3903		let mut producer_b = accept_track(&mut dynamic_b, "video").await;
3904		settle().await;
3905
3906		// The old copy's demand is capped at the boundary; the new copy's starts
3907		// there. Both propagate as the subscriber polls.
3908		sub.assert_no_group();
3909		assert_eq!(producer_a.subscription().unwrap().group_end, Some(1));
3910		assert_eq!(producer_b.subscription().unwrap().group_start, Some(2));
3911
3912		// The old copy racing past its cap is filtered; the new copy serves on.
3913		producer_a.create_group(group::Info { sequence: 2 }).unwrap();
3914		producer_b.create_group(group::Info { sequence: 2 }).unwrap();
3915		producer_b.create_group(group::Info { sequence: 3 }).unwrap();
3916		assert_eq!(sub.assert_group().sequence, 2);
3917		assert_eq!(sub.assert_group().sequence, 3);
3918		sub.assert_no_group();
3919		sub.assert_not_closed();
3920	}
3921
3922	/// A graceful detach (deliberate unannounce) closes immediately: no linger, so
3923	/// the unannounce propagates promptly and a re-create is a fresh broadcast.
3924	#[tokio::test(start_paused = true)]
3925	async fn test_route_unannounce_immediate() {
3926		let origin = Origin::random().produce();
3927		let consumer = origin.consume();
3928		let mut announced = consumer.announced();
3929
3930		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3931		let mut source = origin
3932			.create_broadcast("test", announce().with_hops(hops.clone()))
3933			.unwrap();
3934		settle().await;
3935		let broadcast = consumer.request_broadcast("test").await.unwrap();
3936		announced.assert_next_some("test");
3937
3938		// The peer deliberately unannounced: no reconnect window, the broadcast is
3939		// gone as soon as the teardown task observes the close.
3940		source.finish();
3941		settle().await;
3942		announced.assert_next_none("test");
3943
3944		// A re-create at the same path is a brand-new broadcast.
3945		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3946		settle().await;
3947		let fresh = consumer.request_broadcast("test").await.unwrap();
3948		announced.assert_next_some("test");
3949		assert!(
3950			!fresh.is_clone(&broadcast),
3951			"re-create must not splice the old broadcast"
3952		);
3953	}
3954
3955	/// With the default zero linger, a dying source (a session drop, not a
3956	/// deliberate unannounce) closes the broadcast just as promptly as a graceful
3957	/// one: no reconnect window, the tracks abort, and a re-create is a fresh
3958	/// broadcast rather than a splice.
3959	#[tokio::test(start_paused = true)]
3960	async fn test_route_detach_immediate() {
3961		let origin = Origin::random().produce();
3962		let consumer = origin.consume();
3963		let mut announced = consumer.announced();
3964
3965		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
3966		let source = origin
3967			.create_broadcast("test", announce().with_hops(hops.clone()))
3968			.unwrap();
3969		let mut dynamic = source.dynamic();
3970		settle().await;
3971		settle().await;
3972		let broadcast = consumer.request_broadcast("test").await.unwrap();
3973		announced.assert_next_some("test");
3974
3975		let subscribing = broadcast.track("video").unwrap().subscribe(None);
3976		let producer = accept_track(&mut dynamic, "video").await;
3977		settle().await;
3978		let mut sub = subscribing.await.unwrap();
3979
3980		// The session dies without unannouncing.
3981		drop(producer);
3982		source.abort(Error::Dropped).unwrap();
3983		drop(dynamic);
3984
3985		settle().await;
3986		announced.assert_next_none("test");
3987		sub.assert_error();
3988
3989		// A reconnecting session gets a brand-new broadcast, not a splice into
3990		// the old one.
3991		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
3992		settle().await;
3993		settle().await;
3994		let fresh = consumer.request_broadcast("test").await.unwrap();
3995		announced.assert_next_some("test");
3996		assert!(
3997			!fresh.is_clone(&broadcast),
3998			"re-create must not splice the old broadcast"
3999		);
4000	}
4001
4002	/// A track nobody reads keeps the source's copy for [`TRACK_IDLE_LINGER`], then
4003	/// releases it. Crucially, the release must not immediately re-splice: the same
4004	/// demand signal gates both directions, so an idle track settles instead of
4005	/// re-requesting the track (and its info) every linger.
4006	#[tokio::test(start_paused = true)]
4007	async fn test_idle_track_releases_without_respinning() {
4008		let origin = Info::new(Origin::random()).produce();
4009		let consumer = origin.consume();
4010
4011		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4012		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4013		let mut dynamic = source.dynamic();
4014		settle().await;
4015		let broadcast = consumer.request_broadcast("test").await.unwrap();
4016
4017		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4018		let producer = accept_track(&mut dynamic, "video").await;
4019		settle().await;
4020		let sub = subscribing.await.unwrap();
4021
4022		// The reader leaves, but the copy stays warm inside the window so a viewer
4023		// coming back (or a follow-up fetch) reuses it.
4024		drop(sub);
4025		tokio::time::sleep(TRACK_IDLE_LINGER / 2).await;
4026		settle().await;
4027		assert!(
4028			producer.poll_unused(&kio::Waiter::noop()).is_pending(),
4029			"the copy must stay spliced inside the linger",
4030		);
4031
4032		// Past the window the segment is released, so the serving session sees its
4033		// copy go unused and can drop it (along with the track info).
4034		tokio::time::sleep(TRACK_IDLE_LINGER).await;
4035		settle().await;
4036		assert!(
4037			producer.poll_unused(&kio::Waiter::noop()).is_ready(),
4038			"an idle copy must be released after the linger",
4039		);
4040
4041		// The anti-spin property: the release must not re-arm the splice. Ungated,
4042		// the loop re-attaches the copy immediately and drops it again every linger,
4043		// re-requesting the track (and its info) from the session each time it dies.
4044		for _ in 0..3 {
4045			tokio::time::sleep(TRACK_IDLE_LINGER).await;
4046			settle().await;
4047			assert!(
4048				producer.poll_unused(&kio::Waiter::noop()).is_ready(),
4049				"an unread copy must stay released, not be re-spliced",
4050			);
4051		}
4052		assert!(
4053			dynamic.requested_track().now_or_never().is_none(),
4054			"an unread track must not be re-requested",
4055		);
4056		drop(producer);
4057
4058		// A returning reader re-splices: the origin asks the source for a fresh copy.
4059		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4060		let mut producer = accept_track(&mut dynamic, "video").await;
4061		settle().await;
4062		let mut sub = subscribing.await.unwrap();
4063		producer.append_group().unwrap();
4064		assert_eq!(sub.assert_group().sequence, 0);
4065	}
4066
4067	/// Back-to-back fetches reuse the source's copy: only the first asks the source
4068	/// for the track, so a fetch-driven consumer (HLS pulling segment after segment)
4069	/// doesn't re-request the track, and its `TRACK_INFO`, for every group.
4070	#[tokio::test(start_paused = true)]
4071	async fn test_back_to_back_fetches_reuse_the_track() {
4072		let origin = Info::new(Origin::random()).produce();
4073		let consumer = origin.consume();
4074
4075		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4076		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4077		let mut dynamic = source.dynamic();
4078		settle().await;
4079		let broadcast = consumer.request_broadcast("test").await.unwrap();
4080
4081		// The first fetch has to ask the source for the track.
4082		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
4083		let mut producer = accept_track(&mut dynamic, "video").await;
4084		producer.append_group().unwrap().finish().unwrap();
4085		settle().await;
4086		let first = fetching.await.expect("first fetch");
4087		drop(first);
4088
4089		// A second fetch inside the linger reuses the copy already spliced in.
4090		settle().await;
4091		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
4092		settle().await;
4093		assert!(
4094			dynamic.requested_track().now_or_never().is_none(),
4095			"a fetch inside the linger must reuse the track, not re-request it",
4096		);
4097		drop(fetching.await.expect("second fetch"));
4098
4099		// Once the fetches stop, the copy is released like any other idle track.
4100		tokio::time::sleep(TRACK_IDLE_LINGER * 2).await;
4101		settle().await;
4102		assert!(
4103			producer.poll_unused(&kio::Waiter::noop()).is_ready(),
4104			"the copy must be released once the fetches stop",
4105		);
4106		drop(producer);
4107
4108		// And a later fetch re-requests it: `accept_track` times out if it doesn't.
4109		settle().await;
4110		let fetching = broadcast.track("video").unwrap().fetch_group(0, None);
4111		let mut producer = accept_track(&mut dynamic, "video").await;
4112		producer.append_group().unwrap().finish().unwrap();
4113		settle().await;
4114		fetching.await.expect("fetch after the linger");
4115	}
4116
4117	/// With a linger configured, an ungraceful source loss keeps the broadcast
4118	/// alive and announced; a source re-attaching within the window splices in and
4119	/// consumers resume at the group boundary, never observing the outage.
4120	#[tokio::test(start_paused = true)]
4121	async fn test_linger_reconnect_splices() {
4122		let origin = Info::new(Origin::random())
4123			.with_linger(Duration::from_secs(5))
4124			.produce();
4125		let consumer = origin.consume();
4126		let mut announced = consumer.announced();
4127
4128		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4129		let source = origin
4130			.create_broadcast("test", announce().with_hops(hops.clone()))
4131			.unwrap();
4132		let mut dynamic = source.dynamic();
4133		settle().await;
4134		settle().await;
4135		let broadcast = consumer.request_broadcast("test").await.unwrap();
4136		announced.assert_next_some("test");
4137
4138		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4139		let mut producer = accept_track(&mut dynamic, "video").await;
4140		settle().await;
4141		let mut sub = subscribing.await.unwrap();
4142
4143		producer.append_group().unwrap();
4144		producer.append_group().unwrap();
4145		assert_eq!(sub.assert_group().sequence, 0);
4146		assert_eq!(sub.assert_group().sequence, 1);
4147
4148		// The session dies without unannouncing: the broadcast enters the linger
4149		// window instead of closing.
4150		drop(producer);
4151		source.abort(Error::Dropped).unwrap();
4152		drop(dynamic);
4153		settle().await;
4154
4155		// No unannounce, no track error: the outage is invisible so far.
4156		announced.assert_next_wait();
4157		sub.assert_no_group();
4158		sub.assert_not_closed();
4159
4160		// A consumer arriving mid-outage still resolves the lingering broadcast.
4161		let during = consumer.request_broadcast("test").await.unwrap();
4162		assert!(during.is_clone(&broadcast), "the lingering broadcast still resolves");
4163
4164		// The session reconnects within the window: the new source splices into
4165		// the same broadcast.
4166		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4167		let mut dynamic = source.dynamic();
4168		settle().await;
4169		settle().await;
4170		announced.assert_next_wait();
4171		let again = consumer.request_broadcast("test").await.unwrap();
4172		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
4173
4174		// The pending track re-splices from the new source and resumes one past
4175		// the groups the old source already delivered. Demand registers as the
4176		// subscriber polls.
4177		let mut producer = accept_track(&mut dynamic, "video").await;
4178		settle().await;
4179		sub.assert_no_group();
4180		assert_eq!(producer.subscription().unwrap().group_start, Some(2));
4181		producer.create_group(group::Info { sequence: 2 }).unwrap();
4182		assert_eq!(sub.assert_group().sequence, 2);
4183		sub.assert_not_closed();
4184	}
4185
4186	/// The linger window expiring without a replacement closes the broadcast: the
4187	/// path unannounces, tracks abort, and a later re-create is a fresh broadcast.
4188	#[tokio::test(start_paused = true)]
4189	async fn test_linger_expiry_closes() {
4190		let origin = Info::new(Origin::random())
4191			.with_linger(Duration::from_secs(5))
4192			.produce();
4193		let consumer = origin.consume();
4194		let mut announced = consumer.announced();
4195
4196		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4197		let source = origin
4198			.create_broadcast("test", announce().with_hops(hops.clone()))
4199			.unwrap();
4200		let mut dynamic = source.dynamic();
4201		settle().await;
4202		settle().await;
4203		let broadcast = consumer.request_broadcast("test").await.unwrap();
4204		announced.assert_next_some("test");
4205
4206		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4207		let producer = accept_track(&mut dynamic, "video").await;
4208		settle().await;
4209		let mut sub = subscribing.await.unwrap();
4210
4211		drop(producer);
4212		source.abort(Error::Dropped).unwrap();
4213		drop(dynamic);
4214		settle().await;
4215		announced.assert_next_wait();
4216
4217		// Nobody comes back: the window expires and the broadcast closes.
4218		tokio::time::sleep(std::time::Duration::from_secs(6)).await;
4219		settle().await;
4220		announced.assert_next_none("test");
4221		sub.assert_error();
4222
4223		// A session reconnecting after the window gets a brand-new broadcast.
4224		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4225		settle().await;
4226		settle().await;
4227		let fresh = consumer.request_broadcast("test").await.unwrap();
4228		announced.assert_next_some("test");
4229		assert!(
4230			!fresh.is_clone(&broadcast),
4231			"a late re-create must not splice the expired broadcast"
4232		);
4233	}
4234
4235	/// A linger too large to represent as a deadline never expires: the broadcast
4236	/// outlives an arbitrarily long outage (a reconnect loop with no give-up
4237	/// timeout promises to retry forever).
4238	#[tokio::test(start_paused = true)]
4239	async fn test_linger_forever() {
4240		let origin = Info::new(Origin::random()).with_linger(Duration::MAX).produce();
4241		let consumer = origin.consume();
4242		let mut announced = consumer.announced();
4243
4244		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4245		let source = origin
4246			.create_broadcast("test", announce().with_hops(hops.clone()))
4247			.unwrap();
4248		settle().await;
4249		let broadcast = consumer.request_broadcast("test").await.unwrap();
4250		announced.assert_next_some("test");
4251
4252		source.abort(Error::Dropped).unwrap();
4253		settle().await;
4254
4255		// Days later the broadcast is still announced and still splices a reconnect.
4256		tokio::time::sleep(std::time::Duration::from_secs(60 * 60 * 24 * 3)).await;
4257		announced.assert_next_wait();
4258		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4259		settle().await;
4260		settle().await;
4261		let again = consumer.request_broadcast("test").await.unwrap();
4262		assert!(again.is_clone(&broadcast), "the reconnect must splice, not replace");
4263		drop(source);
4264	}
4265
4266	/// A lingering broadcast with a live subscription parks, then resumes on the
4267	/// reconnect.
4268	///
4269	/// An ungraceful loss empties the route table while the front waits out its
4270	/// linger, so the route a track is spliced from is gone with no replacement to
4271	/// serve it. The task has to park for the whole window on the strength of
4272	/// dropping the departed route, because nothing else bounds it: a "route gone"
4273	/// edge that keeps firing yields a wait that is Ready on every poll, a full
4274	/// core per subscribed track, and the runtime that has to deliver the
4275	/// reconnect starved along with it.
4276	///
4277	/// Under a paused clock that spin never yields, so a regression hangs here
4278	/// rather than failing.
4279	#[tokio::test(start_paused = true)]
4280	async fn test_linger_parks_a_live_subscription() {
4281		let origin = Info::new(Origin::random())
4282			.with_linger(Duration::from_secs(5))
4283			.produce();
4284		let consumer = origin.consume();
4285
4286		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4287		let source = origin
4288			.create_broadcast("test", announce().with_hops(hops.clone()))
4289			.unwrap();
4290		let mut dynamic = source.dynamic();
4291		settle().await;
4292		settle().await;
4293		let broadcast = consumer.request_broadcast("test").await.unwrap();
4294
4295		// A viewer reading a track that is written once and then stays quiet, like a
4296		// catalog.
4297		let subscribing = broadcast.track("catalog.json").unwrap().subscribe(None);
4298		let mut producer = accept_track(&mut dynamic, "catalog.json").await;
4299		settle().await;
4300		let mut sub = subscribing.await.unwrap();
4301		producer.append_group().unwrap();
4302		assert_eq!(sub.assert_group().sequence, 0);
4303
4304		// The publisher's session dies abruptly: the table empties and the front
4305		// lingers, holding the subscription open for a reconnect. The track copy
4306		// outlives the broadcast (closes don't cascade), so nothing reports the
4307		// spliced segment as ended: the departed route is the only edge left.
4308		source.abort(Error::Dropped).unwrap();
4309		settle().await;
4310		settle().await;
4311		sub.assert_not_closed();
4312
4313		// Most of the window with no source at all: the subscription stays parked
4314		// rather than being cut, which is what the linger promises a reconnect.
4315		tokio::time::sleep(Duration::from_secs(4)).await;
4316		settle().await;
4317		sub.assert_not_closed();
4318
4319		// The reconnect inside the window resumes the same subscription.
4320		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4321		let mut dynamic = source.dynamic();
4322		settle().await;
4323		let mut producer = accept_track(&mut dynamic, "catalog.json").await;
4324		settle().await;
4325		producer.create_group(group::Info { sequence: 1 }).unwrap();
4326		assert_eq!(sub.assert_group().sequence, 1);
4327		sub.assert_not_closed();
4328	}
4329
4330	/// An idle segment is released even once the route that produced it is gone.
4331	///
4332	/// A departed route drops the loop's handle on it, so the idle countdown has to
4333	/// key off the segment itself. Otherwise the segment is stranded for the life of
4334	/// the front, pinning the dead source's cached groups.
4335	///
4336	/// Asserted through the boundary a stranded segment would leave behind, since
4337	/// `resume` is private and delivery is what a viewer actually feels: a released
4338	/// segment set splices the next source unbounded, so its first group arrives,
4339	/// while a retained one caps it below that edge.
4340	#[tokio::test(start_paused = true)]
4341	async fn test_idle_release_survives_the_route_leaving() {
4342		let origin = Info::new(Origin::random())
4343			.with_linger(Duration::from_secs(600))
4344			.produce();
4345		let consumer = origin.consume();
4346
4347		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4348		let source = origin
4349			.create_broadcast("test", announce().with_hops(hops.clone()))
4350			.unwrap();
4351		let mut dynamic = source.dynamic();
4352		settle().await;
4353		settle().await;
4354		let broadcast = consumer.request_broadcast("test").await.unwrap();
4355
4356		let subscribing = broadcast.track("catalog.json").unwrap().subscribe(None);
4357		let mut producer = accept_track(&mut dynamic, "catalog.json").await;
4358		settle().await;
4359		let mut sub = subscribing.await.unwrap();
4360		producer.append_group().unwrap();
4361		assert_eq!(sub.assert_group().sequence, 0);
4362
4363		// The publisher's session dies, then the viewer gives up: nothing holds the
4364		// segment, and nothing is left to serve the track.
4365		source.abort(Error::Dropped).unwrap();
4366		settle().await;
4367		settle().await;
4368		drop(sub);
4369		drop(producer);
4370		drop(dynamic);
4371		settle().await;
4372		tokio::time::sleep(TRACK_IDLE_LINGER + Duration::from_secs(1)).await;
4373		settle().await;
4374
4375		// The publisher reconnects under the same identity, so it joins the front as
4376		// a route rather than replacing it, and restarts its group numbering.
4377		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4378		let mut dynamic = source.dynamic();
4379		settle().await;
4380		settle().await;
4381		let broadcast = consumer.request_broadcast("test").await.unwrap();
4382		let subscribing = broadcast.track("catalog.json").unwrap().subscribe(None);
4383		let mut producer = accept_track(&mut dynamic, "catalog.json").await;
4384		settle().await;
4385		let mut sub = subscribing.await.unwrap();
4386		producer.append_group().unwrap();
4387		assert_eq!(
4388			sub.assert_group().sequence,
4389			0,
4390			"the reconnect's first group must not be filtered by a stale boundary"
4391		);
4392	}
4393
4394	/// A deliberate finish never lingers, even with a linger configured: a clean
4395	/// unannounce from a peer must propagate immediately.
4396	#[tokio::test(start_paused = true)]
4397	async fn test_linger_skipped_on_finish() {
4398		let origin = Info::new(Origin::random())
4399			.with_linger(Duration::from_secs(5))
4400			.produce();
4401		let consumer = origin.consume();
4402		let mut announced = consumer.announced();
4403
4404		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4405		let mut source = origin
4406			.create_broadcast("test", announce().with_hops(hops.clone()))
4407			.unwrap();
4408		settle().await;
4409		let broadcast = consumer.request_broadcast("test").await.unwrap();
4410		announced.assert_next_some("test");
4411
4412		// A clean unannounce: the broadcast is gone as soon as the teardown task
4413		// observes the close, with no reconnect window.
4414		source.finish();
4415		settle().await;
4416		announced.assert_next_none("test");
4417
4418		// A re-create at the same path is a brand-new broadcast.
4419		let _source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4420		settle().await;
4421		let fresh = consumer.request_broadcast("test").await.unwrap();
4422		announced.assert_next_some("test");
4423		assert!(
4424			!fresh.is_clone(&broadcast),
4425			"a finish must not leave a lingering broadcast to splice into"
4426		);
4427	}
4428
4429	/// A non-live broadcast is reachable by exact path but never announced;
4430	/// toggling `live` announces and unannounces without touching the broadcast.
4431	#[tokio::test]
4432	async fn test_announce_toggle() {
4433		tokio::time::pause();
4434
4435		let origin = Origin::random().produce();
4436		let consumer = origin.consume();
4437		let mut announced = consumer.announced();
4438
4439		let mut source = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
4440		settle().await;
4441
4442		// Routable but not announced.
4443		announced.assert_next_wait();
4444		let broadcast = consumer
4445			.get_broadcast("test")
4446			.expect("offline broadcast is still routable");
4447		assert!(!broadcast.route().announce);
4448
4449		// request_broadcast resolves the offline broadcast too.
4450		let requested = consumer.request_broadcast("test").await.unwrap();
4451		assert!(requested.is_clone(&broadcast));
4452
4453		// Going live announces.
4454		source.set_route(announce()).unwrap();
4455		settle().await;
4456		let face = announced.assert_next_some("test");
4457		assert!(face.is_clone(&broadcast));
4458
4459		// A fresh consumer replays only announced broadcasts.
4460		let mut fresh = origin.consume().announced();
4461		fresh.assert_next_some("test");
4462		fresh.assert_next_wait();
4463
4464		// Going offline unannounces but stays routable.
4465		source.set_route(broadcast::Route::new()).unwrap();
4466		settle().await;
4467		announced.assert_next_none("test");
4468		assert!(consumer.get_broadcast("test").is_some());
4469		let mut fresh = origin.consume().announced();
4470		fresh.assert_next_wait();
4471
4472		source.finish();
4473		settle().await;
4474		assert!(consumer.get_broadcast("test").is_none());
4475	}
4476
4477	/// An announced source outranks a cheaper offline one, so the broadcast
4478	/// stays announced and serves from it.
4479	#[tokio::test]
4480	async fn test_announce_beats_offline() {
4481		tokio::time::pause();
4482
4483		let origin = Origin::random().produce();
4484		let consumer = origin.consume();
4485		let mut announced = consumer.announced();
4486
4487		// An unannounced source with the best cost.
4488		let _offline = origin.create_broadcast("test", broadcast::Route::new()).unwrap();
4489		settle().await;
4490		announced.assert_next_wait();
4491
4492		// An announced source with a worse cost still wins: the path announces
4493		// and advertises its route.
4494		let mut announced_source = origin.create_broadcast("test", announce().with_cost(10)).unwrap();
4495		settle().await;
4496		announced.assert_next_some("test");
4497		let face = consumer.get_broadcast("test").unwrap();
4498		assert!(face.route().announce);
4499		assert_eq!(face.route().cost, 10);
4500
4501		// The announced source leaving falls back to the offline one: the path
4502		// unannounces but stays routable.
4503		announced_source.finish();
4504		settle().await;
4505		announced.assert_next_none("test");
4506		assert!(consumer.get_broadcast("test").is_some());
4507	}
4508
4509	/// A better source attaching does not churn announces: the broadcast identity
4510	/// is origin-owned, so the swap is invisible to consumers.
4511	#[tokio::test]
4512	async fn test_better_source_no_churn() {
4513		tokio::time::pause();
4514
4515		let origin = Origin::random().produce();
4516		let mut announced = origin.consume().announced();
4517
4518		// `a` carries two hops; `b` reaches the same publisher in one, so `b`
4519		// wins dispatch when it joins.
4520		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap(), Origin::new(3).unwrap()]).unwrap();
4521		let hops_b = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4522		let _a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
4523		settle().await;
4524		let face = announced.assert_next_some("test");
4525
4526		let _b = origin
4527			.create_broadcast("test", announce().with_hops(hops_b.clone()))
4528			.unwrap();
4529		settle().await;
4530		announced.assert_next_wait();
4531		let current = origin.consume().get_broadcast("test").unwrap();
4532		assert!(current.is_clone(&face), "the broadcast identity must not change");
4533		// The face now advertises the winning (shorter) route.
4534		assert_eq!(current.route().hops, hops_b);
4535	}
4536
4537	/// A second source with a different original publisher (first hop) is new
4538	/// content, not a standby: it must not splice into the incumbent's
4539	/// subscribers. It takes the path over immediately, as a real unannounce +
4540	/// announce, rather than waiting out an incumbent whose session may only be
4541	/// alive because the transport has not timed it out yet.
4542	#[tokio::test]
4543	async fn test_publisher_mismatch_replaces() {
4544		tokio::time::pause();
4545
4546		let origin = Origin::random().produce();
4547		let consumer = origin.consume();
4548		let mut announced = consumer.announced();
4549
4550		let hops_a = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4551		let hops_b = OriginList::try_from(vec![Origin::new(2).unwrap()]).unwrap();
4552
4553		let mut source_a = origin
4554			.create_broadcast("test", announce().with_hops(hops_a.clone()))
4555			.unwrap();
4556		settle().await;
4557		let face_a = announced.assert_next_some("test");
4558
4559		// A different publisher at the same path: the newcomer wins it now, while
4560		// the incumbent is still attached and still believes it is publishing.
4561		let _source_b = origin
4562			.create_broadcast("test", announce().with_hops(hops_b.clone()))
4563			.unwrap();
4564		settle().await;
4565		settle().await;
4566		announced.assert_next_none("test");
4567		let face_b = announced.assert_next_some("test");
4568		assert!(!face_b.is_clone(&face_a), "a replacement, never a splice");
4569		assert_eq!(consumer.get_broadcast("test").unwrap().route().hops, hops_b);
4570		// The displaced front is torn down, not merely unpublished: leaving it
4571		// running would strand its subscribers and its source watchers on a face
4572		// nothing can reach.
4573		assert!(face_a.is_closed(), "the displaced front must close");
4574
4575		// The displaced incumbent ending is invisible: it no longer owns the path.
4576		source_a.finish();
4577		settle().await;
4578		settle().await;
4579		announced.assert_next_wait();
4580		assert_eq!(consumer.get_broadcast("test").unwrap().route().hops, hops_b);
4581	}
4582
4583	/// A publisher reconnecting under the same identity attaches as a second
4584	/// route with an identical hop chain and cost. The new session is the one
4585	/// actually carrying frames, so it must win selection immediately rather than
4586	/// waiting for the transport to retire the old one.
4587	#[tokio::test]
4588	async fn test_reconnect_wins_over_stale_route() {
4589		tokio::time::pause();
4590
4591		let origin = Origin::random().produce();
4592		let consumer = origin.consume();
4593
4594		let publisher = Origin::new(1).unwrap();
4595		let hops = OriginList::try_from(vec![publisher]).unwrap();
4596
4597		// The original session, still attached: its QUIC connection has not been
4598		// declared dead yet.
4599		let stale = origin
4600			.create_broadcast("test", announce().with_hops(hops.clone()))
4601			.unwrap();
4602		let mut stale_dynamic = stale.dynamic();
4603		settle().await;
4604
4605		// The same publisher reconnecting over a fresh session.
4606		let fresh = origin
4607			.create_broadcast("test", announce().with_hops(hops.clone()))
4608			.unwrap();
4609		let mut fresh_dynamic = fresh.dynamic();
4610		settle().await;
4611		settle().await;
4612
4613		// Track requests dispatch to the reconnect, not the corpse.
4614		let broadcast = consumer.request_broadcast("test").await.unwrap();
4615		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4616		settle().await;
4617		let _producer = accept_track(&mut fresh_dynamic, "video").await;
4618		settle().await;
4619		subscribing.await.unwrap();
4620		stale_dynamic.assert_no_request();
4621	}
4622
4623	/// The same reconnect, but arriving while a subscription is already spliced
4624	/// onto the stale route: the live track must re-splice onto the new session
4625	/// rather than ride the dead one until the transport gives up. The gate
4626	/// [`FrontState::reselect`] applies while carrying is pinned separately, in
4627	/// [`test_carrying_switches_to_benign_routes`].
4628	#[tokio::test]
4629	async fn test_carrying_reconnect_switches_immediately() {
4630		tokio::time::pause();
4631
4632		let origin = Origin::random().produce();
4633		let consumer = origin.consume();
4634
4635		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4636
4637		let stale = origin
4638			.create_broadcast("test", announce().with_hops(hops.clone()))
4639			.unwrap();
4640		let mut stale_dynamic = stale.dynamic();
4641		settle().await;
4642
4643		// A live subscription riding the original session.
4644		let broadcast = consumer.request_broadcast("test").await.unwrap();
4645		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4646		settle().await;
4647		let _stale_producer = accept_track(&mut stale_dynamic, "video").await;
4648		settle().await;
4649		// Held: the splice only follows the active route while the track is read.
4650		let _subscription = subscribing.await.unwrap();
4651
4652		// The reconnect arrives with the front already carrying.
4653		let fresh = origin
4654			.create_broadcast("test", announce().with_hops(hops.clone()))
4655			.unwrap();
4656		let mut fresh_dynamic = fresh.dynamic();
4657		settle().await;
4658		settle().await;
4659
4660		// The carrying front re-splices onto the reconnect rather than waiting for
4661		// the stale session to die.
4662		let _fresh_producer = accept_track(&mut fresh_dynamic, "video").await;
4663	}
4664
4665	/// Taking the path over is scoped to a newcomer that would actually outrank
4666	/// the incumbent. An offline source (a cache, or an on-demand handler) is
4667	/// ranked below every announced route, so arriving under a different
4668	/// publisher must not unannounce a live broadcast and cut its subscribers.
4669	///
4670	/// The tail of this test covers the park's *exit*: the parked source has to
4671	/// wake and attach once the incumbent ends. Nothing else drives that wait, so
4672	/// without this a lost wakeup would strand the source invisibly forever
4673	/// rather than failing anything.
4674	#[tokio::test]
4675	async fn test_offline_mismatch_never_evicts_a_live_front() {
4676		tokio::time::pause();
4677
4678		let origin = Origin::random().produce();
4679		let consumer = origin.consume();
4680		let mut announced = consumer.announced();
4681
4682		let hops_live = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4683		let hops_cache = OriginList::try_from(vec![Origin::new(2).unwrap()]).unwrap();
4684
4685		let mut live = origin
4686			.create_broadcast("test", announce().with_hops(hops_live.clone()))
4687			.unwrap();
4688		let mut live_dynamic = live.dynamic();
4689		settle().await;
4690		let face = announced.assert_next_some("test");
4691
4692		// A subscriber riding the live broadcast, which the eviction would cut.
4693		let broadcast = consumer.request_broadcast("test").await.unwrap();
4694		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4695		settle().await;
4696		let _producer = accept_track(&mut live_dynamic, "video").await;
4697		settle().await;
4698		subscribing.await.unwrap();
4699
4700		// An offline source for different content at the same path: it stays
4701		// invisible rather than displacing the live one.
4702		let cache = origin
4703			.create_broadcast("test", broadcast::Route::new().with_hops(hops_cache.clone()))
4704			.unwrap();
4705		settle().await;
4706		settle().await;
4707		announced.assert_next_wait();
4708		assert!(!face.is_closed(), "the live front must survive");
4709		assert_eq!(consumer.get_broadcast("test").unwrap().route().hops, hops_live);
4710
4711		// The incumbent ending hands the path to the parked source, which is the
4712		// only thing that ever wakes that wait.
4713		live.finish();
4714		settle().await;
4715		settle().await;
4716		announced.assert_next_none("test");
4717		let taken = consumer
4718			.get_broadcast("test")
4719			.expect("the parked source must take over");
4720		assert_eq!(taken.route().hops, hops_cache);
4721		// Offline, so it holds the path without being advertised.
4722		announced.assert_next_wait();
4723		drop(cache);
4724	}
4725
4726	/// A subscription from a peer the active chain flows through is served from
4727	/// the best clean source directly (data-plane split horizon), while every
4728	/// other consumer keeps the shared spliced broadcast fed by the active
4729	/// source. The two selections match what the announce loop advertises, so
4730	/// the data plane keeps the control plane's promise.
4731	#[tokio::test]
4732	async fn test_dispatch_excludes_requester() {
4733		tokio::time::pause();
4734
4735		let origin = Origin::random().produce();
4736		let consumer = origin.consume();
4737
4738		let peer = Origin::new(5).unwrap();
4739		let publisher = Origin::new(1).unwrap();
4740		// The route through the peer is cheaper, so it is the active source.
4741		let tainted = OriginList::try_from(vec![publisher, peer]).unwrap();
4742		let clean = OriginList::try_from(vec![publisher]).unwrap();
4743
4744		let source_a = origin.create_broadcast("test", announce().with_hops(tainted)).unwrap();
4745		let mut dynamic_a = source_a.dynamic();
4746		settle().await;
4747		let source_b = origin
4748			.create_broadcast("test", announce().with_hops(clean).with_cost(5))
4749			.unwrap();
4750		let mut dynamic_b = source_b.dynamic();
4751		settle().await;
4752		settle().await;
4753
4754		// An ordinary consumer rides the shared front, dispatched to the active
4755		// (peer-tainted) source.
4756		let shared = consumer.request_broadcast("test").await.unwrap();
4757		let subscribing = shared.track("video").unwrap().subscribe(None);
4758		let _producer_a = accept_track(&mut dynamic_a, "video").await;
4759		settle().await;
4760		subscribing.await.unwrap();
4761
4762		// The peer is pinned to the clean source. Crucially, nothing reaches the
4763		// via-peer source: a track request on it is what a session would forward
4764		// upstream as a SUBSCRIBE, and forwarding this one would send the peer's
4765		// own subscription back to them.
4766		let scoped = consumer.clone().excluding(peer);
4767		let pinned = scoped.request_broadcast("test").await.unwrap();
4768		let subscribing = pinned.track("video").unwrap().subscribe(None);
4769		let _producer_b = accept_track(&mut dynamic_b, "video").await;
4770		settle().await;
4771		subscribing.await.unwrap();
4772		dynamic_a.assert_no_request();
4773	}
4774
4775	/// Two publishers that never declared an identity both arrive with a first
4776	/// hop of UNKNOWN. They are unrelated content, so the second MUST replace the
4777	/// first rather than joining it as an interchangeable standby: splicing them
4778	/// would cut one publisher's subscribers over to the other's stream.
4779	#[tokio::test]
4780	async fn test_unknown_publishers_do_not_splice() {
4781		tokio::time::pause();
4782
4783		let origin = Origin::random().produce();
4784		let consumer = origin.consume();
4785		let mut announced = consumer.announced();
4786
4787		let unknown_a = OriginList::try_from(vec![Origin::UNKNOWN]).unwrap();
4788		let unknown_b = OriginList::try_from(vec![Origin::UNKNOWN]).unwrap();
4789
4790		let source_a = origin
4791			.create_broadcast("test", announce().with_hops(unknown_a))
4792			.unwrap();
4793		settle().await;
4794		settle().await;
4795		announced.assert_next_some("test");
4796
4797		// Same first hop by value, but it identifies nothing, so this is a
4798		// replacement: the path is unannounced and re-announced rather than
4799		// silently gaining a standby.
4800		let source_b = origin
4801			.create_broadcast("test", announce().with_hops(unknown_b))
4802			.unwrap();
4803		settle().await;
4804		settle().await;
4805		announced.assert_next_none("test");
4806		announced.assert_next_some("test");
4807
4808		drop(source_a);
4809		drop(source_b);
4810	}
4811
4812	/// The same two sources under a real shared publisher id do splice, which is
4813	/// what keeps the rule above about UNKNOWN rather than about hop chains.
4814	#[tokio::test]
4815	async fn test_known_publishers_still_splice() {
4816		tokio::time::pause();
4817
4818		let origin = Origin::random().produce();
4819		let consumer = origin.consume();
4820		let mut announced = consumer.announced();
4821
4822		let publisher = Origin::new(1).unwrap();
4823		let hops_a = OriginList::try_from(vec![publisher]).unwrap();
4824		let hops_b = OriginList::try_from(vec![publisher, Origin::new(3).unwrap()]).unwrap();
4825
4826		let source_a = origin.create_broadcast("test", announce().with_hops(hops_a)).unwrap();
4827		settle().await;
4828		settle().await;
4829		announced.assert_next_some("test");
4830
4831		// Shares the publisher, so it joins silently as a standby: no churn.
4832		let source_b = origin.create_broadcast("test", announce().with_hops(hops_b)).unwrap();
4833		settle().await;
4834		settle().await;
4835		announced.assert_next_wait();
4836
4837		drop(source_a);
4838		drop(source_b);
4839	}
4840
4841	/// A local standby with the same original publisher joining a front that is
4842	/// carrying the broadcast from a peer must splice the live subscription onto
4843	/// the new source, never tear it down (#2473, e2e finding 2). Redundant
4844	/// publishers sharing an origin id MUST produce the same tracks, so the
4845	/// splice resumes seamlessly at the group boundary.
4846	#[tokio::test]
4847	async fn test_standby_join_splices_live_subscriber() {
4848		tokio::time::pause();
4849
4850		let origin = Origin::random().produce();
4851		let consumer = origin.consume();
4852
4853		let publisher = Origin::new(1).unwrap();
4854		let peer = Origin::new(5).unwrap();
4855		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
4856		let local = OriginList::try_from(vec![publisher]).unwrap();
4857
4858		// Carrying via the peer, with a live subscriber mid-stream.
4859		let source_remote = origin
4860			.create_broadcast("test", announce().with_hops(via_peer).with_cost(2))
4861			.unwrap();
4862		let mut dynamic_remote = source_remote.dynamic();
4863		settle().await;
4864		settle().await;
4865		let broadcast = consumer.request_broadcast("test").await.unwrap();
4866		let subscribing = broadcast.track("video").unwrap().subscribe(None);
4867		let mut producer_remote = accept_track(&mut dynamic_remote, "video").await;
4868		settle().await;
4869		let mut sub = subscribing.await.unwrap();
4870		producer_remote.append_group().unwrap();
4871		assert_eq!(sub.assert_group().sequence, 0);
4872
4873		// The local standby joins with the same first hop and a cheaper route:
4874		// it wins dispatch and the live track re-splices at the boundary.
4875		let source_local = origin.create_broadcast("test", announce().with_hops(local)).unwrap();
4876		let mut dynamic_local = source_local.dynamic();
4877		settle().await;
4878		let mut producer_local = accept_track(&mut dynamic_local, "video").await;
4879		settle().await;
4880		sub.assert_no_group();
4881		assert_eq!(producer_local.subscription().unwrap().group_start, Some(1));
4882		producer_local.create_group(group::Info { sequence: 1 }).unwrap();
4883		assert_eq!(sub.assert_group().sequence, 1);
4884		sub.assert_not_closed();
4885	}
4886
4887	/// A standby that wins dispatch before creating a track must not kill a
4888	/// subscription the incumbent is serving: its refusal only rules it out of
4889	/// this track, and the incumbent keeps delivering. The refusal is still
4890	/// never retried: once the incumbent goes away every remaining source has
4891	/// refused, so the subscription aborts and the consumer's next request asks
4892	/// afresh.
4893	#[tokio::test]
4894	async fn test_standby_missing_track_keeps_incumbent() {
4895		tokio::time::pause();
4896
4897		let origin = Origin::random().produce();
4898		let consumer = origin.consume();
4899
4900		let publisher = Origin::new(1).unwrap();
4901		let peer = Origin::new(5).unwrap();
4902		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
4903		let local = OriginList::try_from(vec![publisher]).unwrap();
4904
4905		// Carrying via the peer, with a live subscriber mid-stream.
4906		let source_remote = origin
4907			.create_broadcast("test", announce().with_hops(via_peer).with_cost(2))
4908			.unwrap();
4909		let mut dynamic_remote = source_remote.dynamic();
4910		settle().await;
4911		settle().await;
4912		let broadcast = consumer.request_broadcast("test").await.unwrap();
4913		let subscribing = broadcast.track("audio").unwrap().subscribe(None);
4914		let mut producer_remote = accept_track(&mut dynamic_remote, "audio").await;
4915		settle().await;
4916		let mut sub = subscribing.await.unwrap();
4917		producer_remote.append_group().unwrap();
4918		assert_eq!(sub.assert_group().sequence, 0);
4919
4920		// The standby joins and wins dispatch, but has not created "audio" yet.
4921		// Its refusal must cost the incumbent nothing.
4922		let source_local = origin.create_broadcast("test", announce().with_hops(local)).unwrap();
4923		let mut dynamic_local = source_local.dynamic();
4924		settle().await;
4925		let request = dynamic_local.requested_track().await.unwrap();
4926		assert_eq!(request.name(), "audio");
4927		request.reject(Error::NotFound);
4928		settle().await;
4929
4930		// Still spliced to the incumbent, still delivering.
4931		producer_remote.append_group().unwrap();
4932		assert_eq!(sub.assert_group().sequence, 1);
4933		sub.assert_not_closed();
4934
4935		// The incumbent leaving exhausts the table (the standby's refusal is
4936		// never retried): the subscription aborts.
4937		source_remote.abort(Error::Dropped).unwrap();
4938		settle().await;
4939		settle().await;
4940		sub.assert_closed();
4941		dynamic_local.assert_no_request();
4942
4943		// A fresh consumer request asks the standby anew, which has the track now.
4944		let retry = broadcast.track("audio").unwrap().subscribe(None);
4945		let mut producer_local = accept_track(&mut dynamic_local, "audio").await;
4946		settle().await;
4947		let mut sub = retry.await.expect("a fresh request must reach the standby");
4948		producer_local.create_group(group::Info { sequence: 2 }).unwrap();
4949		assert_eq!(sub.assert_group().sequence, 2);
4950	}
4951
4952	/// A refused track aborts, but the verdict is not cached: it belongs to the
4953	/// request that received it, so a later request re-asks the source. Otherwise
4954	/// one early request for a track the publisher had not created yet leaves the
4955	/// name dead for the life of the front.
4956	#[tokio::test]
4957	async fn test_unservable_track_retried_by_a_later_request() {
4958		tokio::time::pause();
4959
4960		let origin = Origin::random().produce();
4961		let consumer = origin.consume();
4962
4963		let source = origin.create_broadcast("test", announce()).unwrap();
4964		let mut dynamic = source.dynamic();
4965		settle().await;
4966		settle().await;
4967		let broadcast = consumer.request_broadcast("test").await.unwrap();
4968
4969		// Nothing serves it: the refusal aborts the track with the source's error.
4970		let subscribing = broadcast.track("audio").unwrap().subscribe(None);
4971		let request = dynamic.requested_track().await.unwrap();
4972		request.reject(Error::NotFound);
4973		settle().await;
4974		assert!(matches!(subscribing.await, Err(Error::NotFound)));
4975
4976		// The publisher has the track now; a fresh request must reach it.
4977		let retry = broadcast.track("audio").unwrap().subscribe(None);
4978		let mut producer = accept_track(&mut dynamic, "audio").await;
4979		settle().await;
4980		let mut sub = retry.await.expect("a fresh request must reach the source");
4981		producer.append_group().unwrap();
4982		assert_eq!(sub.assert_group().sequence, 0);
4983	}
4984
4985	/// A copy that dies before delivering anything is a refusal, not a failover:
4986	/// a source whose track keeps dying right after acceptance must not
4987	/// re-splice forever. With every attached source refused, the track aborts
4988	/// with the copy's error, and the verdict belongs to that request: a fresh
4989	/// consumer request asks again.
4990	#[tokio::test]
4991	async fn test_track_dying_without_progress_aborts() {
4992		tokio::time::pause();
4993
4994		let origin = Origin::random().produce();
4995		let consumer = origin.consume();
4996
4997		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
4998		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
4999		let mut dynamic = source.dynamic();
5000		settle().await;
5001		settle().await;
5002		let broadcast = consumer.request_broadcast("test").await.unwrap();
5003
5004		let subscribing = broadcast.track("video").unwrap().subscribe(None);
5005		let producer = accept_track(&mut dynamic, "video").await;
5006		settle().await;
5007		let mut sub = subscribing.await.unwrap();
5008
5009		// The copy dies without delivering a single group, from a source that is
5010		// alive and well: that's its answer for the track, not a failover.
5011		drop(producer);
5012		settle().await;
5013		sub.assert_closed();
5014		dynamic.assert_no_request();
5015
5016		// A fresh request asks the (still attached) source anew.
5017		let retry = broadcast.track("video").unwrap().subscribe(None);
5018		let mut producer = accept_track(&mut dynamic, "video").await;
5019		settle().await;
5020		let mut sub = retry.await.expect("a fresh request must reach the source");
5021		producer.append_group().unwrap();
5022		assert_eq!(sub.assert_group().sequence, 0);
5023	}
5024
5025	/// A copy that delivered and later died is a failover even when unrelated
5026	/// wakes happened between its last group and its death: progress is tracked
5027	/// per splice, so a demand edge must not launder it into a zero-progress
5028	/// refusal that aborts the only route.
5029	#[tokio::test]
5030	async fn test_delivered_copy_death_survives_unrelated_wakes() {
5031		tokio::time::pause();
5032
5033		let origin = Origin::random().produce();
5034		let consumer = origin.consume();
5035
5036		let hops = OriginList::try_from(vec![Origin::new(1).unwrap()]).unwrap();
5037		let source = origin.create_broadcast("test", announce().with_hops(hops)).unwrap();
5038		let mut dynamic = source.dynamic();
5039		settle().await;
5040		settle().await;
5041		let broadcast = consumer.request_broadcast("test").await.unwrap();
5042
5043		let subscribing = broadcast.track("video").unwrap().subscribe(None);
5044		let mut producer = accept_track(&mut dynamic, "video").await;
5045		settle().await;
5046		let mut sub = subscribing.await.unwrap();
5047		producer.append_group().unwrap();
5048		assert_eq!(sub.assert_group().sequence, 0);
5049
5050		// Unrelated demand-edge wakes after the copy's last group: the reader
5051		// leaves and a new one arrives.
5052		drop(sub);
5053		settle().await;
5054		let resubscribing = broadcast.track("video").unwrap().subscribe(None);
5055		settle().await;
5056		let mut sub = resubscribing.await.unwrap();
5057		assert_eq!(sub.assert_group().sequence, 0, "cached group re-served");
5058
5059		// The copy then dies having delivered: failover, so the source is
5060		// re-asked and the subscription resumes.
5061		drop(producer);
5062		let mut producer = accept_track(&mut dynamic, "video").await;
5063		settle().await;
5064		producer.create_group(group::Info { sequence: 1 }).unwrap();
5065		assert_eq!(sub.assert_group().sequence, 1);
5066		sub.assert_not_closed();
5067	}
5068
5069	/// The front picks a source per track and re-picks on failover, so a route
5070	/// tainted for a peer is one the front may serve them from even when the
5071	/// active route is clean. Sharing the front therefore has to check the whole
5072	/// table, not just the active route: here the clean route is active but cannot
5073	/// carry the track, and the fallback is the peer's own route.
5074	#[tokio::test]
5075	async fn test_per_track_fallback_respects_exclusion() {
5076		tokio::time::pause();
5077
5078		let origin = Origin::random().produce();
5079		let consumer = origin.consume();
5080
5081		let publisher = Origin::new(1).unwrap();
5082		let peer = Origin::new(5).unwrap();
5083		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
5084		let local = OriginList::try_from(vec![publisher]).unwrap();
5085
5086		// The route through the peer has the track.
5087		let source_tainted = origin
5088			.create_broadcast("test", announce().with_hops(via_peer).with_cost(2))
5089			.unwrap();
5090		let mut dynamic_tainted = source_tainted.dynamic();
5091		settle().await;
5092		settle().await;
5093
5094		// The clean route is cheaper, so it is active, but its publisher has not
5095		// created the track yet.
5096		let source_clean = origin.create_broadcast("test", announce().with_hops(local)).unwrap();
5097		let mut dynamic_clean = source_clean.dynamic();
5098		settle().await;
5099
5100		let scoped = consumer.clone().excluding(peer);
5101		let broadcast = scoped.request_broadcast("test").await.unwrap();
5102		let _subscribing = broadcast.track("video").unwrap().subscribe(None);
5103		settle().await;
5104
5105		// The peer is pinned to the clean source, so the fallback the front would
5106		// take for everyone else is not reachable from their subscription: the
5107		// refusal aborts the track rather than asking the tainted route.
5108		let request = dynamic_clean.requested_track().await.unwrap();
5109		request.reject(Error::NotFound);
5110		settle().await;
5111		dynamic_tainted.assert_no_request();
5112	}
5113
5114	/// The same guarantee under failover: the peer resolves while a clean route is
5115	/// active, then that route dies. Its subscription must not migrate onto the
5116	/// route that flows back through it.
5117	#[tokio::test]
5118	async fn test_exclusion_survives_failover_onto_a_tainted_route() {
5119		tokio::time::pause();
5120
5121		let origin = Origin::random().produce();
5122		let consumer = origin.consume();
5123
5124		let publisher = Origin::new(1).unwrap();
5125		let peer = Origin::new(5).unwrap();
5126		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
5127		let local = OriginList::try_from(vec![publisher]).unwrap();
5128
5129		let source_tainted = origin
5130			.create_broadcast("test", announce().with_hops(via_peer).with_cost(2))
5131			.unwrap();
5132		let mut dynamic_tainted = source_tainted.dynamic();
5133		settle().await;
5134		settle().await;
5135		let source_clean = origin.create_broadcast("test", announce().with_hops(local)).unwrap();
5136		let mut dynamic_clean = source_clean.dynamic();
5137		settle().await;
5138
5139		let scoped = consumer.clone().excluding(peer);
5140		let broadcast = scoped.request_broadcast("test").await.unwrap();
5141		let _subscribing = broadcast.track("video").unwrap().subscribe(None);
5142		let _clean = accept_track(&mut dynamic_clean, "video").await;
5143		settle().await;
5144
5145		// The clean route dies, so the front's only remaining route is the peer's.
5146		source_clean.abort(Error::Dropped).unwrap();
5147		settle().await;
5148		settle().await;
5149		dynamic_tainted.assert_no_request();
5150
5151		// A fresh request now has nowhere clean to go, and says so rather than
5152		// silently serving the peer their own route.
5153		assert!(matches!(scoped.request_broadcast("test").await, Err(Error::Unroutable)));
5154	}
5155
5156	/// The resolve-time check only proves the table is clean for a peer at that
5157	/// instant. A route through them attaching *afterwards* must not be adopted
5158	/// underneath their live subscription: they hold the shared front, so the front
5159	/// stays off that route while a clean one remains.
5160	#[tokio::test]
5161	async fn test_exclusion_holds_when_a_tainted_route_attaches_later() {
5162		tokio::time::pause();
5163
5164		let origin = Origin::random().produce();
5165		let consumer = origin.consume();
5166
5167		let publisher = Origin::new(1).unwrap();
5168		let peer = Origin::new(5).unwrap();
5169		let local = OriginList::try_from(vec![publisher]).unwrap();
5170		let via_peer = OriginList::try_from(vec![publisher, peer]).unwrap();
5171
5172		// Only a clean route exists, so the peer legitimately gets the shared front.
5173		// Priced above the route that arrives later, so the front genuinely prefers
5174		// that one and staying put is the guard's doing, not the tie-break's.
5175		let source_clean = origin
5176			.create_broadcast("test", announce().with_hops(local).with_cost(5))
5177			.unwrap();
5178		let mut dynamic_clean = source_clean.dynamic();
5179		settle().await;
5180		settle().await;
5181		let scoped = consumer.clone().excluding(peer);
5182		let broadcast = scoped.request_broadcast("test").await.unwrap();
5183		let subscribing = broadcast.track("video").unwrap().subscribe(None);
5184		let mut producer_clean = accept_track(&mut dynamic_clean, "video").await;
5185		settle().await;
5186		let mut sub = subscribing.await.unwrap();
5187		producer_clean.append_group().unwrap();
5188		assert_eq!(sub.assert_group().sequence, 0);
5189
5190		// A cheaper route back through the peer attaches. Without the registration
5191		// the front would re-splice onto it and hand the peer its own bytes.
5192		// `advertised` non-zero says the announcing relay is not itself carrying, which
5193		// keeps the simultaneous-activation handover gate (whose key comparison is
5194		// hash-random) out of this test: the front takes the cheaper route outright
5195		// unless the exclusion stops it.
5196		let mut tainted = announce().with_hops(via_peer.clone()).with_cost(0);
5197		tainted.advertised = 1;
5198		let mut source_tainted = origin.create_broadcast("test", tainted).unwrap();
5199		let mut dynamic_tainted = source_tainted.dynamic();
5200		settle().await;
5201		settle().await;
5202		dynamic_tainted.assert_no_request();
5203		producer_clean.append_group().unwrap();
5204		assert_eq!(sub.assert_group().sequence, 1);
5205		sub.assert_not_closed();
5206
5207		// The registration ends with the last handle. The next table change is free
5208		// to take the cheaper route, which is what proves it was released.
5209		drop(sub);
5210		drop(broadcast);
5211		drop(scoped);
5212		settle().await;
5213		let mut bumped = announce().with_hops(via_peer).with_cost(1);
5214		bumped.advertised = 1;
5215		source_tainted.set_route(bumped).unwrap();
5216		settle().await;
5217		let plain = consumer.request_broadcast("test").await.unwrap();
5218		let _plain_track = plain.track("video").unwrap().subscribe(None);
5219		settle().await;
5220		settle().await;
5221		assert!(
5222			dynamic_tainted.requested_track().now_or_never().is_some(),
5223			"the front must be free to use the route again once the peer is gone"
5224		);
5225	}
5226
5227	/// An announced path every route of which loops through the requester is
5228	/// unroutable, not missing: the dynamic handler resolves paths with no route
5229	/// chain to check, so consulting it would route around the split horizon.
5230	#[tokio::test]
5231	async fn test_excluded_path_never_reaches_the_dynamic_handler() {
5232		tokio::time::pause();
5233
5234		let origin = Origin::random().produce();
5235		let consumer = origin.consume();
5236		let mut dynamic = origin.dynamic();
5237
5238		let peer = Origin::new(5).unwrap();
5239		let tainted = OriginList::try_from(vec![Origin::new(1).unwrap(), peer]).unwrap();
5240		let _source = origin.create_broadcast("test", announce().with_hops(tainted)).unwrap();
5241		settle().await;
5242		settle().await;
5243
5244		let scoped = consumer.clone().excluding(peer);
5245		assert!(matches!(scoped.request_broadcast("test").await, Err(Error::Unroutable)));
5246		assert!(
5247			dynamic.requested_broadcast().now_or_never().is_none(),
5248			"the dynamic handler was asked to route around the exclusion"
5249		);
5250
5251		// An unannounced path still reaches the handler, exclusion or not.
5252		let _pending = scoped.request_broadcast("other");
5253		settle().await;
5254		assert!(
5255			dynamic.requested_broadcast().now_or_never().is_some(),
5256			"a genuinely missing path must still fall back"
5257		);
5258	}
5259
5260	/// When every route flows through the requester, the path is unroutable for
5261	/// them: serving it would hand them their own bytes back.
5262	#[tokio::test]
5263	async fn test_dispatch_all_tainted_unroutable() {
5264		tokio::time::pause();
5265
5266		let origin = Origin::random().produce();
5267		let consumer = origin.consume();
5268
5269		let peer = Origin::new(5).unwrap();
5270		let tainted = OriginList::try_from(vec![Origin::new(1).unwrap(), peer]).unwrap();
5271		let _source = origin.create_broadcast("test", announce().with_hops(tainted)).unwrap();
5272		settle().await;
5273		settle().await;
5274
5275		let scoped = consumer.clone().excluding(peer);
5276		match scoped.request_broadcast("test").await {
5277			Err(Error::Unroutable) => {}
5278			Err(err) => panic!("expected Unroutable, got {err:?}"),
5279			Ok(_) => panic!("expected Unroutable, got a broadcast"),
5280		}
5281
5282		// Everyone else still resolves the shared front.
5283		consumer.request_broadcast("test").await.unwrap();
5284	}
5285
5286	#[tokio::test]
5287	async fn test_duplicate_reverse() {
5288		tokio::time::pause();
5289
5290		let origin = Origin::random().produce();
5291
5292		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
5293		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
5294		settle().await;
5295		assert!(origin.consume().get_broadcast("test").is_some());
5296
5297		// This is harder, finishing the newer source first.
5298		broadcast2.finish();
5299		settle().await;
5300		assert!(origin.consume().get_broadcast("test").is_some());
5301
5302		broadcast1.finish();
5303		settle().await;
5304		assert!(origin.consume().get_broadcast("test").is_none());
5305	}
5306
5307	#[tokio::test]
5308	async fn test_deterministic_tiebreak() {
5309		tokio::time::pause();
5310
5311		fn hops(ids: &[u64]) -> OriginList {
5312			OriginList::try_from(
5313				ids.iter()
5314					.copied()
5315					.map(|id| Origin::new(id).unwrap())
5316					.collect::<Vec<_>>(),
5317			)
5318			.unwrap()
5319		}
5320
5321		// Resolve the advertised route for "test" after creating both sources in
5322		// the given order.
5323		async fn winner(first: &[u64], second: &[u64]) -> OriginList {
5324			let origin = Origin::random().produce();
5325			let _a = origin
5326				.create_broadcast("test", announce().with_hops(hops(first)))
5327				.unwrap();
5328			let _b = origin
5329				.create_broadcast("test", announce().with_hops(hops(second)))
5330				.unwrap();
5331			settle().await;
5332			origin.consume().get_broadcast("test").unwrap().route().hops
5333		}
5334
5335		// Two routes with equal hop counts but distinct chains (sharing the first
5336		// hop, so both may attach). The winner is decided by the deterministic
5337		// key, not arrival order, so both publish orders converge.
5338		let forward = winner(&[5, 20], &[5, 40]).await;
5339		let reverse = winner(&[5, 40], &[5, 20]).await;
5340		assert_eq!(forward, reverse, "tie-break must not depend on publish order");
5341
5342		// A strictly shorter chain always wins regardless of the hash.
5343		assert_eq!(winner(&[5, 20], &[5]).await.len(), 1);
5344		assert_eq!(winner(&[5], &[5, 20]).await.len(), 1);
5345	}
5346
5347	// A previous mpsc-based implementation could only deliver the first 127 broadcasts
5348	// instantly via `assert_next` (which uses `now_or_never`). The kio-backed
5349	// implementation polls synchronously and can deliver all of them without yielding.
5350	// Names are zero-padded so lexicographic delivery order matches the loop index.
5351	#[tokio::test]
5352	async fn test_many_announces() {
5353		let origin = Origin::random().produce();
5354
5355		let mut consumer = origin.consume().announced();
5356		// Held for the duration: a dropped source unannounces immediately.
5357		let mut broadcasts = Vec::new();
5358		for i in 0..256 {
5359			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
5360			settle().await;
5361		}
5362
5363		for i in 0..256 {
5364			consumer.assert_next_some(format!("test{i:03}"));
5365		}
5366		consumer.assert_next_wait();
5367	}
5368
5369	#[tokio::test]
5370	async fn test_many_announces_try() {
5371		let origin = Origin::random().produce();
5372
5373		let mut consumer = origin.consume().announced();
5374		// Held for the duration: a dropped source unannounces immediately.
5375		let mut broadcasts = Vec::new();
5376		for i in 0..256 {
5377			broadcasts.push(origin.create_broadcast(format!("test{i:03}"), announce()).unwrap());
5378			settle().await;
5379		}
5380
5381		for i in 0..256 {
5382			consumer.assert_try_next_some(format!("test{i:03}"));
5383		}
5384	}
5385
5386	#[tokio::test]
5387	async fn test_with_root_basic() {
5388		let origin = Origin::random().produce();
5389
5390		// Create a producer with root "/foo"
5391		let foo_producer = origin.with_root("foo").expect("should create root");
5392		assert_eq!(foo_producer.root().as_str(), "foo");
5393
5394		let mut consumer = origin.consume().announced();
5395
5396		// When publishing to "bar/baz", it should actually publish to "foo/bar/baz"
5397		let _broadcast = foo_producer
5398			.create_broadcast("bar/baz", announce())
5399			.expect("publish allowed");
5400		settle().await;
5401		// The original consumer should see the full path
5402		consumer.assert_next_some("foo/bar/baz");
5403
5404		// A consumer created from the rooted producer should see the stripped path
5405		let mut foo_consumer = foo_producer.consume().announced();
5406		foo_consumer.assert_next_some("bar/baz");
5407	}
5408
5409	#[tokio::test]
5410	async fn test_with_root_nested() {
5411		let origin = Origin::random().produce();
5412
5413		// Create nested roots
5414		let foo_producer = origin.with_root("foo").expect("should create foo root");
5415		let foo_bar_producer = foo_producer.with_root("bar").expect("should create bar root");
5416		assert_eq!(foo_bar_producer.root().as_str(), "foo/bar");
5417
5418		let mut consumer = origin.consume().announced();
5419
5420		// Publishing to "baz" should actually publish to "foo/bar/baz"
5421		let _broadcast = foo_bar_producer
5422			.create_broadcast("baz", announce())
5423			.expect("publish allowed");
5424		settle().await;
5425		// The original consumer sees the full path
5426		consumer.assert_next_some("foo/bar/baz");
5427
5428		// Consumer from foo_bar_producer sees just "baz"
5429		let mut foo_bar_consumer = foo_bar_producer.consume().announced();
5430		foo_bar_consumer.assert_next_some("baz");
5431	}
5432
5433	#[tokio::test]
5434	async fn test_publish_scope_allows() {
5435		let origin = Origin::random().produce();
5436
5437		// Create a producer that can only publish to "allowed" paths
5438		let limited_producer = origin
5439			.scope(&["allowed/path1".into(), "allowed/path2".into()])
5440			.expect("should create limited producer");
5441
5442		// Should be able to publish to allowed paths
5443		let _broadcast = limited_producer
5444			.create_broadcast("allowed/path1", announce())
5445			.expect("publish allowed");
5446		let _keep2 = limited_producer
5447			.create_broadcast("allowed/path1/nested", announce())
5448			.expect("publish allowed");
5449		let _keep3 = limited_producer
5450			.create_broadcast("allowed/path2", announce())
5451			.expect("publish allowed");
5452		settle().await;
5453
5454		// Should not be able to publish to disallowed paths
5455		assert!(limited_producer.create_broadcast("notallowed", announce()).is_err());
5456		assert!(limited_producer.create_broadcast("allowed", announce()).is_err()); // Parent of allowed path
5457		assert!(limited_producer.create_broadcast("other/path", announce()).is_err());
5458	}
5459
5460	#[tokio::test]
5461	async fn test_publish_max_parts() {
5462		let origin = Origin::random().produce();
5463
5464		let at_limit = (0..Path::MAX_PARTS)
5465			.map(|i| i.to_string())
5466			.collect::<Vec<_>>()
5467			.join("/");
5468		let _broadcast = origin
5469			.create_broadcast(at_limit.as_str(), announce())
5470			.expect("publish allowed");
5471		settle().await;
5472
5473		let too_deep = format!("{at_limit}/extra");
5474		assert!(origin.create_broadcast(too_deep.as_str(), announce()).is_err());
5475
5476		// The root counts toward the limit; a joined path past 32 parts is rejected.
5477		let rooted = origin.with_root("root").expect("wildcard allows any root");
5478		assert!(rooted.create_broadcast(at_limit.as_str(), announce()).is_err());
5479	}
5480
5481	#[tokio::test]
5482	async fn test_publish_scope_empty() {
5483		let origin = Origin::random().produce();
5484
5485		// Creating a producer with no allowed paths should return None
5486		assert!(origin.scope(&[]).is_none());
5487	}
5488
5489	#[tokio::test]
5490	async fn test_consume_scope_filters() {
5491		let origin = Origin::random().produce();
5492
5493		let mut consumer = origin.consume().announced();
5494
5495		// Publish to different paths
5496		let _broadcast1 = origin.create_broadcast("allowed", announce()).unwrap();
5497		let _broadcast2 = origin.create_broadcast("allowed/nested", announce()).unwrap();
5498		let _broadcast3 = origin.create_broadcast("notallowed", announce()).unwrap();
5499		settle().await;
5500
5501		// Create a consumer that only sees "allowed" paths
5502		let mut limited_consumer = origin
5503			.consume()
5504			.scope(&["allowed".into()])
5505			.expect("should create limited consumer")
5506			.announced();
5507
5508		// Should only receive broadcasts under "allowed"
5509		limited_consumer.assert_next_some("allowed");
5510		limited_consumer.assert_next_some("allowed/nested");
5511		limited_consumer.assert_next_wait(); // Should not see "notallowed"
5512
5513		// Unscoped consumer should see all
5514		consumer.assert_next_some("allowed");
5515		consumer.assert_next_some("allowed/nested");
5516		consumer.assert_next_some("notallowed");
5517	}
5518
5519	#[tokio::test]
5520	async fn test_consume_scope_multiple_prefixes() {
5521		let origin = Origin::random().produce();
5522
5523		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
5524		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
5525		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
5526		settle().await;
5527
5528		// Consumer that only sees "foo" and "bar" paths
5529		let mut limited_consumer = origin
5530			.consume()
5531			.scope(&["foo".into(), "bar".into()])
5532			.expect("should create limited consumer")
5533			.announced();
5534
5535		// Order depends on PathPrefixes canonical sort (lexicographic for same length)
5536		limited_consumer.assert_next_some("bar/test");
5537		limited_consumer.assert_next_some("foo/test");
5538		limited_consumer.assert_next_wait(); // Should not see "baz/test"
5539	}
5540
5541	#[tokio::test]
5542	async fn test_with_root_and_publish_scope() {
5543		let origin = Origin::random().produce();
5544
5545		// User connects to /foo root
5546		let foo_producer = origin.with_root("foo").expect("should create foo root");
5547
5548		// Limit them to publish only to "bar" and "goop/pee" within /foo
5549		let limited_producer = foo_producer
5550			.scope(&["bar".into(), "goop/pee".into()])
5551			.expect("should create limited producer");
5552
5553		let mut consumer = origin.consume().announced();
5554
5555		// Should be able to publish to foo/bar and foo/goop/pee (but user sees as bar and goop/pee)
5556		let _broadcast = limited_producer
5557			.create_broadcast("bar", announce())
5558			.expect("publish allowed");
5559		let _keep2 = limited_producer
5560			.create_broadcast("bar/nested", announce())
5561			.expect("publish allowed");
5562		let _keep3 = limited_producer
5563			.create_broadcast("goop/pee", announce())
5564			.expect("publish allowed");
5565		let _keep4 = limited_producer
5566			.create_broadcast("goop/pee/nested", announce())
5567			.expect("publish allowed");
5568		settle().await;
5569
5570		// Should not be able to publish outside allowed paths
5571		assert!(limited_producer.create_broadcast("baz", announce()).is_err());
5572		assert!(limited_producer.create_broadcast("goop", announce()).is_err()); // Parent of allowed
5573		assert!(limited_producer.create_broadcast("goop/other", announce()).is_err());
5574
5575		// Original consumer sees full paths
5576		consumer.assert_next_some("foo/bar");
5577		consumer.assert_next_some("foo/bar/nested");
5578		consumer.assert_next_some("foo/goop/pee");
5579		consumer.assert_next_some("foo/goop/pee/nested");
5580	}
5581
5582	#[tokio::test]
5583	async fn test_with_root_and_consume_scope() {
5584		let origin = Origin::random().produce();
5585
5586		// Publish broadcasts
5587		let _broadcast1 = origin.create_broadcast("foo/bar/test", announce()).unwrap();
5588		let _broadcast2 = origin.create_broadcast("foo/goop/pee/test", announce()).unwrap();
5589		let _broadcast3 = origin.create_broadcast("foo/other/test", announce()).unwrap();
5590		settle().await;
5591
5592		// User connects to /foo root
5593		let foo_producer = origin.with_root("foo").expect("should create foo root");
5594
5595		// Create consumer limited to "bar" and "goop/pee" within /foo
5596		let mut limited_consumer = foo_producer
5597			.consume()
5598			.scope(&["bar".into(), "goop/pee".into()])
5599			.expect("should create limited consumer")
5600			.announced();
5601
5602		// Should only see allowed paths (without foo prefix)
5603		limited_consumer.assert_next_some("bar/test");
5604		limited_consumer.assert_next_some("goop/pee/test");
5605		limited_consumer.assert_next_wait(); // Should not see "other/test"
5606	}
5607
5608	#[tokio::test]
5609	async fn test_with_root_unauthorized() {
5610		let origin = Origin::random().produce();
5611
5612		// First limit the producer to specific paths
5613		let limited_producer = origin
5614			.scope(&["allowed".into()])
5615			.expect("should create limited producer");
5616
5617		// Trying to create a root outside allowed paths should fail
5618		assert!(limited_producer.with_root("notallowed").is_none());
5619
5620		// But creating a root within allowed paths should work
5621		let allowed_root = limited_producer
5622			.with_root("allowed")
5623			.expect("should create allowed root");
5624		assert_eq!(allowed_root.root().as_str(), "allowed");
5625	}
5626
5627	#[tokio::test]
5628	async fn test_wildcard_permission() {
5629		let origin = Origin::random().produce();
5630
5631		// Producer with root access (empty string means wildcard)
5632		let root_producer = origin.clone();
5633
5634		// Should be able to publish anywhere
5635		let _broadcast = root_producer
5636			.create_broadcast("any/path", announce())
5637			.expect("publish allowed");
5638		let _keep2 = root_producer
5639			.create_broadcast("other/path", announce())
5640			.expect("publish allowed");
5641		settle().await;
5642
5643		// Can create any root
5644		let foo_producer = root_producer.with_root("foo").expect("should create any root");
5645		assert_eq!(foo_producer.root().as_str(), "foo");
5646	}
5647
5648	#[tokio::test]
5649	async fn test_consume_broadcast_with_permissions() {
5650		let origin = Origin::random().produce();
5651
5652		let _broadcast1 = origin.create_broadcast("allowed/test", announce()).unwrap();
5653		let _broadcast2 = origin.create_broadcast("notallowed/test", announce()).unwrap();
5654		settle().await;
5655
5656		// Create limited consumer
5657		let limited_consumer = origin
5658			.consume()
5659			.scope(&["allowed".into()])
5660			.expect("should create limited consumer");
5661
5662		// Should be able to get allowed broadcast
5663		let result = limited_consumer.get_broadcast("allowed/test");
5664		assert!(result.is_some());
5665		assert!(
5666			result
5667				.unwrap()
5668				.is_clone(&origin.consume().get_broadcast("allowed/test").unwrap())
5669		);
5670
5671		// Should not be able to get disallowed broadcast
5672		assert!(limited_consumer.get_broadcast("notallowed/test").is_none());
5673
5674		// Original consumer can get both
5675		let consumer = origin.consume();
5676		assert!(consumer.get_broadcast("allowed/test").is_some());
5677		assert!(consumer.get_broadcast("notallowed/test").is_some());
5678	}
5679
5680	#[tokio::test]
5681	async fn test_nested_paths_with_permissions() {
5682		let origin = Origin::random().produce();
5683
5684		// Create producer limited to "a/b/c"
5685		let limited_producer = origin.scope(&["a/b/c".into()]).expect("should create limited producer");
5686
5687		// Should be able to publish to exact path and nested paths
5688		let _broadcast = limited_producer
5689			.create_broadcast("a/b/c", announce())
5690			.expect("publish allowed");
5691		let _keep2 = limited_producer
5692			.create_broadcast("a/b/c/d", announce())
5693			.expect("publish allowed");
5694		let _keep3 = limited_producer
5695			.create_broadcast("a/b/c/d/e", announce())
5696			.expect("publish allowed");
5697		settle().await;
5698
5699		// Should not be able to publish to parent or sibling paths
5700		assert!(limited_producer.create_broadcast("a", announce()).is_err());
5701		assert!(limited_producer.create_broadcast("a/b", announce()).is_err());
5702		assert!(limited_producer.create_broadcast("a/b/other", announce()).is_err());
5703	}
5704
5705	#[tokio::test]
5706	async fn test_multiple_consumers_with_different_permissions() {
5707		let origin = Origin::random().produce();
5708
5709		// Publish to different paths
5710		let _broadcast1 = origin.create_broadcast("foo/test", announce()).unwrap();
5711		let _broadcast2 = origin.create_broadcast("bar/test", announce()).unwrap();
5712		let _broadcast3 = origin.create_broadcast("baz/test", announce()).unwrap();
5713		settle().await;
5714
5715		// Create consumers with different permissions
5716		let mut foo_consumer = origin
5717			.consume()
5718			.scope(&["foo".into()])
5719			.expect("should create foo consumer")
5720			.announced();
5721
5722		let mut bar_consumer = origin
5723			.consume()
5724			.scope(&["bar".into()])
5725			.expect("should create bar consumer")
5726			.announced();
5727
5728		let mut foobar_consumer = origin
5729			.consume()
5730			.scope(&["foo".into(), "bar".into()])
5731			.expect("should create foobar consumer")
5732			.announced();
5733
5734		// Each consumer should only see their allowed paths
5735		foo_consumer.assert_next_some("foo/test");
5736		foo_consumer.assert_next_wait();
5737
5738		bar_consumer.assert_next_some("bar/test");
5739		bar_consumer.assert_next_wait();
5740
5741		foobar_consumer.assert_next_some("bar/test");
5742		foobar_consumer.assert_next_some("foo/test");
5743		foobar_consumer.assert_next_wait();
5744	}
5745
5746	#[tokio::test]
5747	async fn test_select_with_empty_prefix() {
5748		let origin = Origin::random().produce();
5749
5750		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
5751		let demo_producer = origin.with_root("demo").expect("should create demo root");
5752		let limited_producer = demo_producer
5753			.scope(&["worm-node".into(), "foobar".into()])
5754			.expect("should create limited producer");
5755
5756		// Publish some broadcasts
5757		let _broadcast1 = limited_producer
5758			.create_broadcast("worm-node/test", announce())
5759			.expect("publish allowed");
5760		let _broadcast2 = limited_producer
5761			.create_broadcast("foobar/test", announce())
5762			.expect("publish allowed");
5763		settle().await;
5764
5765		// scope with empty prefix should keep the exact same "worm-node" and "foobar" nodes
5766		let mut consumer = limited_producer
5767			.consume()
5768			.scope(&["".into()])
5769			.expect("should create consumer with empty prefix")
5770			.announced();
5771
5772		// Should see both broadcasts (order depends on PathPrefixes sort)
5773		let a1 = consumer.try_next().expect("expected first announcement");
5774		let a2 = consumer.try_next().expect("expected second announcement");
5775		consumer.assert_next_wait();
5776
5777		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
5778		paths.sort();
5779		assert_eq!(paths, ["foobar/test", "worm-node/test"]);
5780	}
5781
5782	#[tokio::test]
5783	async fn test_select_narrowing_scope() {
5784		let origin = Origin::random().produce();
5785
5786		// User with root "demo" allowed to subscribe to "worm-node" and "foobar"
5787		let demo_producer = origin.with_root("demo").expect("should create demo root");
5788		let limited_producer = demo_producer
5789			.scope(&["worm-node".into(), "foobar".into()])
5790			.expect("should create limited producer");
5791
5792		// Publish broadcasts at different levels
5793		let _broadcast1 = limited_producer
5794			.create_broadcast("worm-node", announce())
5795			.expect("publish allowed");
5796		let _broadcast2 = limited_producer
5797			.create_broadcast("worm-node/foo", announce())
5798			.expect("publish allowed");
5799		let _broadcast3 = limited_producer
5800			.create_broadcast("foobar/bar", announce())
5801			.expect("publish allowed");
5802		settle().await;
5803
5804		// Test 1: scope("worm-node") should result in a single "" node with contents of "worm-node" ONLY
5805		let mut worm_consumer = limited_producer
5806			.consume()
5807			.scope(&["worm-node".into()])
5808			.expect("should create worm-node consumer")
5809			.announced();
5810
5811		// Should see worm-node content with paths stripped to ""
5812		worm_consumer.assert_next_some("worm-node");
5813		worm_consumer.assert_next_some("worm-node/foo");
5814		worm_consumer.assert_next_wait(); // Should NOT see foobar content
5815
5816		// Test 2: scope("worm-node/foo") should result in a "" node with contents of "worm-node/foo"
5817		let mut foo_consumer = limited_producer
5818			.consume()
5819			.scope(&["worm-node/foo".into()])
5820			.expect("should create worm-node/foo consumer")
5821			.announced();
5822
5823		foo_consumer.assert_next_some("worm-node/foo");
5824		foo_consumer.assert_next_wait(); // Should NOT see other content
5825	}
5826
5827	#[tokio::test]
5828	async fn test_select_multiple_roots_with_empty_prefix() {
5829		let origin = Origin::random().produce();
5830
5831		// Producer with multiple allowed roots
5832		let limited_producer = origin
5833			.scope(&["app1".into(), "app2".into(), "shared".into()])
5834			.expect("should create limited producer");
5835
5836		// Publish to each root
5837		let _broadcast1 = limited_producer
5838			.create_broadcast("app1/data", announce())
5839			.expect("publish allowed");
5840		let _broadcast2 = limited_producer
5841			.create_broadcast("app2/config", announce())
5842			.expect("publish allowed");
5843		let _broadcast3 = limited_producer
5844			.create_broadcast("shared/resource", announce())
5845			.expect("publish allowed");
5846		settle().await;
5847
5848		// scope with empty prefix should maintain all roots
5849		let mut consumer = limited_producer
5850			.consume()
5851			.scope(&["".into()])
5852			.expect("should create consumer with empty prefix")
5853			.announced();
5854
5855		// Should see all broadcasts from all roots
5856		consumer.assert_next_some("app1/data");
5857		consumer.assert_next_some("app2/config");
5858		consumer.assert_next_some("shared/resource");
5859		consumer.assert_next_wait();
5860	}
5861
5862	#[tokio::test]
5863	async fn test_publish_scope_with_empty_prefix() {
5864		let origin = Origin::random().produce();
5865
5866		// Producer with specific allowed paths
5867		let limited_producer = origin
5868			.scope(&["services/api".into(), "services/web".into()])
5869			.expect("should create limited producer");
5870
5871		// scope with empty prefix should keep the same restrictions
5872		let same_producer = limited_producer
5873			.scope(&["".into()])
5874			.expect("should create producer with empty prefix");
5875
5876		// Should still have the same publishing restrictions
5877		let _broadcast = same_producer
5878			.create_broadcast("services/api", announce())
5879			.expect("publish allowed");
5880		let _keep2 = same_producer
5881			.create_broadcast("services/web", announce())
5882			.expect("publish allowed");
5883		assert!(same_producer.create_broadcast("services/db", announce()).is_err());
5884		assert!(same_producer.create_broadcast("other", announce()).is_err());
5885	}
5886
5887	#[tokio::test]
5888	async fn test_select_narrowing_to_deeper_path() {
5889		let origin = Origin::random().produce();
5890
5891		// Producer with broad permission
5892		let limited_producer = origin.scope(&["org".into()]).expect("should create limited producer");
5893
5894		// Publish at various depths
5895		let _broadcast1 = limited_producer
5896			.create_broadcast("org/team1/project1", announce())
5897			.expect("publish allowed");
5898		let _broadcast2 = limited_producer
5899			.create_broadcast("org/team1/project2", announce())
5900			.expect("publish allowed");
5901		let _broadcast3 = limited_producer
5902			.create_broadcast("org/team2/project1", announce())
5903			.expect("publish allowed");
5904		settle().await;
5905
5906		// Narrow down to team2 only
5907		let mut team2_consumer = limited_producer
5908			.consume()
5909			.scope(&["org/team2".into()])
5910			.expect("should create team2 consumer")
5911			.announced();
5912
5913		team2_consumer.assert_next_some("org/team2/project1");
5914		team2_consumer.assert_next_wait(); // Should NOT see team1 content
5915
5916		// Further narrow down to team1/project1
5917		let mut project1_consumer = limited_producer
5918			.consume()
5919			.scope(&["org/team1/project1".into()])
5920			.expect("should create project1 consumer")
5921			.announced();
5922
5923		// Should only see project1 content at root
5924		project1_consumer.assert_next_some("org/team1/project1");
5925		project1_consumer.assert_next_wait();
5926	}
5927
5928	#[tokio::test]
5929	async fn test_select_with_non_matching_prefix() {
5930		let origin = Origin::random().produce();
5931
5932		// Producer with specific allowed paths
5933		let limited_producer = origin
5934			.scope(&["allowed/path".into()])
5935			.expect("should create limited producer");
5936
5937		// Trying to scope with a completely different prefix should return None
5938		assert!(limited_producer.consume().scope(&["different/path".into()]).is_none());
5939
5940		// Similarly for scope
5941		assert!(limited_producer.scope(&["other/path".into()]).is_none());
5942	}
5943
5944	// Regression test for https://github.com/moq-dev/moq/issues/910
5945	// with_root panics when String has trailing slash (AsPath for String skips normalization)
5946	#[tokio::test]
5947	async fn test_with_root_trailing_slash_consumer() {
5948		let origin = Origin::random().produce();
5949
5950		// Use an owned String so the trailing slash is NOT normalized away.
5951		let prefix = "some_prefix/".to_string();
5952		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
5953
5954		let _b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
5955		settle().await;
5956		consumer.assert_next_some("test");
5957	}
5958
5959	// Same issue but for the producer side of with_root
5960	#[tokio::test]
5961	async fn test_with_root_trailing_slash_producer() {
5962		let origin = Origin::random().produce();
5963
5964		// Use an owned String so the trailing slash is NOT normalized away.
5965		let prefix = "some_prefix/".to_string();
5966		let rooted = origin.with_root(prefix).unwrap();
5967
5968		let _b = rooted.create_broadcast("test", announce()).unwrap();
5969		settle().await;
5970
5971		let mut consumer = rooted.consume().announced();
5972		consumer.assert_next_some("test");
5973	}
5974
5975	// Verify unannounce also doesn't panic with trailing slash
5976	#[tokio::test]
5977	async fn test_with_root_trailing_slash_unannounce() {
5978		tokio::time::pause();
5979
5980		let origin = Origin::random().produce();
5981
5982		let prefix = "some_prefix/".to_string();
5983		let mut consumer = origin.consume().with_root(prefix).unwrap().announced();
5984
5985		let mut b = origin.create_broadcast("some_prefix/test", announce()).unwrap();
5986		settle().await;
5987		consumer.assert_next_some("test");
5988
5989		// Finish the broadcast to trigger an immediate unannounce.
5990		b.finish();
5991		settle().await;
5992
5993		// unannounce also calls strip_prefix(&self.root).unwrap()
5994		consumer.assert_next_none("test");
5995	}
5996
5997	#[tokio::test]
5998	async fn test_select_maintains_access_with_wider_prefix() {
5999		let origin = Origin::random().produce();
6000
6001		// Setup: user with root "demo" allowed to subscribe to specific paths
6002		let demo_producer = origin.with_root("demo").expect("should create demo root");
6003		let user_producer = demo_producer
6004			.scope(&["worm-node".into(), "foobar".into()])
6005			.expect("should create user producer");
6006
6007		// Publish some data
6008		let _broadcast1 = user_producer
6009			.create_broadcast("worm-node/data", announce())
6010			.expect("publish allowed");
6011		let _broadcast2 = user_producer
6012			.create_broadcast("foobar", announce())
6013			.expect("publish allowed");
6014		settle().await;
6015
6016		// Key test: scope with "" should maintain access to allowed roots
6017		let mut consumer = user_producer
6018			.consume()
6019			.scope(&["".into()])
6020			.expect("scope with empty prefix should not fail when user has specific permissions")
6021			.announced();
6022
6023		// Should still receive broadcasts from allowed paths (order not guaranteed)
6024		let a1 = consumer.try_next().expect("expected first announcement");
6025		let a2 = consumer.try_next().expect("expected second announcement");
6026		consumer.assert_next_wait();
6027
6028		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
6029		paths.sort();
6030		assert_eq!(paths, ["foobar", "worm-node/data"]);
6031
6032		// Also test that we can still narrow the scope
6033		let mut narrow_consumer = user_producer
6034			.consume()
6035			.scope(&["worm-node".into()])
6036			.expect("should be able to narrow scope to worm-node")
6037			.announced();
6038
6039		narrow_consumer.assert_next_some("worm-node/data");
6040		narrow_consumer.assert_next_wait(); // Should not see foobar
6041	}
6042
6043	#[tokio::test]
6044	async fn test_duplicate_prefixes_deduped() {
6045		let origin = Origin::random().produce();
6046
6047		// scope with duplicate prefixes should work (deduped internally)
6048		let producer = origin
6049			.scope(&["demo".into(), "demo".into()])
6050			.expect("should create producer");
6051
6052		let _broadcast = producer
6053			.create_broadcast("demo/stream", announce())
6054			.expect("publish allowed");
6055		settle().await;
6056
6057		let mut consumer = producer.consume().announced();
6058		consumer.assert_next_some("demo/stream");
6059		consumer.assert_next_wait();
6060	}
6061
6062	#[tokio::test]
6063	async fn test_overlapping_prefixes_deduped() {
6064		let origin = Origin::random().produce();
6065
6066		// "demo" and "demo/foo". "demo/foo" is redundant, only "demo" should remain
6067		let producer = origin
6068			.scope(&["demo".into(), "demo/foo".into()])
6069			.expect("should create producer");
6070
6071		// Can still publish under "demo/bar" since "demo" covers everything
6072		let _broadcast = producer
6073			.create_broadcast("demo/bar/stream", announce())
6074			.expect("publish allowed");
6075		settle().await;
6076
6077		let mut consumer = producer.consume().announced();
6078		consumer.assert_next_some("demo/bar/stream");
6079		consumer.assert_next_wait();
6080	}
6081
6082	#[tokio::test]
6083	async fn test_overlapping_prefixes_no_duplicate_announcements() {
6084		let origin = Origin::random().produce();
6085
6086		// Both "demo" and "demo/foo" are requested. Should only have one node
6087		let producer = origin
6088			.scope(&["demo".into(), "demo/foo".into()])
6089			.expect("should create producer");
6090
6091		let _broadcast = producer
6092			.create_broadcast("demo/foo/stream", announce())
6093			.expect("publish allowed");
6094		settle().await;
6095
6096		let mut consumer = producer.consume().announced();
6097		// Should only get ONE announcement (not two from overlapping nodes)
6098		consumer.assert_next_some("demo/foo/stream");
6099		consumer.assert_next_wait();
6100	}
6101
6102	#[tokio::test]
6103	async fn test_allowed_returns_deduped_prefixes() {
6104		let origin = Origin::random().produce();
6105
6106		let producer = origin
6107			.scope(&["demo".into(), "demo/foo".into(), "anon".into()])
6108			.expect("should create producer");
6109
6110		let allowed: Vec<_> = producer.allowed().collect();
6111		assert_eq!(allowed.len(), 2, "demo/foo should be subsumed by demo");
6112	}
6113
6114	#[tokio::test]
6115	async fn test_announced_broadcast_already_announced() {
6116		let origin = Origin::random().produce();
6117
6118		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
6119		settle().await;
6120
6121		let consumer = origin.consume();
6122		let result = consumer.announced_broadcast("test").await.expect("should find it");
6123		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
6124	}
6125
6126	#[tokio::test]
6127	async fn test_announced_broadcast_delayed() {
6128		tokio::time::pause();
6129
6130		let origin = Origin::random().produce();
6131
6132		let consumer = origin.consume();
6133
6134		// Start waiting before it's announced.
6135		let wait = tokio::spawn({
6136			let consumer = consumer.clone();
6137			async move { consumer.announced_broadcast("test").await }
6138		});
6139
6140		// Give the spawned task a chance to subscribe.
6141		tokio::task::yield_now().await;
6142
6143		let _broadcast = origin.create_broadcast("test", announce()).unwrap();
6144		settle().await;
6145
6146		let result = wait.await.unwrap().expect("should find it");
6147		assert!(result.is_clone(&consumer.get_broadcast("test").unwrap()));
6148	}
6149
6150	#[tokio::test]
6151	async fn test_announced_broadcast_ignores_unrelated_paths() {
6152		tokio::time::pause();
6153
6154		let origin = Origin::random().produce();
6155
6156		let consumer = origin.consume();
6157
6158		let wait = tokio::spawn({
6159			let consumer = consumer.clone();
6160			async move { consumer.announced_broadcast("target").await }
6161		});
6162
6163		tokio::task::yield_now().await;
6164
6165		// Publish an unrelated broadcast first. announced_broadcast should skip it.
6166		let _other = origin.create_broadcast("other", announce()).unwrap();
6167		settle().await;
6168		tokio::task::yield_now().await;
6169		assert!(!wait.is_finished(), "must not resolve on unrelated path");
6170
6171		let _target = origin.create_broadcast("target", announce()).unwrap();
6172		settle().await;
6173		let result = wait.await.unwrap().expect("should find target");
6174		assert!(result.is_clone(&consumer.get_broadcast("target").unwrap()));
6175	}
6176
6177	#[tokio::test]
6178	async fn test_announced_broadcast_skips_nested_paths() {
6179		tokio::time::pause();
6180
6181		let origin = Origin::random().produce();
6182
6183		let consumer = origin.consume();
6184
6185		let wait = tokio::spawn({
6186			let consumer = consumer.clone();
6187			async move { consumer.announced_broadcast("foo").await }
6188		});
6189
6190		tokio::task::yield_now().await;
6191
6192		// "foo/bar" is under the prefix scope, but it's not the exact path. Skip it.
6193		let _nested = origin.create_broadcast("foo/bar", announce()).unwrap();
6194		settle().await;
6195		tokio::task::yield_now().await;
6196		assert!(!wait.is_finished(), "must not resolve on a nested path");
6197
6198		let _exact = origin.create_broadcast("foo", announce()).unwrap();
6199		settle().await;
6200		let result = wait.await.unwrap().expect("should find foo exactly");
6201		assert!(result.is_clone(&consumer.get_broadcast("foo").unwrap()));
6202	}
6203
6204	#[tokio::test]
6205	async fn test_announced_broadcast_disallowed() {
6206		let origin = Origin::random().produce();
6207		let limited = origin
6208			.consume()
6209			.scope(&["allowed".into()])
6210			.expect("should create limited");
6211
6212		// Path is outside allowed prefixes. Should return None immediately.
6213		assert!(limited.announced_broadcast("notallowed").await.is_none());
6214	}
6215
6216	#[tokio::test]
6217	async fn test_announced_broadcast_scope_too_narrow() {
6218		// Consumer's scope is narrower than the requested path: asking for `foo` on a consumer
6219		// limited to `foo/specific` can never resolve. Must return None, not loop forever.
6220		let origin = Origin::random().produce();
6221		let limited = origin
6222			.consume()
6223			.scope(&["foo/specific".into()])
6224			.expect("should create limited");
6225
6226		// now_or_never so we fail fast instead of hanging if the guard regresses.
6227		let result = limited
6228			.announced_broadcast("foo")
6229			.now_or_never()
6230			.expect("must not block");
6231		assert!(result.is_none());
6232	}
6233
6234	// Coalescing tests: a slow cursor that doesn't drain between updates
6235	// should observe a bounded number of deliveries.
6236
6237	#[tokio::test]
6238	async fn test_coalesce_announce_then_unannounce() {
6239		// announce + unannounce that the cursor hasn't observed yet collapses to nothing.
6240		tokio::time::pause();
6241
6242		let origin = Origin::random().produce();
6243		let mut announced = origin.consume().announced();
6244
6245		let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
6246		settle().await;
6247		broadcast.finish();
6248
6249		settle().await;
6250
6251		announced.assert_next_wait();
6252	}
6253
6254	#[tokio::test]
6255	async fn test_coalesce_announce_unannounce_announce() {
6256		// announce, unannounce, announce that the cursor hasn't drained collapses
6257		// to a single Announce of the latest broadcast.
6258		tokio::time::pause();
6259
6260		let origin = Origin::random().produce();
6261		let mut announced = origin.consume().announced();
6262
6263		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
6264		settle().await;
6265		broadcast1.finish();
6266		settle().await;
6267		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
6268		settle().await;
6269
6270		announced.assert_next_some("test");
6271		announced.assert_next_wait();
6272	}
6273
6274	#[tokio::test]
6275	async fn test_coalesce_unannounce_announce_preserved() {
6276		// unannounce followed by announce of a different broadcast must be preserved
6277		// as two deliveries so the cursor learns the origin changed.
6278		tokio::time::pause();
6279
6280		let origin = Origin::random().produce();
6281		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
6282		settle().await;
6283
6284		let mut announced = origin.consume().announced();
6285		announced.assert_next_some("test");
6286
6287		// Finish, then publish a fresh broadcast at the same path.
6288		broadcast1.finish();
6289		settle().await;
6290
6291		let _broadcast2 = origin.create_broadcast("test", announce()).unwrap();
6292		settle().await;
6293
6294		// The cursor must see the unannounce before the new announce.
6295		announced.assert_next_none("test");
6296		announced.assert_next_some("test");
6297		announced.assert_next_wait();
6298	}
6299
6300	#[tokio::test]
6301	async fn test_coalesce_unannounce_announce_unannounce() {
6302		// unannounce + announce + unannounce collapses to a single unannounce: the
6303		// embedded announce was never observed.
6304		tokio::time::pause();
6305
6306		let origin = Origin::random().produce();
6307		let mut broadcast1 = origin.create_broadcast("test", announce()).unwrap();
6308		settle().await;
6309
6310		let mut announced = origin.consume().announced();
6311		announced.assert_next_some("test");
6312
6313		broadcast1.finish();
6314		settle().await;
6315
6316		let mut broadcast2 = origin.create_broadcast("test", announce()).unwrap();
6317		settle().await;
6318		broadcast2.finish();
6319		settle().await;
6320
6321		announced.assert_next_none("test");
6322		announced.assert_next_wait();
6323	}
6324
6325	#[tokio::test]
6326	async fn test_coalesce_churn_bounded() {
6327		// A churn loop on a single path should keep the pending set bounded.
6328		// Backup promotion during cleanup can leave the cursor with zero or one
6329		// pending update for "test" depending on the order tasks run; we only
6330		// require that churn doesn't accumulate across iterations.
6331		tokio::time::pause();
6332
6333		let origin = Origin::random().produce();
6334		let mut announced = origin.consume().announced();
6335
6336		for _ in 0..1000 {
6337			let mut broadcast = origin.create_broadcast("test", announce()).unwrap();
6338			settle().await;
6339			broadcast.finish();
6340		}
6341		settle().await;
6342
6343		let mut collected = Vec::new();
6344		while let Some(update) = announced.try_next() {
6345			collected.push(update);
6346		}
6347		assert!(
6348			collected.len() <= 1,
6349			"expected at most one pending update, got {}",
6350			collected.len()
6351		);
6352		assert!(
6353			collected.iter().all(|a| a.path == Path::new("test")),
6354			"unexpected path in pending updates",
6355		);
6356	}
6357
6358	// Consumer should be cheap to clone: cloning must NOT drain any
6359	// other cursor's announce channel. A freshly-built AnnounceConsumer
6360	// still receives the active backlog.
6361	#[tokio::test]
6362	async fn test_consumer_clone_is_side_effect_free() {
6363		let origin = Origin::random().produce();
6364
6365		let _broadcast1 = origin.create_broadcast("test1", announce()).unwrap();
6366		let _broadcast2 = origin.create_broadcast("test2", announce()).unwrap();
6367		settle().await;
6368
6369		let consumer = origin.consume();
6370		let mut announced = consumer.announced();
6371
6372		// Cloning the Consumer many times and looking up broadcasts
6373		// must not consume any events from the existing cursor.
6374		for _ in 0..16 {
6375			let cloned = consumer.clone();
6376			assert!(cloned.get_broadcast("test1").is_some());
6377			assert!(cloned.get_broadcast("test2").is_some());
6378		}
6379
6380		// The original cursor still sees both announcements in their
6381		// natural order, undisturbed by the clones above.
6382		let a1 = announced.try_next().expect("first announcement");
6383		let a2 = announced.try_next().expect("second announcement");
6384		announced.assert_next_wait();
6385
6386		let mut paths: Vec<_> = [&a1, &a2].iter().map(|a| a.path.to_string()).collect();
6387		paths.sort();
6388		assert_eq!(paths, ["test1", "test2"]);
6389
6390		// A freshly-built AnnounceConsumer still receives the active backlog.
6391		let mut fresh = consumer.announced();
6392		let b1 = fresh.try_next().expect("backlog: first");
6393		let b2 = fresh.try_next().expect("backlog: second");
6394		fresh.assert_next_wait();
6395
6396		let mut paths: Vec<_> = [&b1, &b2].iter().map(|a| a.path.to_string()).collect();
6397		paths.sort();
6398		assert_eq!(paths, ["test1", "test2"]);
6399	}
6400
6401	// With no Dynamic handler, an unannounced path resolves to Unroutable.
6402	#[tokio::test]
6403	async fn dynamic_request_unroutable_without_handler() {
6404		let origin = Origin::random().produce();
6405		let consumer = origin.consume();
6406		assert!(matches!(
6407			consumer.request_broadcast("missing").await,
6408			Err(Error::Unroutable)
6409		));
6410	}
6411
6412	// A dynamically served broadcast resolves the requester and serves tracks, but is
6413	// never announced.
6414	#[tokio::test(start_paused = true)]
6415	async fn dynamic_request_served_not_announced() {
6416		let origin = Origin::random().produce();
6417		let mut dynamic = origin.dynamic();
6418		let consumer = origin.consume();
6419
6420		// A separate announce cursor must never observe the dynamic broadcast.
6421		let mut announced = origin.consume().announced();
6422		announced.assert_next_wait();
6423
6424		let served = broadcast::Info::new().produce();
6425		// Request a path that nobody announced; the future stays pending until served.
6426		// Registration happens up front, so the handler sees the request immediately.
6427		let request_fut = consumer.request_broadcast("fallback");
6428
6429		// The handler serves it with a live broadcast it keeps producing into.
6430		let mut served_dynamic = served.dynamic();
6431
6432		let request = dynamic.requested_broadcast().await.unwrap();
6433		assert_eq!(request.path(), &Path::new("fallback"));
6434		request.accept(&served);
6435
6436		let broadcast = request_fut.await.unwrap();
6437		assert!(broadcast.is_clone(&served.consume()));
6438
6439		// The served broadcast is live: a track subscription resolves via its handler.
6440		let track_fut = broadcast.track("video").unwrap().subscribe(None);
6441		let mut producer = served_dynamic.requested_track().await.unwrap().accept(None);
6442		let mut track = track_fut.await.unwrap();
6443		producer.append_group().unwrap();
6444		track.assert_group();
6445
6446		// Still nothing announced.
6447		announced.assert_next_wait();
6448	}
6449
6450	// Concurrent requests for the same queued path coalesce onto one handler request.
6451	#[tokio::test(start_paused = true)]
6452	async fn dynamic_request_coalesces() {
6453		let origin = Origin::random().produce();
6454		let mut dynamic = origin.dynamic();
6455		let consumer = origin.consume();
6456
6457		// Both register before the handler drains either.
6458		let f1 = consumer.request_broadcast("dup");
6459		let f2 = consumer.request_broadcast("dup");
6460
6461		// Exactly one request reaches the handler.
6462		let request = dynamic.requested_broadcast().await.unwrap();
6463		assert_eq!(request.path(), &Path::new("dup"));
6464		assert!(
6465			dynamic.requested_broadcast().now_or_never().is_none(),
6466			"a coalesced request must not be served twice"
6467		);
6468
6469		// Accepting resolves both awaiting requesters with the same broadcast.
6470		let served = broadcast::Info::new().produce();
6471		request.accept(&served);
6472		assert!(f1.await.unwrap().is_clone(&served.consume()));
6473		assert!(f2.await.unwrap().is_clone(&served.consume()));
6474	}
6475
6476	// A repeat request for an already-served, still-live path shares the same broadcast
6477	// instead of asking the handler again (no duplicate upstream subscription).
6478	#[tokio::test(start_paused = true)]
6479	async fn dynamic_request_dedups_served() {
6480		let origin = Origin::random().produce();
6481		let mut dynamic = origin.dynamic();
6482		let consumer = origin.consume();
6483
6484		let request_fut = consumer.request_broadcast("fallback");
6485		let request = dynamic.requested_broadcast().await.unwrap();
6486		let served = broadcast::Info::new().produce();
6487		request.accept(&served);
6488		let first = request_fut.await.unwrap();
6489		assert!(first.is_clone(&served.consume()));
6490
6491		// The repeat resolves immediately to the same broadcast...
6492		let second = consumer.request_broadcast("fallback").await.unwrap();
6493		assert!(second.is_clone(&served.consume()));
6494
6495		// ...and the handler never sees a second request.
6496		assert!(
6497			dynamic.requested_broadcast().now_or_never().is_none(),
6498			"a still-live served broadcast must not be re-requested from the handler"
6499		);
6500	}
6501
6502	// Once a served broadcast closes, its cache entry is stale, so the next request re-serves.
6503	#[tokio::test(start_paused = true)]
6504	async fn dynamic_request_reserves_after_close() {
6505		let origin = Origin::random().produce();
6506		let mut dynamic = origin.dynamic();
6507		let consumer = origin.consume();
6508
6509		let request_fut = consumer.request_broadcast("fallback");
6510		let request = dynamic.requested_broadcast().await.unwrap();
6511		let served = broadcast::Info::new().produce();
6512		request.accept(&served);
6513		request_fut.await.unwrap();
6514
6515		// Close the first served broadcast; the weak cache entry goes stale.
6516		drop(served);
6517
6518		// A fresh request must reach the handler again and resolve to the new broadcast.
6519		let request_fut = consumer.request_broadcast("fallback");
6520		let request = dynamic.requested_broadcast().await.unwrap();
6521		assert_eq!(request.path(), &Path::new("fallback"));
6522		let served = broadcast::Info::new().produce();
6523		request.accept(&served);
6524		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
6525	}
6526
6527	// Serving many distinct one-shot paths that each close must not grow the `served` cache
6528	// unboundedly: the amortized GC on `accept` reclaims the stale entries left by closed ones.
6529	#[tokio::test(start_paused = true)]
6530	async fn dynamic_request_served_cache_bounded() {
6531		let origin = Origin::random().produce();
6532		let mut dynamic = origin.dynamic();
6533		let consumer = origin.consume();
6534
6535		for i in 0..100 {
6536			let path = format!("one-shot/{i}");
6537			let request_fut = consumer.request_broadcast(&path);
6538			let request = dynamic.requested_broadcast().await.unwrap();
6539			let served = broadcast::Info::new().produce();
6540			request.accept(&served);
6541			request_fut.await.unwrap();
6542			// Close the served broadcast; its cache entry is now stale.
6543			drop(served);
6544		}
6545
6546		// The GC keeps the map bounded by the live count (zero here) plus a small probe window,
6547		// rather than one entry per distinct path.
6548		assert!(
6549			origin.dynamic.read().served.len() <= 4,
6550			"stale served entries must be reclaimed, not accumulate per distinct path: {}",
6551			origin.dynamic.read().served.len()
6552		);
6553	}
6554
6555	// A repeat request in the window after the handler picks one up but before it accepts
6556	// coalesces onto the in-flight request instead of queuing a duplicate.
6557	#[tokio::test(start_paused = true)]
6558	async fn dynamic_request_coalesces_after_handoff() {
6559		let origin = Origin::random().produce();
6560		let mut dynamic = origin.dynamic();
6561		let consumer = origin.consume();
6562
6563		let f1 = consumer.request_broadcast("fallback");
6564		// Handler drains the request but has not accepted yet.
6565		let request = dynamic.requested_broadcast().await.unwrap();
6566
6567		// A second request in this window must not queue another handler request.
6568		let f2 = consumer.request_broadcast("fallback");
6569		assert!(
6570			dynamic.requested_broadcast().now_or_never().is_none(),
6571			"a repeat request during hand-off must coalesce, not re-queue"
6572		);
6573
6574		// Accepting resolves both awaiting requesters with the same broadcast.
6575		let served = broadcast::Info::new().produce();
6576		request.accept(&served);
6577		assert!(f1.await.unwrap().is_clone(&served.consume()));
6578		assert!(f2.await.unwrap().is_clone(&served.consume()));
6579	}
6580
6581	// Dropping a handed-off request without accept/reject rejects every coalesced requester.
6582	#[tokio::test(start_paused = true)]
6583	async fn dynamic_request_dropped_after_handoff() {
6584		let origin = Origin::random().produce();
6585		let mut dynamic = origin.dynamic();
6586		let consumer = origin.consume();
6587
6588		let f1 = consumer.request_broadcast("fallback");
6589		let request = dynamic.requested_broadcast().await.unwrap();
6590		let f2 = consumer.request_broadcast("fallback");
6591
6592		// Abandon it; both requesters resolve to Unroutable instead of hanging.
6593		drop(request);
6594		assert!(matches!(f1.await, Err(Error::Unroutable)));
6595		assert!(matches!(f2.await, Err(Error::Unroutable)));
6596	}
6597
6598	// Rejecting a request resolves the requester with the error.
6599	#[tokio::test(start_paused = true)]
6600	async fn dynamic_request_rejected() {
6601		let origin = Origin::random().produce();
6602		let mut dynamic = origin.dynamic();
6603		let consumer = origin.consume();
6604
6605		let request_fut = consumer.request_broadcast("fallback");
6606
6607		let request = dynamic.requested_broadcast().await.unwrap();
6608		request.reject(Error::Cancel);
6609
6610		assert!(matches!(request_fut.await, Err(Error::Cancel)));
6611	}
6612
6613	// After a rejected hand-off, a fresh request for the same path reaches the handler again:
6614	// the rejected `Request`'s removal + `Drop` leave the request queue consistent
6615	// (a stale/clobbered entry would strand this request or panic the handler).
6616	#[tokio::test(start_paused = true)]
6617	async fn dynamic_request_rerequest_after_reject() {
6618		let origin = Origin::random().produce();
6619		let mut dynamic = origin.dynamic();
6620		let consumer = origin.consume();
6621
6622		let f1 = consumer.request_broadcast("fallback");
6623		dynamic.requested_broadcast().await.unwrap().reject(Error::Unroutable);
6624		assert!(matches!(f1.await, Err(Error::Unroutable)));
6625
6626		let served = broadcast::Info::new().produce();
6627		// A fresh request re-reaches the handler and can be served.
6628		let f2 = consumer.request_broadcast("fallback");
6629		let request = dynamic.requested_broadcast().await.unwrap();
6630		assert_eq!(request.path(), &Path::new("fallback"));
6631		request.accept(&served);
6632		assert!(f2.await.unwrap().is_clone(&served.consume()));
6633	}
6634
6635	// Dropping the last handler resolves queued requests with an error and reverts to
6636	// resolving Unroutable.
6637	#[tokio::test(start_paused = true)]
6638	async fn dynamic_request_handler_dropped() {
6639		let origin = Origin::random().produce();
6640		let dynamic = origin.dynamic();
6641		let consumer = origin.consume();
6642
6643		let request_fut = consumer.request_broadcast("fallback");
6644		drop(dynamic);
6645		assert!(matches!(request_fut.await, Err(Error::Unroutable)));
6646
6647		// With no handler left, a fresh request resolves Unroutable.
6648		assert!(matches!(
6649			consumer.request_broadcast("again").await,
6650			Err(Error::Unroutable)
6651		));
6652	}
6653
6654	// `accept` is decoupled from the dynamic count: once a handler has picked a request up,
6655	// it can still serve it even if every handler (including itself) drops first, flipping the
6656	// count to zero. The in-flight request must not be rejected as `Unroutable`.
6657	#[tokio::test(start_paused = true)]
6658	async fn dynamic_request_accept_after_handler_dropped() {
6659		let origin = Origin::random().produce();
6660		let mut dynamic = origin.dynamic();
6661		let consumer = origin.consume();
6662
6663		let request_fut = consumer.request_broadcast("fallback");
6664
6665		// The handler picks the request up, then every handler drops (count -> 0).
6666		let request = dynamic.requested_broadcast().await.unwrap();
6667		drop(dynamic);
6668
6669		let served = broadcast::Info::new().produce();
6670		// Accept still resolves the awaiting requester with the served broadcast.
6671		request.accept(&served);
6672		assert!(request_fut.await.unwrap().is_clone(&served.consume()));
6673	}
6674
6675	// A published broadcast wins over the dynamic fallback; no request is queued.
6676	#[tokio::test(start_paused = true)]
6677	async fn dynamic_request_prefers_announced() {
6678		let origin = Origin::random().produce();
6679		let mut dynamic = origin.dynamic();
6680		let consumer = origin.consume();
6681
6682		let _broadcast = origin.create_broadcast("live", announce()).unwrap();
6683		settle().await;
6684
6685		let got = consumer.request_broadcast("live").await.unwrap();
6686		assert!(
6687			got.is_clone(&consumer.get_broadcast("live").unwrap()),
6688			"should return the published broadcast"
6689		);
6690		assert!(
6691			dynamic.requested_broadcast().now_or_never().is_none(),
6692			"a published path must not queue a fallback request"
6693		);
6694	}
6695
6696	// Cloning a handler and dropping the clone must not flip the count to zero.
6697	#[tokio::test(start_paused = true)]
6698	async fn dynamic_clone_keeps_alive() {
6699		let origin = Origin::random().produce();
6700		let dynamic = origin.dynamic();
6701		let consumer = origin.consume();
6702
6703		drop(dynamic.clone());
6704
6705		// The original handle is still live, so the request registers (stays pending)
6706		// instead of resolving Unroutable.
6707		let request_fut = consumer.request_broadcast("fallback");
6708		assert!(
6709			request_fut.now_or_never().is_none(),
6710			"request should stay pending until served"
6711		);
6712	}
6713}