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