Skip to main content

liminal_server/server/connection/
supervisor.rs

1use std::collections::HashMap;
2#[cfg(test)]
3use std::collections::VecDeque;
4use std::collections::hash_map::Entry;
5use std::net::{SocketAddr, TcpStream};
6use std::os::fd::RawFd;
7#[cfg(test)]
8use std::sync::Barrier;
9use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
10use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender, TryRecvError, channel};
11use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError, Weak};
12use std::thread;
13use std::time::{Duration, Instant};
14
15use beamr::atom::{Atom, AtomTable};
16use beamr::module::ModuleRegistry;
17use beamr::native::native_process::NativeHandlerFactory;
18use beamr::process::ExitReason;
19use beamr::scheduler::{
20    ExitEvent, ExitEventSubscription, NativeBifs, ReadinessToken, Scheduler, SchedulerConfig,
21    SchedulerServices,
22};
23use beamr::timer::TimerRef;
24
25use liminal::protocol::WorkerRegistration;
26use liminal_protocol::wire::ConnectionIncarnation;
27
28use super::incarnation::ConnectionIncarnationAuthority;
29use super::loopback::{LoopbackConnectionProcess, LoopbackServerEnd};
30use super::notifier::ConnectionNotifier;
31use super::outbound::OutboundError;
32use super::process::ConnectionProcess;
33use super::refusal::AdmissionRefusal;
34#[cfg(any(test, feature = "test-support"))]
35use super::services::LiminalConnectionServices;
36use super::services::{ConnectionServices, build_connection_services};
37#[cfg(test)]
38use super::services::{SubsystemFactory, build_connection_services_via};
39use crate::ServerError;
40use crate::auth_pass::{PassPrincipal, PassVerifier};
41use crate::config::types::{AuthConfig, LimitsConfig, ServerConfig};
42use crate::health::AdmissionReadiness;
43use crate::server::mount::MountKind;
44use crate::server::participant::{
45    CandidateLaneListener, ConnectionFateClass, InstalledParticipantService,
46    ParticipantSemanticHandler, ParticipantServiceFatal,
47};
48use crate::server::shutdown::ShutdownHandle;
49use crate::server::signals::ServerSignals;
50
51const CONNECTION_SCHEDULER_THREADS: usize = 4;
52const CONNECTION_SHUTDOWN_CONTROL_ATOM: &str = "liminal_server_connection_shutdown_control";
53/// R6 (§1.2(4)): the single `READY` wake vocabulary for a connection. One atom;
54/// any marker (or N coalesced) triggers one full slice servicing all sources.
55const CONNECTION_READY_ATOM: &str = "liminal_server_connection_ready";
56
57#[path = "supervisor_fate.rs"]
58mod fate;
59use fate::ParkedConnectionFates;
60#[cfg(test)]
61#[path = "supervisor_fate_tests.rs"]
62mod fate_tests;
63#[cfg(test)]
64#[path = "supervisor_shutdown_wake_tests.rs"]
65mod shutdown_wake_tests;
66#[cfg(test)]
67#[path = "supervisor_tests.rs"]
68mod tests;
69
70/// Supervisor that owns the beamr scheduler for per-connection processes.
71#[derive(Clone, Debug)]
72pub struct ConnectionSupervisor {
73    inner: Arc<SupervisorInner>,
74}
75
76impl ConnectionSupervisor {
77    pub(in crate::server) fn install_signals(
78        &self,
79        signals: ServerSignals,
80    ) -> Result<(), ServerError> {
81        self.inner
82            .runtime
83            .signals
84            .set(signals)
85            .map_err(|_| ServerError::ListenerAccept {
86                message: "connection signals were already installed".to_owned(),
87            })
88    }
89
90    #[cfg(test)]
91    pub(in crate::server) fn signals_installed(&self) -> bool {
92        self.inner.runtime.signals.get().is_some()
93    }
94
95    pub(crate) fn current_server_incarnation(&self) -> Result<Option<u64>, ServerError> {
96        self.inner
97            .incarnations
98            .as_ref()
99            .map(|authority| authority.current_server_incarnation())
100            .transpose()
101    }
102
103    /// Creates a connection supervisor backed by the services the config's
104    /// `[services]` profile selects: the full liminal channel/conversation stack
105    /// (the default) or the capability-scoped worker front door. Profile
106    /// enforcement is [`build_connection_services`](super::services::build_connection_services)'s,
107    /// so this constructor can never build full services for a worker-front-door
108    /// config.
109    ///
110    /// # Errors
111    /// Returns [`ServerError`] when service construction or scheduler startup fails.
112    pub fn from_config(config: &ServerConfig) -> Result<Self, ServerError> {
113        Self::from_config_with_services(config, build_connection_services(config)?)
114    }
115
116    /// [`Self::from_config`] with the §9 D2 subsystem factory injected.
117    ///
118    /// The factory is the only route to every scheduler-owning subsystem the
119    /// services construction builds, so a recording factory observes exactly what
120    /// was constructed; the connection scheduler itself (built below for BOTH
121    /// profiles) is the census baseline, not a census entry.
122    #[cfg(test)]
123    fn from_config_via(
124        config: &ServerConfig,
125        subsystems: &dyn SubsystemFactory,
126    ) -> Result<Self, ServerError> {
127        Self::from_config_with_services(config, build_connection_services_via(config, subsystems)?)
128    }
129
130    fn from_config_with_services(
131        config: &ServerConfig,
132        services: Arc<dyn ConnectionServices>,
133    ) -> Result<Self, ServerError> {
134        // Absent `[auth]` leaves both `None`, so the connection stays open-access.
135        let authentication = config
136            .auth
137            .as_ref()
138            .map(configured_authentication)
139            .transpose()?;
140        let (auth_token, pass_verifier) =
141            authentication.map_or((None, None), |(token, verifier)| (Some(token), verifier));
142        SupervisorInner::new(
143            services,
144            None,
145            auth_token,
146            pass_verifier,
147            config
148                .auth
149                .as_ref()
150                .map(|auth| configured_lys(auth, tokio::runtime::Handle::try_current().ok()))
151                .transpose()?
152                .flatten(),
153            config.limits,
154            None,
155        )
156        .map(|inner| Self {
157            inner: Arc::new(inner),
158        })
159    }
160
161    /// Starts composing a connection supervisor over services the caller built
162    /// and keeps: the embedder's constructor.
163    ///
164    /// Every other public constructor fixes one of the three optional parts —
165    /// [`Self::from_config`] installs the configured `[auth]` (bearer AND pass
166    /// verifier) but builds its own services, [`Self::with_services_and_auth`]
167    /// carries a bearer and never a pass verifier, and
168    /// [`Self::with_services_and_notifier`] carries a notifier and no auth at
169    /// all. An application that embeds liminal — holding the
170    /// `Arc<LiminalConnectionServices>` to publish in-process, verifying
171    /// registry passes at Connect, and observing connection lifecycle through a
172    /// notifier — needs all three at once, and this is the one route to them.
173    /// Nothing set on the builder defaults exactly as the non-config
174    /// constructors default it: open access, no notifier, signed default
175    /// limits.
176    #[must_use]
177    pub fn builder(services: Arc<dyn ConnectionServices>) -> ConnectionSupervisorBuilder {
178        ConnectionSupervisorBuilder {
179            services,
180            notifier: None,
181            auth_token: None,
182            pass_verifier: None,
183            lys: None,
184            limits: LimitsConfig::default(),
185        }
186    }
187
188    /// Creates a connection supervisor with no configured channels.
189    ///
190    /// # Errors
191    /// Returns [`ServerError`] when scheduler startup fails.
192    #[cfg(any(test, feature = "test-support"))]
193    pub fn new() -> Result<Self, ServerError> {
194        Self::with_services(Arc::new(LiminalConnectionServices::empty()?))
195    }
196
197    /// Creates a connection supervisor using an explicit service adapter.
198    ///
199    /// # Errors
200    /// Returns [`ServerError`] when scheduler startup fails.
201    pub fn with_services(services: Arc<dyn ConnectionServices>) -> Result<Self, ServerError> {
202        SupervisorInner::new(
203            services,
204            None,
205            None,
206            None,
207            None,
208            LimitsConfig::default(),
209            None,
210        )
211        .map(|inner| Self {
212            inner: Arc::new(inner),
213        })
214    }
215
216    /// Creates a connection supervisor with an explicit service adapter and the
217    /// configured connection auth token.
218    ///
219    /// This is the production constructor for callers that build services
220    /// themselves (the runtime needs the shared channel cluster before the
221    /// supervisor takes ownership) and therefore cannot use
222    /// [`Self::from_config`]: without it the configured `[auth]` token would be
223    /// silently dropped and the server would run open-access.
224    ///
225    /// # Errors
226    /// Returns [`ServerError`] when scheduler startup fails.
227    pub fn with_services_and_auth(
228        services: Arc<dyn ConnectionServices>,
229        auth_token: Option<Vec<u8>>,
230    ) -> Result<Self, ServerError> {
231        Self::with_services_auth_and_limits(services, auth_token, LimitsConfig::default())
232    }
233
234    /// Creates a connection supervisor with explicit services, authentication,
235    /// and operational limits.
236    ///
237    /// Production runtime construction uses this form so the durable
238    /// incarnation stream's complete-reference bound is the same signed
239    /// `max_connections` bound enforced by connection admission.
240    ///
241    /// # Errors
242    /// Returns [`ServerError`] when incarnation startup or scheduler startup fails.
243    pub fn with_services_auth_and_limits(
244        services: Arc<dyn ConnectionServices>,
245        auth_token: Option<Vec<u8>>,
246        limits: LimitsConfig,
247    ) -> Result<Self, ServerError> {
248        Self::with_services_auth_limits_and_fatal_shutdown(services, auth_token, limits, None)
249    }
250
251    /// Production composition with the process-wide shutdown activation that a
252    /// post-Open participant fatal must join, without a Lys install: the
253    /// fixtures' constructor.
254    #[cfg(test)]
255    pub(crate) fn with_fatal_shutdown(
256        services: Arc<dyn ConnectionServices>,
257        auth_token: Option<Vec<u8>>,
258        pass_verifier: Option<PassVerifier>,
259        limits: LimitsConfig,
260        fatal_shutdown: ShutdownHandle,
261    ) -> Result<Self, ServerError> {
262        Self::with_lys_and_fatal_shutdown(
263            services,
264            auth_token,
265            pass_verifier,
266            None,
267            limits,
268            fatal_shutdown,
269        )
270    }
271
272    /// The production constructor: `[auth]` whole, the Lys install
273    /// (`[auth.lys]`: admitter, link host, service bindings) included, with
274    /// the fatal shutdown handle. The server's `run` builds through here so
275    /// Lys reaches every connection it admits (ACCESS-003 R1 FOUND: the
276    /// path without `lys` installed none in production).
277    pub(crate) fn with_lys_and_fatal_shutdown(
278        services: Arc<dyn ConnectionServices>,
279        auth_token: Option<Vec<u8>>,
280        pass_verifier: Option<PassVerifier>,
281        lys: Option<LysInstall>,
282        limits: LimitsConfig,
283        fatal_shutdown: ShutdownHandle,
284    ) -> Result<Self, ServerError> {
285        SupervisorInner::new(
286            services,
287            None,
288            auth_token,
289            pass_verifier,
290            lys,
291            limits,
292            Some(fatal_shutdown),
293        )
294        .map(|inner| Self {
295            inner: Arc::new(inner),
296        })
297    }
298
299    fn with_services_auth_limits_and_fatal_shutdown(
300        services: Arc<dyn ConnectionServices>,
301        auth_token: Option<Vec<u8>>,
302        limits: LimitsConfig,
303        fatal_shutdown: Option<ShutdownHandle>,
304    ) -> Result<Self, ServerError> {
305        SupervisorInner::new(
306            services,
307            None,
308            auth_token,
309            None,
310            None,
311            limits,
312            fatal_shutdown,
313        )
314        .map(|inner| Self {
315            inner: Arc::new(inner),
316        })
317    }
318
319    /// Creates a connection supervisor with an explicit service adapter and a
320    /// connection-keyed worker-registration notifier.
321    ///
322    /// The `notifier` is invoked when a worker registers on a connection and when
323    /// such a connection closes, and when a pass-stamped connection attaches
324    /// and closes. Supervisors built via [`Self::with_services`],
325    /// [`Self::from_config`], or [`Self::new`] carry no notifier, so liminal still
326    /// runs standalone; a `WorkerRegister` frame is then accepted without any
327    /// application callback.
328    ///
329    /// # Errors
330    /// Returns [`ServerError`] when scheduler startup fails.
331    pub fn with_services_and_notifier(
332        services: Arc<dyn ConnectionServices>,
333        notifier: Arc<dyn ConnectionNotifier>,
334    ) -> Result<Self, ServerError> {
335        SupervisorInner::new(
336            services,
337            Some(notifier),
338            None,
339            None,
340            None,
341            LimitsConfig::default(),
342            None,
343        )
344        .map(|inner| Self {
345            inner: Arc::new(inner),
346        })
347    }
348
349    /// A handle on whether this server can admit a connection, for the
350    /// readiness probe (P0 #56 R4).
351    ///
352    /// Handed to `SharedReadinessState::track_admission` once the supervisor
353    /// exists. The health endpoint binds BEFORE the supervisor is built —
354    /// liveness has to be answerable while the rest of the server is still
355    /// coming up — so this cannot be wired at readiness construction and is
356    /// installed afterwards instead.
357    #[must_use]
358    pub fn admission_readiness(&self) -> AdmissionReadiness {
359        self.inner.admission_readiness.clone()
360    }
361
362    /// The channel membership gate `[auth.lys.membership]` installed
363    /// (ACCESS-006 R2), for the ACCESS-005 grant owner: it installs itself
364    /// with [`crate::auth_membership::MembershipGate::install_authority`] and
365    /// drives each applied change and resume into the gate through
366    /// [`crate::auth_membership::MembershipListener`]. `None` without the
367    /// section.
368    #[must_use]
369    pub fn membership_gate(&self) -> Option<Arc<crate::auth_membership::MembershipGate>> {
370        self.inner.runtime.membership_gate().cloned()
371    }
372
373    /// Counts one refused admission, classified by reason.
374    ///
375    /// Hung on the THREE public admission doors — TCP accept, the sibling
376    /// transport spawn (WebSocket), and the in-process loopback — because those
377    /// are the three places a connection can be turned away, and each of them
378    /// reaches the shared inner body exactly once. Recording deeper would
379    /// double-count the loopback (which calls the inner body directly);
380    /// recording shallower would miss the doors that only log.
381    fn record_admission_refusal(error: &ServerError) {
382        crate::metrics::admission_refused(AdmissionRefusal::classify(error));
383    }
384
385    /// The runtime every connection process shares. Test-only.
386    #[cfg(test)]
387    pub(super) fn runtime_for_test(&self) -> Arc<ConnectionRuntime> {
388        Arc::clone(&self.inner.runtime)
389    }
390
391    /// Spawns one supervised beamr process that owns `stream`.
392    ///
393    /// # Errors
394    /// Returns [`ServerError`] when stream configuration or beamr spawn fails.
395    pub fn spawn_connection(&self, stream: TcpStream) -> Result<ConnectionHandle, ServerError> {
396        self.inner
397            .spawn_connection(stream)
398            .inspect_err(Self::record_admission_refusal)
399    }
400
401    /// Returns the underlying beamr scheduler.
402    #[must_use]
403    pub fn scheduler(&self) -> Arc<Scheduler> {
404        Arc::clone(&self.inner.scheduler)
405    }
406
407    /// Reaps connection processes that have exited outside the normal handler path.
408    #[must_use]
409    pub fn reap_crashed_connections(&self) -> usize {
410        self.inner.runtime.reap_crashed(&self.inner.scheduler)
411    }
412
413    /// Returns true when `pid` is still tracked by the supervisor.
414    #[must_use]
415    pub fn is_tracked(&self, pid: u64) -> bool {
416        self.inner.runtime.contains(pid)
417    }
418
419    /// Returns the number of tracked live connections.
420    #[must_use]
421    pub fn active_connection_count(&self) -> usize {
422        self.inner.runtime.active_count()
423    }
424
425    /// How many connections have departed this supervisor: the count of
426    /// connection records removed, raised by the one removal funnel every exit
427    /// route reaches. A teardown that completes its connection's fate does so
428    /// before the record is removed, so a departure counted here is one whose
429    /// fate has already completed. Pass it to [`Self::await_departures`].
430    ///
431    /// # Errors
432    /// Returns [`ServerError`] when the departure count's lock is poisoned.
433    pub fn departures(&self) -> Result<u64, ServerError> {
434        self.inner.runtime.departures()
435    }
436
437    /// Parks until `count` connections have departed since `since`, a value
438    /// [`Self::departures`] returned, or until `deadline` elapses. Returns
439    /// `true` when they have departed and `false` when the deadline won.
440    ///
441    /// Woken by each departure through the removal `Condvar`, never by a timer
442    /// or a count scan; the deadline is the one bound on the park.
443    ///
444    /// # Errors
445    /// Returns [`ServerError`] when the departure count's lock is poisoned.
446    pub fn await_departures(
447        &self,
448        since: u64,
449        count: u64,
450        deadline: Instant,
451    ) -> Result<bool, ServerError> {
452        self.inner.runtime.await_departures(since, count, deadline)
453    }
454
455    /// Parks until every tracked connection has been removed or `deadline`
456    /// elapses, returning `true` when the drain completed and `false` when the
457    /// single admitted deadline won.
458    ///
459    /// The TOLD drain-completion replacement (W4 leg 3, §4.3): the waiter is woken
460    /// by the one `remove()` funnel every connection exit reaches — never by a
461    /// reap/count timer. Both the graceful drain and the force-close settle call
462    /// this same waiter, each with its own one-shot deadline; there is no second
463    /// settle poll loop.
464    #[must_use]
465    pub(crate) fn wait_for_connections_drained(&self, deadline: Instant) -> bool {
466        self.inner
467            .runtime
468            .wait_for_active_connections_drained(deadline)
469    }
470
471    /// FIX A-ii shutdown flush barrier: parks until every active connection has
472    /// fanned out its accepted publishes to its socket, or `deadline` elapses.
473    /// Called in `run_shutdown_sequence` BEFORE the shutdown Disconnect broadcast
474    /// so an accepted-but-unfanned-out publish can no longer be overtaken by it.
475    #[must_use]
476    pub(crate) fn wait_for_delivery_quiesced(&self, deadline: Instant) -> bool {
477        self.inner.runtime.wait_for_delivery_quiesced(deadline)
478    }
479
480    /// Returns the first latched post-Open participant fatal, if any.
481    ///
482    /// The production runtime reads this after its existing shutdown handle wakes,
483    /// then returns the typed fatal after the ordinary drain and durable flush.
484    pub(crate) fn participant_service_fatal(
485        &self,
486    ) -> Result<Option<ParticipantServiceFatal>, ServerError> {
487        self.inner.runtime.participant_service_fatal()
488    }
489
490    /// Returns the beamr process ids of the currently tracked live connections.
491    ///
492    /// Useful for addressing a specific connection — e.g. as the `pid` argument to
493    /// [`push_to_connection`](Self::push_to_connection) when the caller knows there
494    /// is a single connected client.
495    #[must_use]
496    pub fn active_connection_pids(&self) -> Vec<u64> {
497        self.inner
498            .runtime
499            .active_connections()
500            .into_iter()
501            .map(|connection| connection.pid)
502            .collect()
503    }
504
505    /// Broadcasts a best-effort shutdown notification to active connections.
506    ///
507    /// Connections with no active subscriptions ignore the notification. Failures
508    /// to enqueue the control message are logged and skipped; they are not retried.
509    pub fn notify_shutdown_subscribers(&self) {
510        self.inner
511            .broadcast_control(&ConnectionControl::NotifyShutdown);
512    }
513
514    /// Sends the orderly close control message to every tracked connection
515    /// process.
516    ///
517    /// Each live process enqueues one protocol `Disconnect`, drains its
518    /// outbound buffer, completes its connection fate as `ServerShutdown` and —
519    /// on the WebSocket transport — writes a close frame carrying
520    /// `CloseCode::Away` and the reason "server shutdown" before exiting
521    /// normally. A peer reads a shutdown, not an error. Enqueue failures are
522    /// logged and skipped.
523    ///
524    /// Since 0.14.3 this is the ONLY close route in `run_shutdown_sequence`:
525    /// nothing waits for a peer to hang up first, so the log line below is the
526    /// ordinary shutdown record rather than a warning about an expired drain.
527    pub fn force_close_active_connections(&self) {
528        for connection in self.inner.runtime.active_connections() {
529            tracing::info!(
530                connection_pid = connection.pid,
531                peer_addr = ?connection.peer_addr,
532                "closing connection for server shutdown"
533            );
534            if !self
535                .inner
536                .enqueue_control(connection.pid, ConnectionControl::ForceClose)
537            {
538                // Observation, not diagnosis — see `broadcast_control`.
539                tracing::warn!(
540                    connection_pid = connection.pid,
541                    peer_addr = ?connection.peer_addr,
542                    "forceful connection close was not published to the process"
543                );
544            }
545        }
546    }
547
548    /// Installs the services registry (SRV-028), once: lifecycle requests
549    /// are served from it and its processes' credentials admitted.
550    ///
551    /// # Errors
552    /// [`ServerError::ConfigValidation`] when one is already installed.
553    pub fn install_lifecycle(
554        &self,
555        lifecycle: Arc<crate::services::lifecycle::Lifecycle>,
556    ) -> Result<(), ServerError> {
557        self.inner
558            .runtime
559            .lifecycle
560            .set(lifecycle)
561            .map_err(|_| ServerError::ConfigValidation {
562                message: "a services registry was already installed".to_owned(),
563            })
564    }
565
566    /// Installs the grant-revocation policy (ACCESS-005), once, before the
567    /// first connection. From then on every `Connect` must present a pass
568    /// whose grant-decision binding the authority admits and must negotiate
569    /// the reason-bearing close; a bearer or open-access `Connect` is refused
570    /// `revocation_binding_missing`.
571    ///
572    /// # Errors
573    /// [`ServerError::ConfigValidation`] when an authority is already
574    /// installed.
575    pub fn install_revocation(
576        &self,
577        authority: Arc<crate::auth_revocation::RevocationAuthority>,
578    ) -> Result<(), ServerError> {
579        self.inner
580            .runtime
581            .install_revocation(authority)
582            .map_err(|_| ServerError::ConfigValidation {
583                message: "a grant-revocation authority was already installed".to_owned(),
584            })
585    }
586
587    /// Applies one event of the Lys grant-change stream the caller owns and
588    /// reads (ACCESS-005 R2, R4), then tells each affected connection once.
589    /// The denied state is in force before any connection is told; when a
590    /// connection's control cannot be published the refusal is counted by
591    /// name and the connection's own ready wake delivers the sweep. Nothing
592    /// is retried on a timer.
593    ///
594    /// # Errors
595    /// [`ServerError::ConfigValidation`] when no authority is installed.
596    pub fn apply_grant_event(
597        &self,
598        event: &crate::auth_revocation::GrantFeedEvent,
599    ) -> Result<crate::auth_revocation::ApplyOutcome, ServerError> {
600        let Some(authority) = self.inner.runtime.revocation() else {
601            return Err(ServerError::ConfigValidation {
602                message: "no grant-revocation authority is installed".to_owned(),
603            });
604        };
605        let outcome = authority.apply(event);
606        let mut told = std::collections::BTreeSet::new();
607        for effect in &outcome.effects {
608            let pid = effect.pid();
609            if !told.insert(pid) {
610                continue;
611            }
612            if !self
613                .inner
614                .enqueue_control(pid, ConnectionControl::Revocation)
615            {
616                authority.record_close_publication_refused(pid);
617                let woken = self
618                    .inner
619                    .runtime
620                    .ready_waker(pid)
621                    .as_ref()
622                    .is_some_and(super::wake::ReadyWaker::fire);
623                tracing::warn!(
624                    connection_pid = pid,
625                    woken,
626                    "revocation control refused; the connection's ready wake carries the sweep"
627                );
628            }
629        }
630        Ok(outcome)
631    }
632
633    /// Pushes an opaque payload to a specific connected client over that client's
634    /// existing connection and returns an awaiter for the client's correlated reply.
635    ///
636    /// This is the server-initiated leg (server-to-client), the inverse of every
637    /// other request frame. It allocates a correlation id, registers a one-shot
638    /// reply slot keyed by that id, and enqueues a [`ConnectionControl::Push`] for
639    /// the connection process owning `pid`; that process writes a [`Frame::Push`]
640    /// out on its socket. When the client answers with a `PushReply` carrying the
641    /// same correlation id, the connection process resolves the awaiter's slot. The
642    /// returned [`PushReplyAwaiter`] blocks (bounded) for that reply.
643    ///
644    /// The reply's lifetime belongs to the push, not to any one
645    /// [`PushReplyAwaiter::receive`] call: this no-deadline push reserves a slot
646    /// that is reclaimed only by (a) the reply being consumed or (b) the
647    /// connection closing. An elapsed `receive` poll is a benign re-arm, never a
648    /// failure and never a cancellation. The §5
649    /// `max_pending_pushes_per_connection` cap bounds abandonment; use
650    /// [`push_to_connection_with_deadline`](Self::push_to_connection_with_deadline)
651    /// when the reply must have an explicit expiry.
652    ///
653    /// # Errors
654    /// Returns [`ServerError`] when the correlation id cannot be allocated, the
655    /// reply slot cannot be registered, or the control message cannot be enqueued
656    /// for the (possibly already-gone or concurrently-closing) connection
657    /// process. PUBLICATION INVARIANT: an `Err` guarantees no `Push` control was
658    /// published — the client never sees a `Push` frame for a failed call.
659    /// Conversely `Ok` promises ADMISSION, not delivery: the awaiter's outcome
660    /// is the delivery truth (a push admitted just as its connection closes
661    /// resolves to the truthful disconnected outcome, never to a lost reply).
662    pub fn push_to_connection(
663        &self,
664        pid: u64,
665        payload: Vec<u8>,
666    ) -> Result<PushReplyAwaiter, ServerError> {
667        self.push_with_deadline(pid, payload, None, None)
668    }
669
670    /// Like [`push_to_connection`](Self::push_to_connection), with the push
671    /// made ON `subject`: the reply slot retains the subject, and a connection
672    /// admitted on a Lys pass has its `PushReply` decided as `serve` on that
673    /// subject before the slot is resolved (the access matrix's `push_binding`
674    /// source). A refused reply resolves the awaiter to
675    /// [`ServerError::PushReplyRefused`], so the pusher learns the outcome by
676    /// type rather than waiting out a deadline. A push made without a subject
677    /// to a Lys-admitted connection is refused the same way when its reply
678    /// arrives: the subject is recorded here, at push time, or not at all.
679    /// `deadline` is as in
680    /// [`push_to_connection_with_deadline`](Self::push_to_connection_with_deadline).
681    ///
682    /// # Errors
683    /// As [`push_to_connection_with_deadline`](Self::push_to_connection_with_deadline).
684    pub fn push_to_subject(
685        &self,
686        pid: u64,
687        subject: &str,
688        payload: Vec<u8>,
689        deadline: Option<Duration>,
690    ) -> Result<PushReplyAwaiter, ServerError> {
691        self.push_with_deadline(pid, payload, deadline, Some(subject.to_owned()))
692    }
693
694    /// Like [`push_to_connection`](Self::push_to_connection) but attaches an
695    /// explicit reply deadline to the reserved slot: `deadline` is a DURATION
696    /// FROM NOW bounding the reply's lifetime — a property of THIS push rather
697    /// than of any [`PushReplyAwaiter::receive`] wait quantum.
698    ///
699    /// Deadline expiry is evaluated HOST-SIDE and LAZILY — at the next `receive`
700    /// touch, and at connection close at the latest. It never wakes the connection
701    /// process, adds no timer thread, and runs no periodic sweeper: a push that is
702    /// abandoned and never polled resolves at the next host-side touch (connection
703    /// close). At expiry the slot resolves to [`ServerError::PushReplyExpired`],
704    /// is removed, and its §5 `max_pending_pushes_per_connection` cap admission is
705    /// released. An elapsed `receive` poll BEFORE the deadline is still a benign
706    /// re-arm. A `receive` call in flight when the deadline falls due returns
707    /// the terminal expiry PROMPTLY — it waits the earlier of its quantum and
708    /// the deadline, so a large quantum can never extend the reply's lifetime
709    /// and the terminal outcome is quantum-independent.
710    ///
711    /// The deadline is evaluated at OBSERVATION POINTS, not enforced against the
712    /// wall clock: a reply that arrives before expiry is observed is delivered
713    /// normally, even if it arrives after the deadline instant. The deadline
714    /// bounds waiting and slot occupancy; it is not a delivery-freshness
715    /// guarantee. (This is deliberate — a reply is checked for at every
716    /// observation point before the deadline is, so an answer in hand always
717    /// beats an expiry.)
718    ///
719    /// # Errors
720    /// Returns [`ServerError`] when `deadline` is not representable on the
721    /// monotonic clock (an extreme duration is refused, never a panic), the
722    /// correlation id cannot be allocated, the reply slot cannot be registered,
723    /// or the control message cannot be enqueued for the (possibly already-gone
724    /// or concurrently-closing) connection process. PUBLICATION INVARIANT: an
725    /// `Err` guarantees no `Push` control was published — the client never sees
726    /// a `Push` frame for a failed call. Conversely `Ok` promises ADMISSION,
727    /// not delivery: the awaiter's outcome is the delivery truth.
728    pub fn push_to_connection_with_deadline(
729        &self,
730        pid: u64,
731        payload: Vec<u8>,
732        deadline: Duration,
733    ) -> Result<PushReplyAwaiter, ServerError> {
734        self.push_with_deadline(pid, payload, Some(deadline), None)
735    }
736
737    /// Shared body for the no-deadline and explicit-deadline push paths. With
738    /// `deadline == None` this is byte-for-byte the historical
739    /// `push_to_connection` behaviour (no per-slot deadline); with `Some`, the
740    /// slot carries an absolute expiry evaluated lazily at `receive`. `subject`
741    /// is retained on the slot for the reply's access decision
742    /// ([`push_to_subject`](Self::push_to_subject)); `None` records no binding.
743    fn push_with_deadline(
744        &self,
745        pid: u64,
746        payload: Vec<u8>,
747        deadline: Option<Duration>,
748        subject: Option<String>,
749    ) -> Result<PushReplyAwaiter, ServerError> {
750        // S5: an extreme `Duration` must surface as this fallible API's typed
751        // error, not an `Instant` addition panic. Checked BEFORE any slot is
752        // registered so a refused deadline leaves nothing to roll back.
753        let deadline_at = match deadline {
754            None => None,
755            Some(window) => {
756                Some(
757                    Instant::now()
758                        .checked_add(window)
759                        .ok_or_else(|| ServerError::ListenerAccept {
760                            message: format!(
761                                "cannot push to connection process {pid}: reply deadline of {window:?} overflows the monotonic clock"
762                            ),
763                        })?,
764                )
765            }
766        };
767        let correlation_id = self.inner.runtime.next_push_correlation_id();
768        let receiver =
769            self.inner
770                .runtime
771                .register_push(pid, correlation_id, deadline_at, subject, None)?;
772        // S3+S7 close-vs-register wall, ordered INSERT -> CONFIRM -> PUBLISH.
773        // The confirmation runs BEFORE the control is enqueued, which yields the
774        // PUBLICATION INVARIANT: an `Err` from this method guarantees no `Push`
775        // control was published — the client never sees a Push for a failed
776        // call. (Confirming after the enqueue was S7's non-linearizable race: a
777        // close could sweep, the published Push could already be answered and
778        // resolved, and the failed confirmation then returned `Err` for a push
779        // the client had received.) A close landing AFTER a successful confirm
780        // linearizes after push admission: the enqueue either fails (process
781        // gone — rollback below, `Err` truthful, nothing delivered) or succeeds
782        // with the slot already swept, and the awaiter then reads the truthful
783        // DISCONNECTED while a late client reply is the pinned harmless no-op.
784        // The exactly-one-side-observes argument lives at
785        // `confirm_push_registration`.
786        if !self
787            .inner
788            .runtime
789            .confirm_push_registration(pid, correlation_id)
790        {
791            return Err(ServerError::ListenerAccept {
792                message: format!(
793                    "cannot push to connection process {pid}: the connection closed during push registration"
794                ),
795            });
796        }
797        let control = ConnectionControl::Push {
798            correlation_id,
799            payload,
800        };
801        if self.inner.enqueue_control(pid, control) {
802            Ok(PushReplyAwaiter {
803                correlation_id,
804                receiver,
805                deadline: deadline_at,
806                runtime: Arc::downgrade(&self.inner.runtime),
807            })
808        } else {
809            // The control provably never reached a consumer: `enqueue_control`
810            // returns false only when the control was never queued at all, or
811            // when its failed-wake rollback REMOVED the queued control (S8 — an
812            // entry a drain already consumed counts as published and returns
813            // true, with the slot lifecycle carrying the delivery truth).
814            // Dropping the now-unreachable reply slot here therefore keeps the
815            // publication invariant exact on every `Err` path. The message
816            // states that outcome rather than naming a cause this branch cannot
817            // distinguish — the refused-queue case has already warned with its
818            // own error.
819            self.inner.runtime.cancel_push(correlation_id);
820            Err(ServerError::ListenerAccept {
821                message: format!(
822                    "cannot push to connection process {pid}: the control was not published"
823                ),
824            })
825        }
826    }
827
828    /// SRV-027 R4: how many keyed lookups the service directory has made,
829    /// read by the count pins.
830    #[must_use]
831    pub fn directory_lookups(&self) -> u64 {
832        self.inner.runtime.directory_lookups()
833    }
834
835    /// Flushes durable channel state through the configured liminal services.
836    ///
837    /// # Errors
838    /// Returns [`ServerError::ShutdownFlush`] when the underlying service flush fails.
839    pub fn flush_durable_state(&self) -> Result<(), ServerError> {
840        self.inner.runtime.services().flush_durable_state()
841    }
842
843    /// Tells the services the listener is bound, so what they read off the
844    /// start path starts now.
845    pub(crate) fn listening(&self) {
846        // The thread runs on its own; nothing waits for it.
847        drop(self.inner.runtime.services_arc().listening());
848    }
849
850    /// Stops the participant warm and records the clean stop's capacity
851    /// snapshot, before the final flush (DUR-020 R3, R5).
852    pub(crate) fn stop_participant_warm(&self) {
853        self.inner.runtime.services().stop_participant_warm();
854    }
855
856    /// Completes, before a clean stop reports success, every connection fate
857    /// parked on candidate-lane `Precedence`.
858    ///
859    /// # Errors
860    /// Returns [`ServerError::ShutdownUnsettledConnectionFates`] naming every
861    /// durable Open the stop could not complete.
862    pub(crate) fn settle_parked_connection_fates(&self) -> Result<(), ServerError> {
863        self.inner.runtime.settle_parked_connection_fates()
864    }
865
866    /// LP-WS-TRANSPORT R1.3 sibling-transport spawn seam (ADDITIVE ONLY).
867    ///
868    /// Admits, allocates a durable connection incarnation for, spawns, and
869    /// registers a connection process whose handler is built by `build_factory`
870    /// over this supervisor's shared [`ConnectionRuntime`]. The WebSocket
871    /// sibling acceptor uses this so its connections share the ONE §5
872    /// `max_connections` admission bound, the one incarnation authority, the
873    /// one registry (controls, pushes, crash reap, drain, forced close), and the
874    /// one `apply_frame` seam with TCP connections. The TCP accept path above
875    /// (`spawn_connection`) is byte-for-byte untouched and never calls this.
876    ///
877    /// `fd_guard` is a host-held duplicate of the connection's underlying
878    /// socket, exactly like the TCP path's: it keeps the fd alive until the
879    /// single record-removal path has synchronously deregistered readiness. It
880    /// is `None` for a transport that owns no descriptor.
881    ///
882    /// `mount` is the admitting door's own name for itself (design §10), which
883    /// is why this seam takes it rather than deriving it: the caller IS the
884    /// door, and no other party — least of all the client — has any input.
885    ///
886    /// This method exists because Rust module privacy makes the runtime,
887    /// admission counter, incarnation authority, and registry unreachable from
888    /// the sibling `websocket` module family; it is the narrow additive seam
889    /// that shares them without generalizing any TCP hot path.
890    ///
891    /// # Errors
892    /// Returns [`ServerError`] when admission is refused
893    /// ([`ServerError::ConnectionLimitReached`]), incarnation allocation fails,
894    /// or beamr spawn/registration fails.
895    pub(super) fn spawn_transport_connection(
896        &self,
897        peer_addr: Option<SocketAddr>,
898        fd_guard: Option<TcpStream>,
899        mount: MountKind,
900        build_factory: &dyn Fn(
901            Arc<ConnectionRuntime>,
902            Option<ConnectionIncarnation>,
903        ) -> NativeHandlerFactory,
904    ) -> Result<ConnectionHandle, ServerError> {
905        self.inner
906            .spawn_transport_connection(peer_addr, fd_guard, mount, build_factory)
907            .inspect_err(Self::record_admission_refusal)
908    }
909
910    /// Admits one in-process connection over `server_end` (design §8 step 3).
911    ///
912    /// This replaces exactly the listener's `accept()` + `spawn_connection`
913    /// pair, and NOTHING else about admission. It runs the same
914    /// `try_reserve_admission` against the same §5 slot pool — an in-process
915    /// connect at capacity is refused with the same typed
916    /// [`ServerError::ConnectionLimitReached`] a socket connect is — allocates a
917    /// real durable [`ConnectionIncarnation`] from the same authority, so
918    /// participant binding, resume, and fate records work identically, and
919    /// registers the same record. Only two fields differ, and both are honest
920    /// descriptions rather than semantics: no fd guard (there is no descriptor
921    /// to keep alive) and `peer_addr: None` (there is no socket to have an
922    /// address). Nothing on this path reads a socket fact.
923    ///
924    /// The connection's wake is installed by the process itself on its first
925    /// serviced slice, which is the earliest point at which its pid and host
926    /// record — the two things the wake names — both exist.
927    ///
928    /// `pub(crate)` rather than `pub`: the embedding handle that grants
929    /// loopback connections lives in this crate, and widening the surface
930    /// further would hand an outside caller a way to reach the runtime that
931    /// module privacy currently denies it.
932    ///
933    /// # Errors
934    /// Returns [`ServerError`] when admission is refused
935    /// ([`ServerError::ConnectionLimitReached`]), incarnation allocation fails,
936    /// or beamr spawn/registration fails.
937    pub(crate) fn spawn_loopback_connection(
938        &self,
939        server_end: LoopbackServerEnd,
940    ) -> Result<ConnectionHandle, ServerError> {
941        // The same interior-mutability handoff the socket path uses: the native
942        // handler factory is `Fn + Send + Sync`, so the duplex end cannot be
943        // moved into it and the FIRST handler build takes it out exactly once.
944        let holder = Arc::new(Mutex::new(Some(server_end)));
945        let build = move |runtime: Arc<ConnectionRuntime>,
946                          incarnation: Option<ConnectionIncarnation>|
947              -> NativeHandlerFactory {
948            let holder = Arc::clone(&holder);
949            Box::new(move || {
950                Box::new(LoopbackConnectionProcess::from_loopback_holder(
951                    Arc::clone(&runtime),
952                    &holder,
953                    incarnation,
954                ))
955            })
956        };
957        self.inner
958            .spawn_transport_connection(None, None, MountKind::Loopback, &build)
959            .inspect_err(Self::record_admission_refusal)
960    }
961
962    /// Stops the beamr scheduler used by connection processes.
963    pub fn shutdown(&self) {
964        // A connection waiting on a conversation's restore is woken and
965        // answered at once, so the scheduler join below never waits on a
966        // room's replay or rebuild (Gaia, 11 October 2026: 60 s).
967        self.stop_participant_warm();
968        // Remove every host record while the readiness owner is still live. The
969        // removal path ACKs deregistration and only then releases each fd guard;
970        // scheduler shutdown subsequently drops the process-owned handles.
971        for connection in self.inner.runtime.active_connections() {
972            self.inner.runtime.finish(connection.pid);
973        }
974        self.inner.scheduler.shutdown();
975    }
976
977    /// R7 test instrument: slices serviced by connection `pid` since spawn.
978    #[cfg(test)]
979    pub(crate) fn slice_count(&self, pid: u64) -> u64 {
980        self.inner.runtime.slice_count(pid)
981    }
982
983    /// The mount the admitting door stamped on `pid`'s registry record, or
984    /// `None` when no record is tracked (design §10).
985    #[cfg(test)]
986    pub(crate) fn connection_mount(&self, pid: u64) -> Option<MountKind> {
987        self.inner.runtime.connection_mount(pid)
988    }
989
990    /// Whether `pid`'s registry record holds an fd guard, or `None` when no
991    /// record is tracked. A loopback record must answer `Some(false)`: there is
992    /// no descriptor for the guard to keep alive.
993    #[cfg(test)]
994    pub(crate) fn connection_has_fd_guard(&self, pid: u64) -> Option<bool> {
995        self.inner.runtime.connection_has_fd_guard(pid)
996    }
997
998    /// Installs a one-use readiness marker for the next serviced slice of `pid`.
999    #[cfg(test)]
1000    pub(crate) fn observe_next_slice(&self, pid: u64) -> Receiver<u64> {
1001        self.inner.runtime.observe_next_slice(pid)
1002    }
1003
1004    /// Installs a one-use readiness marker for the next genuine scheduler park of
1005    /// `pid`. The delivered value is the process's slice count at the final probe
1006    /// that selected `Wait`.
1007    #[cfg(test)]
1008    pub(crate) fn observe_next_park(&self, pid: u64) -> Receiver<u64> {
1009        self.inner.runtime.observe_next_park(pid)
1010    }
1011
1012    /// Returns a marker for the current park when `pid` is already settled, or
1013    /// the next park when a coalesced readiness event has started another slice.
1014    #[cfg(test)]
1015    pub(crate) fn observe_settled_park(&self, pid: u64) -> Receiver<u64> {
1016        self.inner.runtime.observe_settled_park(pid)
1017    }
1018
1019    /// Queues an explicit outbound capacity for the next TCP process constructed.
1020    #[cfg(test)]
1021    pub(crate) fn queue_next_outbound_capacity(&self, capacity: usize) {
1022        self.inner.runtime.queue_next_outbound_capacity(capacity);
1023    }
1024
1025    #[cfg(test)]
1026    pub(crate) fn install_participant_holdback_pause(&self, pid: u64) -> Receiver<()> {
1027        self.inner.runtime.install_participant_holdback_pause(pid)
1028    }
1029
1030    #[cfg(test)]
1031    pub(crate) fn resume_test_process(&self, pid: u64) -> bool {
1032        self.inner.runtime.ready_waker(pid).is_some_and(|waker| {
1033            waker.fire();
1034            true
1035        })
1036    }
1037
1038    /// Server `Push` frames this supervisor's connections have queued for their
1039    /// sockets since it started. A push that failed, was cancelled or was rolled
1040    /// back never reaches a socket and is not counted.
1041    #[must_use]
1042    pub fn pushes_queued(&self) -> u64 {
1043        self.inner.runtime.pushes_queued()
1044    }
1045
1046    /// Reserved push reply slots outstanding (test observability for the public
1047    /// push paths — lets e2e tests assert slot reclamation and cap accounting).
1048    #[cfg(test)]
1049    pub(super) fn pending_push_count(&self) -> usize {
1050        self.inner.runtime.pending_push_count()
1051    }
1052
1053    /// R6 test seam: a [`ReadyWaker`](super::wake::ReadyWaker) for `pid` — the same
1054    /// handle a subscription-inbox or reply-availability notifier fires.
1055    #[cfg(test)]
1056    pub(super) fn ready_waker(&self, pid: u64) -> Option<super::wake::ReadyWaker> {
1057        self.inner.runtime.ready_waker(pid)
1058    }
1059
1060    /// Registered readiness tokens held in host records (test observability).
1061    #[cfg(test)]
1062    pub(super) fn readiness_registration_count(&self) -> usize {
1063        self.inner.runtime.readiness_registration_count()
1064    }
1065
1066    /// Kernel fd registered for `pid` (test observability for fd-reuse races).
1067    #[cfg(test)]
1068    pub(super) fn readiness_fd(&self, pid: u64) -> Option<RawFd> {
1069        self.inner.runtime.readiness_fd(pid)
1070    }
1071
1072    /// The readiness token registered for `pid` (test observability). Lets the
1073    /// fd-reuse successor oracle capture a stale token before reclamation and
1074    /// replay its deregister afterwards, proving it is a keyed no-op.
1075    #[cfg(test)]
1076    pub(super) fn readiness_token(&self, pid: u64) -> Option<ReadinessToken> {
1077        self.inner.runtime.readiness_token(pid)
1078    }
1079
1080    /// Installs the pid-specific reclamation gate (oracle 26) and returns its
1081    /// `(reached, release, done)` endpoints.
1082    #[cfg(test)]
1083    pub(super) fn install_reclaim_barrier(
1084        &self,
1085        pid: u64,
1086    ) -> (Arc<Barrier>, Arc<Barrier>, Arc<Barrier>) {
1087        self.inner.runtime.install_reclaim_barrier(pid)
1088    }
1089
1090    /// A weak handle to the connection runtime (test observability): lets a
1091    /// lifetime test assert the runtime — and transitively the durable store's
1092    /// writer lock — is released synchronously at supervisor drop rather than
1093    /// held by the detached reclaim reactor.
1094    #[cfg(test)]
1095    pub(super) fn runtime_weak(&self) -> Weak<ConnectionRuntime> {
1096        Arc::downgrade(&self.inner.runtime)
1097    }
1098
1099    /// Installs a one-use observation for the process-owned stream at `fd` being
1100    /// dropped. External scheduler termination removes the process-table entry
1101    /// before an executing native handler is destroyed, so table absence is not
1102    /// sufficient evidence that the descriptor is reusable.
1103    #[cfg(test)]
1104    pub(super) fn observe_process_stream_drop(&self, fd: RawFd) -> Receiver<()> {
1105        self.inner.runtime.observe_process_stream_drop(fd)
1106    }
1107
1108    /// Installs a one-use arm-to-probe barrier and returns its test endpoints.
1109    #[cfg(test)]
1110    pub(super) fn install_pre_wait_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
1111        self.inner.runtime.install_pre_wait_barrier()
1112    }
1113
1114    /// Barrier-staged final probes that observed newly arrived work.
1115    #[cfg(test)]
1116    pub(super) fn pre_wait_probe_hits(&self) -> u64 {
1117        self.inner.runtime.pre_wait_probe_hits()
1118    }
1119
1120    /// Installs the one-use drain-park gate (oracles 18, 19) and returns its
1121    /// `(armed, release)` endpoints.
1122    #[cfg(test)]
1123    pub(super) fn install_drain_park_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
1124        self.inner.runtime.install_drain_park_barrier()
1125    }
1126
1127    /// Drain waiter wakes that observed a real connection removal (oracle 12).
1128    #[cfg(test)]
1129    pub(super) fn drain_exit_wakes(&self) -> u64 {
1130        self.inner.runtime.drain_exit_wakes()
1131    }
1132
1133    /// Drain waiter deadline expirations (oracles 12, 16).
1134    #[cfg(test)]
1135    pub(super) fn drain_deadline_hits(&self) -> u64 {
1136        self.inner.runtime.drain_deadline_hits()
1137    }
1138}
1139
1140/// Handle for one supervised connection process.
1141#[derive(Clone, Debug)]
1142pub struct ConnectionHandle {
1143    pid: u64,
1144    peer_addr: Option<SocketAddr>,
1145    connection_incarnation: Option<ConnectionIncarnation>,
1146    supervisor: Arc<SupervisorInner>,
1147}
1148
1149impl ConnectionHandle {
1150    /// Every later report of the frames this connection writes to its
1151    /// transport. Test-only (R3b F8, ruling 2a).
1152    #[cfg(test)]
1153    pub(crate) fn observe_frames_written(&self) -> Receiver<Vec<liminal::protocol::FrameType>> {
1154        self.supervisor.runtime.observe_frames_written(self.pid)
1155    }
1156
1157    /// Returns the beamr process id for this connection.
1158    #[must_use]
1159    pub const fn pid(&self) -> u64 {
1160        self.pid
1161    }
1162
1163    /// Returns the peer address if it was available from the accepted stream.
1164    #[must_use]
1165    pub const fn peer_addr(&self) -> Option<SocketAddr> {
1166        self.peer_addr
1167    }
1168
1169    /// Returns the durable participant connection incarnation, when this
1170    /// supervisor has a complete participant service installed.
1171    ///
1172    /// `None` identifies a services adapter that does not advertise participant
1173    /// lifecycle semantics.
1174    #[must_use]
1175    pub const fn connection_incarnation(&self) -> Option<ConnectionIncarnation> {
1176        self.connection_incarnation
1177    }
1178
1179    /// Returns whether the beamr process is still live.
1180    #[must_use]
1181    pub fn is_live(&self) -> bool {
1182        self.supervisor
1183            .scheduler
1184            .process_table()
1185            .get(self.pid)
1186            .is_some()
1187    }
1188
1189    /// Requests an error exit for tests and supervisor control paths.
1190    ///
1191    /// # Errors
1192    /// Returns [`ServerError`] when the process is no longer live.
1193    pub fn request_crash(&self) -> Result<(), ServerError> {
1194        if self
1195            .supervisor
1196            .scheduler
1197            .enqueue_atom_message(self.pid, Atom::ERROR)
1198        {
1199            Ok(())
1200        } else {
1201            Err(ServerError::ListenerAccept {
1202                message: format!("connection process {} is not live", self.pid),
1203            })
1204        }
1205    }
1206}
1207
1208/// Awaits the correlated reply to a single server-initiated push.
1209///
1210/// Returned by [`ConnectionSupervisor::push_to_connection`]. The reply slot is
1211/// resolved when the originating connection process receives a `PushReply` frame
1212/// carrying the same correlation id, so [`PushReplyAwaiter::receive`] blocks
1213/// (bounded) for that one correlated answer.
1214#[derive(Debug)]
1215pub struct PushReplyAwaiter {
1216    correlation_id: u64,
1217    receiver: Receiver<PushSlotResolution>,
1218    /// This push's absolute reply deadline, mirrored from its slot. `None` (the
1219    /// default push) selects the no-deadline receive path, which NEVER touches
1220    /// the runtime — byte-compatible with 0.2.3, no shared-lock exposure.
1221    /// `Some` lets `receive` wait `min(caller quantum, time until deadline)` and
1222    /// resolve expiry promptly, so the caller's quantum can never select a
1223    /// deadlined push's terminal outcome.
1224    deadline: Option<Instant>,
1225    /// Weak handle to the owning runtime, used ONLY by the explicit-deadline
1226    /// path to resolve expiry host-side at [`receive`](Self::receive). A
1227    /// no-deadline push never upgrades it. `Weak` so the awaiter never keeps the
1228    /// runtime alive; if it is already gone, the slot (and its sender) is gone
1229    /// with it — the connection side is torn down.
1230    runtime: Weak<ConnectionRuntime>,
1231}
1232
1233impl PushReplyAwaiter {
1234    /// Returns the correlation id this awaiter is matched on.
1235    #[must_use]
1236    pub const fn correlation_id(&self) -> u64 {
1237        self.correlation_id
1238    }
1239
1240    /// Blocks up to `timeout` for the client's correlated reply payload.
1241    ///
1242    /// `timeout` is a WAIT QUANTUM ONLY — a MAXIMUM wait, not a promise to
1243    /// block: an elapsed poll is a benign re-arm, never a failure; the reply's
1244    /// lifetime belongs to the push. A caller may re-invoke `receive`
1245    /// indefinitely after a [`ServerError::PushReplyTimeout`]: the reserved slot
1246    /// is untouched and a later reply is still delivered byte-exact. The poll
1247    /// quantum never changes the protocol outcome — for a deadlined push the
1248    /// call waits no longer than the EARLIER of the caller's quantum and the
1249    /// push's deadline, so the terminal expiry is returned promptly once due,
1250    /// never held until the quantum ends and never deferred past it.
1251    ///
1252    /// A push with no explicit deadline never touches shared supervisor state
1253    /// here: the elapsed quantum returns straight from the channel wait
1254    /// (behaviour-compatible with 0.2.3 — no registry lock, no contention, no
1255    /// poison exposure on the unchanged API).
1256    ///
1257    /// # Errors
1258    /// Returns [`ServerError::PushReplyTimeout`] when no reply arrived within this
1259    /// `timeout` quantum and the push's deadline (if any) is not yet due (a
1260    /// benign re-arm — call again to keep waiting);
1261    /// [`ServerError::PushReplyExpired`] when the push carried an explicit reply
1262    /// deadline (via
1263    /// [`push_to_connection_with_deadline`](ConnectionSupervisor::push_to_connection_with_deadline))
1264    /// and that deadline is due (terminal: the slot is removed and its §5 cap
1265    /// admission released; returned as soon as the deadline passes, even
1266    /// mid-quantum — but evaluated at observation points, not against the wall
1267    /// clock: a reply already delivered when this call observes the slot wins
1268    /// over expiry, even if it arrived after the deadline instant); or
1269    /// [`ServerError::PushReplyDisconnected`] when the connection process
1270    /// dropped the reply slot (the connection closed — the prompt worker-death
1271    /// signal). The variants are distinct so callers classify by type, not
1272    /// message.
1273    pub fn receive(&self, timeout: Duration) -> Result<Vec<u8>, ServerError> {
1274        self.deadline.map_or_else(
1275            || self.receive_no_deadline(timeout),
1276            |deadline| self.receive_deadlined(timeout, deadline),
1277        )
1278    }
1279
1280    /// The default-push receive: exactly the 0.2.3 shape. One bounded channel
1281    /// wait; an elapsed quantum is a benign timeout straight from the channel —
1282    /// no runtime upgrade, no registry lock, EVER (unrelated registry work can
1283    /// never stretch this call past its quantum, and registry poison cannot
1284    /// reach it).
1285    fn receive_no_deadline(&self, timeout: Duration) -> Result<Vec<u8>, ServerError> {
1286        match self.receiver.recv_timeout(timeout) {
1287            Ok(resolution) => self.resolution_into_result(resolution),
1288            Err(RecvTimeoutError::Timeout) => Err(ServerError::PushReplyTimeout {
1289                correlation_id: self.correlation_id,
1290            }),
1291            Err(RecvTimeoutError::Disconnected) => Err(ServerError::PushReplyDisconnected {
1292                correlation_id: self.correlation_id,
1293            }),
1294        }
1295    }
1296
1297    /// The deadlined receive: waits `min(caller quantum, time until deadline)`
1298    /// and re-evaluates reply-first-then-expiry on every wake, so the caller's
1299    /// quantum can never select the terminal outcome (S1). Order per iteration:
1300    ///
1301    /// 1. Deliver a reply already in hand — an answer that is here must never be
1302    ///    reported as a timeout OR an expiry (the observation-point rule).
1303    /// 2. If the deadline is due, resolve expiry atomically against the registry
1304    ///    (`expire_slot`) and return the terminal outcome promptly — even when
1305    ///    the caller's quantum has time left (the quantum is a max wait).
1306    /// 3. Otherwise wait for the earlier of quantum-remaining and deadline; a
1307    ///    wake re-runs 1-2, and an exhausted quantum before the deadline is the
1308    ///    benign `PushReplyTimeout` re-arm with the slot untouched.
1309    fn receive_deadlined(
1310        &self,
1311        timeout: Duration,
1312        deadline: Instant,
1313    ) -> Result<Vec<u8>, ServerError> {
1314        let started = Instant::now();
1315        loop {
1316            if let Some(result) = self.try_take_reply() {
1317                return result;
1318            }
1319            let now = Instant::now();
1320            if now >= deadline {
1321                return self.expire_slot();
1322            }
1323            let quantum_left = timeout.saturating_sub(now.duration_since(started));
1324            if quantum_left.is_zero() {
1325                return Err(ServerError::PushReplyTimeout {
1326                    correlation_id: self.correlation_id,
1327                });
1328            }
1329            match self
1330                .receiver
1331                .recv_timeout(quantum_left.min(deadline.duration_since(now)))
1332            {
1333                Ok(resolution) => return self.resolution_into_result(resolution),
1334                Err(RecvTimeoutError::Disconnected) => {
1335                    return Err(ServerError::PushReplyDisconnected {
1336                        correlation_id: self.correlation_id,
1337                    });
1338                }
1339                // Re-loop: deliver a reply that raced the wake, expire a
1340                // now-due deadline, or report the exhausted quantum benignly.
1341                Err(RecvTimeoutError::Timeout) => {}
1342            }
1343        }
1344    }
1345
1346    /// Resolves a due deadline against the registry's atomic removal transition.
1347    fn expire_slot(&self) -> Result<Vec<u8>, ServerError> {
1348        let timeout_error = || ServerError::PushReplyTimeout {
1349            correlation_id: self.correlation_id,
1350        };
1351        let Some(runtime) = self.runtime.upgrade() else {
1352            // The runtime is gone, and the slot map (with every sender) with it:
1353            // the connection side is torn down. Re-check the channel so the
1354            // dropped sender reads as the established DISCONNECTED outcome — a
1355            // dead runtime must not be misreported as a benign healthy-but-slow
1356            // timeout (S4).
1357            return self
1358                .try_take_reply()
1359                .unwrap_or(Err(ServerError::PushReplyDisconnected {
1360                    correlation_id: self.correlation_id,
1361                }));
1362        };
1363        match runtime.expire_push_if_due(self.correlation_id) {
1364            PushSlotDisposition::Expired => Err(ServerError::PushReplyExpired {
1365                correlation_id: self.correlation_id,
1366            }),
1367            // Unreachable by construction (this is only called with the deadline
1368            // due, and the registry re-reads a monotonic clock); honest benign
1369            // fallback rather than a panic.
1370            PushSlotDisposition::Live => Err(timeout_error()),
1371            // Another path (a concurrent `resolve_push`, or connection close)
1372            // removed the slot under the registry lock while we waited on it. Its
1373            // send, if any, happens under that same lock, so re-check the channel:
1374            // a delivered reply is present now; a dropped sender is disconnected.
1375            PushSlotDisposition::Absent => self
1376                .try_take_reply()
1377                .unwrap_or_else(|| Err(timeout_error())),
1378        }
1379    }
1380
1381    /// SRV-027 R2: the outcome if the slot has resolved, read without waiting
1382    /// by the requester's slice after its wake: `Some(Ok(payload))` for the
1383    /// worker's reply, `Some(Err(..))` for a refusal, a frame the worker's
1384    /// connection could not carry, or the worker gone (the slot swept at its
1385    /// close), `None` while nothing has arrived.
1386    pub(super) fn try_receive(&self) -> Option<Result<Vec<u8>, ServerError>> {
1387        self.try_take_reply()
1388    }
1389
1390    /// Non-blocking check for a reply already sitting in the channel. `Some` with
1391    /// the payload or a disconnected error; `None` when the channel is still empty
1392    /// (no reply yet — the caller re-arms).
1393    fn try_take_reply(&self) -> Option<Result<Vec<u8>, ServerError>> {
1394        match self.receiver.try_recv() {
1395            Ok(resolution) => Some(self.resolution_into_result(resolution)),
1396            Err(TryRecvError::Disconnected) => Some(Err(ServerError::PushReplyDisconnected {
1397                correlation_id: self.correlation_id,
1398            })),
1399            Err(TryRecvError::Empty) => None,
1400        }
1401    }
1402
1403    /// Turns one delivered [`PushSlotResolution`] into this awaiter's result, so
1404    /// all three receive paths read a resolution the same way and none of them
1405    /// can grow its own interpretation of one.
1406    ///
1407    /// Note what is NOT here: a dropped `Sender` never reaches this function. It
1408    /// is a channel disconnect, and so stays the retryable
1409    /// [`ServerError::PushReplyDisconnected`] on every path that kills a slot
1410    /// without naming a reason — connection close included.
1411    fn resolution_into_result(
1412        &self,
1413        resolution: PushSlotResolution,
1414    ) -> Result<Vec<u8>, ServerError> {
1415        match resolution {
1416            PushSlotResolution::Reply(payload) => Ok(payload),
1417            PushSlotResolution::ReplyRefused { reason } => Err(ServerError::PushReplyRefused {
1418                correlation_id: self.correlation_id,
1419                reason,
1420            }),
1421            PushSlotResolution::OutboundFrameTooLarge {
1422                needed,
1423                capacity,
1424                queued,
1425            } => Err(ServerError::PushFrameExceedsOutboundCapacity {
1426                correlation_id: self.correlation_id,
1427                needed,
1428                capacity,
1429                queued,
1430            }),
1431        }
1432    }
1433}
1434
1435/// The kernel-parked exit-event reactor (W4 leg 1 reclamation carve-out, §4.1).
1436/// It is the single TOLD source that reclaims connection host records for
1437/// processes that exit WITHOUT a final handler slice, replacing the retired
1438/// per-accept `reap_crashed` scan for that class.
1439///
1440/// It blocks on beamr's sole exit-event subscription — never polling, never
1441/// timed — and on each delivered [`ExitEvent::Exited`] it (1) drains the
1442/// retained additive outcome so beamr's exactly-once outcome store stays bounded
1443/// (we are the sole subscriber and therefore the sole drainer) and (2) reclaims
1444/// the pid through [`ConnectionRuntime::reclaim_terminated`], which funnels into
1445/// `remove()`. On the bounded queue's [`ExitEvent::Lagged`] overflow marker it
1446/// runs exactly one reconciliation pass over the tracked records (beamr's
1447/// documented recovery), driven by that one TELL — not a timer.
1448///
1449/// It holds WEAK handles to both the scheduler and the runtime and upgrades them
1450/// per event, so it never keeps either alive past supervisor drop. It returns
1451/// when the subscription disconnects (scheduler and publisher dropped) OR when a
1452/// per-event upgrade fails — both observed at an event delivery, never sampled,
1453/// so there is no stop flag (LAW-1). The runtime and its durable store therefore
1454/// release synchronously at supervisor drop rather than after the reactor exits.
1455fn run_reclaim_reactor(
1456    subscription: &ExitEventSubscription,
1457    scheduler: &Weak<Scheduler>,
1458    runtime: &Weak<ConnectionRuntime>,
1459) {
1460    loop {
1461        match subscription.recv() {
1462            Ok(ExitEvent::Exited { pid, reason }) => {
1463                let Some(runtime) = runtime.upgrade() else {
1464                    return;
1465                };
1466                runtime.deliver_reclamation(scheduler, pid, reason);
1467            }
1468            Ok(ExitEvent::Lagged) => {
1469                let (Some(runtime), Some(scheduler)) = (runtime.upgrade(), scheduler.upgrade())
1470                else {
1471                    return;
1472                };
1473                runtime.reap_crashed(&scheduler);
1474            }
1475            Err(error) => {
1476                // The sole crash-reclamation source for a process that dies
1477                // without a final slice is ending. Named at the same volume as
1478                // the startup branch that finds no subscription at all.
1479                tracing::warn!(
1480                    %error,
1481                    "connection reclamation reactor stopping; external-termination \
1482                     reclamation has no further TOLD exit source"
1483                );
1484                return;
1485            }
1486        }
1487    }
1488}
1489
1490pub(super) struct SupervisorInner {
1491    scheduler: Arc<Scheduler>,
1492    runtime: Arc<ConnectionRuntime>,
1493    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
1494    /// P0 #56 R4: the readiness probe's view of whether this server can admit.
1495    /// Shared with the incarnation authority when one is installed.
1496    admission_readiness: AdmissionReadiness,
1497}
1498
1499impl std::fmt::Debug for SupervisorInner {
1500    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1501        formatter
1502            .debug_struct("SupervisorInner")
1503            .field("runtime", &self.runtime)
1504            .finish_non_exhaustive()
1505    }
1506}
1507
1508/// The configured `[auth]` section as the supervisor carries it: the bearer
1509/// token as opaque bytes for the handshake's constant-time comparison, and the
1510/// registry pass verifier built from `auth.pass` when one is configured. The
1511/// single derivation behind [`ConnectionSupervisor::from_config`] and
1512/// [`ConnectionSupervisorBuilder::auth`], so the two cannot drift.
1513fn configured_authentication(
1514    auth: &AuthConfig,
1515) -> Result<(Vec<u8>, Option<PassVerifier>), ServerError> {
1516    let pass_verifier = PassVerifier::from_auth(auth)?;
1517    Ok((auth.token.clone().into_bytes(), pass_verifier))
1518}
1519
1520/// Composes a [`ConnectionSupervisor`] over caller-owned services.
1521///
1522/// Any of the optional parts — `[auth]` (bearer + pass verifier), a connection
1523/// notifier, operational limits — can be set together. Minted by
1524/// [`ConnectionSupervisor::builder`].
1525#[derive(Debug)]
1526pub struct ConnectionSupervisorBuilder {
1527    services: Arc<dyn ConnectionServices>,
1528    notifier: Option<Arc<dyn ConnectionNotifier>>,
1529    auth_token: Option<Vec<u8>>,
1530    pass_verifier: Option<PassVerifier>,
1531    lys: Option<LysInstall>,
1532    limits: LimitsConfig,
1533}
1534
1535/// What `[auth.lys]` installs on the connection runtime: the admitter with
1536/// the network id its subjects are placed under (ACCESS-001 R2), the
1537/// machine's link host (ACCESS-002 R1), the service bindings (ACCESS-003 R1)
1538/// and the channel membership gate (ACCESS-006 R1).
1539#[derive(Debug)]
1540pub struct LysInstall {
1541    /// Admits a Lys pass at `Connect` and decides its routes.
1542    pub(crate) admitter: Arc<dyn crate::auth_lys::session::LysAdmitter>,
1543    /// The network every subject is placed under.
1544    pub(crate) network_id: String,
1545    /// This machine's link host, `[auth.lys.machine]`.
1546    pub(crate) link_host: Option<Arc<crate::link::LinkHost>>,
1547    /// The service effect seams, `[auth.lys.services]`.
1548    pub(crate) services: Option<Arc<crate::services::production::ServiceBindings>>,
1549    /// The channel membership gate `[auth.lys.membership]` configures, over
1550    /// the same issuer.
1551    pub(crate) membership: Option<Arc<crate::auth_membership::MembershipGate>>,
1552}
1553
1554/// The tokio runtime Lys is asked on, live deliberate decisions and the
1555/// service bindings' HTTP alike, owned by the server for its life; `None`
1556/// without `[auth.lys]`. One worker: every ask is one short request made
1557/// from a connection's slice through `Handle::block_on` on a scoped thread,
1558/// never a runtime nested in the connection scheduler.
1559///
1560/// # Errors
1561/// The runtime could not start, by name.
1562pub fn lys_ask_runtime(
1563    auth: Option<&AuthConfig>,
1564) -> Result<Option<tokio::runtime::Runtime>, ServerError> {
1565    if auth.is_none_or(|auth| auth.lys.is_none()) {
1566        return Ok(None);
1567    }
1568    tokio::runtime::Builder::new_multi_thread()
1569        .worker_threads(1)
1570        .thread_name("liminal-lys-ask")
1571        .enable_all()
1572        .build()
1573        .map(Some)
1574        .map_err(|error| ServerError::ConfigValidation {
1575            message: format!("the Lys ask runtime could not start: {error}"),
1576        })
1577}
1578
1579/// Builds the Lys admitter `[auth.lys]` configures, reading the issuer's key
1580/// set once; `None` without the section. `ask_handle` is the runtime live
1581/// asks run on ([`lys_ask_runtime`], or an embedder's own); `auth.lys.live`
1582/// without one is refused at start, by name.
1583///
1584/// # Errors
1585/// A key set, pass or identity that cannot be read or is refused, by name.
1586pub fn configured_lys(
1587    auth: &AuthConfig,
1588    ask_handle: Option<tokio::runtime::Handle>,
1589) -> Result<Option<LysInstall>, ServerError> {
1590    let Some(lys) = auth.lys.as_ref() else {
1591        return Ok(None);
1592    };
1593    let keys_json =
1594        std::fs::read_to_string(&lys.keys_path).map_err(|error| ServerError::ConfigValidation {
1595            message: format!("auth.lys.keys_path {}: {error}", lys.keys_path.display()),
1596        })?;
1597    let mut adapter = crate::auth_lys::lys_pass_adapter::LysPassAdapter::from_keys_json(
1598        &keys_json,
1599        lys.issuer.clone(),
1600    )
1601    .map_err(|error| ServerError::ConfigValidation {
1602        message: format!("auth.lys key set refused: {error}"),
1603    })?;
1604    if lys.live {
1605        // Deliberate decisions ask the issuer live, with the holder's pass,
1606        // on the server's runtime; the ask never nests a runtime.
1607        let Some(handle) = ask_handle else {
1608            return Err(ServerError::ConfigValidation {
1609                message:
1610                    "auth.lys.live needs the server's Lys ask runtime at start; none was given"
1611                        .to_owned(),
1612            });
1613        };
1614        let mut live = crate::auth_lys::live::HttpLysAsk::new(
1615            lys.api_url.as_deref().unwrap_or(&lys.issuer),
1616            handle,
1617        )
1618        .map_err(|error| ServerError::ConfigValidation {
1619            message: format!("auth.lys.issuer refused for live asks: {error}"),
1620        })?;
1621        // Lys answers its batch check for an administrator or an app, never
1622        // a seat's pass: with an app credential configured the question is
1623        // asked as this liminal, about the holder.
1624        if let Some(path) = lys.app_credential_path.as_ref() {
1625            let credential =
1626                std::fs::read_to_string(path).map_err(|error| ServerError::ConfigValidation {
1627                    message: format!(
1628                        "auth.lys.app_credential_path '{}' cannot be read: {}",
1629                        path.display(),
1630                        error.kind()
1631                    ),
1632                })?;
1633            live = live.asking_as(credential.trim().to_owned());
1634        }
1635        adapter = adapter.with_live(Arc::new(live));
1636    }
1637    let adapter = Arc::new(adapter);
1638    let link_host = lys
1639        .machine
1640        .as_ref()
1641        .map(|machine| configured_link_host(adapter.as_ref(), machine))
1642        .transpose()?
1643        .map(Arc::new);
1644    let admitter: Arc<dyn crate::auth_lys::session::LysAdmitter> =
1645        Arc::new(crate::auth_lys::session::ConsumerAdmitter::new(adapter));
1646    let membership = lys
1647        .membership
1648        .as_ref()
1649        .map(|membership| crate::auth_membership::configured_gate(membership, &lys.issuer))
1650        .transpose()?;
1651    Ok(Some(LysInstall {
1652        admitter,
1653        network_id: lys.network_id.clone(),
1654        link_host,
1655        services: None,
1656        membership,
1657    }))
1658}
1659
1660/// Reads 32 bytes as 64 hexadecimal digits, naming `what` in the refusal.
1661fn hex_key(what: &str, digits: &str) -> Result<[u8; 32], ServerError> {
1662    let bytes = hex::decode(digits.trim()).map_err(|error| ServerError::ConfigValidation {
1663        message: format!("{what} is not hexadecimal: {error}"),
1664    })?;
1665    <[u8; 32]>::try_from(bytes).map_err(|bytes| ServerError::ConfigValidation {
1666        message: format!("{what} is {} bytes, not 32", bytes.len()),
1667    })
1668}
1669
1670/// Builds this liminal as a machine Lys knows from `[auth.lys.machine]`
1671/// (ACCESS-002 R1): its identity from the seed file, its own link rights from
1672/// its own pass verified like any other, and the recorded machine keys.
1673fn configured_link_host(
1674    adapter: &crate::auth_lys::lys_pass_adapter::LysPassAdapter,
1675    machine: &crate::config::types::MachineConfig,
1676) -> Result<crate::link::LinkHost, ServerError> {
1677    use crate::auth_lys::LysPassConsumer;
1678    let seed_digits = std::fs::read_to_string(&machine.identity_path).map_err(|error| {
1679        ServerError::ConfigValidation {
1680            message: format!(
1681                "auth.lys.machine.identity_path {}: {error}",
1682                machine.identity_path.display()
1683            ),
1684        }
1685    })?;
1686    let seed = zeroize::Zeroizing::new(hex_key("auth.lys.machine identity seed", &seed_digits)?);
1687    let local =
1688        crate::link_handshake::LocalMachine::new(lys_core::keys::Ed25519Identity::from_seed(&seed));
1689    let pass_bytes =
1690        std::fs::read(&machine.pass_path).map_err(|error| ServerError::ConfigValidation {
1691            message: format!(
1692                "auth.lys.machine.pass_path {}: {error}",
1693                machine.pass_path.display()
1694            ),
1695        })?;
1696    let now = std::time::SystemTime::now()
1697        .duration_since(std::time::UNIX_EPOCH)
1698        .map_or(0, |elapsed| elapsed.as_secs());
1699    let pass = adapter
1700        .verify_offline(&pass_bytes, crate::auth_lys::LIMINAL_PASS_AUDIENCE, now)
1701        .map_err(|error| ServerError::ConfigValidation {
1702            message: format!("auth.lys.machine pass refused: {error}"),
1703        })?;
1704    let rights = crate::auth_lys::lys_pass_adapter::MachineLinkRights::new(adapter, pass);
1705    let mut recorded = std::collections::BTreeMap::new();
1706    for (holder, digits) in &machine.machines {
1707        let key = hex_key(&format!("auth.lys.machine.machines.{holder}"), digits)?;
1708        recorded.insert(
1709            holder.clone(),
1710            crate::link_handshake::MachineIdentity::new(key),
1711        );
1712    }
1713    Ok(crate::link::LinkHost::new(
1714        local,
1715        Box::new(rights),
1716        recorded,
1717    ))
1718}
1719
1720impl ConnectionSupervisorBuilder {
1721    /// Installs the configured `[auth]` section: the bearer token, and the
1722    /// registry pass verifier when `auth.pass` is present — derived exactly as
1723    /// [`ConnectionSupervisor::from_config`] derives them.
1724    ///
1725    /// # Errors
1726    /// Returns [`ServerError::ConfigValidation`] when `auth.pass` names a key
1727    /// that is not exactly one valid Ed25519 verifying key as 64 hexadecimal
1728    /// digits — the same error, with the same text, that `from_config` returns
1729    /// for that configuration.
1730    pub fn auth(mut self, auth: &AuthConfig) -> Result<Self, ServerError> {
1731        let (auth_token, pass_verifier) = configured_authentication(auth)?;
1732        self.auth_token = Some(auth_token);
1733        self.pass_verifier = pass_verifier;
1734        self.lys = configured_lys(auth, tokio::runtime::Handle::try_current().ok())?;
1735        Ok(self)
1736    }
1737
1738    /// Test seam: installs `lys` exactly as `[auth.lys]` would, with no
1739    /// configuration file and no issuer key behind it.
1740    #[cfg(test)]
1741    #[must_use]
1742    pub(super) fn lys_for_tests(mut self, lys: LysInstall) -> Self {
1743        self.lys = Some(lys);
1744        self
1745    }
1746
1747    /// Installs the connection-keyed notifier, invoked on worker registration
1748    /// lifecycle and on pass-stamped attach/detach.
1749    #[must_use]
1750    pub fn notifier(mut self, notifier: Arc<dyn ConnectionNotifier>) -> Self {
1751        self.notifier = Some(notifier);
1752        self
1753    }
1754
1755    /// Installs the operational limits (§5); unset, the signed defaults apply.
1756    #[must_use]
1757    pub const fn limits(mut self, limits: LimitsConfig) -> Self {
1758        self.limits = limits;
1759        self
1760    }
1761
1762    /// Starts the supervisor.
1763    ///
1764    /// # Errors
1765    /// Returns [`ServerError`] when incarnation startup or scheduler startup
1766    /// fails.
1767    pub fn build(self) -> Result<ConnectionSupervisor, ServerError> {
1768        SupervisorInner::new(
1769            self.services,
1770            self.notifier,
1771            self.auth_token,
1772            self.pass_verifier,
1773            self.lys,
1774            self.limits,
1775            None,
1776        )
1777        .map(|inner| ConnectionSupervisor {
1778            inner: Arc::new(inner),
1779        })
1780    }
1781}
1782
1783/// Installs the `[auth.lys]` parts on the runtime, each once: the admitter,
1784/// the link host, the membership gate and the service bindings.
1785fn install_lys(runtime: &ConnectionRuntime, lys: LysInstall) -> Result<(), ServerError> {
1786    let LysInstall {
1787        admitter,
1788        network_id,
1789        link_host,
1790        services: bindings,
1791        membership,
1792    } = lys;
1793    runtime
1794        .install_lys_admitter(admitter, network_id)
1795        .map_err(|_| ServerError::ConfigValidation {
1796            message: "auth.lys admitter was already installed".to_owned(),
1797        })?;
1798    if let Some(host) = link_host {
1799        runtime
1800            .install_link_host(host)
1801            .map_err(|_| ServerError::ConfigValidation {
1802                message: "auth.lys.machine was already installed".to_owned(),
1803            })?;
1804    }
1805    if let Some(gate) = membership {
1806        runtime
1807            .install_membership_gate(gate)
1808            .map_err(|_| ServerError::ConfigValidation {
1809                message: "auth.lys.membership was already installed".to_owned(),
1810            })?;
1811    }
1812    if let Some(bindings) = bindings {
1813        runtime
1814            .install_service_bindings(bindings)
1815            .map_err(|_| ServerError::ConfigValidation {
1816                message: "auth.lys.services was already installed".to_owned(),
1817            })?;
1818    }
1819    Ok(())
1820}
1821
1822impl SupervisorInner {
1823    fn new(
1824        services: Arc<dyn ConnectionServices>,
1825        notifier: Option<Arc<dyn ConnectionNotifier>>,
1826        auth_token: Option<Vec<u8>>,
1827        pass_verifier: Option<PassVerifier>,
1828        lys: Option<LysInstall>,
1829        limits: LimitsConfig,
1830        fatal_shutdown: Option<ShutdownHandle>,
1831    ) -> Result<Self, ServerError> {
1832        let installed_services = ConnectionServiceInstallation::capture(services);
1833        // P0 #56 R4. Owned by the supervisor so the readiness probe has a stable
1834        // handle whether or not a participant service (and therefore an
1835        // incarnation authority) is installed: a server with no authority has
1836        // nothing that can hold admission, and reports available.
1837        let admission_readiness = AdmissionReadiness::available();
1838        let incarnations = installed_services
1839            .participant_service
1840            .as_ref()
1841            .map(
1842                |service| -> Result<Arc<ConnectionIncarnationAuthority>, ServerError> {
1843                    ConnectionIncarnationAuthority::startup(
1844                        service.durable_store(),
1845                        limits.max_connections,
1846                        service.publication_conversation_limit(),
1847                        service,
1848                        admission_readiness.clone(),
1849                    )
1850                    .map(Arc::new)
1851                },
1852            )
1853            .transpose()?;
1854        let atoms = AtomTable::with_common_atoms();
1855        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
1856        let registry = Arc::new(ModuleRegistry::new());
1857
1858        let scheduler = Scheduler::with_services(
1859            SchedulerConfig {
1860                thread_count: Some(CONNECTION_SCHEDULER_THREADS),
1861                ..SchedulerConfig::default()
1862            },
1863            SchedulerServices::from_config().owned_readiness(),
1864            registry,
1865            // The strongest of the three cases: `registry` here is a bare
1866            // `ModuleRegistry::new()` with NO module ever inserted, so this
1867            // scheduler cannot execute a single bytecode instruction, let alone
1868            // a guard BIF. Connections run as native processes via
1869            // `NativeHandlerFactory`. beamr 0.19 requires the answer be written
1870            // down rather than inherited; the answer is none.
1871            NativeBifs::none(),
1872        )
1873        .map_err(|message| ServerError::ListenerAccept {
1874            message: format!("failed to start connection scheduler: {message}"),
1875        })?;
1876        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
1877        let scheduler = Arc::new(scheduler);
1878        // The runtime captures a WEAK handle to the connection scheduler so
1879        // notifier wakes (R3/R1(vi)) can be fired from another actor's slice
1880        // without a strong scheduler↔process↔runtime cycle that would leak the
1881        // whole connection scheduler.
1882        let runtime = Arc::new(ConnectionRuntime::new(
1883            ConnectionRuntimeInstallation {
1884                services: installed_services,
1885                incarnations: incarnations.clone(),
1886                fatal_shutdown,
1887            },
1888            control_atom,
1889            ready_atom,
1890            Arc::downgrade(&scheduler),
1891            notifier,
1892            (auth_token, pass_verifier),
1893            limits,
1894        ));
1895        if let Some(lys) = lys {
1896            install_lys(&runtime, lys)?;
1897        }
1898        // A connection fate parked on candidate-lane `Precedence` is retried by
1899        // the drain that clears it, so the runtime is told about every drain.
1900        if let (Some(service), Some(notifier)) = (runtime.participant_service(), runtime.notifier())
1901        {
1902            service.install_checkpoint_crossing_observer(Arc::clone(notifier));
1903        }
1904        if let Some(service) = runtime.participant_service() {
1905            let runtime_weak: Weak<ConnectionRuntime> = Arc::downgrade(&runtime);
1906            let listener: Weak<dyn CandidateLaneListener> = runtime_weak;
1907            service
1908                .install_candidate_lane_listener(listener)
1909                .map_err(|error| ServerError::ParticipantIncarnation {
1910                    phase: "candidate-lane listener installation",
1911                    message: error.to_string(),
1912                })?;
1913        }
1914        // W4 leg 1 reclamation carve-out (§4.1): the kernel-parked exit-event
1915        // reactor is the TOLD source that reclaims a connection host record whose
1916        // process exited WITHOUT a final handler slice (external/panic
1917        // termination). It blocks on beamr's single exit-event subscription —
1918        // never a poll — and routes every reclamation through the same `remove()`
1919        // funnel as an ordinary exit. Detached on purpose: it exits when the
1920        // scheduler (and so its event publisher) drops, so there is no stop flag
1921        // to sample (LAW-1).
1922        match scheduler.subscribe_exit_events() {
1923            Some(subscription) => {
1924                let reactor_scheduler = Arc::downgrade(&scheduler);
1925                // WEAK, symmetric with the scheduler handle: the reactor must not
1926                // keep the runtime (and its durable store's writer lock) alive past
1927                // supervisor drop. It upgrades per event and exits on a failed
1928                // upgrade, so the runtime is released synchronously at drop.
1929                let reactor_runtime = Arc::downgrade(&runtime);
1930                thread::Builder::new()
1931                    .name("liminal-connection-reclaim".to_owned())
1932                    .spawn(move || {
1933                        run_reclaim_reactor(&subscription, &reactor_scheduler, &reactor_runtime);
1934                    })
1935                    .map_err(|error| ServerError::ListenerAccept {
1936                        message: format!("failed to start connection reclamation reactor: {error}"),
1937                    })?;
1938            }
1939            None => {
1940                tracing::error!(
1941                    "connection scheduler exit-event subscription unavailable; \
1942                     external-termination reclamation has no TOLD exit source (the \
1943                     shutdown-drain scan that once backstopped it was retired by W4 leg 3)"
1944                );
1945            }
1946        }
1947        Ok(Self {
1948            scheduler,
1949            runtime,
1950            incarnations,
1951            admission_readiness,
1952        })
1953    }
1954
1955    fn spawn_connection(
1956        self: &Arc<Self>,
1957        stream: TcpStream,
1958    ) -> Result<ConnectionHandle, ServerError> {
1959        // §5 `max_connections`: ATOMIC admission reservation acquired BEFORE any
1960        // process construction (review round 1 item 7 — a signed bound must not
1961        // be exceedable by concurrent callers; check-then-spawn across an
1962        // unlocked window was). The CAS reservation is released on every failure
1963        // path below and converts into the connection record at `register`;
1964        // thereafter the single record-removal path (`remove`) releases it. An
1965        // over-cap accept therefore costs nothing and the bound holds under any
1966        // concurrency.
1967        self.runtime.try_reserve_admission()?;
1968        let reservation = AdmissionReservation {
1969            runtime: &self.runtime,
1970            armed: true,
1971        };
1972        stream
1973            .set_nonblocking(true)
1974            .map_err(|error| ServerError::ListenerAccept {
1975                message: format!("failed to configure connection stream: {error}"),
1976            })?;
1977        // A failed `peer_addr` is folded to `None` rather than refusing the
1978        // accept: an unidentifiable peer is still a serviceable connection. The
1979        // cost is paid in triage, not in service — every later log line for this
1980        // connection reports `peer_addr=None`, so nothing that happens on it can
1981        // be tied back to a remote endpoint.
1982        let peer_addr = stream.peer_addr().ok();
1983        // The host-held duplicate keeps the fd alive until the single record-removal
1984        // path has synchronously deregistered readiness. External process death can
1985        // therefore never let fd reuse overtake host-side deregistration.
1986        let fd_guard = stream
1987            .try_clone()
1988            .map_err(|error| ServerError::ListenerAccept {
1989                message: format!("failed to retain connection fd for teardown: {error}"),
1990            })?;
1991        let connection_incarnation = self.allocate_connection_incarnation()?;
1992        let holder = Arc::new(Mutex::new(Some(stream)));
1993        let runtime = Arc::clone(&self.runtime);
1994        let process_holder = Arc::clone(&holder);
1995        let factory: NativeHandlerFactory = Box::new(move || {
1996            Box::new(ConnectionProcess::from_holder(
1997                Arc::clone(&runtime),
1998                peer_addr,
1999                &process_holder,
2000                connection_incarnation,
2001            ))
2002        });
2003        let pid =
2004            self.scheduler
2005                .spawn_native(factory)
2006                .map_err(|error| ServerError::ListenerAccept {
2007                    message: format!("failed to spawn connection process: {error}"),
2008                })?;
2009        if let Err(error) = self.runtime.register_connection(
2010            pid,
2011            peer_addr,
2012            connection_incarnation,
2013            MountKind::Tcp,
2014            Some(fd_guard),
2015        ) {
2016            // Registration failure leaves no host record to reap. Terminate the
2017            // just-spawned process explicitly so neither its stream nor admission
2018            // reservation can escape this failed spawn.
2019            self.scheduler.terminate_process(pid, ExitReason::Error);
2020            return Err(error);
2021        }
2022        // The reservation is now owned by the registered record: `remove` (the
2023        // single record-removal path — finish/mark_crashed/reap all funnel
2024        // through it) releases the admission when the record goes away.
2025        reservation.convert();
2026        Ok(ConnectionHandle {
2027            pid,
2028            peer_addr,
2029            connection_incarnation,
2030            supervisor: Arc::clone(self),
2031        })
2032    }
2033
2034    /// LP-WS-TRANSPORT R1.3: the sibling-transport spawn body. Mirrors
2035    /// [`Self::spawn_connection`]'s admission → incarnation → spawn → register →
2036    /// convert sequence exactly (same reservation guard, same failure rollback,
2037    /// same single record-removal ownership), differing only in that the caller
2038    /// supplies the native handler factory and the host-held fd guard instead of
2039    /// a raw `TcpStream`. Purely additive; the TCP path never calls this.
2040    fn spawn_transport_connection(
2041        self: &Arc<Self>,
2042        peer_addr: Option<SocketAddr>,
2043        fd_guard: Option<TcpStream>,
2044        mount: MountKind,
2045        build_factory: &dyn Fn(
2046            Arc<ConnectionRuntime>,
2047            Option<ConnectionIncarnation>,
2048        ) -> NativeHandlerFactory,
2049    ) -> Result<ConnectionHandle, ServerError> {
2050        self.runtime.try_reserve_admission()?;
2051        let reservation = AdmissionReservation {
2052            runtime: &self.runtime,
2053            armed: true,
2054        };
2055        let connection_incarnation = self.allocate_connection_incarnation()?;
2056        let factory = build_factory(Arc::clone(&self.runtime), connection_incarnation);
2057        let pid =
2058            self.scheduler
2059                .spawn_native(factory)
2060                .map_err(|error| ServerError::ListenerAccept {
2061                    message: format!("failed to spawn connection process: {error}"),
2062                })?;
2063        if let Err(error) = self.runtime.register_connection(
2064            pid,
2065            peer_addr,
2066            connection_incarnation,
2067            mount,
2068            fd_guard,
2069        ) {
2070            self.scheduler.terminate_process(pid, ExitReason::Error);
2071            return Err(error);
2072        }
2073        reservation.convert();
2074        Ok(ConnectionHandle {
2075            pid,
2076            peer_addr,
2077            connection_incarnation,
2078            supervisor: Arc::clone(self),
2079        })
2080    }
2081
2082    fn allocate_connection_incarnation(
2083        &self,
2084    ) -> Result<Option<ConnectionIncarnation>, ServerError> {
2085        let Some(authority) = self.incarnations.as_ref() else {
2086            return Ok(None);
2087        };
2088        // Production-era uniqueness invariant: every published incarnation is
2089        // unique against ALL durable references — binding epochs committed
2090        // into conversation logs included — by allocator-log monotonicity
2091        // alone, not by the completeness of the reference set below.
2092        //
2093        //   1. Startup replays the durable allocator stream and STRICTLY
2094        //      increments the server incarnation, fsyncing the Startup event
2095        //      before any listener becomes ready
2096        //      (`IncarnationStream::startup`); a server value is never wrapped
2097        //      or reused, so no two process lifetimes share one.
2098        //   2. Within a lifetime, allocations are serialized under this
2099        //      authority's mutex, candidates start strictly above the durable
2100        //      `last_examined_connection_ordinal`
2101        //      (`allocate_connection_incarnation`), and every allocation's
2102        //      event is appended and flushed BEFORE its pair is published
2103        //      (`StartedIncarnationStream::allocate`), so ordinals never
2104        //      repeat within a lifetime and replay restores a head at or
2105        //      above every published ordinal.
2106        //   3. A durable reference can only name a pair this allocator
2107        //      previously PUBLISHED (binding epochs are committed only after
2108        //      their connection was admitted), and the same store's flush
2109        //      barrier orders the allocator event before any conversation-log
2110        //      entry that references it.
2111        //
2112        // The live-connection reference set below is therefore defense in
2113        // depth — a bounded collision skip against a rolled-back or divergent
2114        // allocator stream — never the uniqueness foundation, and never a raw
2115        // caller-supplied matrix.
2116        let references = self.runtime.complete_active_incarnation_references()?;
2117        authority.allocate(&references).map(Some)
2118    }
2119
2120    fn broadcast_control(&self, control: &ConnectionControl) {
2121        for connection in self.runtime.active_connections() {
2122            if !self.enqueue_control(connection.pid, control.clone()) {
2123                // Observation, not diagnosis: the routine cause is a process
2124                // that is no longer live, but a refused control queue reaches
2125                // here too and has already warned with its own error.
2126                tracing::debug!(
2127                    connection_pid = connection.pid,
2128                    peer_addr = ?connection.peer_addr,
2129                    ?control,
2130                    "connection control message was not published to the process"
2131                );
2132            }
2133        }
2134    }
2135
2136    /// Queues `control` for `pid` and wakes the process. Returns whether the
2137    /// control was PUBLISHED (left in the queue with a successful wake, or
2138    /// already consumed by a drain) — `false` guarantees no consumer ever saw
2139    /// it.
2140    ///
2141    /// S8: a failed wake does NOT prove the queued control was never consumed.
2142    /// The insert releases the queue lock before the wake attempt, and a
2143    /// process already executing a control drain (each control atom drains ALL
2144    /// queued controls for the pid) can pop the just-inserted entry in that
2145    /// window, then exit before the wake check. Publication is therefore
2146    /// disambiguated BY OBSERVATION on the failed-wake path: `remove_control`
2147    /// finding and removing the entry proves no consumer saw it (truly
2148    /// unpublished — `false`); finding nothing proves a drain consumed it
2149    /// (`pop_control` is the only other remover of queue entries, and the
2150    /// removal key embeds the push's runtime-unique correlation id, so it can
2151    /// never match a different entry) — the control was published and the
2152    /// caller's slot lifecycle carries the delivery truth (`true`).
2153    fn enqueue_control(&self, pid: u64, control: ConnectionControl) -> bool {
2154        self.runtime.publish_control(&self.scheduler, pid, control)
2155    }
2156}
2157
2158impl ConnectionRuntime {
2159    /// Queues `control` for connection `pid` and wakes it on `scheduler`;
2160    /// `true` means the control was published (queued and the wake landed,
2161    /// or a drain consumed it first). The one publication path for a control,
2162    /// shared by the supervisor's pushes and a routed request's (SRV-027 R2).
2163    pub(super) fn publish_control(
2164        &self,
2165        scheduler: &Scheduler,
2166        pid: u64,
2167        control: ConnectionControl,
2168    ) -> bool {
2169        // Keep a key for the failure-path removal before the control is moved into
2170        // the queue, so a non-`Copy` (push) control can still be located and pulled
2171        // back out if the scheduler wakeup fails.
2172        let removal_key = control.clone();
2173        if let Err(error) = self.push_control(pid, control) {
2174            // The cause is only knowable HERE. Callers see a bare `false` and
2175            // used to render every one of them as "process is not live", which
2176            // is a lie about a live process whose control queue refused the
2177            // entry — and it discarded the one error that said what happened.
2178            tracing::warn!(
2179                connection_pid = pid,
2180                %error,
2181                "connection control queue refused the entry; the control was never queued"
2182            );
2183            return false;
2184        }
2185        // Deterministic test seam in the insert->wake window (S8 staging).
2186        #[cfg(test)]
2187        self.run_pre_wake_barrier();
2188        if scheduler.enqueue_atom_message(pid, self.control_atom()) {
2189            true
2190        } else {
2191            // Failed wake: the entry's fate is the publication verdict. Removed
2192            // here => nobody consumed it => unpublished. Already gone => a
2193            // drain consumed it before the wake check => published. (A poisoned
2194            // queue lock reads as not-removed => published — the safe
2195            // direction: the slot lifecycle then reports the truthful outcome,
2196            // whereas claiming "unpublished" could be a lie.)
2197            !self.remove_control(pid, &removal_key)
2198        }
2199    }
2200
2201    /// SRV-027 R2: pushes a routed request's `payload` to the worker
2202    /// connection `pid` on `subject`, the slot carrying the requester's
2203    /// `wake`, and hands back the awaiter the requester's slice reads
2204    /// non-blocking ([`PushReplyAwaiter::try_receive`]). The same
2205    /// INSERT -> CONFIRM -> PUBLISH order and publication invariant as the
2206    /// supervisor's pushes: an `Err` means no `Push` was published. The slot
2207    /// carries no deadline of its own; the requester's pending-reply deadline
2208    /// governs, and its expiry cancels the slot by correlation id.
2209    ///
2210    /// # Errors
2211    /// The worker's `max_pending_pushes_per_connection` cap, a worker whose
2212    /// record closed during registration, a scheduler that is gone, or a
2213    /// control the queue refused: each by name, with nothing published.
2214    pub(super) fn route_push(
2215        &self,
2216        pid: u64,
2217        subject: &str,
2218        payload: Vec<u8>,
2219        wake: Option<super::wake::ReadyWaker>,
2220    ) -> Result<PushReplyAwaiter, ServerError> {
2221        let correlation_id = self.next_push_correlation_id();
2222        let receiver =
2223            self.register_push(pid, correlation_id, None, Some(subject.to_owned()), wake)?;
2224        if !self.confirm_push_registration(pid, correlation_id) {
2225            return Err(ServerError::ListenerAccept {
2226                message: format!(
2227                    "cannot route to connection process {pid}: the connection closed during push registration"
2228                ),
2229            });
2230        }
2231        let Some(scheduler) = self.scheduler.upgrade() else {
2232            self.cancel_push(correlation_id);
2233            return Err(ServerError::ListenerAccept {
2234                message: format!(
2235                    "cannot route to connection process {pid}: the connection scheduler is gone"
2236                ),
2237            });
2238        };
2239        let control = ConnectionControl::Push {
2240            correlation_id,
2241            payload,
2242        };
2243        if self.publish_control(&scheduler, pid, control) {
2244            Ok(PushReplyAwaiter {
2245                correlation_id,
2246                receiver,
2247                deadline: None,
2248                runtime: Weak::new(),
2249            })
2250        } else {
2251            self.cancel_push(correlation_id);
2252            Err(ServerError::ListenerAccept {
2253                message: format!(
2254                    "cannot route to connection process {pid}: the control was not published"
2255                ),
2256            })
2257        }
2258    }
2259
2260    /// SRV-027 R1: the connection serving `subject`, by one keyed lookup in
2261    /// the directory under the registry lock; `None` when no worker serves it.
2262    pub(super) fn serving(&self, subject: &str) -> Option<u64> {
2263        self.records
2264            .lock()
2265            .ok()
2266            .and_then(|records| records.directory.serving(subject))
2267    }
2268
2269    /// How many directory lookups `serving` has made (R4 count pins).
2270    pub(super) fn directory_lookups(&self) -> u64 {
2271        self.records
2272            .lock()
2273            .map_or(0, |records| records.directory.lookups())
2274    }
2275}
2276
2277/// RAII guard for one §5 `max_connections` admission reservation.
2278///
2279/// Acquired (via [`ConnectionRuntime::try_reserve_admission`]) before any process
2280/// construction in `spawn_connection`; every early-return failure path releases
2281/// it through `Drop`, and a successful `register` converts it into the
2282/// connection record (whose removal releases the admission instead). RAII means
2283/// no failure path — present or future — can leak a reservation.
2284struct AdmissionReservation<'a> {
2285    runtime: &'a ConnectionRuntime,
2286    armed: bool,
2287}
2288
2289impl AdmissionReservation<'_> {
2290    /// Converts the reservation into record ownership: `Drop` no longer releases
2291    /// it, because the registered record's removal will.
2292    fn convert(mut self) {
2293        self.armed = false;
2294    }
2295}
2296
2297impl Drop for AdmissionReservation<'_> {
2298    fn drop(&mut self) {
2299        if self.armed {
2300            self.runtime.release_admission();
2301        }
2302    }
2303}
2304
2305#[derive(Debug, Clone, PartialEq, Eq)]
2306pub(super) enum ConnectionControl {
2307    NotifyShutdown,
2308    ForceClose,
2309    /// Server-initiated push of an opaque payload, correlated by `correlation_id`,
2310    /// to be written out as a [`Frame::Push`] by the receiving connection process.
2311    Push {
2312        correlation_id: u64,
2313        payload: Vec<u8>,
2314    },
2315    /// ACCESS-005 R4/R6: this connection's rights changed under a grant
2316    /// revocation. The connection sweeps its own `ConnectionGrants`: it
2317    /// retires withdrawn rights' work and, when its admitting right is lost,
2318    /// closes with the reason. The denied state is already in force before
2319    /// this control is read, so a refused publication only delays the sweep
2320    /// to the connection's own wake.
2321    Revocation,
2322}
2323
2324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2325pub struct ActiveConnection {
2326    pid: u64,
2327    peer_addr: Option<SocketAddr>,
2328}
2329
2330#[cfg(test)]
2331#[derive(Debug, Clone)]
2332struct PreWaitBarrier {
2333    armed: Arc<Barrier>,
2334    release: Arc<Barrier>,
2335}
2336
2337/// Pid-specific, one-use deterministic gate on the reclamation delivery path
2338/// (oracle 26). The exit-event reactor stages it only for the targeted pid, so
2339/// unrelated exits still reclaim immediately; for the target it rendezvouses
2340/// `reached` (proving the pid is dead but its record still tracked — the S8
2341/// reclamation window), then `release` (the harness lets the reclaim proceed),
2342/// then `done` (the `remove()` funnel completed). It changes no production
2343/// semantics — the whole type and its call sites are `#[cfg(test)]`.
2344#[cfg(test)]
2345#[derive(Debug, Clone)]
2346struct ReclaimBarrier {
2347    pid: u64,
2348    reached: Arc<Barrier>,
2349    release: Arc<Barrier>,
2350    done: Arc<Barrier>,
2351}
2352
2353#[derive(Debug)]
2354struct ConnectionServiceInstallation {
2355    services: Arc<dyn ConnectionServices>,
2356    participant_service: Option<InstalledParticipantService>,
2357}
2358
2359impl ConnectionServiceInstallation {
2360    /// Captures the service adapter's capability posture exactly once, before
2361    /// participant incarnation startup or connection process construction.
2362    fn capture(services: Arc<dyn ConnectionServices>) -> Self {
2363        let participant_service = services.participant_service();
2364        Self {
2365            services,
2366            participant_service,
2367        }
2368    }
2369}
2370
2371#[derive(Debug)]
2372struct ConnectionRuntimeInstallation {
2373    services: ConnectionServiceInstallation,
2374    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
2375    fatal_shutdown: Option<ShutdownHandle>,
2376}
2377
2378#[cfg(test)]
2379#[derive(Debug)]
2380enum CloseEventFdProbe {
2381    Off,
2382    Armed,
2383    Observed(Result<bool, std::io::Error>),
2384}
2385
2386#[derive(Debug)]
2387pub(super) struct ConnectionRuntime {
2388    signals: OnceLock<ServerSignals>,
2389    /// The Lys pass admitter and the Lys network id liminal's subjects are
2390    /// placed under, installed once before the first connection (ACCESS-001
2391    /// R2). Absent, a Lys pass record is refused at `Connect` by name.
2392    lys_admitter: OnceLock<(Arc<dyn crate::auth_lys::session::LysAdmitter>, String)>,
2393    /// The ACCESS-003 service bindings, installed once from
2394    /// `[auth.lys.services]`; a service request without them is refused by
2395    /// name.
2396    service_bindings: OnceLock<Arc<crate::services::production::ServiceBindings>>,
2397    /// The services registry and lifecycle (SRV-028), installed once after
2398    /// the listener binds; absent, every lifecycle request is refused by
2399    /// name and no service process credential is admitted.
2400    lifecycle: OnceLock<Arc<crate::services::lifecycle::Lifecycle>>,
2401    /// The channel membership gate, installed once with the admitter
2402    /// (ACCESS-006 R1). Absent, no channel is membership-protected.
2403    membership_gate: OnceLock<Arc<crate::auth_membership::MembershipGate>>,
2404    /// This liminal as a machine Lys knows, installed once with the admitter
2405    /// (ACCESS-002 R1). Absent, no link opens here, by name.
2406    link_host: OnceLock<Arc<crate::link::LinkHost>>,
2407    /// The grant-revocation policy, installed once before the first
2408    /// connection (ACCESS-005). Present, every `Connect` must carry a bound
2409    /// pass the authority admits and negotiate the reason-bearing close;
2410    /// absent, admission is exactly as before.
2411    revocation: OnceLock<Arc<crate::auth_revocation::RevocationAuthority>>,
2412    #[cfg(test)]
2413    close_event_fd_probe: Mutex<CloseEventFdProbe>,
2414    services: Arc<dyn ConnectionServices>,
2415    /// Live retry index woken by candidate-lane drains; durable Open rows remain
2416    /// the recovery authority.
2417    parked_connection_fates: Mutex<ParkedConnectionFates>,
2418    /// Complete participant handler/store bundle captured at supervisor startup.
2419    /// `Some` is paired with an incarnation authority on `SupervisorInner`.
2420    participant_service: Option<InstalledParticipantService>,
2421    /// Same started authority used for allocation, shared for terminal Open/Complete.
2422    incarnations: Option<Arc<ConnectionIncarnationAuthority>>,
2423    /// Existing runtime shutdown activation notified by the first post-Open fatal.
2424    /// Test-only/runtime-less constructors intentionally carry `None`.
2425    fatal_shutdown: Option<ShutdownHandle>,
2426    records: Mutex<Registry>,
2427    controls: Mutex<Vec<QueuedConnectionControl>>,
2428    control_atom: Atom,
2429    /// R6 single `READY` wake atom for this connection scheduler. Fired by every
2430    /// wake source's notifier (R3/R1(vi)); coalescing and duplicates are harmless.
2431    ready_atom: Atom,
2432    /// Weak handle to the connection scheduler, used to build [`ReadyWaker`]s a
2433    /// notifier fires from another actor's slice. Weak so it never keeps the
2434    /// scheduler alive (the scheduler owns the processes that own this runtime).
2435    scheduler: Weak<Scheduler>,
2436    /// W4 leg 3 (§4.3) TOLD drain-completion primitive. Reuses the
2437    /// [`ShutdownHandle`] `Condvar` shape (`shutdown.rs` reuse candidate (c)): a
2438    /// monotonic connection-removal generation guarded by [`Self::drain_removed`]'s
2439    /// mutex, bumped once whenever [`Self::remove`] actually drops a record — the
2440    /// single removal funnel every exit route (in-slice `mark_crashed`/`finish`,
2441    /// the reclaim reactor, and the reconciliation scan) reaches. The
2442    /// shutdown-sequence drain/settle waiter parks on the `Condvar` and wakes only
2443    /// on a delivered exit (a generation bump + `notify_all`) or the one admitted
2444    /// deadline it passes to `wait_timeout`. No periodic reap or count scan.
2445    drain_generation: Mutex<u64>,
2446    /// Woken on every connection-record removal; the drain/settle waiter parks
2447    /// here. Paired with [`Self::drain_generation`] under the same mutex so an
2448    /// exit delivered between the waiter's arm-before-observe snapshot and its
2449    /// park cannot be lost (oracle 18).
2450    drain_removed: Condvar,
2451    /// FIX A-ii shutdown flush barrier: a delivery-quiescence generation of the
2452    /// exact TOLD `drain_generation` shape. Bumped whenever a connection parks
2453    /// with every accepted publish already fanned out to its socket — but only
2454    /// while [`Self::settle_armed`] is set (the flush barrier is waiting) — so
2455    /// normal operation pays nothing. Guards [`Self::settle_changed`]'s mutex.
2456    settle_generation: Mutex<u64>,
2457    /// Woken when a connection reaches delivery quiescence during shutdown; the
2458    /// flush barrier parks here, paired with [`Self::settle_generation`] for the
2459    /// same arm-before-observe safety as the drain waiter.
2460    settle_changed: Condvar,
2461    /// Set only while the flush barrier is actively waiting, so a park bumps the
2462    /// settle generation and wakes the barrier ONLY when someone is listening.
2463    settle_armed: AtomicBool,
2464    /// Server `Push` frames this runtime's connections have queued for their
2465    /// sockets, counted once per frame accepted into an outbound buffer. A push
2466    /// that fails, is cancelled or is rolled back is never counted.
2467    pushes_queued: AtomicU64,
2468    /// Observes the real condvar predicate while its generation lock is held.
2469    #[cfg(test)]
2470    settle_wait_observer: Mutex<Option<Sender<()>>>,
2471    /// Test-only count of drain waiter wakes that observed a real removal
2472    /// (generation advanced across the park). A quiet drain records zero — it
2473    /// wakes only for the single deadline (oracle 12).
2474    #[cfg(test)]
2475    drain_exit_wakes: AtomicU64,
2476    /// Test-only count of drain waiter deadline expirations. A quiet drain that
2477    /// times out records exactly one — one arming, one delivery, no helper tick
2478    /// (oracles 12, 16).
2479    #[cfg(test)]
2480    drain_deadline_hits: AtomicU64,
2481    /// Test-only one-use gate in the drain waiter's observe->park window, so a
2482    /// harness can deliver an exit strictly after the completion observation and
2483    /// before the park to pin the arm-before-observe barrier (oracles 18, 19).
2484    #[cfg(test)]
2485    drain_park_barrier: Mutex<Option<PreWaitBarrier>>,
2486    /// R7 (§1.2(6)) test-only per-connection slice counter, keyed by pid. Bumped
2487    /// once at the head of every serviced slice. The park-flip's permanent rule-1
2488    /// assertion (a parked connection's counter must not advance without an event)
2489    /// reads this; the instrument lands now with a test proving it counts slices.
2490    #[cfg(test)]
2491    slice_counts: Mutex<HashMap<u64, u64>>,
2492    /// One-use readiness markers for the next serviced slice of a process.
2493    #[cfg(test)]
2494    slice_observers: Mutex<HashMap<u64, Sender<u64>>>,
2495    /// One-use readiness markers emitted only after the real final probe selects
2496    /// `Wait`, immediately before the native process returns to the scheduler.
2497    #[cfg(test)]
2498    park_observers: Mutex<HashMap<u64, Sender<u64>>>,
2499    /// Most recent slice count whose real final probe selected `Wait`.
2500    #[cfg(test)]
2501    park_counts: Mutex<HashMap<u64, u64>>,
2502    /// Where each report of frames written to a connection's transport goes,
2503    /// by pid (R3b F8, ruling 2a).
2504    #[cfg(test)]
2505    frame_observers: Mutex<HashMap<u64, Sender<Vec<liminal::protocol::FrameType>>>>,
2506    /// Explicit capacities consumed in TCP process construction order.
2507    #[cfg(test)]
2508    next_outbound_capacities: Mutex<VecDeque<usize>>,
2509    #[cfg(test)]
2510    participant_holdback_pauses: Mutex<HashMap<u64, Sender<()>>>,
2511    /// Deterministic test gate placed after arm and before the final probe.
2512    #[cfg(test)]
2513    pre_wait_barrier: Mutex<Option<PreWaitBarrier>>,
2514    /// Deterministic test gate in `enqueue_control`'s insert->wake window (S8).
2515    #[cfg(test)]
2516    pre_wake_barrier: Mutex<Option<PreWaitBarrier>>,
2517    /// Pid-specific one-use gate held on the reclamation delivery path so a test
2518    /// can pin the dead-but-tracked S8 window deterministically (oracle 26).
2519    #[cfg(test)]
2520    reclaim_barrier: Mutex<Option<ReclaimBarrier>>,
2521    /// Barrier-staged slices where the final probe found newly arrived work.
2522    #[cfg(test)]
2523    pre_wait_probe_hits: AtomicU64,
2524    /// One-use observers for process-owned streams reaching their actual drop
2525    /// boundary after external scheduler termination.
2526    #[cfg(test)]
2527    process_stream_drop_observers: Mutex<HashMap<RawFd, Sender<()>>>,
2528    /// One-shot reply slots for in-flight server pushes, keyed by correlation id.
2529    /// The supervisor registers a slot in `push_to_connection`; the connection
2530    /// process resolves it when the matching `PushReply` frame arrives. Each slot
2531    /// records the owning connection pid so the close path can drop a connection's
2532    /// outstanding slots and wake their awaiters with a prompt disconnected error.
2533    push_replies: Mutex<HashMap<u64, PendingPush>>,
2534    /// Monotonic source of push correlation ids. Server-allocated, so it never
2535    /// collides with a client-chosen id on this connection.
2536    next_push_id: AtomicU64,
2537    /// §5 `max_connections` admission counter. Incremented atomically (CAS
2538    /// against the limit) BEFORE a connection process is constructed and
2539    /// decremented on every spawn-failure path and on final record removal, so
2540    /// the signed bound holds under concurrent spawns — admission is never
2541    /// derived from the records-map length across an unlocked window.
2542    admissions: AtomicU64,
2543    /// Optional application hook invoked on worker registration and on the close
2544    /// of a connection that had registered, and on the attach and close of a
2545    /// pass-stamped connection. `None` keeps liminal standalone: a
2546    /// `WorkerRegister` is accepted with no callback, a pass attaches silently.
2547    notifier: Option<Arc<dyn ConnectionNotifier>>,
2548    /// Configured connection auth token (the `[auth]` section's token as opaque
2549    /// bytes). `Some` gates the `Connect` handshake — the frame's `auth_token` must
2550    /// match under a constant-time comparison; `None` leaves the server open-access,
2551    /// byte-identical to the pre-auth behaviour.
2552    auth_token: Option<Vec<u8>>,
2553    pass_verifier: Option<PassVerifier>,
2554    /// Operational caps (§5). Enforced with typed refusals at admission:
2555    /// per-connection subscription, conversation, push, and pending-reply counts,
2556    /// plus the shared inbox byte budget. Non-config constructors carry the signed
2557    /// defaults ([`LimitsConfig::default`]).
2558    limits: LimitsConfig,
2559}
2560
2561impl ConnectionRuntime {
2562    pub(super) fn pressure(&self, connection: u64, response: &str) {
2563        if let Some(signals) = self.signals.get() {
2564            signals.event(crate::server::self_streams::events::ServerEvent::Pressure {
2565                connection,
2566                response,
2567            });
2568        }
2569    }
2570
2571    fn new(
2572        installation: ConnectionRuntimeInstallation,
2573        control_atom: Atom,
2574        ready_atom: Atom,
2575        scheduler: Weak<Scheduler>,
2576        notifier: Option<Arc<dyn ConnectionNotifier>>,
2577        authentication: (Option<Vec<u8>>, Option<PassVerifier>),
2578        limits: LimitsConfig,
2579    ) -> Self {
2580        let (auth_token, pass_verifier) = authentication;
2581        let ConnectionRuntimeInstallation {
2582            services:
2583                ConnectionServiceInstallation {
2584                    services,
2585                    participant_service,
2586                },
2587            incarnations,
2588            fatal_shutdown,
2589        } = installation;
2590        Self {
2591            services,
2592            participant_service,
2593            incarnations,
2594            fatal_shutdown,
2595            signals: OnceLock::new(),
2596            lys_admitter: OnceLock::new(),
2597            service_bindings: OnceLock::new(),
2598            lifecycle: OnceLock::new(),
2599            membership_gate: OnceLock::new(),
2600            link_host: OnceLock::new(),
2601            revocation: OnceLock::new(),
2602            #[cfg(test)]
2603            close_event_fd_probe: Mutex::new(CloseEventFdProbe::Off),
2604            parked_connection_fates: Mutex::new(ParkedConnectionFates::default()),
2605            records: Mutex::new(Registry::default()),
2606            controls: Mutex::new(Vec::new()),
2607            control_atom,
2608            ready_atom,
2609            scheduler,
2610            drain_generation: Mutex::new(0),
2611            drain_removed: Condvar::new(),
2612            settle_generation: Mutex::new(0),
2613            settle_changed: Condvar::new(),
2614            settle_armed: AtomicBool::new(false),
2615            pushes_queued: AtomicU64::new(0),
2616            #[cfg(test)]
2617            settle_wait_observer: Mutex::new(None),
2618            #[cfg(test)]
2619            drain_exit_wakes: AtomicU64::new(0),
2620            #[cfg(test)]
2621            drain_deadline_hits: AtomicU64::new(0),
2622            #[cfg(test)]
2623            drain_park_barrier: Mutex::new(None),
2624            #[cfg(test)]
2625            slice_counts: Mutex::new(HashMap::new()),
2626            #[cfg(test)]
2627            slice_observers: Mutex::new(HashMap::new()),
2628            #[cfg(test)]
2629            park_observers: Mutex::new(HashMap::new()),
2630            #[cfg(test)]
2631            frame_observers: Mutex::new(HashMap::new()),
2632            #[cfg(test)]
2633            park_counts: Mutex::new(HashMap::new()),
2634            #[cfg(test)]
2635            next_outbound_capacities: Mutex::new(VecDeque::new()),
2636            #[cfg(test)]
2637            participant_holdback_pauses: Mutex::new(HashMap::new()),
2638            #[cfg(test)]
2639            pre_wait_barrier: Mutex::new(None),
2640            #[cfg(test)]
2641            pre_wake_barrier: Mutex::new(None),
2642            #[cfg(test)]
2643            reclaim_barrier: Mutex::new(None),
2644            #[cfg(test)]
2645            pre_wait_probe_hits: AtomicU64::new(0),
2646            #[cfg(test)]
2647            process_stream_drop_observers: Mutex::new(HashMap::new()),
2648            push_replies: Mutex::new(HashMap::new()),
2649            next_push_id: AtomicU64::new(1),
2650            admissions: AtomicU64::new(0),
2651            notifier,
2652            auth_token,
2653            pass_verifier,
2654            limits,
2655        }
2656    }
2657
2658    /// Atomically reserves one §5 `max_connections` admission slot: a CAS loop
2659    /// against the configured limit, so N concurrent callers racing for the last
2660    /// slot admit EXACTLY one — the bound cannot be transiently exceeded.
2661    ///
2662    /// # Errors
2663    /// Returns [`ServerError::ConnectionLimitReached`] when every slot is taken.
2664    fn try_reserve_admission(&self) -> Result<(), ServerError> {
2665        self.ensure_participant_service_live()?;
2666        let limit = self.limits.max_connections as u64;
2667        let mut current = self.admissions.load(Ordering::Acquire);
2668        loop {
2669            if current >= limit {
2670                return Err(ServerError::ConnectionLimitReached {
2671                    limit: self.limits.max_connections,
2672                });
2673            }
2674            match self.admissions.compare_exchange_weak(
2675                current,
2676                current + 1,
2677                Ordering::AcqRel,
2678                Ordering::Acquire,
2679            ) {
2680                Ok(_) => return Ok(()),
2681                Err(observed) => current = observed,
2682            }
2683        }
2684    }
2685
2686    /// Releases one admission slot. Called by the spawn failure paths (via the
2687    /// [`AdmissionReservation`] guard) and by [`Self::remove`] when a registered
2688    /// record is removed — exactly one release per reservation. Saturating so a
2689    /// spurious release can never wrap the counter.
2690    fn release_admission(&self) {
2691        let mut current = self.admissions.load(Ordering::Acquire);
2692        loop {
2693            let next = current.saturating_sub(1);
2694            match self.admissions.compare_exchange_weak(
2695                current,
2696                next,
2697                Ordering::AcqRel,
2698                Ordering::Acquire,
2699            ) {
2700                Ok(_) => return,
2701                Err(observed) => current = observed,
2702            }
2703        }
2704    }
2705
2706    /// The operational caps (§5) this runtime enforces.
2707    pub(super) const fn limits(&self) -> &LimitsConfig {
2708        &self.limits
2709    }
2710
2711    /// The connection's single R6 `READY` wake atom.
2712    pub(super) const fn ready_atom(&self) -> Atom {
2713        self.ready_atom
2714    }
2715
2716    /// Builds a [`ReadyWaker`] targeting `pid` on the connection scheduler, if the
2717    /// scheduler is still live. `None` when the scheduler is gone (teardown) or in
2718    /// scheduler-free unit tests — a notifier with no waker simply never wakes,
2719    /// which under the busy loop is redundant anyway (the every-slice pump still
2720    /// services the source). This is the seam every wake source installs its
2721    /// notifier through (R3/R1(vi)).
2722    pub(super) fn ready_waker(&self, pid: u64) -> Option<super::wake::ReadyWaker> {
2723        let scheduler = self.scheduler.upgrade()?;
2724        let ready_pending = self
2725            .records
2726            .lock()
2727            .ok()?
2728            .connections
2729            .get(&pid)
2730            .map(|record| Arc::clone(&record.ready_pending))?;
2731        Some(super::wake::ReadyWaker::new(
2732            &scheduler,
2733            pid,
2734            self.ready_atom,
2735            ready_pending,
2736        ))
2737    }
2738
2739    /// Acknowledges READY edges whose mailbox atoms were drained before this slice.
2740    pub(super) fn acknowledge_ready(&self, pid: u64) {
2741        if let Ok(records) = self.records.lock()
2742            && let Some(record) = records.connections.get(&pid)
2743        {
2744            record.ready_pending.store(false, Ordering::Release);
2745        }
2746    }
2747
2748    /// Reports a READY edge queued while the current process snapshot is executing.
2749    pub(super) fn ready_pending(&self, pid: u64) -> bool {
2750        self.records
2751            .lock()
2752            .ok()
2753            .and_then(|records| {
2754                records
2755                    .connections
2756                    .get(&pid)
2757                    .map(|record| record.ready_pending.load(Ordering::Acquire))
2758            })
2759            .unwrap_or(false)
2760    }
2761
2762    /// FIX A-ii: marks `pid` as executing a slice — not parked, so not yet
2763    /// delivery-quiescent. Reuses the registry lock the slice already takes for
2764    /// `is_registered`; it never touches the barrier condvar.
2765    pub(super) fn mark_running(&self, pid: u64) {
2766        if let Ok(records) = self.records.lock()
2767            && let Some(record) = records.connections.get(&pid)
2768        {
2769            record.parked.store(false, Ordering::Release);
2770        }
2771    }
2772
2773    /// FIX A-ii: marks `pid` as parked with every accepted publish already fanned
2774    /// out to its socket, and — only while the shutdown flush barrier is armed —
2775    /// bumps the settle generation and wakes it. The bump/notify is skipped
2776    /// entirely in normal operation, so a park off the shutdown path is just one
2777    /// flag store.
2778    pub(super) fn mark_parked(&self, pid: u64) {
2779        if let Ok(records) = self.records.lock()
2780            && let Some(record) = records.connections.get(&pid)
2781        {
2782            record.parked.store(true, Ordering::Release);
2783        }
2784        if self.settle_armed.load(Ordering::Acquire) {
2785            self.signal_settle_changed();
2786        }
2787    }
2788
2789    /// Bumps the delivery-quiescence generation under its mutex, then wakes the
2790    /// flush barrier — the same lock-then-notify discipline as
2791    /// [`Self::signal_connection_removed`], so a park published before the notify
2792    /// can never be missed by a waiter holding the mutex across its re-check.
2793    fn signal_settle_changed(&self) {
2794        {
2795            let mut generation = recover_lock(&self.settle_generation);
2796            *generation = generation.wrapping_add(1);
2797        }
2798        self.settle_changed.notify_all();
2799    }
2800
2801    /// Reads the current delivery-quiescence generation under its mutex.
2802    fn settle_generation_snapshot(&self) -> u64 {
2803        *recover_lock(&self.settle_generation)
2804    }
2805
2806    /// True when every tracked connection is parked with no pending READY edge —
2807    /// i.e. every accepted publish has been pumped to its subscriber's outbound
2808    /// and no fan-out wake is still in flight. An empty registry is trivially
2809    /// quiescent.
2810    fn all_connections_delivery_quiesced(&self) -> bool {
2811        let Ok(records) = self.records.lock() else {
2812            return false;
2813        };
2814        records.connections.values().all(|record| {
2815            record.parked.load(Ordering::Acquire) && !record.ready_pending.load(Ordering::Acquire)
2816        })
2817    }
2818
2819    /// FIX A-ii: wakes every tracked connection once so it drains its socket and
2820    /// pumps its subscriptions. This is what makes the flush barrier robust to the
2821    /// readiness gap: a publisher whose fire-and-forget publish bytes have arrived
2822    /// but whose readiness wake has not yet rescheduled it still looks "parked",
2823    /// so without this it could be sampled as quiescent before it admits and fans
2824    /// out those publishes. Firing sets each connection's `ready_pending` edge, so
2825    /// the quiescence check below cannot pass until every woken connection has run
2826    /// its slice (reading and admitting any buffered publish, whose admission then
2827    /// fires its subscribers in turn) and re-parked.
2828    fn wake_all_connections_for_flush(&self) {
2829        let pids: Vec<u64> = self
2830            .records
2831            .lock()
2832            .map(|records| records.connections.keys().copied().collect())
2833            .unwrap_or_default();
2834        for pid in pids {
2835            if let Some(waker) = self.ready_waker(pid) {
2836                waker.fire();
2837            }
2838        }
2839    }
2840
2841    /// FIX A-ii: parks until every tracked connection has fanned out its accepted
2842    /// publishes (delivery quiescence) or `deadline` elapses, returning `true` on
2843    /// quiescence and `false` when the single admitted deadline won. The TOLD
2844    /// shape mirrors [`Self::wait_for_active_connections_drained`]: arm the
2845    /// barrier, then snapshot-before-observe so a park delivered between the
2846    /// observation and the wait bumps a generation the wait detects. It samples
2847    /// nothing on a timer — it wakes only on a delivered park (generation bump) or
2848    /// the one deadline.
2849    pub(super) fn wait_for_delivery_quiesced(&self, deadline: Instant) -> bool {
2850        self.settle_armed.store(true, Ordering::Release);
2851        // Force every connection to run once so a publisher whose buffered publish
2852        // bytes have not yet triggered a readiness wake still drains and admits
2853        // them (and fires its subscribers) before the quiescence check can pass.
2854        self.wake_all_connections_for_flush();
2855        let quiesced = loop {
2856            let snapshot = self.settle_generation_snapshot();
2857            if self.all_connections_delivery_quiesced() {
2858                break true;
2859            }
2860            let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
2861                break false;
2862            };
2863            let (generation, outcome) = self
2864                .settle_changed
2865                .wait_timeout_while(
2866                    recover_lock(&self.settle_generation),
2867                    remaining,
2868                    |current| {
2869                        #[cfg(test)]
2870                        {
2871                            let observer = recover_lock(&self.settle_wait_observer).take();
2872                            if let Some(observer) = observer {
2873                                let _sent = observer.send(());
2874                            }
2875                        }
2876                        *current == snapshot
2877                    },
2878                )
2879                .unwrap_or_else(PoisonError::into_inner);
2880            drop(generation);
2881            // A predicate that became true without its completion signal must
2882            // not turn a missed wake into success when the deadline wakes us.
2883            if outcome.timed_out() {
2884                break false;
2885            }
2886        };
2887        self.settle_armed.store(false, Ordering::Release);
2888        quiesced
2889    }
2890
2891    /// R7: records one serviced slice for `pid`. Bumped at the head of every
2892    /// slice; the park-flip's quiescence assertion reads [`Self::slice_count`].
2893    #[cfg(test)]
2894    pub(super) fn record_slice(&self, pid: u64) {
2895        let count = if let Ok(mut counts) = self.slice_counts.lock() {
2896            let count = counts.entry(pid).or_insert(0);
2897            *count += 1;
2898            *count
2899        } else {
2900            return;
2901        };
2902        if let Ok(mut observers) = self.slice_observers.lock()
2903            && let Some(observer) = observers.remove(&pid)
2904        {
2905            let _ = observer.send(count);
2906        }
2907    }
2908
2909    #[cfg(test)]
2910    fn observe_next_slice(&self, pid: u64) -> Receiver<u64> {
2911        let (sender, receiver) = channel();
2912        if let Ok(mut observers) = self.slice_observers.lock() {
2913            observers.insert(pid, sender);
2914        }
2915        receiver
2916    }
2917
2918    #[cfg(test)]
2919    fn observe_next_park(&self, pid: u64) -> Receiver<u64> {
2920        let (sender, receiver) = channel();
2921        if let Ok(mut observers) = self.park_observers.lock() {
2922            observers.insert(pid, sender);
2923        }
2924        receiver
2925    }
2926
2927    #[cfg(test)]
2928    fn observe_settled_park(&self, pid: u64) -> Receiver<u64> {
2929        let (sender, receiver) = channel();
2930        let Ok(counts) = self.slice_counts.lock() else {
2931            return receiver;
2932        };
2933        let current = counts.get(&pid).copied().unwrap_or(0);
2934        let Ok(parks) = self.park_counts.lock() else {
2935            return receiver;
2936        };
2937        if current > 0 && parks.get(&pid).copied() == Some(current) {
2938            let _ = sender.send(current);
2939        } else if let Ok(mut observers) = self.park_observers.lock() {
2940            observers.insert(pid, sender);
2941        }
2942        drop(parks);
2943        drop(counts);
2944        receiver
2945    }
2946
2947    /// Reports the kinds of the frames `pid` has written to its transport,
2948    /// once its outbound buffer has drained (R3b F8, ruling 2a).
2949    #[cfg(test)]
2950    pub(super) fn record_frames_written(&self, pid: u64, kinds: Vec<liminal::protocol::FrameType>) {
2951        if kinds.is_empty() {
2952            return;
2953        }
2954        let observer = self
2955            .frame_observers
2956            .lock()
2957            .ok()
2958            .and_then(|observers| observers.get(&pid).cloned());
2959        if let Some(observer) = observer {
2960            let _ = observer.send(kinds);
2961        }
2962    }
2963
2964    /// Every later report of the frames `pid` writes to its transport.
2965    #[cfg(test)]
2966    fn observe_frames_written(&self, pid: u64) -> Receiver<Vec<liminal::protocol::FrameType>> {
2967        let (sender, receiver) = channel();
2968        if let Ok(mut observers) = self.frame_observers.lock() {
2969            observers.insert(pid, sender);
2970        }
2971        receiver
2972    }
2973
2974    #[cfg(test)]
2975    pub(super) fn record_park(&self, pid: u64) {
2976        let count = self.slice_count(pid);
2977        if let Ok(mut parks) = self.park_counts.lock() {
2978            parks.insert(pid, count);
2979        }
2980        if let Ok(mut observers) = self.park_observers.lock()
2981            && let Some(observer) = observers.remove(&pid)
2982        {
2983            let _ = observer.send(count);
2984        }
2985    }
2986
2987    #[cfg(test)]
2988    fn queue_next_outbound_capacity(&self, capacity: usize) {
2989        if let Ok(mut capacities) = self.next_outbound_capacities.lock() {
2990            capacities.push_back(capacity);
2991        }
2992    }
2993
2994    #[cfg(test)]
2995    pub(super) fn take_next_outbound_capacity(&self) -> Option<usize> {
2996        self.next_outbound_capacities
2997            .lock()
2998            .ok()
2999            .and_then(|mut capacities| capacities.pop_front())
3000    }
3001
3002    #[cfg(test)]
3003    fn install_participant_holdback_pause(&self, pid: u64) -> Receiver<()> {
3004        let (sender, receiver) = channel();
3005        if let Ok(mut pauses) = self.participant_holdback_pauses.lock() {
3006            pauses.insert(pid, sender);
3007        }
3008        receiver
3009    }
3010
3011    #[cfg(test)]
3012    pub(super) fn pause_participant_holdback(&self, pid: u64) -> bool {
3013        self.participant_holdback_pauses
3014            .lock()
3015            .ok()
3016            .and_then(|mut pauses| pauses.remove(&pid))
3017            .is_some_and(|sender| {
3018                let _ = sender.send(());
3019                true
3020            })
3021    }
3022
3023    /// R7: slices serviced by connection `pid` since spawn (test instrument).
3024    #[cfg(test)]
3025    pub(super) fn slice_count(&self, pid: u64) -> u64 {
3026        self.slice_counts
3027            .lock()
3028            .map_or(0, |counts| counts.get(&pid).copied().unwrap_or(0))
3029    }
3030
3031    #[cfg(test)]
3032    fn install_pre_wait_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
3033        let armed = Arc::new(Barrier::new(2));
3034        let release = Arc::new(Barrier::new(2));
3035        if let Ok(mut slot) = self.pre_wait_barrier.lock() {
3036            *slot = Some(PreWaitBarrier {
3037                armed: Arc::clone(&armed),
3038                release: Arc::clone(&release),
3039            });
3040        }
3041        (armed, release)
3042    }
3043
3044    /// Installs the pid-specific reclamation gate (oracle 26) and returns its
3045    /// `(reached, release, done)` endpoints. The harness rendezvouses `reached`
3046    /// to pin the dead-but-tracked window, `release` to let the reclaim proceed,
3047    /// and `done` to observe the `remove()` funnel completing.
3048    #[cfg(test)]
3049    pub(super) fn install_reclaim_barrier(
3050        &self,
3051        pid: u64,
3052    ) -> (Arc<Barrier>, Arc<Barrier>, Arc<Barrier>) {
3053        let reached = Arc::new(Barrier::new(2));
3054        let release = Arc::new(Barrier::new(2));
3055        let done = Arc::new(Barrier::new(2));
3056        if let Ok(mut slot) = self.reclaim_barrier.lock() {
3057            *slot = Some(ReclaimBarrier {
3058                pid,
3059                reached: Arc::clone(&reached),
3060                release: Arc::clone(&release),
3061                done: Arc::clone(&done),
3062            });
3063        }
3064        (reached, release, done)
3065    }
3066
3067    /// Takes the installed reclamation gate iff it targets `pid` (one-use). Any
3068    /// other pid's delivery is ungated, so unrelated exits reclaim immediately.
3069    #[cfg(test)]
3070    fn stage_reclaim_barrier(&self, pid: u64) -> Option<ReclaimBarrier> {
3071        let mut slot = self.reclaim_barrier.lock().ok()?;
3072        if slot.as_ref().is_some_and(|barrier| barrier.pid == pid) {
3073            slot.take()
3074        } else {
3075            None
3076        }
3077    }
3078
3079    /// Installs the one-use drain-park gate (oracles 18, 19) and returns its
3080    /// `(armed, release)` endpoints. Staged in the drain waiter's observe->park
3081    /// window so a harness can deliver an exit strictly between the completion
3082    /// observation and the park. Entirely `#[cfg(test)]`; changes no production
3083    /// wait semantics.
3084    #[cfg(test)]
3085    pub(super) fn install_drain_park_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
3086        let armed = Arc::new(Barrier::new(2));
3087        let release = Arc::new(Barrier::new(2));
3088        if let Ok(mut slot) = self.drain_park_barrier.lock() {
3089            *slot = Some(PreWaitBarrier {
3090                armed: Arc::clone(&armed),
3091                release: Arc::clone(&release),
3092            });
3093        }
3094        (armed, release)
3095    }
3096
3097    /// Runs the one-use drain-park gate, if installed. One-use so only the staged
3098    /// park rendezvouses; every later park in the same waiter runs ungated.
3099    #[cfg(test)]
3100    fn run_drain_park_barrier(&self) {
3101        let barrier = self
3102            .drain_park_barrier
3103            .lock()
3104            .ok()
3105            .and_then(|mut slot| slot.take());
3106        let Some(barrier) = barrier else {
3107            return;
3108        };
3109        barrier.armed.wait();
3110        barrier.release.wait();
3111    }
3112
3113    /// Test-only count of drain waiter wakes that observed a real removal.
3114    #[cfg(test)]
3115    pub(super) fn drain_exit_wakes(&self) -> u64 {
3116        self.drain_exit_wakes.load(Ordering::SeqCst)
3117    }
3118
3119    /// Test-only count of drain waiter deadline expirations.
3120    #[cfg(test)]
3121    pub(super) fn drain_deadline_hits(&self) -> u64 {
3122        self.drain_deadline_hits.load(Ordering::SeqCst)
3123    }
3124
3125    /// Runs a one-use deterministic test gate after arm. Returns whether the gate
3126    /// was installed so only that staged probe contributes to observability.
3127    #[cfg(test)]
3128    pub(super) fn run_pre_wait_barrier(&self) -> bool {
3129        let barrier = self
3130            .pre_wait_barrier
3131            .lock()
3132            .ok()
3133            .and_then(|mut slot| slot.take());
3134        let Some(barrier) = barrier else {
3135            return false;
3136        };
3137        barrier.armed.wait();
3138        barrier.release.wait();
3139        true
3140    }
3141
3142    /// Installs a one-use barrier in `enqueue_control`'s insert->wake window
3143    /// (S8 staging: lets a test act as the control-drain consumer between the
3144    /// queue insertion and the wake attempt) and returns its test endpoints.
3145    #[cfg(test)]
3146    pub(super) fn install_pre_wake_barrier(&self) -> (Arc<Barrier>, Arc<Barrier>) {
3147        let armed = Arc::new(Barrier::new(2));
3148        let release = Arc::new(Barrier::new(2));
3149        if let Ok(mut slot) = self.pre_wake_barrier.lock() {
3150            *slot = Some(PreWaitBarrier {
3151                armed: Arc::clone(&armed),
3152                release: Arc::clone(&release),
3153            });
3154        }
3155        (armed, release)
3156    }
3157
3158    /// Runs the one-use insert->wake test gate, if installed.
3159    #[cfg(test)]
3160    pub(super) fn run_pre_wake_barrier(&self) {
3161        let barrier = self
3162            .pre_wake_barrier
3163            .lock()
3164            .ok()
3165            .and_then(|mut slot| slot.take());
3166        if let Some(barrier) = barrier {
3167            barrier.armed.wait();
3168            barrier.release.wait();
3169        }
3170    }
3171
3172    #[cfg(test)]
3173    pub(super) fn record_pre_wait_probe_hit(&self) {
3174        self.pre_wait_probe_hits.fetch_add(1, Ordering::AcqRel);
3175    }
3176
3177    #[cfg(test)]
3178    fn pre_wait_probe_hits(&self) -> u64 {
3179        self.pre_wait_probe_hits.load(Ordering::Acquire)
3180    }
3181
3182    #[cfg(test)]
3183    fn observe_process_stream_drop(&self, fd: RawFd) -> Receiver<()> {
3184        let (sender, receiver) = channel();
3185        if let Ok(mut observers) = self.process_stream_drop_observers.lock() {
3186            observers.insert(fd, sender);
3187        }
3188        receiver
3189    }
3190
3191    /// Publishes the process-owned stream's real drop boundary to a waiting test.
3192    #[cfg(test)]
3193    pub(super) fn record_process_stream_drop(&self, fd: RawFd) {
3194        let observer = self
3195            .process_stream_drop_observers
3196            .lock()
3197            .ok()
3198            .and_then(|mut observers| observers.remove(&fd));
3199        if let Some(observer) = observer {
3200            let _ = observer.send(());
3201        }
3202    }
3203
3204    /// Builds a runtime wrapping `services` for unit tests that exercise
3205    /// `apply_frame` without a live scheduler. Uses a fresh interned control atom
3206    /// and no notifier.
3207    #[cfg(test)]
3208    pub(super) fn for_tests(services: Arc<dyn ConnectionServices>) -> Self {
3209        let atoms = AtomTable::with_common_atoms();
3210        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
3211        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
3212        Self::new(
3213            ConnectionRuntimeInstallation {
3214                services: ConnectionServiceInstallation::capture(services),
3215                incarnations: None,
3216                fatal_shutdown: None,
3217            },
3218            control_atom,
3219            ready_atom,
3220            Weak::new(),
3221            None,
3222            (None, None),
3223            LimitsConfig::default(),
3224        )
3225    }
3226
3227    /// Builds a runtime wrapping `services` with explicit `limits` for unit tests
3228    /// that exercise the §5 admission caps without a live scheduler.
3229    #[cfg(test)]
3230    pub(super) fn for_tests_with_limits(
3231        services: Arc<dyn ConnectionServices>,
3232        limits: LimitsConfig,
3233    ) -> Self {
3234        let atoms = AtomTable::with_common_atoms();
3235        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
3236        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
3237        Self::new(
3238            ConnectionRuntimeInstallation {
3239                services: ConnectionServiceInstallation::capture(services),
3240                incarnations: None,
3241                fatal_shutdown: None,
3242            },
3243            control_atom,
3244            ready_atom,
3245            Weak::new(),
3246            None,
3247            (None, None),
3248            limits,
3249        )
3250    }
3251
3252    /// Builds a runtime wrapping `services` with a configured auth `token` for unit
3253    /// tests that exercise the `Connect` handshake enforcement without a live
3254    /// scheduler. Uses a fresh interned control atom and no notifier.
3255    #[cfg(test)]
3256    pub(super) fn for_tests_with_auth_token(
3257        services: Arc<dyn ConnectionServices>,
3258        token: Vec<u8>,
3259    ) -> Self {
3260        let atoms = AtomTable::with_common_atoms();
3261        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
3262        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
3263        Self::new(
3264            ConnectionRuntimeInstallation {
3265                services: ConnectionServiceInstallation::capture(services),
3266                incarnations: None,
3267                fatal_shutdown: None,
3268            },
3269            control_atom,
3270            ready_atom,
3271            Weak::new(),
3272            None,
3273            (Some(token), None),
3274            LimitsConfig::default(),
3275        )
3276    }
3277
3278    /// Builds a runtime wrapping `services` with a `notifier` for unit tests that
3279    /// exercise `apply_frame` and the close path without a live scheduler.
3280    #[cfg(test)]
3281    pub(super) fn for_tests_with_notifier(
3282        services: Arc<dyn ConnectionServices>,
3283        notifier: Arc<dyn ConnectionNotifier>,
3284    ) -> Self {
3285        let atoms = AtomTable::with_common_atoms();
3286        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
3287        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
3288        Self::new(
3289            ConnectionRuntimeInstallation {
3290                services: ConnectionServiceInstallation::capture(services),
3291                incarnations: None,
3292                fatal_shutdown: None,
3293            },
3294            control_atom,
3295            ready_atom,
3296            Weak::new(),
3297            Some(notifier),
3298            (None, None),
3299            LimitsConfig::default(),
3300        )
3301    }
3302
3303    /// Builds a runtime wrapping `services` that verifies registry passes
3304    /// with `verifier`, accepts the bearer `token` when given, and reports to
3305    /// `notifier`, for unit tests of a worker
3306    /// bound to the holder its `Connect` verified (ACCESS-004).
3307    #[cfg(test)]
3308    pub(super) fn for_tests_with_pass(
3309        services: Arc<dyn ConnectionServices>,
3310        notifier: Arc<dyn ConnectionNotifier>,
3311        token: Option<Vec<u8>>,
3312        verifier: PassVerifier,
3313    ) -> Self {
3314        let atoms = AtomTable::with_common_atoms();
3315        let control_atom = atoms.intern(CONNECTION_SHUTDOWN_CONTROL_ATOM);
3316        let ready_atom = atoms.intern(CONNECTION_READY_ATOM);
3317        Self::new(
3318            ConnectionRuntimeInstallation {
3319                services: ConnectionServiceInstallation::capture(services),
3320                incarnations: None,
3321                fatal_shutdown: None,
3322            },
3323            control_atom,
3324            ready_atom,
3325            Weak::new(),
3326            Some(notifier),
3327            (token, Some(verifier)),
3328            LimitsConfig::default(),
3329        )
3330    }
3331
3332    pub(super) fn services(&self) -> &dyn ConnectionServices {
3333        self.services.as_ref()
3334    }
3335
3336    pub(super) fn services_arc(&self) -> Arc<dyn ConnectionServices> {
3337        Arc::clone(&self.services)
3338    }
3339
3340    /// Returns the complete participant service captured at supervisor startup.
3341    pub(super) const fn participant_service(&self) -> Option<&InstalledParticipantService> {
3342        self.participant_service.as_ref()
3343    }
3344
3345    fn participant_service_fatal(&self) -> Result<Option<ParticipantServiceFatal>, ServerError> {
3346        let Some(service) = self.participant_service() else {
3347            return Ok(None);
3348        };
3349        service
3350            .service_fatal()
3351            .map_err(|error| ServerError::ParticipantIncarnation {
3352                phase: "participant fatal latch inspection",
3353                message: error.to_string(),
3354            })
3355    }
3356
3357    fn activate_fatal_shutdown(&self) {
3358        if let Some(shutdown) = self.fatal_shutdown.as_ref() {
3359            shutdown.initiate();
3360        }
3361    }
3362
3363    fn ensure_participant_service_live(&self) -> Result<(), ServerError> {
3364        let Some(fatal) = self.participant_service_fatal()? else {
3365            return Ok(());
3366        };
3367        self.activate_fatal_shutdown();
3368        Err(ServerError::ParticipantServiceFatal { fatal })
3369    }
3370
3371    fn latch_connection_fate_intent_incomplete(
3372        &self,
3373        open_sequence: u64,
3374        conversation_id: u64,
3375    ) -> Result<ParticipantServiceFatal, ServerError> {
3376        let Some(service) = self.participant_service() else {
3377            return Err(ServerError::ParticipantIncarnation {
3378                phase: "connection-fate fatal latch",
3379                message: "a durable Open lacks its installed participant service".to_owned(),
3380            });
3381        };
3382        let fatal = service
3383            .latch_connection_fate_intent_incomplete(open_sequence, conversation_id)
3384            .map_err(|error| ServerError::ParticipantIncarnation {
3385                phase: "connection-fate fatal latch",
3386                message: error.to_string(),
3387            })?;
3388        self.activate_fatal_shutdown();
3389        Ok(fatal)
3390    }
3391
3392    fn complete_connection_fate_fatal(
3393        &self,
3394        open_sequence: u64,
3395        conversations: &[u64],
3396        phase: &'static str,
3397        error: &impl std::fmt::Display,
3398    ) -> ServerError {
3399        tracing::error!(open_sequence, phase, %error, "durable connection-fate intent is incomplete");
3400        let Some(&conversation_id) = conversations.first() else {
3401            return ServerError::ParticipantIncarnation {
3402                phase: "connection-fate fatal target",
3403                message: "a durable Open has no tracked conversation target".to_owned(),
3404            };
3405        };
3406        match self.latch_connection_fate_intent_incomplete(open_sequence, conversation_id) {
3407            Ok(fatal) => ServerError::ParticipantServiceFatal { fatal },
3408            Err(latch_error) => latch_error,
3409        }
3410    }
3411
3412    /// Runs one typed terminal fold after classification and before teardown.
3413    pub(super) fn complete_connection_fate(
3414        &self,
3415        connection_incarnation: Option<ConnectionIncarnation>,
3416        class: ConnectionFateClass,
3417        conversations: &[u64],
3418    ) -> Result<(), ServerError> {
3419        if conversations.is_empty() {
3420            return Ok(());
3421        }
3422        self.ensure_participant_service_live()?;
3423        let (Some(connection_incarnation), Some(_), Some(authority)) = (
3424            connection_incarnation,
3425            self.participant_service(),
3426            self.incarnations.as_ref(),
3427        ) else {
3428            return Err(ServerError::ParticipantIncarnation {
3429                phase: "connection-fate authority composition",
3430                message: "tracked participant conversations lack a complete service/incarnation authority"
3431                    .to_owned(),
3432            });
3433        };
3434        let intent =
3435            authority.open_connection_fate(connection_incarnation, class, conversations)?;
3436        self.fold_or_park_connection_fate(intent)
3437    }
3438
3439    /// Resolves the bound-only protocol-error gate from participant authority.
3440    pub(super) fn connection_has_bound_participant(
3441        &self,
3442        connection_incarnation: Option<ConnectionIncarnation>,
3443        conversations: &[u64],
3444    ) -> Result<bool, ServerError> {
3445        if conversations.is_empty() {
3446            return Ok(false);
3447        }
3448        self.ensure_participant_service_live()?;
3449        let (Some(connection_incarnation), Some(service)) =
3450            (connection_incarnation, self.participant_service())
3451        else {
3452            return Err(ServerError::ParticipantIncarnation {
3453                phase: "bound participant classification",
3454                message:
3455                    "tracked participant conversations lack a complete service/incarnation pair"
3456                        .to_owned(),
3457            });
3458        };
3459        service
3460            .connection_has_bound_participant(connection_incarnation, conversations)
3461            .map_err(|error| ServerError::ParticipantIncarnation {
3462                phase: "bound participant classification",
3463                message: error.to_string(),
3464            })
3465    }
3466
3467    /// Returns the configured connection auth token as opaque bytes, or `None` when
3468    /// no `[auth]` section was configured (open access).
3469    pub(super) fn auth_token(&self) -> Option<&[u8]> {
3470        self.auth_token.as_deref()
3471    }
3472
3473    pub(super) const fn pass_verifier(&self) -> Option<&PassVerifier> {
3474        self.pass_verifier.as_ref()
3475    }
3476
3477    /// The installed Lys admitter with the network id subjects are placed
3478    /// under, or `None` while no deploy has installed one.
3479    pub(super) fn lys_admitter(
3480        &self,
3481    ) -> Option<(&Arc<dyn crate::auth_lys::session::LysAdmitter>, &str)> {
3482        self.lys_admitter
3483            .get()
3484            .map(|(admitter, network)| (admitter, network.as_str()))
3485    }
3486
3487    /// Installs the Lys admitter once; a second install is refused so one
3488    /// deploy cannot swap the authority another relied on.
3489    ///
3490    /// # Errors
3491    /// The admitter already installed.
3492    pub(super) fn install_lys_admitter(
3493        &self,
3494        admitter: Arc<dyn crate::auth_lys::session::LysAdmitter>,
3495        network_id: String,
3496    ) -> Result<(), Arc<dyn crate::auth_lys::session::LysAdmitter>> {
3497        self.lys_admitter
3498            .set((admitter, network_id))
3499            .map_err(|(admitter, _)| admitter)
3500    }
3501
3502    /// This liminal as a machine Lys knows, if `[auth.lys.machine]` named it.
3503    pub(super) fn link_host(&self) -> Option<&Arc<crate::link::LinkHost>> {
3504        self.link_host.get()
3505    }
3506
3507    pub(super) fn service_bindings(
3508        &self,
3509    ) -> Option<&Arc<crate::services::production::ServiceBindings>> {
3510        self.service_bindings.get()
3511    }
3512
3513    /// The services registry, if templates are configured (SRV-028).
3514    pub(super) fn lifecycle(&self) -> Option<&Arc<crate::services::lifecycle::Lifecycle>> {
3515        self.lifecycle.get()
3516    }
3517
3518    /// Installs a services registry on a test runtime, once.
3519    #[cfg(test)]
3520    pub(super) fn install_lifecycle_for_tests(
3521        &self,
3522        lifecycle: Arc<crate::services::lifecycle::Lifecycle>,
3523    ) -> bool {
3524        self.lifecycle.set(lifecycle).is_ok()
3525    }
3526
3527    pub(super) fn install_service_bindings(
3528        &self,
3529        bindings: Arc<crate::services::production::ServiceBindings>,
3530    ) -> Result<(), Arc<crate::services::production::ServiceBindings>> {
3531        self.service_bindings.set(bindings)
3532    }
3533
3534    /// The channel membership gate, if `[auth.lys.membership]` configured
3535    /// one (ACCESS-006 R1).
3536    pub(super) fn membership_gate(&self) -> Option<&Arc<crate::auth_membership::MembershipGate>> {
3537        self.membership_gate.get()
3538    }
3539
3540    /// Installs the membership gate once; a second install is refused so
3541    /// one deploy cannot swap the gate live bindings rely on.
3542    ///
3543    /// # Errors
3544    /// The gate already installed.
3545    pub(super) fn install_membership_gate(
3546        &self,
3547        gate: Arc<crate::auth_membership::MembershipGate>,
3548    ) -> Result<(), Arc<crate::auth_membership::MembershipGate>> {
3549        self.membership_gate.set(gate)
3550    }
3551
3552    /// Installs the link host once; a second install is refused.
3553    ///
3554    /// # Errors
3555    /// The host already installed.
3556    pub(super) fn install_link_host(
3557        &self,
3558        host: Arc<crate::link::LinkHost>,
3559    ) -> Result<(), Arc<crate::link::LinkHost>> {
3560        self.link_host.set(host)
3561    }
3562
3563    /// The grant-revocation authority, when a revocation policy is
3564    /// installed (ACCESS-005).
3565    pub(super) fn revocation(&self) -> Option<&Arc<crate::auth_revocation::RevocationAuthority>> {
3566        self.revocation.get()
3567    }
3568
3569    /// Installs the grant-revocation authority once; a second install is
3570    /// refused.
3571    ///
3572    /// # Errors
3573    /// The authority already installed.
3574    pub(super) fn install_revocation(
3575        &self,
3576        authority: Arc<crate::auth_revocation::RevocationAuthority>,
3577    ) -> Result<(), Arc<crate::auth_revocation::RevocationAuthority>> {
3578        self.revocation.set(authority)
3579    }
3580
3581    /// Returns the configured connection-keyed notifier, if any.
3582    pub(super) fn notifier(&self) -> Option<&Arc<dyn ConnectionNotifier>> {
3583        self.notifier.as_ref()
3584    }
3585
3586    /// Offers a channel publish to the notifier's observability-drain tap, returning
3587    /// `true` when the application consumed it (so the connection process skips the
3588    /// normal fan-out). `false` when no notifier is installed (liminal standalone) or
3589    /// the notifier did not recognise the channel, so the caller can invoke it
3590    /// unconditionally and fall through to the normal publish path.
3591    pub(super) fn notifier_channel_publish(&self, pid: u64, channel: &str, payload: &[u8]) -> bool {
3592        self.notifier
3593            .as_ref()
3594            .is_some_and(|notifier| notifier.on_channel_publish(pid, channel, payload))
3595    }
3596
3597    /// SRV-012 R7: connection `pid`'s pass names `participant`, so the
3598    /// topology shows it on that connection from now on, in lowercase hex.
3599    pub(super) fn pass_attached(&self, pid: u64, mount: MountKind, participant: &[u8]) {
3600        const HEX: &[u8; 16] = b"0123456789abcdef";
3601        if let Some(signals) = self.signals.get() {
3602            let mut text = String::with_capacity(participant.len() * 2);
3603            for byte in participant {
3604                text.push(char::from(HEX[usize::from(byte >> 4)]));
3605                text.push(char::from(HEX[usize::from(byte & 0x0f)]));
3606            }
3607            signals.topology(crate::console::TopologyChange::ConnectionOpened {
3608                id: pid,
3609                mount: mount.into(),
3610                participant: Some(text),
3611            });
3612        }
3613    }
3614
3615    /// SRV-012 `to_stations`: the subscription whose library subscriber is
3616    /// `subscriber` belongs to connection `pid`, so a publish it receives
3617    /// names `pid`'s station.
3618    pub(super) fn receiver(&self, pid: u64, subscriber: u64) {
3619        if let Some(signals) = self.signals.get() {
3620            signals.receiver(subscriber, pid);
3621        }
3622    }
3623
3624    /// SRV-012 R7: connection `pid` starts (`present`) or stops using `line`
3625    /// one way; a worker's flows are its line's stops.
3626    pub(super) fn flow(&self, pid: u64, line: &str, role: crate::console::FlowRole, present: bool) {
3627        if let Some(signals) = self.signals.get() {
3628            signals.topology(crate::console::TopologyChange::Flow {
3629                connection: pid,
3630                line: line.to_owned(),
3631                role,
3632                present,
3633            });
3634        }
3635    }
3636
3637    /// Stores `registration` on the connection record for `pid`, so the close
3638    /// path can later fire `on_worker_unregistered` for exactly the connections
3639    /// that registered. A missing record (the connection already closed) is a
3640    /// no-op.
3641    ///
3642    /// # Errors
3643    /// Returns [`ServerError`] when the connection registry mutex is poisoned.
3644    pub(super) fn set_registration(
3645        &self,
3646        pid: u64,
3647        registration: WorkerRegistration,
3648    ) -> Result<(), ServerError> {
3649        let identity = registration.identity.clone();
3650        let subjects = vec![registration.task_queue.clone()];
3651        let activities = registration
3652            .activities
3653            .iter()
3654            .map(|activity| crate::console::StationActivity {
3655                name: activity.name.clone(),
3656                input_schema_json: activity.input_schema_json.clone(),
3657                output_schema_json: activity.output_schema_json.clone(),
3658            })
3659            .collect();
3660        let mut records = lock(&self.records, "connection registry")?;
3661        let Registry {
3662            connections,
3663            directory,
3664        } = &mut *records;
3665        let stored = match connections.get_mut(&pid) {
3666            Some(record) => {
3667                // SRV-027 R1: the directory follows the record under the one
3668                // lock; a replacement registration moves the subject.
3669                if let Some(earlier) = record.registration.as_ref() {
3670                    directory.forget(pid, &earlier.task_queue);
3671                }
3672                directory.register(pid, &registration.task_queue);
3673                record.registration = Some(registration);
3674                true
3675            }
3676            None => false,
3677        };
3678        drop(records);
3679        // SRV-012 R7: a registered worker is a station on the topology, by
3680        // its identity, until its connection closes; SRV-027 R3: with its
3681        // subjects and activities.
3682        if stored && let Some(signals) = self.signals.get() {
3683            signals.topology(crate::console::TopologyChange::Worker {
3684                connection: pid,
3685                identity,
3686                subjects,
3687                activities,
3688            });
3689        }
3690        Ok(())
3691    }
3692
3693    /// Stores the registry principal a successful pass-stamped `Connect`
3694    /// verified on the connection record for `pid`, then fires
3695    /// `on_pass_attached` — so the close path can later fire the matching
3696    /// `on_pass_detached` for exactly the connections that attached, with the
3697    /// same principal. The store precedes the fire and the registry lock is
3698    /// released before it, so the notifier may call back into the supervisor.
3699    /// A missing record (the connection already closed) stores nothing and
3700    /// fires nothing: an attach with no record could never be paired with a
3701    /// detach.
3702    ///
3703    /// # Errors
3704    /// Returns [`ServerError`] when the connection registry mutex is poisoned.
3705    pub(super) fn attach_pass_principal(
3706        &self,
3707        pid: u64,
3708        principal: &PassPrincipal,
3709    ) -> Result<(), ServerError> {
3710        let stored = lock(&self.records, "connection registry")?
3711            .connections
3712            .get_mut(&pid)
3713            .is_some_and(|record| {
3714                record.pass_principal = Some(principal.clone());
3715                true
3716            });
3717        if stored {
3718            if let Some(notifier) = self.notifier.as_ref() {
3719                notifier.on_pass_attached(pid, principal);
3720            }
3721        }
3722        Ok(())
3723    }
3724
3725    /// Allocates the next monotonic push correlation id.
3726    fn next_push_correlation_id(&self) -> u64 {
3727        self.next_push_id.fetch_add(1, Ordering::Relaxed)
3728    }
3729
3730    /// Registers a one-shot reply slot for `correlation_id`, owned by connection
3731    /// `pid`, and returns its receiver. `deadline` is the slot's optional absolute
3732    /// reply expiry (`None` = the default no-deadline shape). The connection
3733    /// process resolves the slot via [`resolve_push`]; the close path drops the
3734    /// connection's outstanding slots via [`cancel_pushes_for_connection`]; an
3735    /// explicit deadline resolves it via [`expire_push_if_due`].
3736    ///
3737    /// # Errors
3738    /// Returns [`ServerError`] when the correlation registry mutex is poisoned.
3739    fn register_push(
3740        &self,
3741        pid: u64,
3742        correlation_id: u64,
3743        deadline: Option<Instant>,
3744        subject: Option<String>,
3745        wake: Option<super::wake::ReadyWaker>,
3746    ) -> Result<Receiver<PushSlotResolution>, ServerError> {
3747        let (sender, receiver) = channel();
3748        let limit = self.limits.max_pending_pushes_per_connection;
3749        {
3750            let mut slots = lock(&self.push_replies, "push correlation registry")?;
3751            // §5 `max_pending_pushes_per_connection`: refuse a new in-flight push
3752            // once this connection already holds the cap. Counted per owning pid so
3753            // one connection cannot exhaust the shared registry; slots free on
3754            // reply, deadline expiry, or connection close. The count-and-insert
3755            // stays under the one lock so the cap is enforced atomically.
3756            let outstanding = slots.values().filter(|pending| pending.pid == pid).count();
3757            if outstanding >= limit {
3758                return Err(ServerError::ConnectionCapReached {
3759                    operation: "server push".to_owned(),
3760                    cap: "max_pending_pushes_per_connection",
3761                    limit,
3762                });
3763            }
3764            slots.insert(
3765                correlation_id,
3766                PendingPush {
3767                    pid,
3768                    subject,
3769                    sender,
3770                    deadline,
3771                    wake,
3772                },
3773            );
3774        }
3775        Ok(receiver)
3776    }
3777
3778    /// Registers a confirmed, no-deadline push slot for `pid` without a
3779    /// supervisor, so a unit fixture can answer it with a `PushReply` frame
3780    /// through `apply_frame` and read the outcome on the awaiter. The awaiter's
3781    /// runtime handle is empty: the no-deadline receive path never upgrades it.
3782    #[cfg(test)]
3783    pub(super) fn push_for_tests(
3784        &self,
3785        pid: u64,
3786        subject: Option<&str>,
3787    ) -> Result<PushReplyAwaiter, ServerError> {
3788        let correlation_id = self.next_push_correlation_id();
3789        let receiver =
3790            self.register_push(pid, correlation_id, None, subject.map(str::to_owned), None)?;
3791        if !self.confirm_push_registration(pid, correlation_id) {
3792            return Err(ServerError::ListenerAccept {
3793                message: format!("connection process {pid} is not registered"),
3794            });
3795        }
3796        Ok(PushReplyAwaiter {
3797            correlation_id,
3798            receiver,
3799            deadline: None,
3800            runtime: Weak::new(),
3801        })
3802    }
3803
3804    /// The subject retained by `pid`'s reply slot for `correlation_id`, read
3805    /// before the reply is decided. Read and settle are two lock takes on
3806    /// purpose (never nested with a record lock): a slot swept between them is
3807    /// named by the settle as `UnknownCorrelation`, which is the truth then.
3808    pub(super) fn push_binding(&self, pid: u64, correlation_id: u64) -> PushBinding {
3809        let slots = recover_lock(&self.push_replies);
3810        match slots.get(&correlation_id) {
3811            Some(pending) if pending.pid == pid => pending
3812                .subject
3813                .clone()
3814                .map_or(PushBinding::Subjectless, PushBinding::Subject),
3815            Some(_) | None => PushBinding::Unbound,
3816        }
3817    }
3818
3819    /// Host-side, lazy evaluation of a push's reply deadline, called from an
3820    /// elapsed [`PushReplyAwaiter::receive`] quantum. This NEVER wakes the
3821    /// connection process and runs no timer — it inspects supervisor-owned state
3822    /// under the registry lock only.
3823    ///
3824    /// A slot with an explicit deadline that has passed is removed here (dropping
3825    /// its `Sender` and releasing its §5 `max_pending_pushes_per_connection` cap
3826    /// admission, since the cap is the per-pid slot count) and reported
3827    /// [`PushSlotDisposition::Expired`]. A slot with no deadline, or a deadline
3828    /// still in the future, is left UNTOUCHED and reported
3829    /// [`PushSlotDisposition::Live`] — the elapsed quantum is a benign re-arm. A
3830    /// missing slot is [`PushSlotDisposition::Absent`].
3831    fn expire_push_if_due(&self, correlation_id: u64) -> PushSlotDisposition {
3832        // S4: a poisoned registry must NOT read as slot absence — the slot (and
3833        // its cap admission) may still be in the map. Reclamation recovers the
3834        // guard: removal-only operations are sound on a recovered map (a panic
3835        // in another critical section cannot leave the HashMap itself in a
3836        // partial state; only our bookkeeping invariants could be stale, and
3837        // removal restores them). Admission (`register_push`) stays fail-closed.
3838        let mut slots = recover_lock(&self.push_replies);
3839        let Some(pending) = slots.get(&correlation_id) else {
3840            return PushSlotDisposition::Absent;
3841        };
3842        // Copy the deadline out so the immutable borrow of `slots` ends before the
3843        // conditional `remove` below takes a mutable one.
3844        let deadline = pending.deadline;
3845        match deadline {
3846            Some(at) if Instant::now() >= at => {
3847                slots.remove(&correlation_id);
3848                PushSlotDisposition::Expired
3849            }
3850            _ => PushSlotDisposition::Live,
3851        }
3852    }
3853
3854    /// Drops a registered reply slot without resolving it, used on the
3855    /// push-enqueue failure path (the control could not be delivered to a
3856    /// now-gone process, so the just-reserved slot is unreachable). Dropping the
3857    /// slot's `Sender` wakes a still-waiting awaiter with a disconnected error.
3858    ///
3859    /// Returns whether THIS call removed the slot. Removal under the registry
3860    /// mutex is the atomic resolved-vs-cancelled transition: `false` means
3861    /// another path won — [`resolve_push`](Self::resolve_push) already sent the
3862    /// reply (its send happens under the same lock, so the payload is already in
3863    /// the channel when this returns), or the connection's close path dropped
3864    /// the slot (sender gone, channel disconnected).
3865    /// Records one server `Push` frame queued for a connection's socket.
3866    pub(super) fn record_push_queued(&self) {
3867        self.pushes_queued.fetch_add(1, Ordering::Relaxed);
3868    }
3869
3870    /// Server `Push` frames queued for sockets so far.
3871    pub(super) fn pushes_queued(&self) -> u64 {
3872        self.pushes_queued.load(Ordering::Relaxed)
3873    }
3874
3875    pub(super) fn cancel_push(&self, correlation_id: u64) -> bool {
3876        // S4: reclamation recovers a poisoned guard — a rollback that silently
3877        // skipped its removal would strand the slot and its cap admission.
3878        recover_lock(&self.push_replies)
3879            .remove(&correlation_id)
3880            .is_some()
3881    }
3882
3883    /// Settles the reply slot for a push whose frame could not be enqueued,
3884    /// applying the one ruling that separates a certain failure from a
3885    /// recoverable one.
3886    ///
3887    /// A frame larger than the whole outbound buffer can never be carried on
3888    /// this connection at this bound, so its slot resolves to the named terminal
3889    /// [`ServerError::PushFrameExceedsOutboundCapacity`]. EVERYTHING ELSE —
3890    /// congestion behind traffic still draining, an encode failure, a socket
3891    /// write error — cancels the slot exactly as before, and its dropped
3892    /// `Sender` keeps the retryable [`ServerError::PushReplyDisconnected`]
3893    /// reading, because retrying is the correct answer to all three.
3894    ///
3895    /// This is the sole branch between the two, deliberately: a caller that
3896    /// widened it would turn a busy moment into a permanent failure, which is a
3897    /// worse defect than the one the named outcome exists to fix.
3898    pub(super) fn settle_push_enqueue_failure(
3899        &self,
3900        pid: u64,
3901        correlation_id: u64,
3902        error: &OutboundError,
3903    ) {
3904        if let Some(too_large) = error.frame_exceeds_capacity() {
3905            tracing::warn!(
3906                connection_pid = pid,
3907                correlation_id,
3908                needed = too_large.needed,
3909                capacity = too_large.capacity,
3910                queued = too_large.queued,
3911                "server push frame exceeds the connection's whole outbound capacity; \
3912                 it can never be sent on this connection and its reply slot is failed by name"
3913            );
3914            self.fail_push_outbound_frame_too_large(
3915                correlation_id,
3916                too_large.needed,
3917                too_large.capacity,
3918                too_large.queued,
3919            );
3920            return;
3921        }
3922        tracing::warn!(
3923            connection_pid = pid,
3924            correlation_id,
3925            %error,
3926            "server push could not be enqueued; the push reply slot is cancelled"
3927        );
3928        self.cancel_push(correlation_id);
3929    }
3930
3931    /// Resolves the reply slot for `correlation_id` with the ONE named terminal
3932    /// outcome the outbound path can prove: the push's encoded frame is larger
3933    /// than the whole outbound buffer, so it can never be carried on this
3934    /// connection at this bound and no retry could change that.
3935    ///
3936    /// Callers must have established `needed > capacity` first —
3937    /// [`settle_push_enqueue_failure`](Self::settle_push_enqueue_failure) is the
3938    /// only one, and it does. A frame that merely does not fit the CURRENT free
3939    /// space is congestion and belongs on [`cancel_push`](Self::cancel_push),
3940    /// whose dropped `Sender` keeps its retryable disconnect reading.
3941    ///
3942    /// A slot that is already gone — answered, expired, or swept by connection
3943    /// close — is a harmless no-op, exactly as a late reply is to
3944    /// [`resolve_push`](Self::resolve_push): the first path to remove it under
3945    /// the registry lock wins and the outcome it chose stands.
3946    ///
3947    /// Removal and delivery stay under the ONE registry lock for the same reason
3948    /// `resolve_push` does it — an awaiter that observes the slot gone is then
3949    /// guaranteed to find the outcome already in the channel, instead of seeing
3950    /// the removal, finding the channel empty, and reporting something else.
3951    fn fail_push_outbound_frame_too_large(
3952        &self,
3953        correlation_id: u64,
3954        needed: usize,
3955        capacity: usize,
3956        queued: usize,
3957    ) {
3958        // S4: as `cancel_push` — a skipped removal here would strand the slot and
3959        // its cap admission, and lose the only report the caller will ever get.
3960        let mut slots = recover_lock(&self.push_replies);
3961        if let Some(pending) = slots.remove(&correlation_id) {
3962            pending
3963                .sender
3964                .send(PushSlotResolution::OutboundFrameTooLarge {
3965                    needed,
3966                    capacity,
3967                    queued,
3968                })
3969                .ok();
3970        }
3971    }
3972
3973    /// Drops every reply slot owned by connection `pid`, waking each awaiter with a
3974    /// disconnected error (the dropped `Sender` disconnects the awaiter's
3975    /// `Receiver`). Called from the close path so a connection that exits with
3976    /// in-flight pushes signals worker death immediately instead of leaving each
3977    /// awaiter to block the full push-reply timeout. A slot that [`resolve_push`]
3978    /// already removed is gone, so it is untouched here; an unknown pid is a no-op.
3979    fn cancel_pushes_for_connection(&self, pid: u64) {
3980        // S4: the close sweep is the reclamation of last resort ("connection
3981        // close at the latest") — it must complete on a poisoned map too.
3982        // SRV-027 R2: a requester waiting on a routed push to this connection
3983        // is woken now, so its next slice names the worker gone instead of
3984        // waiting out the reply deadline. The senders drop with the slots, so
3985        // each requester reads the disconnected outcome.
3986        let woken = {
3987            let mut slots = recover_lock(&self.push_replies);
3988            let mut woken = Vec::new();
3989            slots.retain(|_correlation_id, pending| {
3990                if pending.pid == pid {
3991                    woken.extend(pending.wake.take());
3992                    false
3993                } else {
3994                    true
3995                }
3996            });
3997            woken
3998        };
3999        for wake in woken {
4000            wake.fire();
4001        }
4002    }
4003
4004    /// S3 second half (shape (b), check-after-insert): pre-publication
4005    /// confirmation that the connection record for `pid` still exists, run in
4006    /// the INSERT -> CONFIRM -> PUBLISH order (S7 — confirming after the
4007    /// enqueue let a close-swept-then-answered push report `Err` for a Push the
4008    /// client had received). `true` leaves the slot in place and the caller may
4009    /// publish; `false` means a concurrent close already removed the record —
4010    /// this call then removes the caller's own just-inserted slot (rolling back
4011    /// its cap admission) so nothing is stranded, and the caller returns
4012    /// WITHOUT publishing: an `Err` from the push methods guarantees no `Push`
4013    /// control was published.
4014    ///
4015    /// Why exactly one side always observes the slot: `remove` (the single
4016    /// record-removal path) removes the host record BEFORE sweeping the pid's
4017    /// push slots, and this check reads the record AFTER inserting the slot and
4018    /// BEFORE the control is published. Both records accesses are serialized by
4019    /// the `records` mutex, so either (i) this read precedes the record removal
4020    /// — then the slot insert precedes the sweep (insert < read < removal <
4021    /// sweep in the happens-before order) and the SWEEP observes and removes
4022    /// the slot: if the control was published in the meantime the awaiter reads
4023    /// the truthful disconnected outcome and a late client reply is a harmless
4024    /// no-op; or (ii) this read follows the record removal — then THIS call
4025    /// observes the absence, rolls the slot back itself, and nothing was
4026    /// published. When both observe (a sweep and a rollback can both run in
4027    /// case (ii) if the insert also preceded the sweep), removal is idempotent
4028    /// and the cap is derived from map membership, so nothing double-releases.
4029    ///
4030    /// Lock discipline: `records` and `push_replies` are NEVER held together —
4031    /// here (`records` read, released, then `push_replies` on rollback), in
4032    /// `remove` (`records` removal, released, then the sweep), and everywhere
4033    /// else in this file the two mutexes are taken strictly sequentially, so no
4034    /// lock-order inversion is possible. This adds ZERO work to the connection
4035    /// slice path: the re-check runs on the push caller's thread only.
4036    pub(super) fn confirm_push_registration(&self, pid: u64, correlation_id: u64) -> bool {
4037        if self.is_registered(pid) {
4038            return true;
4039        }
4040        self.cancel_push(correlation_id);
4041        false
4042    }
4043
4044    /// Number of reserved push reply slots outstanding. A benign wait-quantum
4045    /// timeout must NOT change this (the slot survives); an explicit-deadline
4046    /// expiry, a consumed reply, and connection close each release exactly one.
4047    #[cfg(test)]
4048    pub(super) fn pending_push_count(&self) -> usize {
4049        recover_lock(&self.push_replies).len()
4050    }
4051
4052    /// Reserved push reply slots owned by connection `pid` — the exact quantity
4053    /// the §5 `max_pending_pushes_per_connection` cap counts (test instrument).
4054    #[cfg(test)]
4055    pub(super) fn pending_push_count_for(&self, pid: u64) -> usize {
4056        recover_lock(&self.push_replies)
4057            .values()
4058            .filter(|pending| pending.pid == pid)
4059            .count()
4060    }
4061
4062    /// Resolves the reply slot for `correlation_id` with the client's reply
4063    /// payload, waking the [`PushReplyAwaiter`]. Called by the connection process
4064    /// when a correlated `PushReply` frame arrives. A different connection is
4065    /// refused without consuming the slot. A missing slot — already
4066    /// resolved, expired at its explicit deadline, dropped by connection close, or
4067    /// an unknown id — is a harmless no-op: a late `PushReply` for a slot that is
4068    /// gone is discarded here, never delivered and never a panic or desync.
4069    pub(super) fn resolve_push(
4070        &self,
4071        pid: u64,
4072        correlation_id: u64,
4073        payload: Vec<u8>,
4074    ) -> PushReplyDisposition {
4075        self.settle_push(pid, correlation_id, PushSlotResolution::Reply(payload))
4076    }
4077
4078    /// Settles the reply slot for `correlation_id` with the access refusal the
4079    /// connection process decided for its `PushReply`: the slot is removed
4080    /// exactly as for a delivered reply and the awaiter reads
4081    /// [`ServerError::PushReplyRefused`] carrying `reason`. Same ownership and
4082    /// missing-slot rules as [`resolve_push`](Self::resolve_push).
4083    pub(super) fn refuse_push(
4084        &self,
4085        pid: u64,
4086        correlation_id: u64,
4087        reason: String,
4088    ) -> PushReplyDisposition {
4089        self.settle_push(
4090            pid,
4091            correlation_id,
4092            PushSlotResolution::ReplyRefused { reason },
4093        )
4094    }
4095
4096    /// The one removal-and-send for a reply slot, shared by the delivered reply
4097    /// and the refused one so neither can grow its own slot lifecycle.
4098    fn settle_push(
4099        &self,
4100        pid: u64,
4101        correlation_id: u64,
4102        resolution: PushSlotResolution,
4103    ) -> PushReplyDisposition {
4104        // Recover the guard so a poisoned registry still delivers and reclaims replies.
4105        let mut slots = recover_lock(&self.push_replies);
4106        let Entry::Occupied(entry) = slots.entry(correlation_id) else {
4107            return PushReplyDisposition::UnknownCorrelation;
4108        };
4109        // The connection identity is checked before the slot or its credit is released.
4110        if entry.get().pid != pid {
4111            return PushReplyDisposition::SenderMismatch;
4112        }
4113        let pending = entry.remove();
4114        // Keep removal and send atomic to awaiters checking whether the slot still exists.
4115        // The unbounded send never blocks; an abandoned receiver is a named discard.
4116        let disposition = match pending.sender.send(resolution) {
4117            Ok(()) => PushReplyDisposition::Resolved,
4118            Err(_) => PushReplyDisposition::ReceiverDropped,
4119        };
4120        drop(slots);
4121        // SRV-027 R2: a routed push tells its requester, once, after the
4122        // resolution is in the channel; the requester's slice reads it.
4123        if let Some(wake) = pending.wake {
4124            wake.fire();
4125        }
4126        disposition
4127    }
4128
4129    pub(super) const fn control_atom(&self) -> Atom {
4130        self.control_atom
4131    }
4132
4133    /// Sole registration path for a connection: the spawn thread inserts the
4134    /// record synchronously, before `spawn_connection` returns the handle, so
4135    /// `is_tracked`/`active_connection_count` reflect the connection
4136    /// immediately. The connection handler never writes the registry (it only
4137    /// reads via `mark_crashed`/`finish`), so there is a single writer here and
4138    /// no register/ensure-register race.
4139    ///
4140    /// Ordering note: `spawn_native` only enqueues the process, so its first
4141    /// slice may run on another worker thread before this insert lands. If that
4142    /// first slice exits immediately (e.g. a missing-stream crash) its
4143    /// `mark_crashed`/`finish` removes nothing and this insert then leaves a
4144    /// record for an already-dead pid. W4 leg 1 retires the per-accept
4145    /// `reap_crashed` scan that used to self-heal that orphan continuously;
4146    /// instead [`Self::reconcile_register_orphan`] closes the race with a SINGLE
4147    /// point check on the registration event itself — never a loop.
4148    /// `fd_guard` is `None` for a transport that owns no descriptor. The guard
4149    /// exists to keep an fd alive until readiness deregistration has been
4150    /// acknowledged; a loopback connection registers no readiness and holds no
4151    /// descriptor, so there is nothing to guard and the slot is honestly empty
4152    /// rather than filled with a placeholder.
4153    fn register_connection(
4154        &self,
4155        pid: u64,
4156        peer_addr: Option<SocketAddr>,
4157        connection_incarnation: Option<ConnectionIncarnation>,
4158        mount: MountKind,
4159        fd_guard: Option<TcpStream>,
4160    ) -> Result<(), ServerError> {
4161        self.register_record(pid, peer_addr, connection_incarnation, mount, fd_guard)?;
4162        self.reconcile_register_orphan(pid);
4163        Ok(())
4164    }
4165
4166    /// Closes the register-orphan race (see [`Self::register_with_fd`]) with one
4167    /// point check driven by the registration event — not a periodic scan. If
4168    /// the just-registered pid is already absent from the scheduler process
4169    /// table, its first slice has run and exited, so the record this
4170    /// registration inserted is an orphan the retiring reap scan used to sweep;
4171    /// reclaim it immediately through the ordinary `remove()` funnel. A pid still
4172    /// present is live and needs nothing here: a later external termination rides
4173    /// the exit-event reactor and an ordinary exit its own final slice.
4174    fn reconcile_register_orphan(&self, pid: u64) {
4175        let Some(scheduler) = self.scheduler.upgrade() else {
4176            return;
4177        };
4178        // The process-table lookup returns a sharded guard; bind only the
4179        // presence bool so the guard is released before `reclaim_terminated`
4180        // takes the connection registry lock (no cross-lock hold).
4181        let already_exited = scheduler.process_table().get(pid).is_none();
4182        if !already_exited {
4183            return;
4184        }
4185        let reason = scheduler
4186            .peek_exit_reason(pid)
4187            .unwrap_or(ExitReason::Normal);
4188        self.reclaim_terminated(pid, reason);
4189    }
4190
4191    /// TOLD reclamation of a connection whose process exited WITHOUT running a
4192    /// final handler slice — external/panic termination, where
4193    /// [`ConnectionProcess::Drop`] runs but no `mark_crashed`/`finish` does, and
4194    /// the register-orphan race above. Delivered the instant beamr publishes the
4195    /// process's [`ExitEvent`] (via [`run_reclaim_reactor`]) or at the
4196    /// registration point check, and routed through the SAME [`Self::remove`]
4197    /// funnel as every other teardown — no third funnel, no periodic scan.
4198    /// Idempotent: a record already removed in-slice, by the orphan reconcile, or
4199    /// by a duplicate delivery is a no-op here (remove returns `None`), so the §5
4200    /// admission gauge is released exactly once.
4201    fn reclaim_terminated(&self, pid: u64, reason: ExitReason) {
4202        let Some(record) = self.remove(pid) else {
4203            return;
4204        };
4205        self.fire_close_hooks(pid, &record);
4206        tracing::warn!(
4207            connection_pid = pid,
4208            peer_addr = ?record.peer_addr,
4209            mount = ?record.mount,
4210            reason = ?reason,
4211            "connection process exited without a final slice; host record reclaimed by delivery"
4212        );
4213    }
4214
4215    /// One exit-event delivery: drain beamr's retained outcome (sole drainer,
4216    /// bounding its store) then reclaim through [`Self::reclaim_terminated`]. The
4217    /// only non-production element is the `#[cfg(test)]` reclamation gate, which
4218    /// is staged for at most one targeted pid and compiled out entirely in
4219    /// production — the delivery semantics are identical with or without it.
4220    fn deliver_reclamation(&self, scheduler: &Weak<Scheduler>, pid: u64, reason: ExitReason) {
4221        if let Some(scheduler) = scheduler.upgrade() {
4222            // The reason is already in-hand from the event; the drained outcome
4223            // is discarded, its purpose being only to bound beamr's store.
4224            drop(scheduler.take_exit_outcome(pid));
4225        }
4226        #[cfg(test)]
4227        let staged = self.stage_reclaim_barrier(pid);
4228        #[cfg(test)]
4229        if let Some(barrier) = staged.as_ref() {
4230            // Rendezvous: the pid is now dead but its record is still tracked —
4231            // the S8 reclamation window (oracle 26). Then wait for the harness to
4232            // release the reclaim.
4233            barrier.reached.wait();
4234            barrier.release.wait();
4235        }
4236        self.reclaim_terminated(pid, reason);
4237        #[cfg(test)]
4238        if let Some(barrier) = staged.as_ref() {
4239            // Signal the funnel completed so the harness can observe the record
4240            // gone without sampling.
4241            barrier.done.wait();
4242        }
4243    }
4244
4245    #[cfg(test)]
4246    fn register(&self, pid: u64, peer_addr: Option<SocketAddr>) -> Result<(), ServerError> {
4247        self.register_record(pid, peer_addr, None, MountKind::Tcp, None)
4248    }
4249
4250    /// Gives connection `pid` a record, as a spawned connection has, so a
4251    /// unit test through `apply_frame` can see what is stored on it.
4252    #[cfg(test)]
4253    pub(super) fn register_for_tests(&self, pid: u64) -> Result<(), ServerError> {
4254        self.register(pid, None)
4255    }
4256
4257    /// The worker registration stored on connection `pid`'s record. A
4258    /// registration is stored before its topology change is emitted, and
4259    /// only then (`set_registration`), so `None` also means no
4260    /// `TopologyChange::Worker` was emitted for it.
4261    #[cfg(test)]
4262    pub(super) fn stored_registration_for_tests(
4263        &self,
4264        pid: u64,
4265    ) -> Result<Option<WorkerRegistration>, ServerError> {
4266        Ok(lock(&self.records, "connection registry")?
4267            .connections
4268            .get(&pid)
4269            .and_then(|record| record.registration.clone()))
4270    }
4271
4272    fn register_record(
4273        &self,
4274        pid: u64,
4275        peer_addr: Option<SocketAddr>,
4276        connection_incarnation: Option<ConnectionIncarnation>,
4277        mount: MountKind,
4278        fd_guard: Option<TcpStream>,
4279    ) -> Result<(), ServerError> {
4280        match lock(&self.records, "connection registry")?
4281            .connections
4282            .entry(pid)
4283        {
4284            // ENFORCED (not comment-only): pids are fresh per spawn and a record
4285            // is reclaimed through the single `remove` funnel before its pid can
4286            // recycle, so an occupied slot here is a supervision defect. Refuse
4287            // the whole registration rather than silently replacing — a replace
4288            // would drop the displaced record's `fd_guard` outside the teardown
4289            // funnel (orphaning a live connection's stream) and increment the
4290            // `liminal_connections_active` gauge a second time with no paired
4291            // decrement. The prior record stays intact; both spawn paths roll the
4292            // fresh process back on this error. Same idiom as
4293            // `StreamTable::insert` (entry-vacant → typed error) and the
4294            // fail-closed duplicate-conversation refusal in `apply.rs`.
4295            Entry::Occupied(_) => return Err(ServerError::ConnectionPidCollision { pid }),
4296            Entry::Vacant(entry) => {
4297                entry.insert(ConnectionRecord {
4298                    peer_addr,
4299                    mount,
4300                    connection_incarnation,
4301                    registration: None,
4302                    pass_principal: None,
4303                    readiness: None,
4304                    ready_pending: Arc::new(AtomicBool::new(false)),
4305                    parked: AtomicBool::new(false),
4306                    fd_guard,
4307                });
4308            }
4309        }
4310        // Single-writer vacant-only insert (see doc above and the refusal arm)
4311        // pairs EXACTLY one gauge increment with the decrement in `remove`,
4312        // keeping `liminal_connections_active` equal to the live record count on
4313        // every teardown route; a refused duplicate increments nothing.
4314        crate::metrics::connection_spawned();
4315        if let Some(signals) = self.signals.get() {
4316            signals.connection(pid, true);
4317            signals.topology(crate::console::TopologyChange::ConnectionOpened {
4318                id: pid,
4319                mount: mount.into(),
4320                participant: None,
4321            });
4322        }
4323        Ok(())
4324    }
4325
4326    pub(super) fn mark_crashed(&self, pid: u64, reason: ExitReason, peer_addr: Option<SocketAddr>) {
4327        let removed = self.remove(pid);
4328        if let Some(record) = removed.as_ref() {
4329            self.fire_close_hooks(pid, record);
4330        }
4331        let removed_peer_addr = removed
4332            .as_ref()
4333            .and_then(|record| record.peer_addr)
4334            .or(peer_addr);
4335        // The mount names the door this connection came through — the one fact
4336        // that tells a reader whether a crashed connection was a socket peer or
4337        // a co-resident caller, which `peer_addr: None` alone cannot.
4338        let mount = removed.as_ref().map(|record| record.mount);
4339        tracing::warn!(
4340            connection_pid = pid,
4341            peer_addr = ?removed_peer_addr,
4342            mount = ?mount,
4343            reason = ?reason,
4344            "connection process crashed"
4345        );
4346    }
4347
4348    /// Whether the spawn thread has installed the host record. A first native
4349    /// slice can win the enqueue-vs-record race and must remain runnable until it
4350    /// has somewhere host-reachable to publish its readiness token.
4351    pub(super) fn is_registered(&self, pid: u64) -> bool {
4352        self.contains(pid)
4353    }
4354
4355    /// Removes a token minted in-slice when publishing it to the host record fails.
4356    pub(super) fn deregister_unpublished_readiness(&self, token: ReadinessToken) {
4357        if let Some(scheduler) = self.scheduler.upgrade() {
4358            scheduler.readiness_deregister(token);
4359        }
4360    }
4361
4362    /// Cancels deadline timers detached by reply completion or connection close.
4363    pub(super) fn cancel_deadline_timers(&self, timers: Vec<TimerRef>) {
4364        let Some(scheduler) = self.scheduler.upgrade() else {
4365            return;
4366        };
4367        if let Ok(mut wheel) = scheduler.timers().lock() {
4368            for timer in timers {
4369                wheel.cancel(timer);
4370            }
4371        }
4372    }
4373
4374    pub(super) fn finish(&self, pid: u64) {
4375        if let Some(removed) = self.remove(pid) {
4376            self.fire_close_hooks(pid, &removed);
4377        }
4378    }
4379
4380    /// Records the one readiness token minted for this connection. A live
4381    /// connection never re-registers: later parked slices rearm this identity.
4382    pub(super) fn set_readiness_token_once(
4383        &self,
4384        pid: u64,
4385        token: ReadinessToken,
4386        fd: RawFd,
4387    ) -> Result<(), ServerError> {
4388        let mut records = lock(&self.records, "connection registry")?;
4389        let record =
4390            records
4391                .connections
4392                .get_mut(&pid)
4393                .ok_or_else(|| ServerError::ListenerAccept {
4394                    message: format!(
4395                        "connection {pid} has no host record for readiness registration"
4396                    ),
4397                })?;
4398        if record.readiness.is_some() {
4399            return Err(ServerError::ListenerAccept {
4400                message: format!("connection {pid} attempted to replace its readiness token"),
4401            });
4402        }
4403        record.readiness = Some(ReadinessRegistration { token, fd });
4404        drop(records);
4405        Ok(())
4406    }
4407
4408    /// Fires the close-side notifier hooks for a removed connection record:
4409    /// `on_worker_unregistered` when it carried a worker registration, and
4410    /// `on_pass_detached` (with the principal) when it carried a pass
4411    /// principal. A record with neither — a plain bearer or open-access
4412    /// connection, or a worker connection that never registered — is a no-op,
4413    /// so each close hook fires for exactly the connections whose open-side
4414    /// counterpart fired. Every teardown route (`finish`, `mark_crashed`,
4415    /// `reclaim_terminated`, `reap_crashed`) reaches this through the
4416    /// idempotent [`Self::remove`], so each fires at most once per connection.
4417    fn fire_close_hooks(&self, pid: u64, record: &ConnectionRecord) {
4418        let Some(notifier) = self.notifier.as_ref() else {
4419            return;
4420        };
4421        if record.registration.is_some() {
4422            notifier.on_worker_unregistered(pid);
4423        }
4424        if let Some(principal) = record.pass_principal.as_ref() {
4425            notifier.on_pass_detached(pid, principal);
4426        }
4427    }
4428
4429    fn reap_crashed(&self, scheduler: &Scheduler) -> usize {
4430        let pids = match self.records.lock() {
4431            Ok(records) => records.connections.keys().copied().collect::<Vec<_>>(),
4432            Err(error) => {
4433                tracing::warn!(%error, "connection registry unavailable during crash reap");
4434                return 0;
4435            }
4436        };
4437        let mut reaped = 0;
4438        for pid in pids {
4439            if scheduler.process_table().get(pid).is_none() {
4440                let removed = self.remove(pid);
4441                if let Some(record) = removed.as_ref() {
4442                    self.fire_close_hooks(pid, record);
4443                }
4444                let peer_addr = removed.and_then(|record| record.peer_addr);
4445                // This process exited without ever reaching `mark_crashed`/`finish`
4446                // (e.g. the beamr scheduler terminated it externally). W4 leg 1
4447                // retired this scan from the per-accept listener loop, and W4 leg 3
4448                // retired the shutdown-drain reconciliation that also drove it: the
4449                // reclaimer of these exits is now the TOLD exit-event reactor
4450                // ([`run_reclaim_reactor`]), which also composes drain completion
4451                // through the one `remove()` funnel. This scan now survives only as
4452                // that reactor's exit-event overflow (`Lagged`) recovery, driven a
4453                // bounded number of times, never periodically. beamr 0.15.4 exposes
4454                // a public, non-blocking
4455                // `peek_exit_reason` (and `take_exit_outcome`), so the real reason
4456                // IS recoverable here rather than logged as an opaque literal.
4457                let reason = scheduler.peek_exit_reason(pid);
4458                tracing::warn!(
4459                    connection_pid = pid,
4460                    ?peer_addr,
4461                    ?reason,
4462                    "connection process exited without a final slice; reclaimed by reconciliation"
4463                );
4464                reaped += 1;
4465            }
4466        }
4467        reaped
4468    }
4469
4470    fn contains(&self, pid: u64) -> bool {
4471        self.records
4472            .lock()
4473            .is_ok_and(|records| records.connections.contains_key(&pid))
4474    }
4475
4476    #[cfg(test)]
4477    fn readiness_registration_count(&self) -> usize {
4478        self.records.lock().map_or(0, |records| {
4479            records
4480                .connections
4481                .values()
4482                .filter(|record| record.readiness.is_some())
4483                .count()
4484        })
4485    }
4486
4487    #[cfg(test)]
4488    fn readiness_fd(&self, pid: u64) -> Option<RawFd> {
4489        self.records
4490            .lock()
4491            .ok()?
4492            .connections
4493            .get(&pid)
4494            .and_then(|record| record.readiness.map(|registration| registration.fd))
4495    }
4496
4497    #[cfg(test)]
4498    fn readiness_token(&self, pid: u64) -> Option<ReadinessToken> {
4499        self.records
4500            .lock()
4501            .ok()?
4502            .connections
4503            .get(&pid)
4504            .and_then(|record| record.readiness.map(|registration| registration.token))
4505    }
4506
4507    fn active_connections(&self) -> Vec<ActiveConnection> {
4508        self.records.lock().map_or_else(
4509            |_| Vec::new(),
4510            |records| {
4511                records
4512                    .connections
4513                    .iter()
4514                    .map(|(&pid, record)| ActiveConnection {
4515                        pid,
4516                        peer_addr: record.peer_addr,
4517                    })
4518                    .collect()
4519            },
4520        )
4521    }
4522
4523    /// Reads the complete active-connection incarnation set under the
4524    /// registry lock — the bounded defense-in-depth collision-skip input to
4525    /// incarnation allocation (uniqueness itself comes from allocator-log
4526    /// monotonicity; see `allocate_connection_incarnation`). Poisoning fails
4527    /// admission closed: treating an unreadable registry as empty would
4528    /// silently drop the defense layer.
4529    fn complete_active_incarnation_references(
4530        &self,
4531    ) -> Result<Vec<ConnectionIncarnation>, ServerError> {
4532        Ok(
4533            lock(&self.records, "connection incarnation reference registry")?
4534                .connections
4535                .values()
4536                .filter_map(|record| record.connection_incarnation)
4537                .collect(),
4538        )
4539    }
4540
4541    fn push_control(&self, pid: u64, control: ConnectionControl) -> Result<(), ServerError> {
4542        lock(&self.controls, "connection control queue")?
4543            .push(QueuedConnectionControl { pid, control });
4544        Ok(())
4545    }
4546
4547    pub(super) fn pop_control(&self, pid: u64) -> Option<ConnectionControl> {
4548        let mut controls = self.controls.lock().ok()?;
4549        let index = controls.iter().position(|queued| queued.pid == pid)?;
4550        Some(controls.remove(index).control)
4551    }
4552
4553    /// Non-consuming final-probe query for controls enqueued after mailbox drain.
4554    pub(super) fn has_control(&self, pid: u64) -> bool {
4555        self.controls
4556            .lock()
4557            .is_ok_and(|controls| controls.iter().any(|queued| queued.pid == pid))
4558    }
4559
4560    /// Pulls a queued-but-unconsumed control back out of the queue. Returns
4561    /// whether THIS call removed it — `false` means the entry already left the
4562    /// queue, and since [`Self::pop_control`] is the only other remover, a
4563    /// consumer drain took it (S8's publication disambiguator). Matching is
4564    /// `pid` + full control equality; a `Push` control embeds its
4565    /// runtime-unique correlation id, so this can never remove a different
4566    /// push's entry and misreport.
4567    fn remove_control(&self, pid: u64, control: &ConnectionControl) -> bool {
4568        let Ok(mut controls) = self.controls.lock() else {
4569            return false;
4570        };
4571        let Some(index) = controls
4572            .iter()
4573            .position(|queued| queued.pid == pid && &queued.control == control)
4574        else {
4575            return false;
4576        };
4577        controls.remove(index);
4578        true
4579    }
4580
4581    fn active_count(&self) -> usize {
4582        self.records
4583            .lock()
4584            .map_or(0, |records| records.connections.len())
4585    }
4586
4587    /// Test read of the record's stamped mount (design §10).
4588    #[cfg(test)]
4589    fn connection_mount(&self, pid: u64) -> Option<MountKind> {
4590        self.records
4591            .lock()
4592            .ok()?
4593            .connections
4594            .get(&pid)
4595            .map(|record| record.mount)
4596    }
4597
4598    /// Test read of whether the record holds an fd guard.
4599    #[cfg(test)]
4600    fn connection_has_fd_guard(&self, pid: u64) -> Option<bool> {
4601        self.records
4602            .lock()
4603            .ok()?
4604            .connections
4605            .get(&pid)
4606            .map(|record| record.fd_guard.is_some())
4607    }
4608
4609    /// Removes the connection record for `pid` and, in the same close step, drops
4610    /// every push reply slot that connection still owns so each waiting
4611    /// [`PushReplyAwaiter`] wakes immediately with a disconnected error. This runs
4612    /// on every close route — `finish`, `mark_crashed`, and `reap_crashed` all
4613    /// remove through here — and fires regardless of whether the connection ever
4614    /// registered a worker, so a plain push target is covered too.
4615    ///
4616    /// ORDER MATTERS (S3/S7): the record is removed BEFORE the push sweep. A
4617    /// push registering concurrently runs INSERT -> CONFIRM -> PUBLISH
4618    /// (`confirm_push_registration` reads the record after inserting its slot
4619    /// and before publishing its control), so with this ordering exactly one
4620    /// side always observes a racing slot: a confirm that ran before this
4621    /// removal implies the slot was inserted before the sweep below (which then
4622    /// reaps it — a control published after that confirm is answered into a
4623    /// swept slot, read as the truthful disconnected outcome); a confirm after
4624    /// this removal sees the absence, rolls the slot back itself, and never
4625    /// publishes. Sweeping first (the original order) left a window — sweep,
4626    /// then insert+confirm, then record removal — where NEITHER side observed
4627    /// the slot and it leaked past connection close. The two locks are taken
4628    /// strictly sequentially (never nested), so no lock-order inversion.
4629    fn remove(&self, pid: u64) -> Option<ConnectionRecord> {
4630        let mut removed = self.records.lock().ok().and_then(|mut records| {
4631            let removed = records.connections.remove(&pid);
4632            // SRV-027 R1: the directory entry leaves with its record, in
4633            // this same lock take.
4634            if let Some(registration) = removed
4635                .as_ref()
4636                .and_then(|record| record.registration.as_ref())
4637            {
4638                records.directory.forget(pid, &registration.task_queue);
4639            }
4640            removed
4641        });
4642        #[cfg(test)]
4643        let host_fd = removed
4644            .as_ref()
4645            .and_then(|record| record.fd_guard.as_ref())
4646            .map(std::os::fd::AsRawFd::as_raw_fd);
4647        self.cancel_pushes_for_connection(pid);
4648        if let Some(registration) = removed.as_mut().and_then(|record| record.readiness.take()) {
4649            if let Some(scheduler) = self.scheduler.upgrade() {
4650                // This call is ACK'd: it returns only after the poll owner has
4651                // removed the registration. `fd_guard` is still live here.
4652                scheduler.readiness_deregister(registration.token);
4653                tracing::debug!(
4654                    registered_fd = registration.fd,
4655                    "connection readiness deregistration acknowledged"
4656                );
4657            }
4658        }
4659        if let Some(record) = removed.as_mut() {
4660            // Explicit after-deregister drop documents and enforces the fd wall.
4661            drop(record.fd_guard.take());
4662        }
4663        // Decrement only when a record was actually present so a double-remove
4664        // (e.g. `finish` after `reap_crashed`) cannot drive the gauge negative.
4665        // The §5 admission slot is released on the same guard: the reservation
4666        // acquired in `spawn_connection` converted into this record at
4667        // `register`, so its removal is exactly one release per reservation.
4668        if removed.is_some() {
4669            crate::metrics::connection_closed();
4670            self.release_admission();
4671            // ACCESS-005 R6: the connection's grant-index membership is
4672            // retired on this same once-per-record guard, so revoke, EOF,
4673            // crash and force-close all release it exactly once.
4674            if let Some(revocation) = self.revocation.get()
4675                && revocation.release(pid)
4676            {
4677                tracing::debug!(connection_pid = pid, "grant-decision binding released");
4678            }
4679            if let Some(signals) = self.signals.get() {
4680                #[cfg(test)]
4681                self.probe_close_event_fd(host_fd);
4682                signals.connection(pid, false);
4683                signals.topology(crate::console::TopologyChange::ConnectionClosed { id: pid });
4684            }
4685        }
4686        // TOLD drain-completion tell (W4 leg 3, §4.3): the record map above already
4687        // reflects this removal, so bump the removal generation and wake the
4688        // drain/settle waiter AFTER the observed state is updated. Ordering the
4689        // state update before the generation bump — paired with the waiter arming
4690        // its snapshot before observing `active_count` — is the arm-before-observe
4691        // barrier that makes a concurrently delivered exit un-losable (oracle 18).
4692        // Only a real removal tells, so a double-remove drives no spurious wake.
4693        if removed.is_some() {
4694            self.signal_connection_removed();
4695            // Removal can complete delivery quiescence without another park.
4696            // Tell the earlier flush waiter as well as the later drain waiter.
4697            if self.settle_armed.load(Ordering::Acquire) {
4698                self.signal_settle_changed();
4699            }
4700        }
4701        removed
4702    }
4703
4704    #[cfg(test)]
4705    fn probe_close_event_fd(&self, host_fd: Option<RawFd>) {
4706        let mut probe = match self.close_event_fd_probe.lock() {
4707            Ok(probe) => probe,
4708            Err(error) => {
4709                tracing::error!(%error, "close-event fd probe lock poisoned");
4710                return;
4711            }
4712        };
4713        if !matches!(*probe, CloseEventFdProbe::Armed) {
4714            return;
4715        }
4716        let observed = host_fd.map_or_else(
4717            || Err(std::io::Error::other("close-event probe has no owned fd")),
4718            |fd| match std::fs::File::open(format!("/dev/fd/{fd}")) {
4719                Ok(duplicate) => {
4720                    drop(duplicate);
4721                    Ok(false)
4722                }
4723                Err(error) if matches!(error.raw_os_error(), Some(2 | 9)) => Ok(true),
4724                Err(error) => Err(error),
4725            },
4726        );
4727        *probe = CloseEventFdProbe::Observed(observed);
4728    }
4729
4730    /// Bumps the drain-completion generation and wakes the drain/settle waiter.
4731    /// Called from [`Self::remove`] on every route that actually drops a record.
4732    fn signal_connection_removed(&self) {
4733        // Bump the generation under the lock, release it, THEN notify. A waiter
4734        // holds this lock continuously from its generation re-check through the
4735        // atomic release inside `wait_timeout`, so it can never miss a bump
4736        // published before the notify — the Condvar lost-wakeup contract holds
4737        // without notifying under the guard.
4738        {
4739            let mut generation = recover_lock(&self.drain_generation);
4740            *generation = generation.wrapping_add(1);
4741        }
4742        self.drain_removed.notify_all();
4743    }
4744
4745    /// Parks the calling thread until every tracked connection has been removed
4746    /// or `deadline` elapses, returning `true` when the drain completed and
4747    /// `false` when the single admitted deadline won. This is the TOLD
4748    /// replacement (W4 leg 3, §4.3) for the retired reap/count/sleep drain loop:
4749    /// it never samples completion on a timer. Completion is observed only on a
4750    /// delivered connection-removal wake (composed from the one `remove()` funnel,
4751    /// so a Died/Detached/crash exit and an orderly close decrement through the
4752    /// same path — oracle 15) or the one deadline. Force-close settle reuses this
4753    /// same waiter with its own deadline rather than a second poll loop (oracle
4754    /// 14).
4755    pub(super) fn wait_for_active_connections_drained(&self, deadline: Instant) -> bool {
4756        loop {
4757            // ARM before OBSERVE: snapshot the removal generation first, so an exit
4758            // delivered after the completion observation below and before the park
4759            // bumps a generation the park detects — it is never lost (oracle 18).
4760            let snapshot = self.drain_generation_snapshot();
4761            // OBSERVE completion first: a last exit that reaches zero wins a tie
4762            // with a simultaneously elapsed deadline (oracle 19).
4763            if self.active_count() == 0 {
4764                return true;
4765            }
4766            let Some(remaining) = deadline.checked_duration_since(Instant::now()) else {
4767                #[cfg(test)]
4768                self.drain_deadline_hits.fetch_add(1, Ordering::SeqCst);
4769                return false;
4770            };
4771            #[cfg(test)]
4772            self.run_drain_park_barrier();
4773            self.park_until_removed_or(snapshot, remaining);
4774        }
4775    }
4776
4777    /// Reads the current removal generation under its mutex.
4778    fn drain_generation_snapshot(&self) -> u64 {
4779        *recover_lock(&self.drain_generation)
4780    }
4781
4782    /// The removal generation, read as a departure count. A poisoned lock is
4783    /// an error here, not recovered: a caller waiting on departures must not
4784    /// read a count a panicking holder may have left half-written.
4785    pub(super) fn departures(&self) -> Result<u64, ServerError> {
4786        Ok(*lock(&self.drain_generation, "connection departures")?)
4787    }
4788
4789    /// Parks on the removal `Condvar` until the generation has advanced by
4790    /// `count` since `since`, or until `deadline`. `wait_timeout_while` checks
4791    /// the predicate under the lock before it parks, so departures already
4792    /// counted return at once, and spurious wakes park again inside the call.
4793    pub(super) fn await_departures(
4794        &self,
4795        since: u64,
4796        count: u64,
4797        deadline: Instant,
4798    ) -> Result<bool, ServerError> {
4799        let remaining = deadline.saturating_duration_since(Instant::now());
4800        let (generation, _timed_out) = self
4801            .drain_removed
4802            .wait_timeout_while(
4803                lock(&self.drain_generation, "connection departures")?,
4804                remaining,
4805                |current| current.wrapping_sub(since) < count,
4806            )
4807            .map_err(|error| ServerError::ListenerAccept {
4808                message: format!("connection departures unavailable: {error}"),
4809            })?;
4810        let departed = generation.wrapping_sub(since) >= count;
4811        drop(generation);
4812        Ok(departed)
4813    }
4814
4815    /// Parks on the removal `Condvar` for at most `timeout`, but only while no
4816    /// removal has bumped the generation since `snapshot` — the arm-before-observe
4817    /// barrier. `wait_timeout_while` evaluates the predicate under the lock BEFORE
4818    /// waiting, so a generation already advanced (an exit landed between the
4819    /// observation and here) returns immediately without sleeping; spurious wakes
4820    /// re-wait inside the call and never return early.
4821    fn park_until_removed_or(&self, snapshot: u64, timeout: Duration) {
4822        let outcome = self
4823            .drain_removed
4824            .wait_timeout_while(recover_lock(&self.drain_generation), timeout, |current| {
4825                *current == snapshot
4826            })
4827            .unwrap_or_else(PoisonError::into_inner);
4828        // A non-timed-out return means the predicate went false — a real removal
4829        // bumped the generation and woke this park (as opposed to the deadline).
4830        #[cfg(test)]
4831        if !outcome.1.timed_out() {
4832            self.drain_exit_wakes.fetch_add(1, Ordering::SeqCst);
4833        }
4834        // Release the guard promptly; the caller re-loops unlocked.
4835        drop(outcome);
4836    }
4837}
4838
4839/// A reply either settles its recipient's slot or names why it was discarded.
4840#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4841#[must_use]
4842pub(super) enum PushReplyDisposition {
4843    Resolved,
4844    SenderMismatch,
4845    UnknownCorrelation,
4846    ReceiverDropped,
4847}
4848
4849/// What `pid`'s reply slot retains for a reply's access decision.
4850#[derive(Debug, Clone, PartialEq, Eq)]
4851#[must_use]
4852pub(super) enum PushBinding {
4853    /// No slot for this correlation id is owned by `pid`; the settle names the
4854    /// exact disposition.
4855    Unbound,
4856    /// The push was made with no subject ([`ConnectionSupervisor::push_to_connection`]).
4857    Subjectless,
4858    /// The subject the push was made on ([`ConnectionSupervisor::push_to_subject`]).
4859    Subject(String),
4860}
4861
4862/// One in-flight server-push reply slot, associating the awaiter's reply `sender`
4863/// with the `pid` of the connection that owns the push. The pid lets the close
4864/// path drop exactly that connection's slots; the correlation id (the map key)
4865/// still drives [`ConnectionRuntime::resolve_push`] and
4866/// [`ConnectionRuntime::cancel_push`].
4867#[derive(Debug)]
4868struct PendingPush {
4869    pid: u64,
4870    /// The subject the push was made on, recorded at push time by
4871    /// [`ConnectionSupervisor::push_to_subject`] and read back by
4872    /// [`ConnectionRuntime::push_binding`] when the reply is decided. `None`
4873    /// for a push made without one.
4874    subject: Option<String>,
4875    sender: Sender<PushSlotResolution>,
4876    /// Absolute reply deadline for this push, when one was requested via
4877    /// [`ConnectionSupervisor::push_to_connection_with_deadline`]. `None` is the
4878    /// default 0.2.3 shape: the slot has no per-slot deadline and is reclaimed
4879    /// only by reply-consumed or connection-close. `Some` is evaluated host-side
4880    /// and lazily in [`ConnectionRuntime::expire_push_if_due`].
4881    deadline: Option<Instant>,
4882    /// SRV-027 R2: the requester connection a routed push answers, woken once
4883    /// when the slot resolves (a reply, a refusal, or the worker's close) so
4884    /// its next slice relays the outcome. `None` for a hosting application's
4885    /// push, whose awaiter is received on the caller's own thread.
4886    wake: Option<super::wake::ReadyWaker>,
4887}
4888
4889/// What a reply slot resolves TO when it is woken through its channel.
4890///
4891/// Distinct from [`PushSlotDisposition`], which is what a host-side deadline
4892/// touch FOUND. Every OTHER way a slot dies — connection close, the close sweep,
4893/// the publication rollback — drops the `Sender` instead of sending one of these,
4894/// and so keeps reading as the retryable [`ServerError::PushReplyDisconnected`].
4895/// That is deliberate: only a frame the connection can NEVER carry gets a named
4896/// terminal outcome here.
4897enum PushSlotResolution {
4898    /// The client's correlated reply payload.
4899    Reply(Vec<u8>),
4900    /// The client's correlated reply arrived and was refused by the access
4901    /// decision on the push's retained subject; `reason` is the refusal as the
4902    /// route would have carried it. TERMINAL: the reply was never delivered.
4903    ReplyRefused {
4904        /// The refusal, rendered as the four-field access refusal.
4905        reason: String,
4906    },
4907    /// The push was never queued for the wire: its encoded frame is larger than
4908    /// the whole outbound buffer, so draining could never make room for it.
4909    /// CERTAIN, and therefore terminal. Congestion never arrives here.
4910    OutboundFrameTooLarge {
4911        /// Encoded size of the rejected frame; exceeds `capacity` on its own.
4912        needed: usize,
4913        /// The outbound buffer's whole capacity.
4914        capacity: usize,
4915        /// Bytes queued when the frame was rejected (log context only).
4916        queued: usize,
4917    },
4918}
4919
4920/// Host-side disposition of a reply slot at an elapsed `receive` quantum.
4921enum PushSlotDisposition {
4922    /// The slot carried an explicit deadline that has passed; this call removed
4923    /// it (releasing its §5 cap admission).
4924    Expired,
4925    /// The slot is present with no deadline, or a deadline still in the future:
4926    /// the elapsed quantum is a benign re-arm and the slot is untouched.
4927    Live,
4928    /// No slot for this correlation id — a concurrent resolve or connection close
4929    /// already removed it.
4930    Absent,
4931}
4932
4933/// The connection records and the service directory built from them, under
4934/// one lock (SRV-027 R1): a directory entry is added when its record stores a
4935/// registration and removed in the same lock take that removes or replaces
4936/// the record, so no lookup names a connection whose record is gone.
4937#[derive(Debug, Default)]
4938struct Registry {
4939    connections: HashMap<u64, ConnectionRecord>,
4940    directory: super::service_directory::ServiceDirectory,
4941}
4942
4943#[derive(Debug)]
4944struct ConnectionRecord {
4945    peer_addr: Option<SocketAddr>,
4946    /// Which door admitted this connection (design §10). Written once, by the
4947    /// spawn path, from the server's own knowledge of which path it is; never
4948    /// read from, or influenced by, anything the client sends.
4949    mount: MountKind,
4950    /// Durable pair allocated and flushed before the process was spawned.
4951    connection_incarnation: Option<ConnectionIncarnation>,
4952    /// Worker registration declared on this connection, set by `set_registration`
4953    /// when a `WorkerRegister` frame is accepted. `Some` marks a connection whose
4954    /// close must fire `on_worker_unregistered`.
4955    registration: Option<WorkerRegistration>,
4956    /// Registry principal a pass-stamped `Connect` verified, set by
4957    /// `attach_pass_principal` once the handshake succeeded. `Some` marks a
4958    /// connection whose close must fire `on_pass_detached` with it.
4959    pass_principal: Option<PassPrincipal>,
4960    /// Host-reachable identity for ACK'd deregistration after external death.
4961    readiness: Option<ReadinessRegistration>,
4962    /// Shared edge set before READY enters beamr's process-table pending queue;
4963    /// the executing process reads it at its final probe.
4964    ready_pending: Arc<AtomicBool>,
4965    /// FIX A-ii: `true` while this connection is parked (its last slice returned
4966    /// `Wait`). A parked connection with no `ready_pending` edge has fanned out
4967    /// every accepted publish to its socket, so the shutdown flush barrier reads
4968    /// this with `ready_pending` to know delivery has quiesced before it lets the
4969    /// shutdown Disconnect be broadcast.
4970    parked: AtomicBool,
4971    /// Keeps the fd alive until deregistration has been acknowledged, preventing
4972    /// stale registration delivery to a subsequently reused descriptor number.
4973    fd_guard: Option<TcpStream>,
4974}
4975
4976#[derive(Debug, Clone, Copy)]
4977struct ReadinessRegistration {
4978    token: ReadinessToken,
4979    fd: RawFd,
4980}
4981
4982#[derive(Debug, Clone, PartialEq, Eq)]
4983struct QueuedConnectionControl {
4984    pid: u64,
4985    control: ConnectionControl,
4986}
4987
4988fn lock<'a, T>(mutex: &'a Mutex<T>, context: &str) -> Result<MutexGuard<'a, T>, ServerError> {
4989    mutex.lock().map_err(|error| ServerError::ListenerAccept {
4990        message: format!("{context} unavailable: {error}"),
4991    })
4992}
4993
4994/// Locks `mutex`, RECOVERING a poisoned guard instead of failing (S4). For
4995/// lifecycle-cleanup paths only (reply delivery, expiry, cancellation, the
4996/// close sweep): removal-style operations are sound on a recovered map, and a
4997/// cleanup that silently skipped its removal would strand slots and their §5
4998/// cap admissions forever. Admission paths keep the fail-closed [`lock`].
4999fn recover_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
5000    mutex
5001        .lock()
5002        .unwrap_or_else(std::sync::PoisonError::into_inner)
5003}
5004
5005#[test]
5006fn host_fd_is_closed_before_connection_close_event_handoff()
5007-> Result<(), Box<dyn std::error::Error>> {
5008    use std::os::fd::AsRawFd;
5009    let fixture = crate::server::self_streams::tests::R3Fixture::new()?;
5010    let services: Arc<dyn ConnectionServices> = fixture.services;
5011    let runtime = ConnectionRuntime::for_tests(services);
5012    runtime
5013        .signals
5014        .set(ServerSignals::default())
5015        .map_err(|_| "close-event signals already installed")?;
5016    let listener = std::net::TcpListener::bind("127.0.0.1:0")?;
5017    let client = TcpStream::connect(listener.local_addr()?)?;
5018    let (server, address) = listener.accept()?;
5019    let host_fd = server.as_raw_fd();
5020    let positive_control = std::fs::File::open(format!("/dev/fd/{host_fd}"))?;
5021    drop(positive_control);
5022    runtime.register_record(1, Some(address), None, MountKind::Tcp, Some(server))?;
5023    *runtime
5024        .close_event_fd_probe
5025        .lock()
5026        .map_err(|error| error.to_string())? = CloseEventFdProbe::Armed;
5027    let removed = runtime
5028        .remove(1)
5029        .ok_or("fd-owning connection was not removed")?;
5030    let mut probe = runtime
5031        .close_event_fd_probe
5032        .lock()
5033        .map_err(|error| error.to_string())?;
5034    let CloseEventFdProbe::Observed(observed) =
5035        std::mem::replace(&mut *probe, CloseEventFdProbe::Off)
5036    else {
5037        return Err("close-event handoff was not observed".into());
5038    };
5039    drop(probe);
5040    assert!(
5041        observed?,
5042        "connection-close event was handed off while its host fd remained open"
5043    );
5044    assert!(removed.fd_guard.is_none());
5045    drop(removed);
5046    drop(client);
5047    drop(listener);
5048    Ok(())
5049}
5050
5051/// ACCESS-003 R1: the context watch drains its own subscription the way the
5052/// connection pump does; each reading carries its message id, the stable
5053/// event id Lys charges it under.
5054impl crate::services::context_watch::ContextFeed for super::services::ConnectionSubscription {
5055    fn next_reading(&mut self) -> Option<(String, Vec<u8>)> {
5056        self.try_next()
5057            .map(|envelope| (envelope.message_id.as_uuid().to_string(), envelope.payload))
5058    }
5059}
5060
5061/// Starts the context watch over the `context-watch` channel when the
5062/// service bindings ask for it (ACCESS-003 R1).
5063///
5064/// One subscription whose inbox
5065/// wakes the watcher on every admitted reading, bounded by the same §5
5066/// limits a connection's subscription is. `Ok(None)` when the bindings do not
5067/// watch.
5068///
5069/// # Errors
5070/// The `context-watch` channel refused the subscription (it must be
5071/// configured), or the watcher thread did not start.
5072pub fn start_context_watch(
5073    services: &dyn ConnectionServices,
5074    bindings: &crate::services::production::ServiceBindings,
5075    limits: &LimitsConfig,
5076) -> Result<Option<crate::services::context_watch::ContextWatch>, ServerError> {
5077    if !bindings.watches_context() {
5078        return Ok(None);
5079    }
5080    let wake = crate::services::context_watch::ContextWake::new();
5081    let install = liminal::channel::InboxInstall {
5082        budget: liminal::channel::ConnectionInboxBudget::new(limits.max_connection_inbox_bytes),
5083        depth_cap: limits.max_subscription_inbox_depth,
5084        notifier: Some(wake.notifier()),
5085        capacity: None,
5086    };
5087    let subscription = services.subscribe(
5088        crate::services::ServiceKind::ContextWatch.subject_shape(),
5089        &[],
5090        Some(install),
5091    )?;
5092    crate::services::context_watch::ContextWatch::start(
5093        subscription,
5094        wake,
5095        bindings.context_reporter(),
5096    )
5097    .map(Some)
5098    .map_err(|error| ServerError::ConfigValidation {
5099        message: error.to_string(),
5100    })
5101}