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