Skip to main content

liminal_server/server/connection/
supervisor.rs

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