Skip to main content

liminal_server/server/connection/
supervisor.rs

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