Skip to main content

subc_client_rs/
consumer.rs

1use std::{
2    collections::HashMap,
3    error::Error,
4    fmt,
5    future::Future,
6    io,
7    path::{Path, PathBuf},
8    pin::Pin,
9    sync::{
10        atomic::{AtomicBool, AtomicU64, Ordering},
11        Arc, Mutex, MutexGuard,
12    },
13    task::{Context, Poll},
14    time::Duration,
15};
16
17use subc_control::{
18    CatalogEntry, ClientControlRequest, ClientControlResponse, ConsumerIdentity, PollKind,
19};
20use subc_protocol::{
21    AdmissionClass, BindIdentity, ErrorBody, Flags, Frame, FrameBuildError, FrameType, Priority,
22    RouteTarget, SUBC_LAUNCH_NONCE_ENV, SUBC_MODULE_ID_ENV,
23};
24
25use crate::RouteHandle;
26use subc_transport::{
27    authenticate_client, connection_file, read_frame, write_frame, AuthError, ConnectionFileError,
28    FrameIoError,
29};
30use tokio::{
31    io::{AsyncWrite, AsyncWriteExt, BufWriter},
32    net::{tcp::OwnedReadHalf, TcpStream},
33    sync::{mpsc, oneshot, Notify, OwnedSemaphorePermit, Semaphore},
34    task::JoinHandle,
35    time::{sleep, timeout_at, Instant},
36};
37use tokio_util::sync::CancellationToken;
38
39const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(2);
40const DEFAULT_CALL_TIMEOUT: Duration = Duration::from_secs(30);
41// Sized against the daemon's route.bind relay timeout (12s default): one
42// load-stalled bind relay burns ~12s before the daemon rejects with
43// module_timeout, so a 10s budget could be exhausted by a SINGLE slow bind.
44// 30s leaves room for ~2 full relay waits plus backoff (still clamped by the
45// overall call timeout below).
46const DEFAULT_ROUTE_RETRY_DEADLINE: Duration = Duration::from_secs(30);
47const DEFAULT_RESTORED_DEBOUNCE: Duration = Duration::from_millis(250);
48const EGRESS_BUFFER: usize = 128;
49const DEFAULT_ROUTE_WINDOW: usize = 1024;
50const DEFAULT_SUBSCRIPTION_EVENT_BUFFER: usize = 128;
51const DEFAULT_PUSH_EVENT_BUFFER: usize = 128;
52
53/// Capped exponential backoff used for reconnects and transient route-open retry.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct RetryBackoff {
56    pub base: Duration,
57    pub cap: Duration,
58    /// Maximum attempts, including the first immediate attempt.
59    pub max_attempts: usize,
60}
61
62impl Default for RetryBackoff {
63    fn default() -> Self {
64        Self {
65            base: Duration::from_millis(100),
66            cap: Duration::from_secs(2),
67            max_attempts: 6,
68        }
69    }
70}
71
72impl RetryBackoff {
73    fn delay_after_attempt(self, attempt: usize) -> Duration {
74        let mut delay = self.base;
75        for _ in 1..attempt {
76            delay = (delay * 2).min(self.cap);
77        }
78        delay
79    }
80}
81
82/// Options for [`SubcConsumer::connect`].
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ConsumerOptions {
85    pub handshake_timeout: Duration,
86    /// Deadline for channel-0 calls that do not take per-call options.
87    pub call_timeout: Duration,
88    pub reconnect_backoff: RetryBackoff,
89    pub restored_debounce: Duration,
90}
91
92impl Default for ConsumerOptions {
93    fn default() -> Self {
94        Self {
95            handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT,
96            call_timeout: DEFAULT_CALL_TIMEOUT,
97            reconnect_backoff: RetryBackoff::default(),
98            restored_debounce: DEFAULT_RESTORED_DEBOUNCE,
99        }
100    }
101}
102
103/// Options for [`SubcConsumer::close_route`].
104#[derive(Debug, Clone)]
105pub struct CloseRouteOptions {
106    /// Await in-flight unary requests on the route to settle naturally before tearing
107    /// it down. Defaults to false: close immediately, settling anything in flight as
108    /// at-most-once failures (outcome_unknown if already sent, not_sent otherwise).
109    pub drain: bool,
110    /// Upper bound on the drain wait (ignored when `drain` is false).
111    pub drain_timeout: Duration,
112    /// Override for the consumer identity used to locate the route being closed;
113    /// when absent, SUBC_MODULE_ID and SUBC_LAUNCH_NONCE environment variables
114    /// identify the route for a supervised consumer.
115    pub consumer_identity: Option<ConsumerIdentity>,
116    /// Consumer-declared reverse-request capabilities for the route being closed.
117    /// This is a declaration, not a verified privilege; providers treat an absent
118    /// field as no reverse-request capability. Known MCP method-family values
119    /// today are "elicitation", "sampling", and "roots".
120    pub consumer_capabilities: Option<Vec<String>>,
121}
122
123impl Default for CloseRouteOptions {
124    fn default() -> Self {
125        Self {
126            drain: false,
127            drain_timeout: DEFAULT_CALL_TIMEOUT,
128            consumer_identity: None,
129            consumer_capabilities: None,
130        }
131    }
132}
133
134/// Per-call options for [`SubcConsumer::call`].
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct CallOptions {
137    /// Deadline for the whole managed call, including route-open retry and the response wait.
138    pub timeout: Duration,
139    pub priority: Priority,
140    /// Admission behavior stamped into the request frame. Defaults to NORMAL.
141    pub admission_class: AdmissionClass,
142    pub route_retry: RetryBackoff,
143    /// Maximum real-time limit for retrying route.open attempts when the target is temporarily absent.
144    pub route_retry_deadline: Duration,
145    /// Explicit consumer identity for route.open; when absent, non-empty SUBC_MODULE_ID and SUBC_LAUNCH_NONCE environment variables are used.
146    pub consumer_identity: Option<ConsumerIdentity>,
147    /// Consumer-declared reverse-request capabilities for route.open. This is a
148    /// declaration, not a verified privilege; providers treat an absent field as
149    /// no reverse-request capability. Known MCP method-family values today are
150    /// "elicitation", "sampling", and "roots".
151    pub consumer_capabilities: Option<Vec<String>>,
152}
153
154impl Default for CallOptions {
155    fn default() -> Self {
156        Self {
157            timeout: DEFAULT_CALL_TIMEOUT,
158            priority: Priority::Interactive,
159            admission_class: AdmissionClass::Normal,
160            route_retry: RetryBackoff::default(),
161            route_retry_deadline: DEFAULT_ROUTE_RETRY_DEADLINE,
162            consumer_identity: None,
163            consumer_capabilities: None,
164        }
165    }
166}
167
168/// Options for [`SubcConsumer::subscribe`].
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct SubscribeOptions {
171    pub priority: Priority,
172    /// Admission behavior stamped into the subscription request. Defaults to NORMAL.
173    pub admission_class: AdmissionClass,
174    /// Maximum number of events buffered for the caller before the subscription is dropped.
175    /// The reader task never awaits a slow event consumer; if this bounded channel fills,
176    /// `closed()` resolves with [`CallError::SubscriptionBackpressure`].
177    pub event_buffer: usize,
178    pub route_retry: RetryBackoff,
179    /// Maximum real-time limit for retrying route.open attempts when the target is temporarily absent.
180    pub route_retry_deadline: Duration,
181    /// Deadline for opening the managed route and queuing the held-open request.
182    /// The subscription itself has no response timeout once the request is sent.
183    pub route_open_timeout: Duration,
184    /// Explicit consumer identity for route.open; when absent, non-empty SUBC_MODULE_ID and SUBC_LAUNCH_NONCE environment variables are used.
185    pub consumer_identity: Option<ConsumerIdentity>,
186    /// Consumer-declared reverse-request capabilities for route.open. This is a
187    /// declaration, not a verified privilege; providers treat an absent field as
188    /// no reverse-request capability. Known MCP method-family values today are
189    /// "elicitation", "sampling", and "roots".
190    pub consumer_capabilities: Option<Vec<String>>,
191}
192
193impl Default for SubscribeOptions {
194    fn default() -> Self {
195        Self {
196            priority: Priority::Interactive,
197            admission_class: AdmissionClass::Normal,
198            event_buffer: DEFAULT_SUBSCRIPTION_EVENT_BUFFER,
199            route_retry: RetryBackoff::default(),
200            route_retry_deadline: DEFAULT_ROUTE_RETRY_DEADLINE,
201            route_open_timeout: DEFAULT_CALL_TIMEOUT,
202            consumer_identity: None,
203            consumer_capabilities: None,
204        }
205    }
206}
207
208/// Minimal connection lifecycle signal. It is useful for logging and route-cache invalidation,
209/// but callers must not use the consumer epoch as proof that a target provider is current.
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub enum ConnectionState {
212    Dropped,
213    Restored { epoch: u64 },
214}
215
216/// Result of a route-scoped status or liveness poll.
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct RoutePollResult {
219    pub handle: RouteHandle,
220    pub status: Option<String>,
221    pub live: Option<bool>,
222}
223
224/// Typed response from the daemon's channel-0 `catalog.list` operation.
225///
226/// Each module entry exposes the provider roles and tool definitions advertised by
227/// that module.
228#[derive(Debug, Clone, serde::Deserialize, PartialEq)]
229pub struct CatalogList {
230    pub generation: u64,
231    #[serde(default)]
232    pub modules: Vec<CatalogEntry>,
233    #[serde(default)]
234    pub subc_ops: Vec<String>,
235}
236
237/// A provider-originated push delivered on one live route.
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct PushEvent {
240    /// The connection-fenced route identity on which the push arrived.
241    pub handle: RouteHandle,
242    /// Opaque payload bytes sent by the provider.
243    pub body: Vec<u8>,
244}
245
246/// Managed Rust consumer for subc route calls.
247pub struct SubcConsumer {
248    shared: Arc<Shared>,
249}
250
251/// A live subscription to a provider event stream.
252///
253/// The event receiver yields each `StreamData` payload for the held-open request's
254/// correlation id. Await [`Subscription::closed`] to learn whether the provider ended
255/// the stream cleanly (`StreamEnd`) or the stream was rejected by an Error frame,
256/// route GOODBYE, connection drop, or local backpressure. Dropping the subscription
257/// sends a best-effort Cancel frame, the same as calling [`Subscription::unsubscribe`].
258pub struct Subscription {
259    events: mpsc::Receiver<Vec<u8>>,
260    closed: SubscriptionClosed,
261    cancel: SubscriptionCancel,
262}
263
264impl Subscription {
265    /// Receive event payloads emitted as `StreamData` frames for this subscription.
266    pub fn events(&mut self) -> &mut mpsc::Receiver<Vec<u8>> {
267        &mut self.events
268    }
269
270    /// Future that resolves when the subscription reaches a terminal state.
271    ///
272    /// It resolves with `Ok(())` on `StreamEnd` or local unsubscribe, and returns a
273    /// [`CallError`] for module Error frames, route teardown, connection loss, or
274    /// event-channel backpressure. Await it after the event receiver returns `None`
275    /// to distinguish a clean end from an error.
276    pub fn closed(&mut self) -> &mut SubscriptionClosed {
277        &mut self.closed
278    }
279
280    /// Cancel the held-open request.
281    ///
282    /// This sends a best-effort header-only Cancel frame for the subscription's
283    /// `(channel, corr)` and settles [`Subscription::closed`] promptly with `Ok(())`.
284    /// The provider may still send a terminal frame later; it is ignored because the
285    /// local subscription is already closed.
286    pub fn unsubscribe(&self) -> Result<(), CallError> {
287        self.cancel.unsubscribe()
288    }
289}
290
291impl Drop for Subscription {
292    fn drop(&mut self) {
293        let _ = self.cancel.unsubscribe();
294    }
295}
296
297/// Future returned by [`Subscription::closed`].
298pub struct SubscriptionClosed {
299    rx: oneshot::Receiver<Result<(), CallError>>,
300}
301
302impl Unpin for SubscriptionClosed {}
303
304impl Future for SubscriptionClosed {
305    type Output = Result<(), CallError>;
306
307    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
308        let this = self.get_mut();
309        match Pin::new(&mut this.rx).poll(cx) {
310            Poll::Ready(Ok(result)) => Poll::Ready(result),
311            Poll::Ready(Err(_)) => Poll::Ready(Err(CallError::outcome_unknown(
312                "subscription closed result channel dropped",
313            ))),
314            Poll::Pending => Poll::Pending,
315        }
316    }
317}
318
319struct SubscriptionCancel {
320    shared: Arc<Shared>,
321    key: PendingKey,
322    priority: Priority,
323    cancelled: AtomicBool,
324}
325
326impl SubscriptionCancel {
327    fn new(shared: Arc<Shared>, key: PendingKey, priority: Priority) -> Self {
328        Self {
329            shared,
330            key,
331            priority,
332            cancelled: AtomicBool::new(false),
333        }
334    }
335
336    fn unsubscribe(&self) -> Result<(), CallError> {
337        let handle = RouteHandle::new(self.key.channel, self.key.epoch, self.key.generation);
338        self.shared.validate_current_handle(handle)?;
339        if self.cancelled.swap(true, Ordering::AcqRel) {
340            return Ok(());
341        }
342        self.shared
343            .unsubscribe_subscription(self.key, self.priority)
344    }
345}
346
347impl SubcConsumer {
348    /// Connect, authenticate, and start the connection's I/O. The epoch starts at 1.
349    pub async fn connect(
350        connection_file: &Path,
351        opts: ConsumerOptions,
352    ) -> Result<Self, ConsumerError> {
353        let opened = open_connection(connection_file, opts.handshake_timeout).await?;
354        let shared = Arc::new(Shared::new(connection_file.to_path_buf(), opts));
355        shared.install_initial(opened)?;
356        Ok(Self { shared })
357    }
358
359    /// Open or reuse a managed route and return its connection-fenced handle.
360    pub async fn open_route(
361        &self,
362        target: RouteTarget,
363        identity: BindIdentity,
364        opts: CallOptions,
365    ) -> Result<RouteHandle, CallError> {
366        let deadline = Instant::now() + opts.timeout;
367        let consumer_identity = route_open_consumer_identity(&opts);
368        let consumer_capabilities = route_open_consumer_capabilities(&opts);
369        let key = RouteKey::new(
370            &target,
371            &identity,
372            consumer_identity.as_ref(),
373            consumer_capabilities.as_deref(),
374        );
375        let params = RouteOpenParams {
376            target: &target,
377            identity: &identity,
378            consumer_identity: &consumer_identity,
379            consumer_capabilities: &consumer_capabilities,
380        };
381        self.shared
382            .ensure_route(&key, &params, &opts, deadline)
383            .await
384            .map(|route| route.handle)
385    }
386
387    /// Open one admitted route without entering the managed route cache.
388    ///
389    /// Admitted routes are never cached or reopened after a connection drop. If
390    /// this call fails or the route later closes, the caller must perform admission
391    /// again and call this method with fresh facts.
392    pub async fn open_route_with_admission_facts(
393        &self,
394        target: RouteTarget,
395        identity: BindIdentity,
396        facts: serde_json::Value,
397    ) -> Result<RouteHandle, CallError> {
398        let deadline = Instant::now() + self.shared.opts.call_timeout;
399        let opts = CallOptions::default();
400        let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
401            target,
402            identity,
403            consumer_identity: route_open_consumer_identity(&opts),
404            consumer_capabilities: None,
405            admission_facts: Some(facts),
406        })
407        .map_err(|err| CallError::not_sent(format!("failed to encode route.open: {err}")))?;
408
409        let terminal = self.shared.control_call(body, deadline, true).await?;
410        let TerminalFrame::Response {
411            generation, body, ..
412        } = terminal
413        else {
414            return Err(CallError::not_sent(
415                "route.open returned a non-response frame",
416            ));
417        };
418        let ClientControlResponse::RouteOpen {
419            route_channel,
420            route_epoch,
421        } = serde_json::from_slice(&body).map_err(|err| {
422            CallError::not_sent(format!("failed to decode route.open response: {err}"))
423        })?
424        else {
425            return Err(CallError::not_sent(
426                "route.open returned an unexpected control response",
427            ));
428        };
429        let route = RouteState {
430            handle: RouteHandle::new(route_channel, route_epoch, generation),
431            sem: Arc::new(Semaphore::new(DEFAULT_ROUTE_WINDOW)),
432        };
433        self.shared.install_one_shot_route(route.clone())?;
434        Ok(route.handle)
435    }
436
437    /// Fetch the daemon's module catalog over channel 0.
438    pub async fn catalog_list(&self) -> Result<CatalogList, CallError> {
439        let deadline = Instant::now() + self.shared.opts.call_timeout;
440        let body = serde_json::to_vec(&serde_json::json!({
441            "op": subc_control::ops::CATALOG_LIST,
442        }))
443        .map_err(|err| CallError::not_sent(format!("failed to encode catalog.list: {err}")))?;
444
445        loop {
446            match self
447                .shared
448                .control_call(body.clone(), deadline, false)
449                .await
450            {
451                Ok(TerminalFrame::Response { body, .. }) => {
452                    let response =
453                        serde_json::from_slice::<ClientControlResponse>(&body).map_err(|err| {
454                            CallError::not_sent(format!(
455                                "failed to decode catalog.list response: {err}"
456                            ))
457                        })?;
458                    let ClientControlResponse::CatalogList {
459                        generation,
460                        modules,
461                        subc_ops,
462                    } = response
463                    else {
464                        return Err(CallError::not_sent(
465                            "catalog.list returned an unexpected control response",
466                        ));
467                    };
468                    return Ok(CatalogList {
469                        generation,
470                        modules,
471                        subc_ops,
472                    });
473                }
474                Ok(TerminalFrame::Error { body }) => return Err(CallError::Module(body)),
475                Ok(TerminalFrame::StreamEnd) => {
476                    return Err(CallError::not_sent("catalog.list returned StreamEnd"));
477                }
478                Err(err)
479                    if is_retryable_catalog_transport_error(&err) && Instant::now() < deadline =>
480                {
481                    continue;
482                }
483                Err(err) => return Err(err),
484            }
485        }
486    }
487
488    /// Send one request using an already-opened route handle.
489    pub async fn request(
490        &self,
491        handle: &RouteHandle,
492        body: Vec<u8>,
493        opts: CallOptions,
494    ) -> Result<Vec<u8>, CallError> {
495        let deadline = Instant::now() + opts.timeout;
496        let route = self.shared.route_state(*handle)?;
497        let permit = match timeout_at(deadline, Arc::clone(&route.sem).acquire_owned()).await {
498            Ok(Ok(permit)) => permit,
499            Ok(Err(_)) => return Err(CallError::StaleRouteHandle(*handle)),
500            Err(_) => {
501                return Err(CallError::not_sent(
502                    "call deadline elapsed waiting for route flow-control",
503                ))
504            }
505        };
506        let result = self
507            .shared
508            .send_request(RequestSend {
509                expected_handle: Some(*handle),
510                channel: handle.channel,
511                epoch: handle.epoch,
512                body,
513                priority: opts.priority,
514                admission_class: opts.admission_class,
515                deadline,
516                retain_late_route_open: false,
517            })
518            .await;
519        drop(permit);
520        match result? {
521            TerminalFrame::Response { body, .. } => Ok(body),
522            TerminalFrame::StreamEnd => Ok(Vec::new()),
523            TerminalFrame::Error { body } => Err(CallError::Module(body)),
524        }
525    }
526
527    /// Start a held-open request using an already-opened route handle.
528    pub async fn subscribe_route(
529        &self,
530        handle: &RouteHandle,
531        body: Vec<u8>,
532        opts: SubscribeOptions,
533    ) -> Result<Subscription, CallError> {
534        let deadline = Instant::now() + opts.route_open_timeout;
535        let route = self.shared.route_state(*handle)?;
536        let permit = match timeout_at(deadline, Arc::clone(&route.sem).acquire_owned()).await {
537            Ok(Ok(permit)) => permit,
538            Ok(Err(_)) => return Err(CallError::StaleRouteHandle(*handle)),
539            Err(_) => {
540                return Err(CallError::not_sent(
541                    "subscription deadline elapsed waiting for route flow-control",
542                ))
543            }
544        };
545        self.shared
546            .send_subscription(SubscriptionSend {
547                expected_handle: Some(*handle),
548                channel: handle.channel,
549                epoch: handle.epoch,
550                body,
551                priority: opts.priority,
552                admission_class: opts.admission_class,
553                event_buffer: opts.event_buffer,
554                deadline,
555                permit,
556            })
557            .await
558    }
559
560    /// Poll status or liveness for exactly this route handle.
561    pub async fn poll_route(
562        &self,
563        handle: &RouteHandle,
564        kind: PollKind,
565        timeout: Duration,
566    ) -> Result<RoutePollResult, CallError> {
567        let deadline = Instant::now() + timeout;
568        let body = serde_json::to_vec(&ClientControlRequest::RoutePoll {
569            route_channel: handle.channel,
570            route_epoch: handle.epoch,
571            kind,
572        })
573        .map_err(|err| CallError::not_sent(format!("failed to encode route.poll: {err}")))?;
574        let terminal = self
575            .shared
576            .send_request(RequestSend {
577                expected_handle: Some(*handle),
578                channel: 0,
579                epoch: 0,
580                body,
581                priority: Priority::Interactive,
582                admission_class: AdmissionClass::Normal,
583                deadline,
584                retain_late_route_open: false,
585            })
586            .await?;
587        let TerminalFrame::Response { body, .. } = terminal else {
588            return Err(CallError::not_sent(
589                "route.poll returned a non-response frame",
590            ));
591        };
592        let ClientControlResponse::RoutePoll {
593            route_channel,
594            route_epoch,
595            status,
596            live,
597        } = serde_json::from_slice(&body)
598            .map_err(|err| CallError::not_sent(format!("failed to decode route.poll: {err}")))?
599        else {
600            return Err(CallError::not_sent(
601                "route.poll returned an unexpected control response",
602            ));
603        };
604        if route_channel != handle.channel || route_epoch != handle.epoch {
605            return Err(CallError::not_sent(
606                "route.poll response echoed a different route handle",
607            ));
608        }
609        self.shared.validate_current_handle(*handle)?;
610        Ok(RoutePollResult {
611            handle: *handle,
612            status,
613            live,
614        })
615    }
616
617    /// Locally observed count of unknown or stale route frames dropped by layer-2 validation.
618    pub fn dropped_route_frames(&self) -> u64 {
619        self.shared.lock_inner().dropped_route_frames
620    }
621
622    /// Register a receiver for provider-originated Push frames on exactly one live route.
623    ///
624    /// Registering another receiver for the same route replaces and closes the prior receiver.
625    /// The receiver closes when its route closes, the connection drops, or its bounded buffer
626    /// fills; the reader never waits for an application that is not draining pushes.
627    pub fn push_events(
628        &self,
629        handle: &RouteHandle,
630    ) -> Result<mpsc::Receiver<PushEvent>, CallError> {
631        self.shared.register_push_events(*handle)
632    }
633
634    /// Number of Push frames dropped because their live route has no active receiver.
635    ///
636    /// Push is a one-way latency optimization, not a durable feed: the client does not
637    /// acknowledge it, and callers retain polling as their correctness backstop. Counting
638    /// intentional default-path drops makes an application that has not opted in observable.
639    pub fn pushes_dropped_no_receiver(&self) -> u64 {
640        self.shared
641            .pushes_dropped_no_receiver
642            .load(Ordering::Relaxed)
643    }
644
645    /// Managed unary call. Route-open failures happen before the body is sent and are
646    /// classified as `NotSent`; module handler Error frames are the only `Module` errors.
647    pub async fn call(
648        &self,
649        target: RouteTarget,
650        identity: BindIdentity,
651        body: Vec<u8>,
652        opts: CallOptions,
653    ) -> Result<Vec<u8>, CallError> {
654        let call_deadline = Instant::now() + opts.timeout;
655        let mut retried_unknown_channel = false;
656        let consumer_identity = route_open_consumer_identity(&opts);
657        let consumer_capabilities = route_open_consumer_capabilities(&opts);
658        let route_key = RouteKey::new(
659            &target,
660            &identity,
661            consumer_identity.as_ref(),
662            consumer_capabilities.as_deref(),
663        );
664
665        let route_open = RouteOpenParams {
666            target: &target,
667            identity: &identity,
668            consumer_identity: &consumer_identity,
669            consumer_capabilities: &consumer_capabilities,
670        };
671
672        loop {
673            let route = self
674                .shared
675                .ensure_route(&route_key, &route_open, &opts, call_deadline)
676                .await?;
677            let permit =
678                match timeout_at(call_deadline, Arc::clone(&route.sem).acquire_owned()).await {
679                    Ok(Ok(permit)) => permit,
680                    Ok(Err(_)) => {
681                        return Err(CallError::not_sent("route flow-control semaphore closed"));
682                    }
683                    Err(_) => {
684                        return Err(CallError::not_sent(
685                            "call deadline elapsed waiting for route flow-control",
686                        ));
687                    }
688                };
689
690            if !self.shared.route_is_current(&route_key, &route) {
691                drop(permit);
692                self.shared
693                    .sleep_until_retry(call_deadline, opts.route_retry.base)
694                    .await?;
695                continue;
696            }
697
698            let response = self
699                .shared
700                .send_request(RequestSend {
701                    expected_handle: Some(route.handle),
702                    channel: route.handle.channel,
703                    epoch: route.handle.epoch,
704                    body: body.clone(),
705                    priority: opts.priority,
706                    admission_class: opts.admission_class,
707                    deadline: call_deadline,
708                    retain_late_route_open: false,
709                })
710                .await;
711            drop(permit);
712
713            match response {
714                Ok(TerminalFrame::Response { body, .. }) => return Ok(body),
715                Ok(TerminalFrame::StreamEnd) => return Ok(Vec::new()),
716                // unknown_channel is the daemon ROUTER refusing an unrouted channel:
717                // the request provably never reached a module, so one in-place retry
718                // cannot double-execute. The cached bind is dead (module restarted;
719                // its route-gone GOODBYE raced or was missed) — invalidate it so the
720                // retry re-opens instead of resending into the same dead channel.
721                // Parity with the TS client's retry-once in call().
722                Ok(TerminalFrame::Error { body, .. })
723                    if body.code == "unknown_channel"
724                        && !retried_unknown_channel
725                        && Instant::now() < call_deadline =>
726                {
727                    retried_unknown_channel = true;
728                    self.shared.invalidate_route(&route_key, Some(route.handle));
729                    continue;
730                }
731                Ok(TerminalFrame::Error { body, .. }) => return Err(CallError::Module(body)),
732                Err(err) if err.is_not_sent() && Instant::now() < call_deadline => {
733                    self.shared.invalidate_route(&route_key, Some(route.handle));
734                    self.shared.ensure_connected_for_call(call_deadline).await?;
735                    continue;
736                }
737                Err(err) => return Err(err),
738            }
739        }
740    }
741
742    /// Open a held-open subscription on a managed route.
743    ///
744    /// This opens or reuses the same `(target, identity, consumer_identity, consumer_capabilities)` route as
745    /// [`SubcConsumer::call`], sends one Request that the provider keeps open, and
746    /// returns a [`Subscription`] whose event receiver yields each matching
747    /// `StreamData` payload. The request holds one route flow-control permit until
748    /// `StreamEnd`, an Error frame, route teardown, connection loss, local
749    /// backpressure, or [`Subscription::unsubscribe`]. Reconnects reject the
750    /// subscription; callers that need durable replay should resubscribe with their
751    /// own cursor after observing the failure.
752    pub async fn subscribe(
753        &self,
754        target: RouteTarget,
755        identity: BindIdentity,
756        body: Vec<u8>,
757        opts: SubscribeOptions,
758    ) -> Result<Subscription, CallError> {
759        let open_deadline = Instant::now() + opts.route_open_timeout;
760        let route_opts = CallOptions {
761            timeout: opts.route_open_timeout,
762            priority: opts.priority,
763            admission_class: opts.admission_class,
764            route_retry: opts.route_retry,
765            route_retry_deadline: opts.route_retry_deadline,
766            consumer_identity: opts.consumer_identity.clone(),
767            consumer_capabilities: opts.consumer_capabilities.clone(),
768        };
769        let consumer_identity = route_open_consumer_identity(&route_opts);
770        let consumer_capabilities = route_open_consumer_capabilities(&route_opts);
771        let route_key = RouteKey::new(
772            &target,
773            &identity,
774            consumer_identity.as_ref(),
775            consumer_capabilities.as_deref(),
776        );
777
778        let route_open = RouteOpenParams {
779            target: &target,
780            identity: &identity,
781            consumer_identity: &consumer_identity,
782            consumer_capabilities: &consumer_capabilities,
783        };
784
785        loop {
786            let route = self
787                .shared
788                .ensure_route(&route_key, &route_open, &route_opts, open_deadline)
789                .await?;
790            let permit =
791                match timeout_at(open_deadline, Arc::clone(&route.sem).acquire_owned()).await {
792                    Ok(Ok(permit)) => permit,
793                    Ok(Err(_)) => {
794                        return Err(CallError::not_sent("route flow-control semaphore closed"));
795                    }
796                    Err(_) => {
797                        return Err(CallError::not_sent(
798                            "subscription open deadline elapsed waiting for route flow-control",
799                        ));
800                    }
801                };
802
803            if !self.shared.route_is_current(&route_key, &route) {
804                drop(permit);
805                self.shared
806                    .sleep_until_retry(open_deadline, opts.route_retry.base)
807                    .await?;
808                continue;
809            }
810
811            match self
812                .shared
813                .send_subscription(SubscriptionSend {
814                    expected_handle: Some(route.handle),
815                    channel: route.handle.channel,
816                    epoch: route.handle.epoch,
817                    body: body.clone(),
818                    priority: opts.priority,
819                    admission_class: opts.admission_class,
820                    event_buffer: opts.event_buffer,
821                    deadline: open_deadline,
822                    permit,
823                })
824                .await
825            {
826                Ok(subscription) => return Ok(subscription),
827                Err(err) if err.is_not_sent() && Instant::now() < open_deadline => {
828                    self.shared.invalidate_route(&route_key, Some(route.handle));
829                    self.shared.ensure_connected_for_call(open_deadline).await?;
830                    continue;
831                }
832                Err(err) => return Err(err),
833            }
834        }
835    }
836
837    /// Tear down ONE route, keyed by its route-open identity tuple — the parity of the TS
838    /// client's `closeRoute`. For a long-lived consumer that opens unbounded distinct
839    /// routes (one per session), this releases a route on session-end without dropping
840    /// the whole consumer: it drops the cached route, settles in-flight requests on it
841    /// at-most-once (OutcomeUnknown if already sent, NotSent otherwise), and sends a
842    /// best-effort route GOODBYE so the daemon releases it and notifies the module.
843    ///
844    /// Idempotent: a no-op if the route was never opened or is already closed (callers
845    /// over-call on session-end). NOT a permanent tombstone — a later `call()` for the
846    /// same key opens a fresh route. The close-beats-reopen guard ensures a close that
847    /// races an in-flight route.open WINS (the opened channel is GOODBYE'd, not cached).
848    pub async fn close_route(
849        &self,
850        target: RouteTarget,
851        identity: BindIdentity,
852        opts: CloseRouteOptions,
853    ) {
854        let consumer_identity = close_route_consumer_identity(&opts);
855        let consumer_capabilities = close_route_consumer_capabilities(&opts);
856        let key = RouteKey::new(
857            &target,
858            &identity,
859            consumer_identity.as_ref(),
860            consumer_capabilities.as_deref(),
861        );
862        self.shared.close_route(&key, &opts).await;
863    }
864
865    /// Close exactly this route handle. A stale connection token fails locally and emits no frame.
866    pub async fn close_handle(
867        &self,
868        handle: &RouteHandle,
869        opts: CloseRouteOptions,
870    ) -> Result<(), CallError> {
871        self.shared.close_handle(*handle, &opts).await
872    }
873
874    /// Current transport epoch: 1 on initial connect, then +1 per successful reconnect.
875    pub fn current_epoch(&self) -> u64 {
876        self.shared.lock_inner().epoch
877    }
878
879    /// Register a connection-state callback. Callbacks are best-effort observability hooks.
880    pub fn on_connection_state(&self, cb: impl Fn(ConnectionState) + Send + 'static) {
881        self.shared
882            .lock_inner()
883            .callbacks
884            .push(Arc::new(Mutex::new(Box::new(cb))));
885    }
886
887    /// Close the consumer and settle every pending caller.
888    pub async fn close(&self) {
889        self.shared.close_sync("consumer closed");
890        tokio::task::yield_now().await;
891    }
892}
893
894impl Drop for SubcConsumer {
895    fn drop(&mut self) {
896        self.shared.close_sync("consumer dropped");
897    }
898}
899
900/// Error returned by [`SubcConsumer::connect`].
901#[derive(Debug)]
902pub enum ConsumerError {
903    ConnectionFile {
904        path: PathBuf,
905        source: ConnectionFileError,
906    },
907    NoEndpoint {
908        path: PathBuf,
909    },
910    Connect {
911        path: PathBuf,
912        endpoint: String,
913        source: io::Error,
914    },
915    Auth {
916        path: PathBuf,
917        endpoint: String,
918        source: AuthError,
919    },
920    Closed,
921}
922
923impl fmt::Display for ConsumerError {
924    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
925        match self {
926            Self::ConnectionFile { path, source } => write!(
927                f,
928                "failed to read subc connection file '{}': {source}",
929                path.display()
930            ),
931            Self::NoEndpoint { path } => {
932                write!(
933                    f,
934                    "subc connection file '{}' has no endpoints",
935                    path.display()
936                )
937            }
938            Self::Connect {
939                path,
940                endpoint,
941                source,
942            } => write!(
943                f,
944                "failed to connect to subc endpoint {endpoint} from '{}': {source}",
945                path.display()
946            ),
947            Self::Auth {
948                path,
949                endpoint,
950                source,
951            } => write!(
952                f,
953                "failed to authenticate to subc endpoint {endpoint} from '{}': {source}",
954                path.display()
955            ),
956            Self::Closed => write!(f, "consumer closed"),
957        }
958    }
959}
960
961impl Error for ConsumerError {
962    fn source(&self) -> Option<&(dyn Error + 'static)> {
963        match self {
964            Self::ConnectionFile { source, .. } => Some(source),
965            Self::Connect { source, .. } => Some(source),
966            Self::Auth { source, .. } => Some(source),
967            Self::NoEndpoint { .. } | Self::Closed => None,
968        }
969    }
970}
971
972/// Managed call or subscription failure.
973#[derive(Debug)]
974pub enum CallError {
975    /// The request body was not accepted by the writer path, or route.open failed before data send.
976    NotSent(Box<dyn Error + Send + Sync>),
977    /// The request body was accepted by the writer path, but no terminal response was observed.
978    OutcomeUnknown(Box<dyn Error + Send + Sync>),
979    /// The target module handler returned an Error frame. Application-level rejections
980    /// are returned as ordinary successful response bytes and do not produce this variant.
981    Module(ErrorBody),
982    /// A subscription event receiver stopped keeping up with its bounded channel.
983    ///
984    /// The reader task must never await a slow consumer while it is dispatching frames
985    /// for the whole connection, so a full event channel terminates only that subscription.
986    SubscriptionBackpressure(Box<dyn Error + Send + Sync>),
987    /// The handle belongs to an earlier connection and no frame was emitted.
988    StaleRouteHandle(RouteHandle),
989}
990
991impl CallError {
992    fn not_sent(reason: impl Into<String>) -> Self {
993        Self::NotSent(Box::new(SimpleError(reason.into())))
994    }
995
996    fn outcome_unknown(reason: impl Into<String>) -> Self {
997        Self::OutcomeUnknown(Box::new(SimpleError(reason.into())))
998    }
999
1000    fn is_not_sent(&self) -> bool {
1001        matches!(self, Self::NotSent(_))
1002    }
1003
1004    fn subscription_backpressure(reason: impl Into<String>) -> Self {
1005        Self::SubscriptionBackpressure(Box::new(SimpleError(reason.into())))
1006    }
1007}
1008
1009impl fmt::Display for CallError {
1010    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1011        match self {
1012            Self::NotSent(err) => write!(f, "request not sent: {err}"),
1013            Self::OutcomeUnknown(err) => write!(f, "request outcome unknown: {err}"),
1014            Self::Module(body) => write!(f, "module error {}: {}", body.code, body.message),
1015            Self::SubscriptionBackpressure(err) => {
1016                write!(f, "subscription event channel backpressure: {err}")
1017            }
1018            Self::StaleRouteHandle(handle) => write!(f, "stale route handle: {handle:?}"),
1019        }
1020    }
1021}
1022
1023impl Error for CallError {
1024    fn source(&self) -> Option<&(dyn Error + 'static)> {
1025        match self {
1026            Self::NotSent(err)
1027            | Self::OutcomeUnknown(err)
1028            | Self::SubscriptionBackpressure(err) => Some(err.as_ref()),
1029            Self::Module(_) | Self::StaleRouteHandle(_) => None,
1030        }
1031    }
1032}
1033
1034#[derive(Debug)]
1035struct SimpleError(String);
1036
1037impl fmt::Display for SimpleError {
1038    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1039        f.write_str(&self.0)
1040    }
1041}
1042
1043impl Error for SimpleError {}
1044
1045type Callback = Arc<Mutex<Box<dyn Fn(ConnectionState) + Send + 'static>>>;
1046
1047type OpeningWaiter = oneshot::Sender<Result<RouteState, SharedCallFailure>>;
1048
1049/// An in-flight route.open for one key: the waiters parked behind the lead opener,
1050/// plus a `closed` flag a concurrent `close_route` flips so the lead opener refuses
1051/// to install its channel (the close-beats-reopen guard). The flag lives here, with
1052/// the in-flight open, so it vanishes when the open finishes — no lingering per-key
1053/// state to leak for a long-lived consumer with unbounded distinct routes.
1054struct Opening {
1055    waiters: Vec<OpeningWaiter>,
1056    closed: bool,
1057}
1058
1059struct Shared {
1060    connection_file: PathBuf,
1061    opts: ConsumerOptions,
1062    inner: Mutex<Inner>,
1063    notify: Notify,
1064    close_token: CancellationToken,
1065    pushes_dropped_no_receiver: AtomicU64,
1066}
1067
1068struct Inner {
1069    generation: u64,
1070    epoch: u64,
1071    next_corr: Option<u64>,
1072    writer: Option<mpsc::Sender<WriteCommand>>,
1073    pending: HashMap<PendingKey, PendingEntry>,
1074    routes: HashMap<RouteKey, RouteState>,
1075    route_by_channel: HashMap<u16, RouteKey>,
1076    one_shot_routes: HashMap<u16, RouteState>,
1077    route_epochs: HashMap<u16, RouteHandle>,
1078    push_event_receivers: HashMap<RouteHandle, mpsc::Sender<PushEvent>>,
1079    dropped_route_frames: u64,
1080    openings: HashMap<RouteKey, Opening>,
1081    callbacks: Vec<Callback>,
1082    closed: bool,
1083    reconnect: ReconnectState,
1084    restored_token: u64,
1085    reader_task: Option<JoinHandle<()>>,
1086    writer_task: Option<JoinHandle<()>>,
1087}
1088
1089impl Inner {
1090    fn cache_route(&mut self, key: RouteKey, route: RouteState) -> RouteState {
1091        let cached = self.routes.entry(key.clone()).or_insert(route).clone();
1092        let previous = self
1093            .route_by_channel
1094            .insert(cached.handle.channel, key.clone());
1095        debug_assert!(previous.as_ref().is_none_or(|previous| previous == &key));
1096        self.route_epochs
1097            .insert(cached.handle.channel, cached.handle);
1098        cached
1099    }
1100
1101    fn remove_route(&mut self, key: &RouteKey) -> Option<RouteState> {
1102        let route = self.routes.remove(key)?;
1103        let indexed = self.route_by_channel.remove(&route.handle.channel);
1104        debug_assert_eq!(indexed.as_ref(), Some(key));
1105        Some(route)
1106    }
1107
1108    fn remove_route_by_handle(&mut self, handle: RouteHandle) -> Option<RouteState> {
1109        if let Some(key) = self.route_by_channel.get(&handle.channel).cloned() {
1110            let matches = self
1111                .routes
1112                .get(&key)
1113                .is_some_and(|route| route.handle == handle);
1114            debug_assert!(matches);
1115            if matches {
1116                return self.remove_route(&key);
1117            }
1118        }
1119        self.one_shot_routes
1120            .get(&handle.channel)
1121            .is_some_and(|route| route.handle == handle)
1122            .then(|| self.one_shot_routes.remove(&handle.channel))
1123            .flatten()
1124    }
1125
1126    fn drain_routes(&mut self) -> Vec<RouteState> {
1127        self.route_by_channel.clear();
1128        self.routes
1129            .drain()
1130            .map(|(_, route)| route)
1131            .chain(self.one_shot_routes.drain().map(|(_, route)| route))
1132            .collect()
1133    }
1134
1135    fn close_routes(&mut self) {
1136        self.push_event_receivers.clear();
1137        for route in self.drain_routes() {
1138            route.sem.close();
1139        }
1140    }
1141}
1142
1143impl Shared {
1144    fn new(connection_file: PathBuf, opts: ConsumerOptions) -> Self {
1145        Self {
1146            connection_file,
1147            opts,
1148            inner: Mutex::new(Inner {
1149                generation: 1,
1150                epoch: 1,
1151                next_corr: Some(1),
1152                writer: None,
1153                pending: HashMap::new(),
1154                routes: HashMap::new(),
1155                route_by_channel: HashMap::new(),
1156                one_shot_routes: HashMap::new(),
1157                route_epochs: HashMap::new(),
1158                push_event_receivers: HashMap::new(),
1159                dropped_route_frames: 0,
1160                openings: HashMap::new(),
1161                callbacks: Vec::new(),
1162                closed: false,
1163                reconnect: ReconnectState::Idle,
1164                restored_token: 0,
1165                reader_task: None,
1166                writer_task: None,
1167            }),
1168            notify: Notify::new(),
1169            close_token: CancellationToken::new(),
1170            pushes_dropped_no_receiver: AtomicU64::new(0),
1171        }
1172    }
1173
1174    fn lock_inner(&self) -> MutexGuard<'_, Inner> {
1175        self.inner
1176            .lock()
1177            .unwrap_or_else(|poisoned| poisoned.into_inner())
1178    }
1179
1180    fn install_initial(self: &Arc<Self>, opened: OpenedConnection) -> Result<(), ConsumerError> {
1181        self.install_connection(opened, InstallKind::Initial)
1182            .map(|_| ())
1183    }
1184
1185    fn install_reconnected(
1186        self: &Arc<Self>,
1187        opened: OpenedConnection,
1188    ) -> Result<(u64, u64), ConsumerError> {
1189        let generation_epoch = self.install_connection(opened, InstallKind::Reconnect)?;
1190        self.notify.notify_waiters();
1191        Ok(generation_epoch)
1192    }
1193
1194    fn install_connection(
1195        self: &Arc<Self>,
1196        opened: OpenedConnection,
1197        kind: InstallKind,
1198    ) -> Result<(u64, u64), ConsumerError> {
1199        if self.close_token.is_cancelled() {
1200            return Err(ConsumerError::Closed);
1201        }
1202
1203        let (reader, writer) = opened.stream.into_split();
1204        let (tx, rx) = mpsc::channel(EGRESS_BUFFER);
1205        let (generation, epoch, old_reader, old_writer) = {
1206            let mut inner = self.lock_inner();
1207            if inner.closed {
1208                return Err(ConsumerError::Closed);
1209            }
1210            let (generation, epoch) = match kind {
1211                InstallKind::Initial => (inner.generation, inner.epoch),
1212                InstallKind::Reconnect => {
1213                    inner.generation = inner
1214                        .generation
1215                        .checked_add(1)
1216                        .ok_or(ConsumerError::Closed)?;
1217                    inner.epoch = inner.epoch.checked_add(1).ok_or(ConsumerError::Closed)?;
1218                    (inner.generation, inner.epoch)
1219                }
1220            };
1221            inner.close_routes();
1222            inner.route_epochs.clear();
1223            inner.next_corr = Some(1);
1224            inner.writer = Some(tx);
1225            (
1226                generation,
1227                epoch,
1228                inner.reader_task.take(),
1229                inner.writer_task.take(),
1230            )
1231        };
1232
1233        if let Some(handle) = old_reader {
1234            handle.abort();
1235        }
1236        if let Some(handle) = old_writer {
1237            handle.abort();
1238        }
1239
1240        let reader_shared = Arc::clone(self);
1241        let reader_task = tokio::spawn(async move {
1242            reader_loop(reader_shared, reader, generation).await;
1243        });
1244        let writer_shared = Arc::clone(self);
1245        let writer_task = tokio::spawn(async move {
1246            writer_loop(writer_shared, writer, rx, generation).await;
1247        });
1248
1249        {
1250            let mut inner = self.lock_inner();
1251            if inner.closed || inner.generation != generation {
1252                reader_task.abort();
1253                writer_task.abort();
1254                return Err(ConsumerError::Closed);
1255            }
1256            inner.reader_task = Some(reader_task);
1257            inner.writer_task = Some(writer_task);
1258        }
1259
1260        Ok((generation, epoch))
1261    }
1262
1263    async fn ensure_connected_for_call(
1264        self: &Arc<Self>,
1265        deadline: Instant,
1266    ) -> Result<(), CallError> {
1267        loop {
1268            if Instant::now() >= deadline {
1269                return Err(CallError::not_sent(
1270                    "call deadline elapsed waiting for reconnection",
1271                ));
1272            }
1273            let action = {
1274                let mut inner = self.lock_inner();
1275                if inner.closed {
1276                    return Err(CallError::not_sent("consumer closed"));
1277                }
1278                if inner.writer.is_some() {
1279                    return Ok(());
1280                }
1281                let generation = inner.generation;
1282                let reconnect_is_live = match &inner.reconnect {
1283                    ReconnectState::Idle => false,
1284                    ReconnectState::Inline { generation: active } => *active == generation,
1285                    ReconnectState::Background {
1286                        generation: active,
1287                        task,
1288                    } => *active == generation && !task.is_finished(),
1289                };
1290                if reconnect_is_live {
1291                    EnsureAction::Wait
1292                } else {
1293                    let stale_task =
1294                        match std::mem::replace(&mut inner.reconnect, ReconnectState::Idle) {
1295                            ReconnectState::Background { task, .. } => Some(task),
1296                            ReconnectState::Idle | ReconnectState::Inline { .. } => None,
1297                        };
1298                    inner.reconnect = ReconnectState::Inline { generation };
1299                    EnsureAction::Lead {
1300                        generation,
1301                        stale_task,
1302                    }
1303                }
1304            };
1305
1306            match action {
1307                EnsureAction::Wait => {
1308                    timeout_at(deadline, self.notify.notified())
1309                        .await
1310                        .map_err(|_| {
1311                            CallError::not_sent("call deadline elapsed waiting for reconnection")
1312                        })?;
1313                }
1314                EnsureAction::Lead {
1315                    generation,
1316                    stale_task,
1317                } => {
1318                    if let Some(handle) = stale_task {
1319                        handle.abort();
1320                    }
1321                    let mut guard = InlineReconnectGuard::new(Arc::clone(self), generation);
1322                    let result = timeout_at(deadline, self.reconnect_with_retry(generation)).await;
1323                    guard.finish();
1324                    return match result {
1325                        Ok(Ok(())) => Ok(()),
1326                        Ok(Err(err)) => Err(CallError::not_sent(err.to_string())),
1327                        Err(_) => Err(CallError::not_sent(
1328                            "call deadline elapsed waiting for reconnection",
1329                        )),
1330                    };
1331                }
1332            }
1333        }
1334    }
1335
1336    fn spawn_reconnect(self: &Arc<Self>, generation: u64) -> bool {
1337        // Reconnect ownership is fenced by the dropped transport generation. A
1338        // newer drop replaces an older attempt instead of letting that attempt
1339        // block recovery for the newer transport.
1340        let stale_task = {
1341            let mut inner = self.lock_inner();
1342            if inner.closed || inner.writer.is_some() || inner.generation != generation {
1343                return false;
1344            }
1345            let should_spawn = match &inner.reconnect {
1346                ReconnectState::Idle => true,
1347                ReconnectState::Inline { generation: active } => *active < generation,
1348                ReconnectState::Background {
1349                    generation: active,
1350                    task,
1351                } => *active < generation || (*active == generation && task.is_finished()),
1352            };
1353            if !should_spawn {
1354                return false;
1355            }
1356
1357            let stale_task = match std::mem::replace(&mut inner.reconnect, ReconnectState::Idle) {
1358                ReconnectState::Background { task, .. } => Some(task),
1359                ReconnectState::Idle | ReconnectState::Inline { .. } => None,
1360            };
1361            let shared = Arc::clone(self);
1362            let handle = tokio::spawn(async move {
1363                let _result = shared.reconnect_with_retry(generation).await;
1364                shared.finish_background_reconnect(generation);
1365            });
1366            inner.reconnect = ReconnectState::Background {
1367                generation,
1368                task: handle,
1369            };
1370            stale_task
1371        };
1372        if let Some(handle) = stale_task {
1373            handle.abort();
1374        }
1375        true
1376    }
1377
1378    async fn reconnect_with_retry(
1379        self: &Arc<Self>,
1380        reconnect_generation: u64,
1381    ) -> Result<(), ConsumerError> {
1382        let mut last_error: Option<ConsumerError> = None;
1383        for attempt in 1..=self.opts.reconnect_backoff.max_attempts {
1384            if self.close_token.is_cancelled() {
1385                return Err(ConsumerError::Closed);
1386            }
1387            if !self.reconnect_attempt_is_current(reconnect_generation) {
1388                return Ok(());
1389            }
1390
1391            match open_connection(&self.connection_file, self.opts.handshake_timeout).await {
1392                Ok(opened) => {
1393                    // A newer drop may have installed its own attempt while this
1394                    // connection was opening. Do not let the stale attempt replace it.
1395                    if !self.reconnect_attempt_is_current(reconnect_generation) {
1396                        return Ok(());
1397                    }
1398                    let (generation, epoch) = self.install_reconnected(opened)?;
1399                    self.schedule_restored(generation, epoch);
1400                    return Ok(());
1401                }
1402                Err(err) => {
1403                    let transient = is_reconnect_transient(&err);
1404                    last_error = Some(err);
1405                    if !transient || attempt >= self.opts.reconnect_backoff.max_attempts {
1406                        break;
1407                    }
1408                    let delay = self.opts.reconnect_backoff.delay_after_attempt(attempt);
1409                    tokio::select! {
1410                        () = self.close_token.cancelled() => return Err(ConsumerError::Closed),
1411                        () = sleep(delay) => {}
1412                    }
1413                }
1414            }
1415        }
1416        Err(last_error.unwrap_or(ConsumerError::Closed))
1417    }
1418
1419    fn reconnect_attempt_is_current(&self, generation: u64) -> bool {
1420        let inner = self.lock_inner();
1421        !inner.closed
1422            && inner.writer.is_none()
1423            && inner.generation == generation
1424            && matches!(
1425                &inner.reconnect,
1426                ReconnectState::Inline { generation: active }
1427                    | ReconnectState::Background {
1428                        generation: active,
1429                        ..
1430                    } if *active == generation
1431            )
1432    }
1433
1434    fn finish_inline_reconnect(&self, generation: u64) {
1435        let finished = {
1436            let mut inner = self.lock_inner();
1437            if matches!(
1438                &inner.reconnect,
1439                ReconnectState::Inline { generation: active } if *active == generation
1440            ) {
1441                inner.reconnect = ReconnectState::Idle;
1442                true
1443            } else {
1444                false
1445            }
1446        };
1447        if finished {
1448            self.notify.notify_waiters();
1449        }
1450    }
1451
1452    fn finish_background_reconnect(&self, generation: u64) {
1453        let completed_task = {
1454            let mut inner = self.lock_inner();
1455            if !matches!(
1456                &inner.reconnect,
1457                ReconnectState::Background { generation: active, .. } if *active == generation
1458            ) {
1459                None
1460            } else {
1461                match std::mem::replace(&mut inner.reconnect, ReconnectState::Idle) {
1462                    ReconnectState::Background { task, .. } => Some(task),
1463                    ReconnectState::Idle | ReconnectState::Inline { .. } => unreachable!(),
1464                }
1465            }
1466        };
1467        if completed_task.is_some() {
1468            drop(completed_task);
1469            self.notify.notify_waiters();
1470        }
1471    }
1472
1473    fn schedule_restored(self: &Arc<Self>, generation: u64, epoch: u64) {
1474        let token = {
1475            let mut inner = self.lock_inner();
1476            inner.restored_token = inner.restored_token.saturating_add(1);
1477            inner.restored_token
1478        };
1479        let shared = Arc::clone(self);
1480        tokio::spawn(async move {
1481            tokio::select! {
1482                () = shared.close_token.cancelled() => {}
1483                () = sleep(shared.opts.restored_debounce) => {
1484                    let should_emit = {
1485                        let inner = shared.lock_inner();
1486                        !inner.closed
1487                            && inner.generation == generation
1488                            && inner.epoch == epoch
1489                            && inner.restored_token == token
1490                            && inner.writer.is_some()
1491                    };
1492                    if should_emit {
1493                        shared.emit_connection_state(ConnectionState::Restored { epoch });
1494                    }
1495                }
1496            }
1497        });
1498    }
1499
1500    fn install_one_shot_route(&self, route: RouteState) -> Result<(), CallError> {
1501        let handle = route.handle;
1502        let mut inner = self.lock_inner();
1503        if inner.closed || inner.generation != handle.connection_token() || inner.writer.is_none() {
1504            return Err(CallError::StaleRouteHandle(handle));
1505        }
1506        if inner.route_epochs.contains_key(&handle.channel) {
1507            return Err(CallError::not_sent(
1508                "daemon returned a route channel already in use",
1509            ));
1510        }
1511        inner.one_shot_routes.insert(handle.channel, route);
1512        inner.route_epochs.insert(handle.channel, handle);
1513        Ok(())
1514    }
1515
1516    async fn ensure_route(
1517        self: &Arc<Self>,
1518        key: &RouteKey,
1519        route_open: &RouteOpenParams<'_>,
1520        opts: &CallOptions,
1521        call_deadline: Instant,
1522    ) -> Result<RouteState, CallError> {
1523        loop {
1524            let action = {
1525                let mut inner = self.lock_inner();
1526                if inner.closed {
1527                    return Err(CallError::not_sent("consumer closed"));
1528                }
1529                if let Some(route) = inner.routes.get(key) {
1530                    if route.handle.connection_token() == inner.generation && inner.writer.is_some()
1531                    {
1532                        return Ok(route.clone());
1533                    }
1534                }
1535                if let Some(opening) = inner.openings.get_mut(key) {
1536                    let (tx, rx) = oneshot::channel();
1537                    opening.waiters.push(tx);
1538                    RouteOpenAction::Wait(rx)
1539                } else {
1540                    inner.openings.insert(
1541                        key.clone(),
1542                        Opening {
1543                            waiters: Vec::new(),
1544                            closed: false,
1545                        },
1546                    );
1547                    RouteOpenAction::Lead
1548                }
1549            };
1550
1551            match action {
1552                RouteOpenAction::Wait(rx) => match timeout_at(call_deadline, rx).await {
1553                    Ok(Ok(Ok(route))) => return Ok(route),
1554                    Ok(Ok(Err(err))) => return Err(err.into_call_error()),
1555                    Ok(Err(_)) => continue,
1556                    Err(_) => {
1557                        return Err(CallError::not_sent(
1558                            "call deadline elapsed waiting for route.open",
1559                        ));
1560                    }
1561                },
1562                RouteOpenAction::Lead => {
1563                    let mut guard = OpeningGuard::new(Arc::clone(self), key.clone());
1564                    let result = self
1565                        .open_route_with_retry(key, route_open, opts, call_deadline)
1566                        .await
1567                        .map_err(SharedCallFailure::from);
1568                    guard.finish(result.clone());
1569                    return result.map_err(SharedCallFailure::into_call_error);
1570                }
1571            }
1572        }
1573    }
1574
1575    async fn open_route_with_retry(
1576        self: &Arc<Self>,
1577        key: &RouteKey,
1578        route_open: &RouteOpenParams<'_>,
1579        opts: &CallOptions,
1580        call_deadline: Instant,
1581    ) -> Result<RouteState, CallError> {
1582        let route_deadline = (Instant::now() + opts.route_retry_deadline).min(call_deadline);
1583        let mut attempt = 0usize;
1584        loop {
1585            attempt = attempt.saturating_add(1);
1586            let body = serde_json::to_vec(&ClientControlRequest::RouteOpen {
1587                target: route_open.target.clone(),
1588                identity: route_open.identity.clone(),
1589                consumer_identity: route_open.consumer_identity.clone(),
1590                consumer_capabilities: route_open.consumer_capabilities.clone(),
1591                admission_facts: None,
1592            })
1593            .map_err(|err| CallError::not_sent(format!("failed to encode route.open: {err}")))?;
1594            match self.control_call(body, route_deadline, true).await {
1595                Ok(TerminalFrame::Response {
1596                    generation, body, ..
1597                }) => {
1598                    let response =
1599                        serde_json::from_slice::<ClientControlResponse>(&body).map_err(|err| {
1600                            CallError::not_sent(format!(
1601                                "failed to decode route.open response: {err}"
1602                            ))
1603                        })?;
1604                    let ClientControlResponse::RouteOpen {
1605                        route_channel,
1606                        route_epoch,
1607                    } = response
1608                    else {
1609                        return Err(CallError::not_sent(
1610                            "route.open returned an unexpected control response",
1611                        ));
1612                    };
1613                    let route = RouteState {
1614                        handle: RouteHandle::new(route_channel, route_epoch, generation),
1615                        sem: Arc::new(Semaphore::new(DEFAULT_ROUTE_WINDOW)),
1616                    };
1617                    let install = {
1618                        let mut inner = self.lock_inner();
1619                        if inner.closed {
1620                            return Err(CallError::not_sent("consumer closed"));
1621                        }
1622                        // Close-beats-reopen guard: a close_route may have flipped this
1623                        // opening's `closed` flag WHILE this route.open was in flight. If
1624                        // so, close wins — do NOT cache the channel; GOODBYE it below and
1625                        // fail as NotSent (the route was closed before the open landed).
1626                        let closed_during_open = inner.openings.get(key).is_some_and(|o| o.closed);
1627                        if closed_during_open
1628                            || inner.generation != generation
1629                            || inner.writer.is_none()
1630                        {
1631                            RouteInstall::Discard {
1632                                closed: closed_during_open,
1633                            }
1634                        } else {
1635                            let cached = inner.cache_route(key.clone(), route.clone());
1636                            RouteInstall::Cached(cached)
1637                        }
1638                    };
1639                    match install {
1640                        RouteInstall::Cached(cached) => return Ok(cached),
1641                        RouteInstall::Discard { closed } => {
1642                            if closed {
1643                                // GOODBYE the channel we opened so the daemon/module don't
1644                                // leak it, then report the close as a NotSent failure.
1645                                self.send_route_goodbye(route.handle, true);
1646                                self.uninstall_route_handle(route.handle);
1647                                return Err(CallError::not_sent(
1648                                    "route was closed before route.open completed",
1649                                ));
1650                            }
1651                            // Stale generation / writer gone: fall through to retry.
1652                        }
1653                    }
1654                    self.sleep_until_retry(route_deadline, opts.route_retry.base)
1655                        .await?;
1656                }
1657                Ok(TerminalFrame::Error { body, .. }) => {
1658                    if is_retryable_route_open_code(&body.code)
1659                        && attempt < opts.route_retry.max_attempts
1660                        && Instant::now() < route_deadline
1661                    {
1662                        let delay = opts.route_retry.delay_after_attempt(attempt);
1663                        self.sleep_until_retry(route_deadline, delay).await?;
1664                        continue;
1665                    }
1666                    return Err(CallError::not_sent(format!(
1667                        "route.open failed for target {}: {} ({})",
1668                        key.target_label(),
1669                        body.code,
1670                        body.message
1671                    )));
1672                }
1673                Ok(TerminalFrame::StreamEnd) => {
1674                    return Err(CallError::not_sent("route.open returned StreamEnd"));
1675                }
1676                Err(err)
1677                    if err.is_not_sent()
1678                        && attempt < opts.route_retry.max_attempts
1679                        && Instant::now() < route_deadline =>
1680                {
1681                    let delay = opts.route_retry.delay_after_attempt(attempt);
1682                    self.sleep_until_retry(route_deadline, delay).await?;
1683                }
1684                Err(err) => return Err(err),
1685            }
1686        }
1687
1688        #[allow(unreachable_code)]
1689        Err(CallError::not_sent(format!(
1690            "route.open retry deadline elapsed for target {}",
1691            key.target_label()
1692        )))
1693    }
1694
1695    async fn sleep_until_retry(&self, deadline: Instant, delay: Duration) -> Result<(), CallError> {
1696        if Instant::now() >= deadline {
1697            return Err(CallError::not_sent("retry deadline elapsed"));
1698        }
1699        let bounded = delay.min(deadline.saturating_duration_since(Instant::now()));
1700        tokio::select! {
1701            () = self.close_token.cancelled() => Err(CallError::not_sent("consumer closed")),
1702            () = sleep(bounded) => Ok(()),
1703        }
1704    }
1705
1706    async fn control_call(
1707        self: &Arc<Self>,
1708        body: Vec<u8>,
1709        deadline: Instant,
1710        retain_late_route_open: bool,
1711    ) -> Result<TerminalFrame, CallError> {
1712        self.ensure_connected_for_call(deadline).await?;
1713        self.send_request(RequestSend {
1714            expected_handle: None,
1715            channel: 0,
1716            epoch: 0,
1717            body,
1718            priority: Priority::Interactive,
1719            admission_class: AdmissionClass::Normal,
1720            deadline,
1721            retain_late_route_open,
1722        })
1723        .await
1724    }
1725
1726    async fn send_request(
1727        self: &Arc<Self>,
1728        request: RequestSend,
1729    ) -> Result<TerminalFrame, CallError> {
1730        let RequestSend {
1731            expected_handle,
1732            channel,
1733            epoch,
1734            body,
1735            priority,
1736            admission_class,
1737            deadline,
1738            retain_late_route_open,
1739        } = request;
1740        if Instant::now() >= deadline {
1741            return Err(CallError::not_sent(
1742                "call deadline elapsed before request was sent",
1743            ));
1744        }
1745        let (generation, corr, writer) = {
1746            let mut inner = self.lock_inner();
1747            if inner.closed {
1748                return Err(CallError::not_sent("consumer closed"));
1749            }
1750            let generation = inner.generation;
1751            if let Some(expected) = expected_handle {
1752                let route_pair_matches =
1753                    channel == 0 || (expected.channel == channel && expected.epoch == epoch);
1754                if expected.connection_token() != generation
1755                    || !route_pair_matches
1756                    || inner.route_epochs.get(&expected.channel) != Some(&expected)
1757                {
1758                    return Err(CallError::StaleRouteHandle(expected));
1759                }
1760            }
1761            let Some(writer) = inner.writer.clone() else {
1762                return Err(CallError::not_sent("subc connection is down before send"));
1763            };
1764            let Some(corr) = inner.next_corr else {
1765                drop(inner);
1766                self.handle_generation_drop(
1767                    generation,
1768                    "channel-0 correlation allocator exhausted".to_string(),
1769                );
1770                return Err(CallError::not_sent(
1771                    "correlation allocator exhausted; connection closed",
1772                ));
1773            };
1774            inner.next_corr = corr.checked_add(1);
1775            (generation, corr, writer)
1776        };
1777
1778        let frame = Frame::build(
1779            FrameType::Request,
1780            Flags::new(false, priority, false).with_admission_class(admission_class),
1781            channel,
1782            epoch,
1783            corr,
1784            body,
1785        )
1786        .map_err(|err| CallError::not_sent(format!("failed to build request frame: {err}")))?;
1787        let key = PendingKey {
1788            generation,
1789            channel,
1790            epoch,
1791            corr,
1792        };
1793        let (tx, rx) = oneshot::channel();
1794        {
1795            let mut inner = self.lock_inner();
1796            if inner.closed || inner.generation != generation || inner.writer.is_none() {
1797                return Err(CallError::not_sent(
1798                    "connection changed before request registration",
1799                ));
1800            }
1801            if let Some(expected) = expected_handle {
1802                if inner.route_epochs.get(&expected.channel) != Some(&expected) {
1803                    return Err(CallError::StaleRouteHandle(expected));
1804                }
1805            }
1806            let expected_control_handle = (channel == 0 && !retain_late_route_open)
1807                .then_some(expected_handle)
1808                .flatten();
1809            inner.pending.insert(
1810                key,
1811                PendingEntry::unary(tx, retain_late_route_open, expected_control_handle),
1812            );
1813        }
1814        let mut registration =
1815            PendingRegistration::new(Arc::clone(self), key, retain_late_route_open);
1816
1817        match timeout_at(
1818            deadline,
1819            writer.send(WriteCommand {
1820                frame,
1821                pending: Some(key),
1822            }),
1823        )
1824        .await
1825        {
1826            Ok(Ok(())) => {}
1827            Ok(Err(_)) => {
1828                let accepted = registration.remove_pending().unwrap_or(false);
1829                return Err(classify_failure(
1830                    accepted,
1831                    "writer task closed before accepting request",
1832                ));
1833            }
1834            Err(_) => {
1835                let _ = registration.remove_pending();
1836                return Err(CallError::not_sent(
1837                    "call deadline elapsed waiting for writer capacity",
1838                ));
1839            }
1840        }
1841
1842        tokio::select! {
1843            result = timeout_at(deadline, rx) => match result {
1844                Ok(Ok(result)) => {
1845                    registration.disarm();
1846                    result.into_call_result()
1847                }
1848                Ok(Err(_)) => {
1849                    registration.disarm();
1850                    Err(CallError::not_sent("pending response channel closed"))
1851                }
1852                Err(_) => {
1853                    let accepted = if retain_late_route_open {
1854                        registration.disarm();
1855                        self.pending_accepted(key).unwrap_or(false)
1856                    } else {
1857                        registration.remove_pending().unwrap_or(false)
1858                    };
1859                    Err(classify_failure(
1860                        accepted,
1861                        format!("request on channel {channel} timed out at its deadline"),
1862                    ))
1863                }
1864            },
1865            () = self.close_token.cancelled() => {
1866                let accepted = registration.remove_pending().unwrap_or(false);
1867                Err(classify_failure(accepted, "consumer closed while request was pending"))
1868            }
1869        }
1870    }
1871
1872    async fn send_subscription(
1873        self: &Arc<Self>,
1874        subscription: SubscriptionSend,
1875    ) -> Result<Subscription, CallError> {
1876        let SubscriptionSend {
1877            expected_handle,
1878            channel,
1879            epoch,
1880            body,
1881            priority,
1882            admission_class,
1883            event_buffer,
1884            deadline,
1885            permit,
1886        } = subscription;
1887        if Instant::now() >= deadline {
1888            return Err(CallError::not_sent(
1889                "subscription deadline elapsed before request was sent",
1890            ));
1891        }
1892        let (generation, corr, writer) = {
1893            let mut inner = self.lock_inner();
1894            if inner.closed {
1895                return Err(CallError::not_sent("consumer closed"));
1896            }
1897            let generation = inner.generation;
1898            if let Some(expected) = expected_handle {
1899                if expected.connection_token() != generation
1900                    || expected.channel != channel
1901                    || expected.epoch != epoch
1902                    || inner.route_epochs.get(&channel) != Some(&expected)
1903                {
1904                    return Err(CallError::StaleRouteHandle(expected));
1905                }
1906            }
1907            let Some(writer) = inner.writer.clone() else {
1908                return Err(CallError::not_sent("subc connection is down before send"));
1909            };
1910            let Some(corr) = inner.next_corr else {
1911                drop(inner);
1912                self.handle_generation_drop(
1913                    generation,
1914                    "correlation allocator exhausted".to_string(),
1915                );
1916                return Err(CallError::not_sent(
1917                    "correlation allocator exhausted; connection closed",
1918                ));
1919            };
1920            inner.next_corr = corr.checked_add(1);
1921            (generation, corr, writer)
1922        };
1923
1924        let frame = Frame::build(
1925            FrameType::Request,
1926            Flags::new(false, priority, false).with_admission_class(admission_class),
1927            channel,
1928            epoch,
1929            corr,
1930            body,
1931        )
1932        .map_err(|err| CallError::not_sent(format!("failed to build request frame: {err}")))?;
1933        let key = PendingKey {
1934            generation,
1935            channel,
1936            epoch,
1937            corr,
1938        };
1939        let (events_tx, events_rx) = mpsc::channel(event_buffer.max(1));
1940        let (closed_tx, closed_rx) = oneshot::channel();
1941        {
1942            let mut inner = self.lock_inner();
1943            if inner.closed || inner.generation != generation || inner.writer.is_none() {
1944                return Err(CallError::not_sent(
1945                    "connection changed before subscription registration",
1946                ));
1947            }
1948            if let Some(expected) = expected_handle {
1949                if inner.route_epochs.get(&expected.channel) != Some(&expected) {
1950                    return Err(CallError::StaleRouteHandle(expected));
1951                }
1952            }
1953            inner.pending.insert(
1954                key,
1955                PendingEntry::subscription(events_tx, closed_tx, permit, priority),
1956            );
1957        }
1958        let mut registration = PendingRegistration::new(Arc::clone(self), key, false);
1959
1960        match timeout_at(
1961            deadline,
1962            writer.send(WriteCommand {
1963                frame,
1964                pending: Some(key),
1965            }),
1966        )
1967        .await
1968        {
1969            Ok(Ok(())) => {}
1970            Ok(Err(_)) => {
1971                let accepted = registration.remove_pending().unwrap_or(false);
1972                return Err(classify_failure(
1973                    accepted,
1974                    "writer task closed before accepting subscription request",
1975                ));
1976            }
1977            Err(_) => {
1978                let _ = registration.remove_pending();
1979                return Err(CallError::not_sent(
1980                    "subscription deadline elapsed waiting for writer capacity",
1981                ));
1982            }
1983        }
1984
1985        registration.disarm();
1986        Ok(Subscription {
1987            events: events_rx,
1988            closed: SubscriptionClosed { rx: closed_rx },
1989            cancel: SubscriptionCancel::new(Arc::clone(self), key, priority),
1990        })
1991    }
1992
1993    fn unsubscribe_subscription(
1994        &self,
1995        key: PendingKey,
1996        priority: Priority,
1997    ) -> Result<(), CallError> {
1998        let handle = RouteHandle::new(key.channel, key.epoch, key.generation);
1999        self.validate_current_handle(handle)?;
2000        let entry = self.lock_inner().pending.remove(&key);
2001        if let Some(entry) = entry {
2002            entry.settle_subscription_result(Ok(()));
2003            self.send_cancel(handle, key.corr, priority);
2004        }
2005        Ok(())
2006    }
2007
2008    fn route_stream_data(&self, key: PendingKey, body: Vec<u8>) {
2009        let overflow = {
2010            let mut inner = self.lock_inner();
2011            let Some(entry) = inner.pending.get(&key) else {
2012                return;
2013            };
2014            match entry.try_send_stream_data(body) {
2015                Ok(()) | Err(StreamDataDelivery::NotSubscription) => return,
2016                Err(StreamDataDelivery::Full) => {
2017                    let priority = entry
2018                        .subscription_priority()
2019                        .unwrap_or(Priority::Interactive);
2020                    let entry = inner.pending.remove(&key);
2021                    entry.map(|entry| {
2022                        (
2023                            entry,
2024                            priority,
2025                            "subscription event channel filled; reader dropped the stream instead of blocking",
2026                        )
2027                    })
2028                }
2029                Err(StreamDataDelivery::Closed) => {
2030                    let priority = entry
2031                        .subscription_priority()
2032                        .unwrap_or(Priority::Interactive);
2033                    let entry = inner.pending.remove(&key);
2034                    entry.map(|entry| {
2035                        (
2036                            entry,
2037                            priority,
2038                            "subscription event receiver closed before the stream ended",
2039                        )
2040                    })
2041                }
2042            }
2043        };
2044
2045        if let Some((entry, priority, reason)) = overflow {
2046            entry.settle_call_error(CallError::subscription_backpressure(reason));
2047            self.send_cancel(
2048                RouteHandle::new(key.channel, key.epoch, key.generation),
2049                key.corr,
2050                priority,
2051            );
2052        }
2053    }
2054
2055    fn register_push_events(
2056        &self,
2057        handle: RouteHandle,
2058    ) -> Result<mpsc::Receiver<PushEvent>, CallError> {
2059        let (events_tx, events_rx) = mpsc::channel(DEFAULT_PUSH_EVENT_BUFFER);
2060        let mut inner = self.lock_inner();
2061        if inner.closed
2062            || inner.generation != handle.connection_token()
2063            || inner.writer.is_none()
2064            || inner.route_epochs.get(&handle.channel) != Some(&handle)
2065        {
2066            return Err(CallError::StaleRouteHandle(handle));
2067        }
2068        inner.push_event_receivers.insert(handle, events_tx);
2069        Ok(events_rx)
2070    }
2071
2072    fn route_push(&self, handle: RouteHandle, body: Vec<u8>) {
2073        let should_count_drop = {
2074            let mut inner = self.lock_inner();
2075            if inner.closed
2076                || inner.generation != handle.connection_token()
2077                || inner.route_epochs.get(&handle.channel) != Some(&handle)
2078            {
2079                return;
2080            }
2081            match inner.push_event_receivers.get(&handle) {
2082                None => true,
2083                Some(events) => match events.try_send(PushEvent { handle, body }) {
2084                    Ok(()) => false,
2085                    Err(mpsc::error::TrySendError::Closed(_)) => {
2086                        inner.push_event_receivers.remove(&handle);
2087                        true
2088                    }
2089                    Err(mpsc::error::TrySendError::Full(_)) => {
2090                        inner.push_event_receivers.remove(&handle);
2091                        false
2092                    }
2093                },
2094            }
2095        };
2096        if should_count_drop {
2097            self.pushes_dropped_no_receiver
2098                .fetch_add(1, Ordering::Relaxed);
2099        }
2100    }
2101
2102    fn send_cancel(&self, handle: RouteHandle, corr: u64, priority: Priority) {
2103        let writer = {
2104            let inner = self.lock_inner();
2105            if inner.closed
2106                || inner.generation != handle.connection_token()
2107                || inner.route_epochs.get(&handle.channel) != Some(&handle)
2108            {
2109                return;
2110            }
2111            inner.writer.clone()
2112        };
2113        let Some(writer) = writer else {
2114            return;
2115        };
2116        let Ok(frame) = Frame::build(
2117            FrameType::Cancel,
2118            Flags::new(false, priority, false),
2119            handle.channel,
2120            handle.epoch,
2121            corr,
2122            Vec::new(),
2123        ) else {
2124            return;
2125        };
2126        let _ = writer.try_send(WriteCommand {
2127            frame,
2128            pending: None,
2129        });
2130    }
2131
2132    fn mark_pending_accepted(&self, key: PendingKey) -> bool {
2133        let mut inner = self.lock_inner();
2134        if inner.closed || inner.generation != key.generation {
2135            return false;
2136        }
2137        let Some(entry) = inner.pending.get_mut(&key) else {
2138            return false;
2139        };
2140        entry.accepted = true;
2141        true
2142    }
2143
2144    fn settle_pending(self: &Arc<Self>, key: PendingKey, terminal: PendingTerminal) {
2145        let entry = self.lock_inner().pending.remove(&key);
2146        let Some(entry) = entry else {
2147            return;
2148        };
2149        if entry.retain_late_route_open && entry.completion_is_closed() {
2150            if let PendingTerminal::Response { generation, body } = &terminal {
2151                if let Ok(ClientControlResponse::RouteOpen {
2152                    route_channel,
2153                    route_epoch,
2154                }) = serde_json::from_slice::<ClientControlResponse>(body)
2155                {
2156                    let handle = RouteHandle::new(route_channel, route_epoch, *generation);
2157                    self.send_route_goodbye(handle, true);
2158                    self.uninstall_route_handle(handle);
2159                }
2160            }
2161            return;
2162        }
2163        entry.settle_terminal(terminal);
2164    }
2165
2166    fn pending_accepted(&self, key: PendingKey) -> Option<bool> {
2167        self.lock_inner()
2168            .pending
2169            .get(&key)
2170            .map(|entry| entry.accepted)
2171    }
2172
2173    fn handle_generation_drop(self: &Arc<Self>, generation: u64, reason: String) {
2174        let (should_emit, pending, openings, callbacks) = {
2175            let mut inner = self.lock_inner();
2176            if inner.closed || inner.generation != generation || inner.writer.is_none() {
2177                return;
2178            }
2179            inner.writer = None;
2180            inner.restored_token = inner.restored_token.saturating_add(1);
2181            inner.close_routes();
2182            inner.route_epochs.clear();
2183            let pending = drain_pending_generation(&mut inner.pending, generation);
2184            let openings = drain_openings(&mut inner.openings);
2185            let callbacks = inner.callbacks.clone();
2186            (true, pending, openings, callbacks)
2187        };
2188
2189        if should_emit {
2190            settle_pending_entries(pending, reason.clone());
2191            fail_openings(openings, SharedCallFailure::not_sent(reason.clone()));
2192            emit_callbacks(callbacks, ConnectionState::Dropped);
2193            self.notify.notify_waiters();
2194            let _ = self.spawn_reconnect(generation);
2195        }
2196    }
2197
2198    fn close_sync(&self, reason: &str) {
2199        let (pending, openings, routes, reader, writer, reconnect) = {
2200            let mut inner = self.lock_inner();
2201            if inner.closed {
2202                return;
2203            }
2204            inner.closed = true;
2205            inner.writer = None;
2206            inner.route_epochs.clear();
2207            inner.push_event_receivers.clear();
2208            self.close_token.cancel();
2209            let reconnect = match std::mem::replace(&mut inner.reconnect, ReconnectState::Idle) {
2210                ReconnectState::Background { task, .. } => Some(task),
2211                ReconnectState::Idle | ReconnectState::Inline { .. } => None,
2212            };
2213            (
2214                inner
2215                    .pending
2216                    .drain()
2217                    .map(|(_, entry)| entry)
2218                    .collect::<Vec<_>>(),
2219                inner
2220                    .openings
2221                    .drain()
2222                    .map(|(_, opening)| opening.waiters)
2223                    .collect::<Vec<_>>(),
2224                inner.drain_routes(),
2225                inner.reader_task.take(),
2226                inner.writer_task.take(),
2227                reconnect,
2228            )
2229        };
2230        for route in routes {
2231            route.sem.close();
2232        }
2233        if let Some(handle) = reader {
2234            handle.abort();
2235        }
2236        if let Some(handle) = writer {
2237            handle.abort();
2238        }
2239        if let Some(handle) = reconnect {
2240            handle.abort();
2241        }
2242        settle_pending_entries(pending, reason.to_string());
2243        fail_openings(openings, SharedCallFailure::not_sent(reason.to_string()));
2244        self.notify.notify_waiters();
2245    }
2246
2247    fn validate_current_handle(&self, handle: RouteHandle) -> Result<(), CallError> {
2248        let inner = self.lock_inner();
2249        if inner.closed
2250            || inner.generation != handle.connection_token()
2251            || inner.writer.is_none()
2252            || inner.route_epochs.get(&handle.channel) != Some(&handle)
2253        {
2254            Err(CallError::StaleRouteHandle(handle))
2255        } else {
2256            Ok(())
2257        }
2258    }
2259
2260    fn route_state(&self, handle: RouteHandle) -> Result<RouteState, CallError> {
2261        let inner = self.lock_inner();
2262        if inner.closed
2263            || inner.generation != handle.connection_token()
2264            || inner.writer.is_none()
2265            || inner.route_epochs.get(&handle.channel) != Some(&handle)
2266        {
2267            return Err(CallError::StaleRouteHandle(handle));
2268        }
2269
2270        let route = inner
2271            .route_by_channel
2272            .get(&handle.channel)
2273            .and_then(|key| inner.routes.get(key))
2274            .or_else(|| inner.one_shot_routes.get(&handle.channel));
2275        debug_assert!(route.is_none_or(|route| route.handle == handle));
2276        route
2277            .filter(|route| route.handle == handle)
2278            .cloned()
2279            .ok_or(CallError::StaleRouteHandle(handle))
2280    }
2281
2282    fn route_is_current(&self, key: &RouteKey, route: &RouteState) -> bool {
2283        let inner = self.lock_inner();
2284        if inner.closed
2285            || inner.generation != route.handle.connection_token()
2286            || inner.writer.is_none()
2287        {
2288            return false;
2289        }
2290        inner.routes.get(key).is_some_and(|cached| {
2291            cached.handle == route.handle && Arc::ptr_eq(&cached.sem, &route.sem)
2292        })
2293    }
2294
2295    fn invalidate_route(&self, key: &RouteKey, expected_handle: Option<RouteHandle>) {
2296        let removed = {
2297            let mut inner = self.lock_inner();
2298            match inner.routes.get(key) {
2299                Some(route) if expected_handle.is_none_or(|expected| expected == route.handle) => {
2300                    let removed = inner.remove_route(key);
2301                    if let Some(route) = &removed {
2302                        if inner.route_epochs.get(&route.handle.channel) == Some(&route.handle) {
2303                            inner.route_epochs.remove(&route.handle.channel);
2304                        }
2305                        inner.push_event_receivers.remove(&route.handle);
2306                    }
2307                    removed
2308                }
2309                _ => None,
2310            }
2311        };
2312        if let Some(route) = removed {
2313            route.sem.close();
2314        }
2315    }
2316
2317    fn finish_opening(&self, key: &RouteKey, result: Result<RouteState, SharedCallFailure>) {
2318        let opening = self.lock_inner().openings.remove(key);
2319        for waiter in opening.map(|o| o.waiters).unwrap_or_default() {
2320            let _ = waiter.send(result.clone());
2321        }
2322    }
2323
2324    async fn close_handle(
2325        self: &Arc<Self>,
2326        handle: RouteHandle,
2327        opts: &CloseRouteOptions,
2328    ) -> Result<(), CallError> {
2329        self.validate_current_handle(handle)?;
2330        let routes = {
2331            let mut inner = self.lock_inner();
2332            inner
2333                .remove_route_by_handle(handle)
2334                .into_iter()
2335                .collect::<Vec<_>>()
2336        };
2337        if opts.drain {
2338            self.drain_channel(handle, opts.drain_timeout).await;
2339        }
2340        for route in routes {
2341            route.sem.close();
2342        }
2343        self.fail_channel_pending(handle, "route closed by close_handle");
2344        self.send_route_goodbye(handle, false);
2345        self.uninstall_route_handle(handle);
2346        Ok(())
2347    }
2348
2349    /// Tear down one route by key. See [`SubcConsumer::close_route`].
2350    async fn close_route(self: &Arc<Self>, key: &RouteKey, opts: &CloseRouteOptions) {
2351        // Under the lock: flip the close-beats-reopen flag on any in-flight open for
2352        // this key (so a lead-opener whose channel hasn't been cached yet refuses to
2353        // install it), and remove the cached route if one exists.
2354        let route = {
2355            let mut inner = self.lock_inner();
2356            if let Some(opening) = inner.openings.get_mut(key) {
2357                opening.closed = true;
2358            }
2359            inner.remove_route(key)
2360        };
2361
2362        // Nothing cached: either never opened (idempotent no-op) or still opening (the
2363        // racing lead-opener will see the flag and GOODBYE whatever channel it opens).
2364        let Some(route) = route else {
2365            return;
2366        };
2367
2368        if opts.drain {
2369            // Wait for in-flight UNARY requests on this channel to settle naturally,
2370            // bounded by drain_timeout, before tearing the route down.
2371            self.drain_channel(route.handle, opts.drain_timeout).await;
2372        }
2373
2374        // Closing the semaphore makes any not-yet-sent acquire() return Err -> the
2375        // caller classifies it NotSent. Already-sent pending requests are settled
2376        // at-most-once (OutcomeUnknown if the writer accepted their bytes).
2377        route.sem.close();
2378        self.fail_channel_pending(route.handle, "route closed by close_route");
2379
2380        // Best-effort route GOODBYE: the daemon releases the route + relays the module
2381        // route-gone GOODBYE the module's reaper consumes. One-way, no ack.
2382        self.send_route_goodbye(route.handle, false);
2383        self.uninstall_route_handle(route.handle);
2384    }
2385
2386    fn uninstall_route_handle(&self, handle: RouteHandle) {
2387        let mut inner = self.lock_inner();
2388        if inner.route_epochs.get(&handle.channel) == Some(&handle) {
2389            inner.route_epochs.remove(&handle.channel);
2390            inner.push_event_receivers.remove(&handle);
2391        }
2392    }
2393
2394    /// Settle every in-flight pending request on `channel` (this generation) as an
2395    /// at-most-once failure: OutcomeUnknown if the writer already accepted its bytes,
2396    /// NotSent otherwise. Mirrors the connection-drop path, scoped to one channel.
2397    fn fail_channel_pending(&self, handle: RouteHandle, reason: &str) {
2398        let entries = {
2399            let mut inner = self.lock_inner();
2400            drain_pending_handle(&mut inner.pending, handle, true)
2401        };
2402        settle_pending_entries(entries, reason.to_string());
2403    }
2404
2405    /// Resolve once every in-flight unary pending on `channel` has settled, or the
2406    /// timeout elapses. Polls the pending map (entries are removed on settle); the
2407    /// volume here is tiny (a route window is small) so a short poll is adequate.
2408    async fn drain_channel(&self, handle: RouteHandle, timeout: Duration) {
2409        let deadline = Instant::now() + timeout;
2410        loop {
2411            let has_inflight = {
2412                let inner = self.lock_inner();
2413                inner.pending.iter().any(|(key, entry)| {
2414                    key.generation == handle.connection_token()
2415                        && key.channel == handle.channel
2416                        && key.epoch == handle.epoch
2417                        && !entry.is_subscription()
2418                })
2419            };
2420            if !has_inflight || Instant::now() >= deadline {
2421                return;
2422            }
2423            sleep(Duration::from_millis(5)).await;
2424        }
2425    }
2426
2427    /// Queue a header-only route GOODBYE if `handle` is still live on this connection.
2428    /// Late successful route.open cleanup sets `close_on_failure`: orphan prevention then
2429    /// requires closing the connection when the GOODBYE cannot enter the writer queue.
2430    fn send_route_goodbye(self: &Arc<Self>, handle: RouteHandle, close_on_failure: bool) -> bool {
2431        let writer = {
2432            let inner = self.lock_inner();
2433            if inner.closed
2434                || inner.generation != handle.connection_token()
2435                || inner.route_epochs.get(&handle.channel) != Some(&handle)
2436            {
2437                return false;
2438            }
2439            inner.writer.clone()
2440        };
2441        let Some(writer) = writer else {
2442            return false;
2443        };
2444        let Ok(frame) = Frame::build(
2445            FrameType::Goodbye,
2446            Flags::new(false, Priority::Interactive, false),
2447            handle.channel,
2448            handle.epoch,
2449            0,
2450            Vec::new(),
2451        ) else {
2452            return false;
2453        };
2454        if writer
2455            .try_send(WriteCommand {
2456                frame,
2457                pending: None,
2458            })
2459            .is_ok()
2460        {
2461            return true;
2462        }
2463        if close_on_failure {
2464            self.handle_generation_drop(
2465                handle.connection_token(),
2466                "failed to queue late route.open cleanup GOODBYE".to_string(),
2467            );
2468        }
2469        false
2470    }
2471
2472    fn emit_connection_state(&self, state: ConnectionState) {
2473        let callbacks = self.lock_inner().callbacks.clone();
2474        emit_callbacks(callbacks, state);
2475    }
2476}
2477
2478#[derive(Clone, Copy)]
2479enum InstallKind {
2480    Initial,
2481    Reconnect,
2482}
2483
2484enum EnsureAction {
2485    Wait,
2486    Lead {
2487        generation: u64,
2488        stale_task: Option<JoinHandle<()>>,
2489    },
2490}
2491
2492/// The reconnect state is fenced by the generation whose transport failed. A
2493/// newer generation can replace an older attempt, and completion only changes
2494/// the state when its generation still owns the slot.
2495enum ReconnectState {
2496    Idle,
2497    Inline {
2498        generation: u64,
2499    },
2500    Background {
2501        generation: u64,
2502        task: JoinHandle<()>,
2503    },
2504}
2505
2506/// Outcome of the install decision after a route.open response arrives, taken under
2507/// the inner lock so a racing close_route is observed atomically.
2508enum RouteInstall {
2509    /// Install (or reuse) the cached route and return it.
2510    Cached(RouteState),
2511    /// Do not install. `closed` => a close_route won the race (GOODBYE + NotSent);
2512    /// otherwise the generation moved (retry the open).
2513    Discard { closed: bool },
2514}
2515
2516enum RouteOpenAction {
2517    Wait(oneshot::Receiver<Result<RouteState, SharedCallFailure>>),
2518    Lead,
2519}
2520
2521struct RouteOpenParams<'a> {
2522    target: &'a RouteTarget,
2523    identity: &'a BindIdentity,
2524    consumer_identity: &'a Option<ConsumerIdentity>,
2525    consumer_capabilities: &'a Option<Vec<String>>,
2526}
2527
2528struct RequestSend {
2529    expected_handle: Option<RouteHandle>,
2530    channel: u16,
2531    epoch: u32,
2532    body: Vec<u8>,
2533    priority: Priority,
2534    admission_class: AdmissionClass,
2535    deadline: Instant,
2536    retain_late_route_open: bool,
2537}
2538
2539struct SubscriptionSend {
2540    expected_handle: Option<RouteHandle>,
2541    channel: u16,
2542    epoch: u32,
2543    body: Vec<u8>,
2544    priority: Priority,
2545    admission_class: AdmissionClass,
2546    event_buffer: usize,
2547    deadline: Instant,
2548    permit: OwnedSemaphorePermit,
2549}
2550
2551struct OpeningGuard {
2552    shared: Arc<Shared>,
2553    key: RouteKey,
2554    finished: bool,
2555}
2556
2557impl OpeningGuard {
2558    fn new(shared: Arc<Shared>, key: RouteKey) -> Self {
2559        Self {
2560            shared,
2561            key,
2562            finished: false,
2563        }
2564    }
2565
2566    fn finish(&mut self, result: Result<RouteState, SharedCallFailure>) {
2567        self.shared.finish_opening(&self.key, result);
2568        self.finished = true;
2569    }
2570}
2571
2572impl Drop for OpeningGuard {
2573    fn drop(&mut self) {
2574        if !self.finished {
2575            self.shared.finish_opening(
2576                &self.key,
2577                Err(SharedCallFailure::not_sent(
2578                    "route.open future was cancelled",
2579                )),
2580            );
2581        }
2582    }
2583}
2584
2585struct InlineReconnectGuard {
2586    shared: Arc<Shared>,
2587    generation: u64,
2588    finished: bool,
2589}
2590
2591impl InlineReconnectGuard {
2592    fn new(shared: Arc<Shared>, generation: u64) -> Self {
2593        Self {
2594            shared,
2595            generation,
2596            finished: false,
2597        }
2598    }
2599
2600    fn finish(&mut self) {
2601        self.shared.finish_inline_reconnect(self.generation);
2602        self.finished = true;
2603    }
2604}
2605
2606impl Drop for InlineReconnectGuard {
2607    fn drop(&mut self) {
2608        if !self.finished {
2609            self.shared.finish_inline_reconnect(self.generation);
2610        }
2611    }
2612}
2613
2614#[derive(Clone)]
2615struct RouteState {
2616    handle: RouteHandle,
2617    sem: Arc<Semaphore>,
2618}
2619
2620#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2621struct RouteKey {
2622    target: RouteTargetKey,
2623    project_root: PathBuf,
2624    harness: String,
2625    session: String,
2626    consumer_identity: Option<ConsumerIdentityKey>,
2627    consumer_capabilities: Option<ConsumerCapabilitiesKey>,
2628}
2629
2630impl RouteKey {
2631    fn new(
2632        target: &RouteTarget,
2633        identity: &BindIdentity,
2634        consumer_identity: Option<&ConsumerIdentity>,
2635        consumer_capabilities: Option<&[String]>,
2636    ) -> Self {
2637        Self {
2638            target: RouteTargetKey::from(target),
2639            project_root: identity.project_root.clone(),
2640            harness: identity.harness.clone(),
2641            session: identity.session.clone(),
2642            consumer_identity: consumer_identity.map(ConsumerIdentityKey::from),
2643            consumer_capabilities: consumer_capabilities.map(ConsumerCapabilitiesKey::from_slice),
2644        }
2645    }
2646
2647    fn target_label(&self) -> String {
2648        match &self.target {
2649            RouteTargetKey::ToolProvider { module_id } => format!("tool_provider:{module_id}"),
2650            RouteTargetKey::ManagementSurface { module_id } => {
2651                format!("management_surface:{module_id}")
2652            }
2653            RouteTargetKey::InternalService {
2654                module_id,
2655                service_id,
2656            } => format!("internal_service:{module_id}:{service_id}"),
2657        }
2658    }
2659}
2660
2661#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2662struct ConsumerIdentityKey {
2663    module_id: String,
2664    launch_nonce: String,
2665}
2666
2667impl From<&ConsumerIdentity> for ConsumerIdentityKey {
2668    fn from(value: &ConsumerIdentity) -> Self {
2669        Self {
2670            module_id: value.module_id.clone(),
2671            launch_nonce: value.launch_nonce.clone(),
2672        }
2673    }
2674}
2675
2676#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2677struct ConsumerCapabilitiesKey {
2678    values: Vec<String>,
2679}
2680
2681impl ConsumerCapabilitiesKey {
2682    fn from_slice(values: &[String]) -> Self {
2683        let mut values = values.to_vec();
2684        values.sort();
2685        values.dedup();
2686        Self { values }
2687    }
2688}
2689
2690#[derive(Debug, Clone, Hash, PartialEq, Eq)]
2691enum RouteTargetKey {
2692    ToolProvider {
2693        module_id: String,
2694    },
2695    ManagementSurface {
2696        module_id: String,
2697    },
2698    InternalService {
2699        module_id: String,
2700        service_id: String,
2701    },
2702}
2703
2704impl From<&RouteTarget> for RouteTargetKey {
2705    fn from(value: &RouteTarget) -> Self {
2706        match value {
2707            RouteTarget::ToolProvider { module_id } => Self::ToolProvider {
2708                module_id: module_id.clone(),
2709            },
2710            RouteTarget::ManagementSurface { module_id } => Self::ManagementSurface {
2711                module_id: module_id.clone(),
2712            },
2713            RouteTarget::InternalService {
2714                module_id,
2715                service_id,
2716            } => Self::InternalService {
2717                module_id: module_id.clone(),
2718                service_id: service_id.clone(),
2719            },
2720        }
2721    }
2722}
2723
2724#[derive(Debug, Clone)]
2725struct SharedCallFailure {
2726    kind: FailureKind,
2727    message: String,
2728}
2729
2730impl SharedCallFailure {
2731    fn not_sent(message: impl Into<String>) -> Self {
2732        Self {
2733            kind: FailureKind::NotSent,
2734            message: message.into(),
2735        }
2736    }
2737
2738    fn into_call_error(self) -> CallError {
2739        match self.kind {
2740            FailureKind::NotSent => CallError::not_sent(self.message),
2741            FailureKind::OutcomeUnknown => CallError::outcome_unknown(self.message),
2742        }
2743    }
2744}
2745
2746impl From<CallError> for SharedCallFailure {
2747    fn from(value: CallError) -> Self {
2748        match value {
2749            CallError::NotSent(err) => Self {
2750                kind: FailureKind::NotSent,
2751                message: err.to_string(),
2752            },
2753            CallError::OutcomeUnknown(err) => Self {
2754                kind: FailureKind::OutcomeUnknown,
2755                message: err.to_string(),
2756            },
2757            CallError::Module(body) => Self {
2758                kind: FailureKind::OutcomeUnknown,
2759                message: format!(
2760                    "unexpected module error during route.open: {} ({})",
2761                    body.code, body.message
2762                ),
2763            },
2764            CallError::SubscriptionBackpressure(err) => Self {
2765                kind: FailureKind::OutcomeUnknown,
2766                message: err.to_string(),
2767            },
2768            CallError::StaleRouteHandle(handle) => Self {
2769                kind: FailureKind::NotSent,
2770                message: format!("stale route handle: {handle:?}"),
2771            },
2772        }
2773    }
2774}
2775
2776#[derive(Debug, Clone, Copy)]
2777enum FailureKind {
2778    NotSent,
2779    OutcomeUnknown,
2780}
2781
2782#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
2783struct PendingKey {
2784    generation: u64,
2785    channel: u16,
2786    epoch: u32,
2787    corr: u64,
2788}
2789
2790struct PendingEntry {
2791    accepted: bool,
2792    retain_late_route_open: bool,
2793    expected_control_handle: Option<RouteHandle>,
2794    completion: PendingCompletion,
2795}
2796
2797enum PendingCompletion {
2798    Unary(oneshot::Sender<PendingResult>),
2799    Subscription {
2800        events: mpsc::Sender<Vec<u8>>,
2801        closed: oneshot::Sender<Result<(), CallError>>,
2802        _permit: OwnedSemaphorePermit,
2803        priority: Priority,
2804    },
2805}
2806
2807enum StreamDataDelivery {
2808    NotSubscription,
2809    Full,
2810    Closed,
2811}
2812
2813impl PendingEntry {
2814    fn unary(
2815        tx: oneshot::Sender<PendingResult>,
2816        retain_late_route_open: bool,
2817        expected_control_handle: Option<RouteHandle>,
2818    ) -> Self {
2819        Self {
2820            accepted: false,
2821            retain_late_route_open,
2822            expected_control_handle,
2823            completion: PendingCompletion::Unary(tx),
2824        }
2825    }
2826
2827    fn subscription(
2828        events: mpsc::Sender<Vec<u8>>,
2829        closed: oneshot::Sender<Result<(), CallError>>,
2830        permit: OwnedSemaphorePermit,
2831        priority: Priority,
2832    ) -> Self {
2833        Self {
2834            accepted: false,
2835            retain_late_route_open: false,
2836            expected_control_handle: None,
2837            completion: PendingCompletion::Subscription {
2838                events,
2839                closed,
2840                _permit: permit,
2841                priority,
2842            },
2843        }
2844    }
2845
2846    fn completion_is_closed(&self) -> bool {
2847        match &self.completion {
2848            PendingCompletion::Unary(tx) => tx.is_closed(),
2849            PendingCompletion::Subscription { closed, .. } => closed.is_closed(),
2850        }
2851    }
2852
2853    fn is_subscription(&self) -> bool {
2854        matches!(&self.completion, PendingCompletion::Subscription { .. })
2855    }
2856
2857    fn subscription_priority(&self) -> Option<Priority> {
2858        match &self.completion {
2859            PendingCompletion::Subscription { priority, .. } => Some(*priority),
2860            PendingCompletion::Unary(_) => None,
2861        }
2862    }
2863
2864    fn try_send_stream_data(&self, body: Vec<u8>) -> Result<(), StreamDataDelivery> {
2865        let PendingCompletion::Subscription { events, .. } = &self.completion else {
2866            return Err(StreamDataDelivery::NotSubscription);
2867        };
2868        events.try_send(body).map_err(|err| match err {
2869            mpsc::error::TrySendError::Full(_) => StreamDataDelivery::Full,
2870            mpsc::error::TrySendError::Closed(_) => StreamDataDelivery::Closed,
2871        })
2872    }
2873
2874    fn settle_terminal(self, terminal: PendingTerminal) {
2875        match self.completion {
2876            PendingCompletion::Unary(tx) => {
2877                let _ = tx.send(PendingResult::Terminal(terminal));
2878            }
2879            PendingCompletion::Subscription { closed, .. } => {
2880                let result = match terminal {
2881                    PendingTerminal::Response { .. } | PendingTerminal::StreamEnd => Ok(()),
2882                    PendingTerminal::Error { body } => Err(CallError::Module(body)),
2883                };
2884                let _ = closed.send(result);
2885            }
2886        }
2887    }
2888
2889    fn settle_failure(self, reason: String) {
2890        let accepted = self.accepted;
2891        self.settle_call_error(classify_failure(accepted, reason));
2892    }
2893
2894    fn settle_call_error(self, err: CallError) {
2895        match self.completion {
2896            PendingCompletion::Unary(tx) => {
2897                let _ = tx.send(PendingResult::Failure {
2898                    accepted: self.accepted,
2899                    reason: err.to_string(),
2900                });
2901            }
2902            PendingCompletion::Subscription { closed, .. } => {
2903                let _ = closed.send(Err(err));
2904            }
2905        }
2906    }
2907
2908    fn settle_subscription_result(self, result: Result<(), CallError>) {
2909        match self.completion {
2910            PendingCompletion::Subscription { closed, .. } => {
2911                let _ = closed.send(result);
2912            }
2913            PendingCompletion::Unary(tx) => {
2914                let _ = tx.send(PendingResult::Failure {
2915                    accepted: self.accepted,
2916                    reason: "subscription cancel matched a unary request".to_string(),
2917                });
2918            }
2919        }
2920    }
2921}
2922
2923struct PendingRegistration {
2924    shared: Arc<Shared>,
2925    key: PendingKey,
2926    active: bool,
2927    retain_on_drop: bool,
2928}
2929
2930impl PendingRegistration {
2931    fn new(shared: Arc<Shared>, key: PendingKey, retain_on_drop: bool) -> Self {
2932        Self {
2933            shared,
2934            key,
2935            active: true,
2936            retain_on_drop,
2937        }
2938    }
2939
2940    fn remove_pending(&mut self) -> Option<bool> {
2941        if !self.active {
2942            return None;
2943        }
2944        self.active = false;
2945        self.shared
2946            .lock_inner()
2947            .pending
2948            .remove(&self.key)
2949            .map(|entry| entry.accepted)
2950    }
2951
2952    fn disarm(&mut self) {
2953        self.active = false;
2954    }
2955}
2956
2957impl Drop for PendingRegistration {
2958    fn drop(&mut self) {
2959        if self.retain_on_drop {
2960            self.disarm();
2961        } else {
2962            let _ = self.remove_pending();
2963        }
2964    }
2965}
2966
2967enum PendingResult {
2968    Terminal(PendingTerminal),
2969    Failure { accepted: bool, reason: String },
2970}
2971
2972impl PendingResult {
2973    fn into_call_result(self) -> Result<TerminalFrame, CallError> {
2974        match self {
2975            Self::Terminal(terminal) => Ok(terminal.into_terminal_frame()),
2976            Self::Failure { accepted, reason } => Err(classify_failure(accepted, reason)),
2977        }
2978    }
2979}
2980
2981enum PendingTerminal {
2982    Response { generation: u64, body: Vec<u8> },
2983    Error { body: ErrorBody },
2984    StreamEnd,
2985}
2986
2987impl PendingTerminal {
2988    fn into_terminal_frame(self) -> TerminalFrame {
2989        match self {
2990            Self::Response { generation, body } => TerminalFrame::Response { generation, body },
2991            Self::Error { body } => TerminalFrame::Error { body },
2992            Self::StreamEnd => TerminalFrame::StreamEnd,
2993        }
2994    }
2995}
2996
2997#[derive(Debug)]
2998enum TerminalFrame {
2999    Response { generation: u64, body: Vec<u8> },
3000    Error { body: ErrorBody },
3001    StreamEnd,
3002}
3003
3004struct WriteCommand {
3005    frame: Frame,
3006    pending: Option<PendingKey>,
3007}
3008
3009struct OpenedConnection {
3010    stream: TcpStream,
3011}
3012
3013async fn open_connection(
3014    path: &Path,
3015    deadline: Duration,
3016) -> Result<OpenedConnection, ConsumerError> {
3017    let conn =
3018        connection_file::read_for_client(path).map_err(|source| ConsumerError::ConnectionFile {
3019            path: path.to_path_buf(),
3020            source,
3021        })?;
3022    let endpoint = conn
3023        .endpoints
3024        .first()
3025        .ok_or_else(|| ConsumerError::NoEndpoint {
3026            path: path.to_path_buf(),
3027        })?;
3028    let endpoint_label = format!("{}:{}", endpoint.host, endpoint.port);
3029    let mut stream = TcpStream::connect(&endpoint_label)
3030        .await
3031        .map_err(|source| ConsumerError::Connect {
3032            path: path.to_path_buf(),
3033            endpoint: endpoint_label.clone(),
3034            source,
3035        })?;
3036    // Consumers send a request and wait for its reply, so there is no following
3037    // write for Nagle to coalesce with -- it can only hold the request back until
3038    // an ACK returns. Both ends of the hop must disable it for either to help.
3039    //
3040    // Dropped rather than logged for the same reason as the module path: no logging
3041    // dependency here, and a socket too broken to take the option fails the
3042    // handshake on the next line with a typed error.
3043    let _ = stream.set_nodelay(true);
3044    authenticate_client(&mut stream, &conn, deadline)
3045        .await
3046        .map_err(|source| ConsumerError::Auth {
3047            path: path.to_path_buf(),
3048            endpoint: endpoint_label,
3049            source,
3050        })?;
3051    Ok(OpenedConnection { stream })
3052}
3053
3054async fn reader_loop(shared: Arc<Shared>, mut reader: OwnedReadHalf, generation: u64) {
3055    loop {
3056        match read_frame(&mut reader).await {
3057            Ok(Some(frame)) => {
3058                if !dispatch_frame(&shared, generation, frame).await {
3059                    return;
3060                }
3061            }
3062            Ok(None) => {
3063                shared.handle_generation_drop(generation, "subc connection closed".to_string());
3064                return;
3065            }
3066            Err(err) => {
3067                shared.handle_generation_drop(generation, err.to_string());
3068                return;
3069            }
3070        }
3071    }
3072}
3073
3074async fn dispatch_frame(shared: &Arc<Shared>, generation: u64, frame: Frame) -> bool {
3075    if !shared.generation_is_current(generation) {
3076        return false;
3077    }
3078    if frame.header.channel != 0
3079        && !shared.validate_ingress_handle(generation, frame.header.channel, frame.header.epoch)
3080    {
3081        return true;
3082    }
3083
3084    let key = PendingKey {
3085        generation,
3086        channel: frame.header.channel,
3087        epoch: frame.header.epoch,
3088        corr: frame.header.corr,
3089    };
3090
3091    if frame.header.channel == 0 && frame.header.ty == FrameType::Response {
3092        if let Some(expected) = shared.pending_expected_control_handle(key) {
3093            let echoes_expected = matches!(
3094                serde_json::from_slice::<ClientControlResponse>(&frame.body),
3095                Ok(ClientControlResponse::RoutePoll {
3096                    route_channel,
3097                    route_epoch,
3098                    ..
3099                }) if route_channel == expected.channel && route_epoch == expected.epoch
3100            );
3101            if !echoes_expected {
3102                shared.count_dropped_route_frame();
3103                return true;
3104            }
3105        }
3106    }
3107
3108    // A route.open handle is published before its waiter is resolved. The socket reader
3109    // cannot consume a following same-route frame until this synchronous install finishes.
3110    if frame.header.channel == 0
3111        && frame.header.ty == FrameType::Response
3112        && shared.pending_expects_route_open(key)
3113    {
3114        if let Ok(ClientControlResponse::RouteOpen {
3115            route_channel,
3116            route_epoch,
3117        }) = serde_json::from_slice::<ClientControlResponse>(&frame.body)
3118        {
3119            shared.install_ingress_handle(RouteHandle::new(route_channel, route_epoch, generation));
3120        }
3121    }
3122
3123    match frame.header.ty {
3124        FrameType::Response => shared.settle_pending(
3125            key,
3126            PendingTerminal::Response {
3127                generation,
3128                body: frame.body,
3129            },
3130        ),
3131        FrameType::Error => {
3132            let body =
3133                serde_json::from_slice::<ErrorBody>(&frame.body).unwrap_or_else(|err| ErrorBody {
3134                    code: "invalid_error_body".to_string(),
3135                    message: err.to_string(),
3136                });
3137            shared.settle_pending(key, PendingTerminal::Error { body });
3138        }
3139        FrameType::StreamEnd => shared.settle_pending(key, PendingTerminal::StreamEnd),
3140        FrameType::StreamData => shared.route_stream_data(key, frame.body),
3141        FrameType::Push => shared.route_push(
3142            RouteHandle::new(frame.header.channel, frame.header.epoch, generation),
3143            frame.body,
3144        ),
3145        FrameType::Goodbye if frame.header.channel == 0 => {
3146            shared.handle_generation_drop(generation, "subc sent GOODBYE".to_string());
3147            return false;
3148        }
3149        FrameType::Goodbye => {
3150            let handle = RouteHandle::new(frame.header.channel, frame.header.epoch, generation);
3151            shared.invalidate_routes_for_handle(handle);
3152            let pending = {
3153                let mut inner = shared.lock_inner();
3154                drain_pending_handle(&mut inner.pending, handle, true)
3155            };
3156            settle_pending_entries(pending, "route closed by subc".to_string());
3157        }
3158        FrameType::Ping if frame.header.channel == 0 => {
3159            if let Ok(pong) = Frame::build_with_version(
3160                frame.header.ver,
3161                FrameType::Pong,
3162                frame.header.flags,
3163                0,
3164                0,
3165                frame.header.corr,
3166                Vec::new(),
3167            ) {
3168                let writer = shared.lock_inner().writer.clone();
3169                if let Some(writer) = writer {
3170                    let _ = writer
3171                        .send(WriteCommand {
3172                            frame: pong,
3173                            pending: None,
3174                        })
3175                        .await;
3176                }
3177            }
3178        }
3179        _ => {}
3180    }
3181    true
3182}
3183
3184impl Shared {
3185    fn generation_is_current(&self, generation: u64) -> bool {
3186        let inner = self.lock_inner();
3187        !inner.closed && inner.generation == generation && inner.writer.is_some()
3188    }
3189
3190    fn pending_expected_control_handle(&self, key: PendingKey) -> Option<RouteHandle> {
3191        self.lock_inner()
3192            .pending
3193            .get(&key)
3194            .and_then(|entry| entry.expected_control_handle)
3195    }
3196
3197    fn count_dropped_route_frame(&self) {
3198        let mut inner = self.lock_inner();
3199        inner.dropped_route_frames = inner.dropped_route_frames.saturating_add(1);
3200    }
3201
3202    fn pending_expects_route_open(&self, key: PendingKey) -> bool {
3203        self.lock_inner()
3204            .pending
3205            .get(&key)
3206            .is_some_and(|entry| entry.retain_late_route_open)
3207    }
3208
3209    fn validate_ingress_handle(&self, generation: u64, channel: u16, epoch: u32) -> bool {
3210        let mut inner = self.lock_inner();
3211        let expected = RouteHandle::new(channel, epoch, generation);
3212        if inner.route_epochs.get(&channel) == Some(&expected) {
3213            true
3214        } else {
3215            inner.dropped_route_frames = inner.dropped_route_frames.saturating_add(1);
3216            false
3217        }
3218    }
3219
3220    fn install_ingress_handle(&self, handle: RouteHandle) {
3221        let mut inner = self.lock_inner();
3222        if !inner.closed && inner.generation == handle.connection_token() && inner.writer.is_some()
3223        {
3224            inner.route_epochs.insert(handle.channel, handle);
3225        }
3226    }
3227
3228    fn invalidate_routes_for_handle(&self, handle: RouteHandle) {
3229        let removed = {
3230            let mut inner = self.lock_inner();
3231            if inner.route_epochs.get(&handle.channel) != Some(&handle) {
3232                return;
3233            }
3234            inner.route_epochs.remove(&handle.channel);
3235            inner.push_event_receivers.remove(&handle);
3236            inner
3237                .remove_route_by_handle(handle)
3238                .into_iter()
3239                .collect::<Vec<_>>()
3240        };
3241        for route in removed {
3242            route.sem.close();
3243        }
3244    }
3245}
3246
3247async fn writer_loop<W>(
3248    shared: Arc<Shared>,
3249    writer: W,
3250    mut rx: mpsc::Receiver<WriteCommand>,
3251    generation: u64,
3252) where
3253    W: AsyncWrite + Unpin,
3254{
3255    let mut writer = BufWriter::new(writer);
3256    while let Some(command) = rx.recv().await {
3257        if let Some(key) = command.pending {
3258            if !shared.mark_pending_accepted(key) {
3259                continue;
3260            }
3261        }
3262        if let Err(err) = write_frame(&mut writer, &command.frame).await {
3263            shared.handle_generation_drop(generation, err.to_string());
3264            return;
3265        }
3266        while let Ok(command) = rx.try_recv() {
3267            if let Some(key) = command.pending {
3268                if !shared.mark_pending_accepted(key) {
3269                    continue;
3270                }
3271            }
3272            if let Err(err) = write_frame(&mut writer, &command.frame).await {
3273                shared.handle_generation_drop(generation, err.to_string());
3274                return;
3275            }
3276        }
3277        if let Err(err) = writer.flush().await.map_err(FrameIoError::Io) {
3278            shared.handle_generation_drop(generation, err.to_string());
3279            return;
3280        }
3281    }
3282}
3283
3284fn route_open_consumer_identity(opts: &CallOptions) -> Option<ConsumerIdentity> {
3285    opts.consumer_identity
3286        .clone()
3287        .or_else(consumer_identity_from_env)
3288}
3289
3290fn close_route_consumer_identity(opts: &CloseRouteOptions) -> Option<ConsumerIdentity> {
3291    opts.consumer_identity
3292        .clone()
3293        .or_else(consumer_identity_from_env)
3294}
3295
3296fn route_open_consumer_capabilities(opts: &CallOptions) -> Option<Vec<String>> {
3297    opts.consumer_capabilities.clone()
3298}
3299
3300fn close_route_consumer_capabilities(opts: &CloseRouteOptions) -> Option<Vec<String>> {
3301    opts.consumer_capabilities.clone()
3302}
3303
3304fn consumer_identity_from_env() -> Option<ConsumerIdentity> {
3305    let module_id = std::env::var(SUBC_MODULE_ID_ENV)
3306        .ok()
3307        .filter(|value| !value.is_empty())?;
3308    let launch_nonce = std::env::var(SUBC_LAUNCH_NONCE_ENV)
3309        .ok()
3310        .filter(|value| !value.is_empty())?;
3311    Some(ConsumerIdentity {
3312        module_id,
3313        launch_nonce,
3314    })
3315}
3316
3317fn classify_failure(accepted: bool, reason: impl Into<String>) -> CallError {
3318    if accepted {
3319        CallError::outcome_unknown(reason)
3320    } else {
3321        CallError::not_sent(reason)
3322    }
3323}
3324
3325fn settle_pending_entries(entries: Vec<PendingEntry>, reason: String) {
3326    for entry in entries {
3327        entry.settle_failure(reason.clone());
3328    }
3329}
3330
3331fn drain_pending_generation(
3332    pending: &mut HashMap<PendingKey, PendingEntry>,
3333    generation: u64,
3334) -> Vec<PendingEntry> {
3335    let keys = pending
3336        .keys()
3337        .copied()
3338        .filter(|key| key.generation == generation)
3339        .collect::<Vec<_>>();
3340    keys.into_iter()
3341        .filter_map(|key| pending.remove(&key))
3342        .collect()
3343}
3344
3345fn drain_pending_handle(
3346    pending: &mut HashMap<PendingKey, PendingEntry>,
3347    handle: RouteHandle,
3348    include_subscriptions: bool,
3349) -> Vec<PendingEntry> {
3350    let keys = pending
3351        .iter()
3352        .filter_map(|(key, entry)| {
3353            (key.generation == handle.connection_token()
3354                && key.channel == handle.channel
3355                && key.epoch == handle.epoch
3356                && (include_subscriptions || !entry.is_subscription()))
3357            .then_some(*key)
3358        })
3359        .collect::<Vec<_>>();
3360    keys.into_iter()
3361        .filter_map(|key| pending.remove(&key))
3362        .collect()
3363}
3364
3365fn drain_openings(openings: &mut HashMap<RouteKey, Opening>) -> Vec<Vec<OpeningWaiter>> {
3366    openings
3367        .drain()
3368        .map(|(_, opening)| opening.waiters)
3369        .collect()
3370}
3371
3372fn fail_openings(openings: Vec<Vec<OpeningWaiter>>, failure: SharedCallFailure) {
3373    for waiters in openings {
3374        for waiter in waiters {
3375            let _ = waiter.send(Err(failure.clone()));
3376        }
3377    }
3378}
3379
3380fn emit_callbacks(callbacks: Vec<Callback>, state: ConnectionState) {
3381    for callback in callbacks {
3382        if let Ok(callback) = callback.lock() {
3383            callback(state.clone());
3384        }
3385    }
3386}
3387
3388fn is_retryable_route_open_code(code: &str) -> bool {
3389    matches!(
3390        code,
3391        "unknown_module" | "module_reloading" | "target_unavailable" | "module_timeout"
3392    )
3393}
3394
3395fn is_retryable_catalog_transport_error(err: &CallError) -> bool {
3396    matches!(err, CallError::NotSent(_) | CallError::OutcomeUnknown(_))
3397}
3398
3399fn is_reconnect_transient(err: &ConsumerError) -> bool {
3400    match err {
3401        ConsumerError::Connect { source, .. } => matches!(
3402            source.kind(),
3403            io::ErrorKind::ConnectionRefused
3404                | io::ErrorKind::ConnectionReset
3405                | io::ErrorKind::ConnectionAborted
3406                | io::ErrorKind::TimedOut
3407                | io::ErrorKind::NotConnected
3408                | io::ErrorKind::AddrNotAvailable
3409        ),
3410        ConsumerError::ConnectionFile { source, .. } => match source {
3411            ConnectionFileError::Io { source, .. } => source.kind() == io::ErrorKind::NotFound,
3412            _ => false,
3413        },
3414        // Auth failure is transient during reconnect: the daemon rotates its key
3415        // on every restart, and with a fixed port a client racing the restart can
3416        // read the pre-rotation connection file yet still connect — the proof
3417        // mismatch then means "stale key mid-rotation", not "impostor". Each
3418        // retry re-reads the connection file (open_connection), so the next
3419        // attempt picks up the rotated key, and server-proves-first protects
3420        // every attempt. First-connect auth failures stay permanent: connect()
3421        // surfaces them directly without entering the reconnect classifier.
3422        ConsumerError::Auth { .. } => true,
3423        ConsumerError::NoEndpoint { .. } | ConsumerError::Closed => false,
3424    }
3425}
3426
3427impl From<FrameBuildError> for CallError {
3428    fn from(err: FrameBuildError) -> Self {
3429        Self::not_sent(err.to_string())
3430    }
3431}
3432
3433#[cfg(test)]
3434mod tests {
3435    use super::*;
3436
3437    #[derive(Clone)]
3438    struct InstrumentedWriter {
3439        state: Arc<InstrumentedWriterState>,
3440        fail_flush: bool,
3441    }
3442
3443    #[derive(Default)]
3444    struct InstrumentedWriterState {
3445        bytes: Mutex<Vec<u8>>,
3446        flushes: std::sync::atomic::AtomicUsize,
3447    }
3448
3449    impl InstrumentedWriter {
3450        fn new(fail_flush: bool) -> Self {
3451            Self {
3452                state: Arc::new(InstrumentedWriterState::default()),
3453                fail_flush,
3454            }
3455        }
3456
3457        fn bytes(&self) -> Vec<u8> {
3458            self.state
3459                .bytes
3460                .lock()
3461                .unwrap_or_else(|poisoned| poisoned.into_inner())
3462                .clone()
3463        }
3464
3465        fn flush_count(&self) -> usize {
3466            self.state.flushes.load(Ordering::SeqCst)
3467        }
3468    }
3469
3470    impl AsyncWrite for InstrumentedWriter {
3471        fn poll_write(
3472            self: Pin<&mut Self>,
3473            _cx: &mut Context<'_>,
3474            buf: &[u8],
3475        ) -> Poll<io::Result<usize>> {
3476            self.state
3477                .bytes
3478                .lock()
3479                .unwrap_or_else(|poisoned| poisoned.into_inner())
3480                .extend_from_slice(buf);
3481            Poll::Ready(Ok(buf.len()))
3482        }
3483
3484        fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
3485            self.state.flushes.fetch_add(1, Ordering::SeqCst);
3486            if self.fail_flush {
3487                Poll::Ready(Err(io::Error::new(
3488                    io::ErrorKind::BrokenPipe,
3489                    "instrumented flush failure",
3490                )))
3491            } else {
3492                Poll::Ready(Ok(()))
3493            }
3494        }
3495
3496        fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
3497            Poll::Ready(Ok(()))
3498        }
3499    }
3500
3501    fn writer_test_shared() -> Arc<Shared> {
3502        Arc::new(Shared::new(
3503            PathBuf::from("/tmp/does-not-exist"),
3504            ConsumerOptions {
3505                reconnect_backoff: RetryBackoff {
3506                    max_attempts: 1,
3507                    ..RetryBackoff::default()
3508                },
3509                ..ConsumerOptions::default()
3510            },
3511        ))
3512    }
3513
3514    #[tokio::test]
3515    async fn writer_batches_ready_frames_into_one_flush() {
3516        const FRAME_COUNT: usize = 8;
3517
3518        let shared = writer_test_shared();
3519        let (live_writer, _live_rx) = mpsc::channel(1);
3520        let (tx, rx) = mpsc::channel(FRAME_COUNT + 1);
3521        let instrumented = InstrumentedWriter::new(false);
3522        let observer = instrumented.clone();
3523        let mut expected = Vec::with_capacity(FRAME_COUNT);
3524        let mut keys = Vec::with_capacity(FRAME_COUNT);
3525
3526        {
3527            let mut inner = shared.lock_inner();
3528            inner.writer = Some(live_writer);
3529            for index in 0..FRAME_COUNT {
3530                let corr = index as u64 + 1;
3531                let frame = response_frame(7, 1, corr, vec![index as u8; index + 1]);
3532                let key = PendingKey {
3533                    generation: 1,
3534                    channel: 7,
3535                    epoch: 1,
3536                    corr,
3537                };
3538                let (pending_tx, _pending_rx) = oneshot::channel();
3539                inner
3540                    .pending
3541                    .insert(key, PendingEntry::unary(pending_tx, false, None));
3542                expected.push(frame.clone());
3543                keys.push(key);
3544                tx.try_send(WriteCommand {
3545                    frame,
3546                    pending: Some(key),
3547                })
3548                .expect("the burst should fit in the writer queue");
3549                if index == 0 {
3550                    tx.try_send(WriteCommand {
3551                        frame: response_frame(7, 1, 999, b"skip".to_vec()),
3552                        pending: Some(PendingKey {
3553                            generation: 1,
3554                            channel: 7,
3555                            epoch: 1,
3556                            corr: 999,
3557                        }),
3558                    })
3559                    .expect("the skipped command should fit in the writer queue");
3560                }
3561            }
3562        }
3563        drop(tx);
3564
3565        writer_loop(Arc::clone(&shared), instrumented, rx, 1).await;
3566
3567        {
3568            let inner = shared.lock_inner();
3569            for key in keys {
3570                assert!(
3571                    inner.pending.get(&key).is_some_and(|entry| entry.accepted),
3572                    "every written command must be marked accepted"
3573                );
3574            }
3575        }
3576
3577        let mut wire = std::io::Cursor::new(observer.bytes());
3578        for expected_frame in expected {
3579            let actual = read_frame(&mut wire)
3580                .await
3581                .expect("the emitted frame should decode")
3582                .expect("the emitted frame should be present");
3583            assert_eq!(actual, expected_frame);
3584        }
3585        assert!(
3586            read_frame(&mut wire)
3587                .await
3588                .expect("the end of the emitted burst should be clean")
3589                .is_none(),
3590            "the writer must not emit extra frames"
3591        );
3592
3593        let flush_count = observer.flush_count();
3594        assert_eq!(
3595            flush_count, 1,
3596            "a ready burst must be coalesced into one flush"
3597        );
3598        shared.close_sync("test complete");
3599    }
3600
3601    #[tokio::test]
3602    async fn writer_flush_failure_drops_generation_and_preserves_acceptance_classification() {
3603        let shared = writer_test_shared();
3604        let (live_writer, _live_rx) = mpsc::channel(1);
3605        let accepted_key = PendingKey {
3606            generation: 1,
3607            channel: 3,
3608            epoch: 1,
3609            corr: 1,
3610        };
3611        let not_sent_key = PendingKey {
3612            corr: 2,
3613            ..accepted_key
3614        };
3615        let (accepted_tx, accepted_rx) = oneshot::channel();
3616        let (not_sent_tx, not_sent_rx) = oneshot::channel();
3617        {
3618            let mut inner = shared.lock_inner();
3619            inner.writer = Some(live_writer);
3620            inner
3621                .pending
3622                .insert(accepted_key, PendingEntry::unary(accepted_tx, false, None));
3623            inner
3624                .pending
3625                .insert(not_sent_key, PendingEntry::unary(not_sent_tx, false, None));
3626        }
3627
3628        let (tx, rx) = mpsc::channel(1);
3629        tx.send(WriteCommand {
3630            frame: response_frame(3, 1, accepted_key.corr, b"accepted".to_vec()),
3631            pending: Some(accepted_key),
3632        })
3633        .await
3634        .unwrap();
3635        drop(tx);
3636
3637        writer_loop(Arc::clone(&shared), InstrumentedWriter::new(true), rx, 1).await;
3638
3639        assert!(
3640            shared.lock_inner().writer.is_none(),
3641            "a flush failure must drop the active generation"
3642        );
3643        let accepted_error = accepted_rx
3644            .await
3645            .expect("the accepted request should be settled")
3646            .into_call_result()
3647            .unwrap_err();
3648        assert!(matches!(accepted_error, CallError::OutcomeUnknown(_)));
3649        let not_sent_error = not_sent_rx
3650            .await
3651            .expect("the unwritten request should be settled")
3652            .into_call_result()
3653            .unwrap_err();
3654        assert!(matches!(not_sent_error, CallError::NotSent(_)));
3655        shared.close_sync("test complete");
3656    }
3657
3658    #[test]
3659    fn reconnect_classifier_treats_auth_failure_as_transient() {
3660        // Key rotation across a daemon restart on the fixed port: a client racing
3661        // the restart reads the pre-rotation file, connects, and fails the proof.
3662        // That must be retryable — each retry re-reads the file, so the next
3663        // attempt picks up the rotated key. Treating it as a permanent impostor
3664        // verdict would turn every daemon restart into a permanent client wedge.
3665        let auth = ConsumerError::Auth {
3666            path: PathBuf::from("/tmp/subc-connection.json"),
3667            endpoint: "127.0.0.1:8757".to_string(),
3668            source: subc_transport::AuthError::InvalidServerProof,
3669        };
3670        assert!(is_reconnect_transient(&auth), "rotation race must retry");
3671
3672        // Absent file mid-restart stays transient; malformed file stays permanent.
3673        let absent = ConsumerError::ConnectionFile {
3674            path: PathBuf::from("/tmp/subc-connection.json"),
3675            source: ConnectionFileError::Io {
3676                op: "read",
3677                path: PathBuf::from("/tmp/subc-connection.json"),
3678                source: io::Error::new(io::ErrorKind::NotFound, "gone"),
3679            },
3680        };
3681        assert!(is_reconnect_transient(&absent));
3682    }
3683
3684    #[tokio::test]
3685    async fn newer_drop_supersedes_reconnect_and_ignores_stale_completion() {
3686        let shared = Arc::new(Shared::new(
3687            PathBuf::from("/tmp/does-not-exist"),
3688            ConsumerOptions::default(),
3689        ));
3690        let stale_task = tokio::spawn(std::future::pending::<()>());
3691        {
3692            let mut inner = shared.lock_inner();
3693            inner.generation = 2;
3694            inner.reconnect = ReconnectState::Background {
3695                generation: 1,
3696                task: stale_task,
3697            };
3698        }
3699
3700        assert!(shared.spawn_reconnect(2));
3701        assert!(matches!(
3702            &shared.lock_inner().reconnect,
3703            ReconnectState::Background { generation, .. } if *generation == 2
3704        ));
3705
3706        shared.finish_background_reconnect(1);
3707        assert!(matches!(
3708            &shared.lock_inner().reconnect,
3709            ReconnectState::Background { generation, .. } if *generation == 2
3710        ));
3711        shared.close_sync("test complete");
3712    }
3713
3714    #[test]
3715    fn retryable_route_open_codes_are_code_specific() {
3716        for code in [
3717            "unknown_module",
3718            "module_reloading",
3719            "target_unavailable",
3720            "module_timeout",
3721        ] {
3722            assert!(is_retryable_route_open_code(code), "{code} should retry");
3723        }
3724        assert!(!is_retryable_route_open_code("invalid_project_root"));
3725        assert!(!is_retryable_route_open_code("route_rejected"));
3726    }
3727
3728    #[tokio::test]
3729    async fn close_route_flips_inflight_opening_so_a_racing_open_discards() {
3730        // The load-bearing close-beats-reopen guard, in isolation: a close that lands
3731        // while a route.open is in flight (channel not yet cached) must flip the
3732        // opening's `closed` flag, so the lead opener re-checks it before installing and
3733        // GOODBYEs the channel it opened instead of caching it.
3734        let shared = Arc::new(Shared::new(
3735            PathBuf::from("/tmp/does-not-exist"),
3736            ConsumerOptions::default(),
3737        ));
3738        let key = RouteKey::new(
3739            &RouteTarget::ToolProvider {
3740                module_id: "m".into(),
3741            },
3742            &BindIdentity {
3743                project_root: PathBuf::from("/tmp/p"),
3744                harness: "h".into(),
3745                session: "s".into(),
3746            },
3747            None,
3748            None,
3749        );
3750        // Simulate an in-flight lead open: an openings entry exists, not yet closed,
3751        // with no cached route (channel hasn't been installed yet).
3752        shared.lock_inner().openings.insert(
3753            key.clone(),
3754            Opening {
3755                waiters: Vec::new(),
3756                closed: false,
3757            },
3758        );
3759
3760        // close_route with no cached route is an idempotent no-op on routes, but MUST
3761        // flip the in-flight opening's flag so the racing open discards.
3762        shared
3763            .close_route(&key, &CloseRouteOptions::default())
3764            .await;
3765        assert!(
3766            shared
3767                .lock_inner()
3768                .openings
3769                .get(&key)
3770                .is_some_and(|o| o.closed),
3771            "close_route must flip the in-flight opening's closed flag (close-beats-reopen)"
3772        );
3773
3774        // And closing a key with neither a route nor an in-flight open is a no-op.
3775        let absent = RouteKey::new(
3776            &RouteTarget::ToolProvider {
3777                module_id: "absent".into(),
3778            },
3779            &BindIdentity {
3780                project_root: PathBuf::from("/tmp/p"),
3781                harness: "h".into(),
3782                session: "s".into(),
3783            },
3784            None,
3785            None,
3786        );
3787        shared
3788            .close_route(&absent, &CloseRouteOptions::default())
3789            .await;
3790    }
3791
3792    #[test]
3793    fn route_key_is_structured() {
3794        let target = RouteTarget::InternalService {
3795            module_id: "a\0b".into(),
3796            service_id: "svc".into(),
3797        };
3798        let identity = BindIdentity {
3799            project_root: PathBuf::from("/tmp/project"),
3800            harness: "h".into(),
3801            session: "s".into(),
3802        };
3803        let key = RouteKey::new(&target, &identity, None, None);
3804        assert_eq!(key.project_root, PathBuf::from("/tmp/project"));
3805        assert!(matches!(key.target, RouteTargetKey::InternalService { .. }));
3806    }
3807
3808    #[tokio::test]
3809    async fn route_channel_index_tracks_lookup_close_and_generation_drop() {
3810        let shared = writer_test_shared();
3811        let (writer, _rx) = mpsc::channel(32);
3812        let mut expected = Vec::new();
3813        {
3814            let mut inner = shared.lock_inner();
3815            inner.writer = Some(writer);
3816            for channel in 1..=8 {
3817                let key = RouteKey::new(
3818                    &RouteTarget::ToolProvider {
3819                        module_id: format!("module-{channel}"),
3820                    },
3821                    &BindIdentity {
3822                        project_root: PathBuf::from("/tmp/project"),
3823                        harness: "test".into(),
3824                        session: format!("session-{channel}"),
3825                    },
3826                    None,
3827                    None,
3828                );
3829                let route = RouteState {
3830                    handle: RouteHandle::new(channel, channel.into(), 1),
3831                    sem: Arc::new(Semaphore::new(DEFAULT_ROUTE_WINDOW)),
3832                };
3833                inner.cache_route(key.clone(), route.clone());
3834                expected.push((key, route));
3835            }
3836        }
3837
3838        for (key, expected_route) in &expected {
3839            let resolved = shared
3840                .route_state(expected_route.handle)
3841                .expect("an indexed route handle should resolve");
3842            assert_eq!(resolved.handle, expected_route.handle);
3843            assert!(Arc::ptr_eq(&resolved.sem, &expected_route.sem));
3844
3845            let inner = shared.lock_inner();
3846            assert_eq!(
3847                inner.route_by_channel.get(&expected_route.handle.channel),
3848                Some(key)
3849            );
3850            assert_eq!(
3851                inner.route_epochs.get(&expected_route.handle.channel),
3852                Some(&expected_route.handle)
3853            );
3854            assert!(inner
3855                .routes
3856                .get(key)
3857                .is_some_and(|route| route.handle == expected_route.handle));
3858        }
3859
3860        let (closed_key, closed_route) = &expected[3];
3861        shared
3862            .close_route(closed_key, &CloseRouteOptions::default())
3863            .await;
3864        {
3865            let inner = shared.lock_inner();
3866            assert!(!inner.routes.contains_key(closed_key));
3867            assert!(!inner
3868                .route_by_channel
3869                .contains_key(&closed_route.handle.channel));
3870            assert!(!inner
3871                .route_epochs
3872                .contains_key(&closed_route.handle.channel));
3873        }
3874        assert!(matches!(
3875            shared.route_state(closed_route.handle),
3876            Err(CallError::StaleRouteHandle(handle)) if handle == closed_route.handle
3877        ));
3878
3879        shared.handle_generation_drop(1, "test generation dropped".into());
3880        {
3881            let inner = shared.lock_inner();
3882            assert!(inner.routes.is_empty());
3883            assert!(inner.route_by_channel.is_empty());
3884            assert!(inner.route_epochs.is_empty());
3885        }
3886        shared.close_sync("test complete");
3887    }
3888
3889    #[tokio::test]
3890    async fn stale_push_is_not_delivered_after_connection_generation_changes() {
3891        let shared = writer_test_shared();
3892        let old_handle = RouteHandle::new(7, 1, 1);
3893        let (writer, _writer_rx) = mpsc::channel(1);
3894        {
3895            let mut inner = shared.lock_inner();
3896            inner.writer = Some(writer);
3897            inner.route_epochs.insert(old_handle.channel, old_handle);
3898        }
3899        let mut pushes = shared
3900            .register_push_events(old_handle)
3901            .expect("the old live route should accept a receiver");
3902
3903        {
3904            let mut inner = shared.lock_inner();
3905            inner.generation = 2;
3906            inner.close_routes();
3907            inner.route_epochs.clear();
3908        }
3909        shared.route_push(old_handle, b"stale".to_vec());
3910
3911        assert!(
3912            pushes.recv().await.is_none(),
3913            "connection teardown must end the old receiver before a stale Push can arrive"
3914        );
3915        shared.close_sync("test complete");
3916    }
3917
3918    #[test]
3919    fn route_key_canonicalizes_consumer_capabilities() {
3920        let target = RouteTarget::ToolProvider {
3921            module_id: "aft".into(),
3922        };
3923        let identity = BindIdentity {
3924            project_root: PathBuf::from("/tmp/project"),
3925            harness: "h".into(),
3926            session: "s".into(),
3927        };
3928        let left = RouteKey::new(
3929            &target,
3930            &identity,
3931            None,
3932            Some(&["sampling".to_string(), "elicitation".to_string()]),
3933        );
3934        let right = RouteKey::new(
3935            &target,
3936            &identity,
3937            None,
3938            Some(&[
3939                "elicitation".to_string(),
3940                "sampling".to_string(),
3941                "sampling".to_string(),
3942            ]),
3943        );
3944        assert_eq!(left, right);
3945    }
3946
3947    #[test]
3948    fn drain_pending_channel_can_skip_subscriptions() {
3949        let mut pending = HashMap::new();
3950        let generation = 7;
3951        let channel = 11;
3952        let handle = RouteHandle::new(channel, 3, generation);
3953        let unary_key = PendingKey {
3954            generation,
3955            channel,
3956            epoch: handle.epoch,
3957            corr: 1,
3958        };
3959        let subscription_key = PendingKey {
3960            generation,
3961            channel,
3962            epoch: handle.epoch,
3963            corr: 2,
3964        };
3965        let (unary_tx, _unary_rx) = oneshot::channel();
3966        pending.insert(unary_key, PendingEntry::unary(unary_tx, false, None));
3967
3968        let (events_tx, _events_rx) = mpsc::channel(1);
3969        let (closed_tx, _closed_rx) = oneshot::channel();
3970        let permit = Arc::new(Semaphore::new(1))
3971            .try_acquire_owned()
3972            .expect("test semaphore permit should be available");
3973        pending.insert(
3974            subscription_key,
3975            PendingEntry::subscription(events_tx, closed_tx, permit, Priority::Interactive),
3976        );
3977
3978        let drained = drain_pending_handle(&mut pending, handle, false);
3979        assert_eq!(drained.len(), 1);
3980        assert!(pending.contains_key(&subscription_key));
3981
3982        let drained = drain_pending_handle(&mut pending, handle, true);
3983        assert_eq!(drained.len(), 1);
3984        assert!(pending.is_empty());
3985    }
3986
3987    fn response_frame(channel: u16, epoch: u32, corr: u64, body: Vec<u8>) -> Frame {
3988        Frame::build(
3989            FrameType::Response,
3990            Flags::new(false, Priority::Interactive, false),
3991            channel,
3992            epoch,
3993            corr,
3994            body,
3995        )
3996        .unwrap()
3997    }
3998
3999    #[tokio::test]
4000    async fn stale_epoch_ingress_drops_without_settling_matching_corr() {
4001        let shared = Arc::new(Shared::new(
4002            PathBuf::from("/tmp/does-not-exist"),
4003            ConsumerOptions::default(),
4004        ));
4005        let (writer, _rx) = mpsc::channel(4);
4006        let current = RouteHandle::new(9, 2, 1);
4007        let stale_key = PendingKey {
4008            generation: 1,
4009            channel: 9,
4010            epoch: 1,
4011            corr: 77,
4012        };
4013        let key = PendingKey {
4014            generation: 1,
4015            channel: 9,
4016            epoch: 2,
4017            corr: 77,
4018        };
4019        let (stale_tx, mut stale_response) = oneshot::channel();
4020        let (tx, mut response) = oneshot::channel();
4021        {
4022            let mut inner = shared.lock_inner();
4023            inner.writer = Some(writer);
4024            inner.route_epochs.insert(9, current);
4025            inner
4026                .pending
4027                .insert(stale_key, PendingEntry::unary(stale_tx, false, None));
4028            inner
4029                .pending
4030                .insert(key, PendingEntry::unary(tx, false, None));
4031        }
4032
4033        assert!(dispatch_frame(&shared, 1, response_frame(9, 1, 77, b"stale".to_vec())).await);
4034        assert!(matches!(
4035            response.try_recv(),
4036            Err(oneshot::error::TryRecvError::Empty)
4037        ));
4038        assert!(matches!(
4039            stale_response.try_recv(),
4040            Err(oneshot::error::TryRecvError::Empty)
4041        ));
4042        assert!(shared.lock_inner().pending.contains_key(&stale_key));
4043        assert!(shared.lock_inner().pending.contains_key(&key));
4044        assert_eq!(shared.lock_inner().dropped_route_frames, 1);
4045
4046        assert!(dispatch_frame(&shared, 1, response_frame(9, 2, 77, b"current".to_vec())).await);
4047        let PendingResult::Terminal(PendingTerminal::Response { body, .. }) =
4048            response.await.unwrap()
4049        else {
4050            panic!("current epoch must settle its own pending request");
4051        };
4052        assert_eq!(body, b"current");
4053        assert!(shared.lock_inner().pending.contains_key(&stale_key));
4054    }
4055
4056    #[tokio::test]
4057    async fn route_poll_response_must_echo_expected_handle_before_settling() {
4058        let shared = Arc::new(Shared::new(
4059            PathBuf::from("/tmp/does-not-exist"),
4060            ConsumerOptions::default(),
4061        ));
4062        let (writer, _rx) = mpsc::channel(4);
4063        let handle = RouteHandle::new(3, 9, 1);
4064        let key = PendingKey {
4065            generation: 1,
4066            channel: 0,
4067            epoch: 0,
4068            corr: 88,
4069        };
4070        let (tx, mut response) = oneshot::channel();
4071        {
4072            let mut inner = shared.lock_inner();
4073            inner.writer = Some(writer);
4074            inner.route_epochs.insert(handle.channel, handle);
4075            inner
4076                .pending
4077                .insert(key, PendingEntry::unary(tx, false, Some(handle)));
4078        }
4079        let wrong = serde_json::to_vec(&ClientControlResponse::RoutePoll {
4080            route_channel: handle.channel,
4081            route_epoch: handle.epoch + 1,
4082            status: Some("wrong".to_string()),
4083            live: Some(true),
4084        })
4085        .unwrap();
4086        assert!(dispatch_frame(&shared, 1, response_frame(0, 0, key.corr, wrong)).await);
4087        assert!(matches!(
4088            response.try_recv(),
4089            Err(oneshot::error::TryRecvError::Empty)
4090        ));
4091        assert!(shared.lock_inner().pending.contains_key(&key));
4092
4093        let correct = serde_json::to_vec(&ClientControlResponse::RoutePoll {
4094            route_channel: handle.channel,
4095            route_epoch: handle.epoch,
4096            status: Some("ready".to_string()),
4097            live: Some(true),
4098        })
4099        .unwrap();
4100        assert!(dispatch_frame(&shared, 1, response_frame(0, 0, key.corr, correct)).await);
4101        assert!(matches!(
4102            response.await.unwrap(),
4103            PendingResult::Terminal(PendingTerminal::Response { .. })
4104        ));
4105    }
4106
4107    #[tokio::test]
4108    async fn stale_connection_handle_emits_no_request_cancel_or_goodbye() {
4109        let shared = Arc::new(Shared::new(
4110            PathBuf::from("/tmp/does-not-exist"),
4111            ConsumerOptions::default(),
4112        ));
4113        let (writer, mut rx) = mpsc::channel(4);
4114        let stale = RouteHandle::new(4, 1, 1);
4115        let current = RouteHandle::new(4, 1, 2);
4116        {
4117            let mut inner = shared.lock_inner();
4118            inner.generation = 2;
4119            inner.writer = Some(writer);
4120            inner.route_epochs.insert(4, current);
4121        }
4122
4123        let err = shared
4124            .send_request(RequestSend {
4125                expected_handle: Some(stale),
4126                channel: stale.channel,
4127                epoch: stale.epoch,
4128                body: b"request".to_vec(),
4129                priority: Priority::Interactive,
4130                admission_class: AdmissionClass::Normal,
4131                deadline: Instant::now() + Duration::from_millis(10),
4132                retain_late_route_open: false,
4133            })
4134            .await
4135            .unwrap_err();
4136        assert!(matches!(err, CallError::StaleRouteHandle(handle) if handle == stale));
4137        shared.send_cancel(stale, 8, Priority::Interactive);
4138        assert!(!shared.send_route_goodbye(stale, false));
4139        assert!(
4140            rx.try_recv().is_err(),
4141            "stale operations must not queue frames"
4142        );
4143    }
4144
4145    #[tokio::test]
4146    async fn late_route_open_queues_goodbye_and_full_queue_closes_connection() {
4147        let shared = Arc::new(Shared::new(
4148            PathBuf::from("/tmp/does-not-exist"),
4149            ConsumerOptions::default(),
4150        ));
4151        let (writer, mut rx) = mpsc::channel(2);
4152        let key = PendingKey {
4153            generation: 1,
4154            channel: 0,
4155            epoch: 0,
4156            corr: 41,
4157        };
4158        let (tx, response) = oneshot::channel();
4159        drop(response);
4160        {
4161            let mut inner = shared.lock_inner();
4162            inner.writer = Some(writer);
4163            inner
4164                .pending
4165                .insert(key, PendingEntry::unary(tx, true, None));
4166        }
4167        let body = serde_json::to_vec(&ClientControlResponse::RouteOpen {
4168            route_channel: 12,
4169            route_epoch: 7,
4170        })
4171        .unwrap();
4172        assert!(dispatch_frame(&shared, 1, response_frame(0, 0, 41, body)).await);
4173        let cleanup = rx.recv().await.unwrap().frame;
4174        assert_eq!(cleanup.header.ty, FrameType::Goodbye);
4175        assert_eq!((cleanup.header.channel, cleanup.header.epoch), (12, 7));
4176
4177        let shared = Arc::new(Shared::new(
4178            PathBuf::from("/tmp/does-not-exist"),
4179            ConsumerOptions {
4180                reconnect_backoff: RetryBackoff {
4181                    max_attempts: 1,
4182                    ..RetryBackoff::default()
4183                },
4184                ..ConsumerOptions::default()
4185            },
4186        ));
4187        let (writer, _rx) = mpsc::channel(1);
4188        let filler = response_frame(0, 0, 1, Vec::new());
4189        writer
4190            .try_send(WriteCommand {
4191                frame: filler,
4192                pending: None,
4193            })
4194            .unwrap();
4195        let key = PendingKey {
4196            generation: 1,
4197            channel: 0,
4198            epoch: 0,
4199            corr: 42,
4200        };
4201        let (tx, response) = oneshot::channel();
4202        drop(response);
4203        {
4204            let mut inner = shared.lock_inner();
4205            inner.writer = Some(writer);
4206            inner
4207                .pending
4208                .insert(key, PendingEntry::unary(tx, true, None));
4209        }
4210        let body = serde_json::to_vec(&ClientControlResponse::RouteOpen {
4211            route_channel: 13,
4212            route_epoch: 8,
4213        })
4214        .unwrap();
4215        assert!(dispatch_frame(&shared, 1, response_frame(0, 0, 42, body)).await);
4216        assert!(shared.lock_inner().writer.is_none());
4217    }
4218
4219    #[tokio::test]
4220    async fn correlation_allocator_emits_max_once_then_closes_without_reuse() {
4221        let shared = Arc::new(Shared::new(
4222            PathBuf::from("/tmp/does-not-exist"),
4223            ConsumerOptions {
4224                reconnect_backoff: RetryBackoff {
4225                    max_attempts: 1,
4226                    ..RetryBackoff::default()
4227                },
4228                ..ConsumerOptions::default()
4229            },
4230        ));
4231        let (writer, mut rx) = mpsc::channel(4);
4232        {
4233            let mut inner = shared.lock_inner();
4234            inner.writer = Some(writer);
4235            inner.next_corr = Some(u64::MAX);
4236        }
4237        let request_shared = Arc::clone(&shared);
4238        let request = tokio::spawn(async move {
4239            request_shared
4240                .send_request(RequestSend {
4241                    expected_handle: None,
4242                    channel: 0,
4243                    epoch: 0,
4244                    body: Vec::new(),
4245                    priority: Priority::Interactive,
4246                    admission_class: AdmissionClass::Normal,
4247                    deadline: Instant::now() + Duration::from_secs(1),
4248                    retain_late_route_open: false,
4249                })
4250                .await
4251        });
4252        let command = rx.recv().await.unwrap();
4253        assert_eq!(command.frame.header.corr, u64::MAX);
4254        assert!(dispatch_frame(&shared, 1, response_frame(0, 0, u64::MAX, Vec::new()),).await);
4255        assert!(request.await.unwrap().is_ok());
4256
4257        let exhausted = shared
4258            .send_request(RequestSend {
4259                expected_handle: None,
4260                channel: 0,
4261                epoch: 0,
4262                body: Vec::new(),
4263                priority: Priority::Interactive,
4264                admission_class: AdmissionClass::Normal,
4265                deadline: Instant::now() + Duration::from_millis(10),
4266                retain_late_route_open: false,
4267            })
4268            .await
4269            .unwrap_err();
4270        assert!(matches!(exhausted, CallError::NotSent(_)));
4271        assert!(rx.try_recv().is_err());
4272        assert!(shared.lock_inner().writer.is_none());
4273    }
4274
4275    #[tokio::test]
4276    async fn managed_call_deadline_bounds_flow_control_wait() {
4277        let shared = Arc::new(Shared::new(
4278            PathBuf::from("/tmp/does-not-exist"),
4279            ConsumerOptions::default(),
4280        ));
4281        let (writer, mut rx) = mpsc::channel(4);
4282        let target = RouteTarget::ToolProvider {
4283            module_id: "flow-controlled".to_string(),
4284        };
4285        let identity = BindIdentity {
4286            project_root: PathBuf::from("/tmp/project"),
4287            harness: "test".to_string(),
4288            session: "deadline".to_string(),
4289        };
4290        let consumer_identity = Some(ConsumerIdentity {
4291            module_id: "caller".to_string(),
4292            launch_nonce: "nonce".to_string(),
4293        });
4294        let first_opts = CallOptions {
4295            timeout: Duration::from_secs(1),
4296            consumer_identity: consumer_identity.clone(),
4297            ..CallOptions::default()
4298        };
4299        let second_opts = CallOptions {
4300            timeout: Duration::from_millis(25),
4301            consumer_identity,
4302            ..CallOptions::default()
4303        };
4304        let key = RouteKey::new(
4305            &target,
4306            &identity,
4307            first_opts.consumer_identity.as_ref(),
4308            None,
4309        );
4310        let handle = RouteHandle::new(5, 3, 1);
4311        {
4312            let mut inner = shared.lock_inner();
4313            inner.writer = Some(writer);
4314            inner.cache_route(
4315                key,
4316                RouteState {
4317                    handle,
4318                    sem: Arc::new(Semaphore::new(1)),
4319                },
4320            );
4321        }
4322
4323        let first = tokio::spawn({
4324            let consumer = SubcConsumer {
4325                shared: Arc::clone(&shared),
4326            };
4327            let target = target.clone();
4328            let identity = identity.clone();
4329            async move {
4330                consumer
4331                    .call(target, identity, b"first".to_vec(), first_opts)
4332                    .await
4333            }
4334        });
4335        let first_frame = rx
4336            .recv()
4337            .await
4338            .expect("the first request should enter the fake daemon queue");
4339        assert_eq!(first_frame.frame.header.ty, FrameType::Request);
4340        assert_eq!(first_frame.frame.body, b"first");
4341        assert!(shared.mark_pending_accepted(
4342            first_frame
4343                .pending
4344                .expect("request commands retain their pending key"),
4345        ));
4346
4347        let consumer = SubcConsumer {
4348            shared: Arc::clone(&shared),
4349        };
4350        let result = tokio::time::timeout(
4351            Duration::from_millis(250),
4352            consumer.call(target, identity, b"second".to_vec(), second_opts),
4353        )
4354        .await
4355        .expect("a flow-controlled call must finish at its own deadline")
4356        .unwrap_err();
4357        assert!(matches!(result, CallError::NotSent(_)));
4358        assert!(
4359            rx.try_recv().is_err(),
4360            "the timed-out second request must not reach the fake daemon"
4361        );
4362
4363        first.abort();
4364        let _ = first.await;
4365    }
4366
4367    #[tokio::test]
4368    async fn admitted_route_open_emits_one_frame_without_retrying_daemon_errors() {
4369        let shared = Arc::new(Shared::new(
4370            PathBuf::from("/tmp/does-not-exist"),
4371            ConsumerOptions {
4372                call_timeout: Duration::from_secs(1),
4373                ..ConsumerOptions::default()
4374            },
4375        ));
4376        let (writer, mut rx) = mpsc::channel(4);
4377        {
4378            let mut inner = shared.lock_inner();
4379            inner.writer = Some(writer);
4380        }
4381
4382        let consumer = SubcConsumer {
4383            shared: Arc::clone(&shared),
4384        };
4385        let target = RouteTarget::ToolProvider {
4386            module_id: "admitted-target".to_string(),
4387        };
4388        let identity = BindIdentity {
4389            project_root: PathBuf::from("/tmp/project"),
4390            harness: "test".to_string(),
4391            session: "admitted".to_string(),
4392        };
4393        let task = tokio::spawn(async move {
4394            consumer
4395                .open_route_with_admission_facts(
4396                    target,
4397                    identity,
4398                    serde_json::json!({"schema": 1, "verified_class": "member"}),
4399                )
4400                .await
4401        });
4402
4403        let command = rx.recv().await.expect("one route.open must be queued");
4404        let request: ClientControlRequest = serde_json::from_slice(&command.frame.body).unwrap();
4405        let ClientControlRequest::RouteOpen {
4406            admission_facts, ..
4407        } = request
4408        else {
4409            panic!("expected route.open")
4410        };
4411        assert_eq!(
4412            admission_facts,
4413            Some(serde_json::json!({"schema": 1, "verified_class": "member"}))
4414        );
4415
4416        let error_body = serde_json::to_vec(&ErrorBody {
4417            code: "admission_facts_not_permitted".to_string(),
4418            message: "not permitted".to_string(),
4419        })
4420        .unwrap();
4421        assert!(
4422            dispatch_frame(
4423                &shared,
4424                1,
4425                Frame::build(
4426                    FrameType::Error,
4427                    Flags::new(false, Priority::Interactive, false),
4428                    0,
4429                    0,
4430                    command.frame.header.corr,
4431                    error_body,
4432                )
4433                .unwrap(),
4434            )
4435            .await
4436        );
4437        let result = task.await.unwrap();
4438        assert!(matches!(result, Err(CallError::NotSent(_))));
4439        assert!(rx.try_recv().is_err(), "one-shot route.open must not retry");
4440    }
4441
4442    #[tokio::test]
4443    async fn route_open_waiter_deadline_is_not_sent_without_writing() {
4444        let shared = Arc::new(Shared::new(
4445            PathBuf::from("/tmp/does-not-exist"),
4446            ConsumerOptions::default(),
4447        ));
4448        let (writer, mut rx) = mpsc::channel(4);
4449        let target = RouteTarget::ToolProvider {
4450            module_id: "single-flight".to_string(),
4451        };
4452        let identity = BindIdentity {
4453            project_root: PathBuf::from("/tmp/project"),
4454            harness: "test".to_string(),
4455            session: "route-open".to_string(),
4456        };
4457        let opts = CallOptions {
4458            timeout: Duration::from_millis(25),
4459            consumer_identity: Some(ConsumerIdentity {
4460                module_id: "caller".to_string(),
4461                launch_nonce: "nonce".to_string(),
4462            }),
4463            ..CallOptions::default()
4464        };
4465        let key = RouteKey::new(&target, &identity, opts.consumer_identity.as_ref(), None);
4466        {
4467            let mut inner = shared.lock_inner();
4468            inner.writer = Some(writer);
4469            inner.openings.insert(
4470                key,
4471                Opening {
4472                    waiters: Vec::new(),
4473                    closed: false,
4474                },
4475            );
4476        }
4477
4478        let consumer = SubcConsumer { shared };
4479        let result = tokio::time::timeout(
4480            Duration::from_millis(250),
4481            consumer.open_route(target, identity, opts),
4482        )
4483        .await
4484        .expect("a route.open waiter must finish at its own deadline")
4485        .unwrap_err();
4486        assert!(matches!(result, CallError::NotSent(_)));
4487        assert!(
4488            rx.try_recv().is_err(),
4489            "a timed-out route.open waiter must not write a control frame"
4490        );
4491    }
4492
4493    #[tokio::test]
4494    async fn route_poll_deadline_bounds_writer_capacity() {
4495        let shared = Arc::new(Shared::new(
4496            PathBuf::from("/tmp/does-not-exist"),
4497            ConsumerOptions::default(),
4498        ));
4499        let (writer, mut rx) = mpsc::channel(1);
4500        let handle = RouteHandle::new(8, 4, 1);
4501        writer
4502            .try_send(WriteCommand {
4503                frame: response_frame(0, 0, 99, Vec::new()),
4504                pending: None,
4505            })
4506            .expect("the fake daemon queue should accept its filler frame");
4507        {
4508            let mut inner = shared.lock_inner();
4509            inner.writer = Some(writer);
4510            inner.route_epochs.insert(handle.channel, handle);
4511        }
4512
4513        let consumer = SubcConsumer { shared };
4514        let result = tokio::time::timeout(
4515            Duration::from_millis(250),
4516            consumer.poll_route(&handle, PollKind::Liveness, Duration::from_millis(25)),
4517        )
4518        .await
4519        .expect("a control request must finish at its own deadline")
4520        .unwrap_err();
4521        assert!(matches!(result, CallError::NotSent(_)));
4522        assert_eq!(
4523            rx.recv()
4524                .await
4525                .expect("the filler must still be the only queued frame")
4526                .frame
4527                .header
4528                .corr,
4529            99
4530        );
4531        assert!(
4532            rx.try_recv().is_err(),
4533            "the timed-out control request must not reach the fake daemon"
4534        );
4535    }
4536
4537    #[tokio::test]
4538    async fn subscription_deadline_bounds_writer_capacity() {
4539        let shared = Arc::new(Shared::new(
4540            PathBuf::from("/tmp/does-not-exist"),
4541            ConsumerOptions::default(),
4542        ));
4543        let (writer, mut rx) = mpsc::channel(1);
4544        let handle = RouteHandle::new(9, 2, 1);
4545        let route_sem = Arc::new(Semaphore::new(1));
4546        writer
4547            .try_send(WriteCommand {
4548                frame: response_frame(0, 0, 100, Vec::new()),
4549                pending: None,
4550            })
4551            .expect("the fake daemon queue should accept its filler frame");
4552        {
4553            let mut inner = shared.lock_inner();
4554            inner.writer = Some(writer);
4555            inner.cache_route(
4556                RouteKey::new(
4557                    &RouteTarget::ToolProvider {
4558                        module_id: "subscriptions".to_string(),
4559                    },
4560                    &BindIdentity {
4561                        project_root: PathBuf::from("/tmp/project"),
4562                        harness: "test".to_string(),
4563                        session: "subscription".to_string(),
4564                    },
4565                    None,
4566                    None,
4567                ),
4568                RouteState {
4569                    handle,
4570                    sem: Arc::clone(&route_sem),
4571                },
4572            );
4573        }
4574
4575        let consumer = SubcConsumer { shared };
4576        let result = tokio::time::timeout(
4577            Duration::from_millis(250),
4578            consumer.subscribe_route(
4579                &handle,
4580                b"subscribe".to_vec(),
4581                SubscribeOptions {
4582                    route_open_timeout: Duration::from_millis(25),
4583                    ..SubscribeOptions::default()
4584                },
4585            ),
4586        )
4587        .await
4588        .expect("a subscription must finish at its route-open deadline");
4589        let result = match result {
4590            Ok(_) => panic!("a subscription blocked before writing must time out"),
4591            Err(err) => err,
4592        };
4593        assert!(matches!(result, CallError::NotSent(_)));
4594        assert_eq!(
4595            rx.recv()
4596                .await
4597                .expect("the filler must still be the only queued frame")
4598                .frame
4599                .header
4600                .corr,
4601            100
4602        );
4603        assert!(
4604            rx.try_recv().is_err(),
4605            "the timed-out subscription must not reach the fake daemon"
4606        );
4607        assert!(
4608            route_sem.try_acquire().is_ok(),
4609            "a pre-write subscription timeout must release its route credit"
4610        );
4611    }
4612
4613    #[test]
4614    fn catalog_list_deserializes_golden_reply_and_ignores_unknown_fields() {
4615        let mut reply: serde_json::Value = serde_json::from_str(include_str!(
4616            "../../subc-control/tests/golden/client_control_response_catalog_list.json"
4617        ))
4618        .expect("the catalog.list golden reply must be valid JSON");
4619        reply["future_top_level"] = serde_json::json!(true);
4620        reply["modules"][0]["future_module_field"] = serde_json::json!("ignored");
4621
4622        let catalog: CatalogList =
4623            serde_json::from_value(reply).expect("catalog.list should tolerate additive fields");
4624        assert_eq!(catalog.generation, 7);
4625        assert_eq!(catalog.modules.len(), 1);
4626        assert!(catalog.subc_ops.iter().any(|op| op == "catalog.list"));
4627
4628        let tools = catalog.modules[0]
4629            .roles
4630            .iter()
4631            .find_map(|role| match role {
4632                subc_protocol::manifest::ProviderRole::ToolProvider { tools, .. } => Some(tools),
4633                _ => None,
4634            })
4635            .expect("the golden module must advertise a tool_provider role");
4636        let tool = tools
4637            .first()
4638            .expect("the golden tool_provider role must advertise a tool");
4639        assert!(!tool.name.is_empty());
4640        assert_eq!(
4641            tool.schema.get("type").and_then(serde_json::Value::as_str),
4642            Some("object")
4643        );
4644        assert!(matches!(
4645            tool.execution_mode,
4646            subc_protocol::manifest::ExecutionMode::Pure
4647        ));
4648    }
4649
4650    #[tokio::test]
4651    async fn catalog_list_sends_an_unfiltered_channel_zero_request() {
4652        let shared = Arc::new(Shared::new(
4653            PathBuf::from("/tmp/does-not-exist"),
4654            ConsumerOptions::default(),
4655        ));
4656        let (writer, mut rx) = mpsc::channel(1);
4657        shared.lock_inner().writer = Some(writer);
4658
4659        let consumer = SubcConsumer {
4660            shared: Arc::clone(&shared),
4661        };
4662        let request = tokio::spawn(async move { consumer.catalog_list().await });
4663        let command = rx
4664            .recv()
4665            .await
4666            .expect("catalog.list must queue a channel-0 request");
4667        assert_eq!(command.frame.header.channel, 0);
4668        let body: serde_json::Value = serde_json::from_slice(&command.frame.body).unwrap();
4669        assert_eq!(body["op"], "catalog.list");
4670        assert!(
4671            body.get("module_id").is_none(),
4672            "catalog.list must request the complete catalog without a module filter"
4673        );
4674
4675        let response = serde_json::to_vec(&ClientControlResponse::CatalogList {
4676            generation: 9,
4677            modules: Vec::new(),
4678            subc_ops: vec!["catalog.list".to_string()],
4679        })
4680        .unwrap();
4681        assert!(
4682            dispatch_frame(
4683                &shared,
4684                1,
4685                response_frame(0, 0, command.frame.header.corr, response),
4686            )
4687            .await
4688        );
4689        let catalog = request.await.unwrap().unwrap();
4690        assert_eq!(catalog.generation, 9);
4691        assert!(catalog.modules.is_empty());
4692    }
4693
4694    #[tokio::test]
4695    async fn catalog_list_deadline_is_not_sent_when_reconnection_stays_down() {
4696        let shared = Arc::new(Shared::new(
4697            PathBuf::from("/tmp/subc-client-rs-catalog-list-unavailable"),
4698            ConsumerOptions {
4699                call_timeout: Duration::from_millis(25),
4700                reconnect_backoff: RetryBackoff {
4701                    base: Duration::from_millis(1),
4702                    cap: Duration::from_millis(1),
4703                    max_attempts: 100,
4704                },
4705                ..ConsumerOptions::default()
4706            },
4707        ));
4708        let consumer = SubcConsumer { shared };
4709        let result = tokio::time::timeout(Duration::from_millis(250), consumer.catalog_list())
4710            .await
4711            .expect("catalog.list must finish at its configured deadline")
4712            .unwrap_err();
4713        assert!(matches!(result, CallError::NotSent(_)));
4714    }
4715}