Skip to main content

liminal_server/server/connection/
worker_front_door.rs

1//! Capability-scoped worker front door services (D2).
2//!
3//! [`WorkerFrontDoorServices`] is the [`ConnectionServices`] adapter for
4//! worker-only ("worker front door") deployments — aion-style hosts that use the
5//! bus for worker registration, correlated server push/reply, and
6//! notifier-consumed reserved publishes, and nothing else. It constructs no
7//! channel supervisor, conversation supervisor, haematite database/store, dedup
8//! cache, or temporary directory: it is a stateless adapter, so the only runtime
9//! the worker-front-door construction path spins up is the connection supervisor's
10//! own scheduler (owned by [`super::supervisor`], shared by both profiles).
11//!
12//! Worker registration, correlated push/reply, and the reserved observability tap
13//! do NOT flow through this adapter at all — they are served by the connection
14//! supervisor and the [`ConnectionNotifier`](super::notifier::ConnectionNotifier)
15//! independently of which `ConnectionServices` is installed, so they behave exactly
16//! as in full mode. This adapter carries only the channel/conversation operations,
17//! every one of which it rejects with a typed error frame.
18
19use liminal::protocol::{MessageEnvelope, SchemaId as ProtocolSchemaId};
20
21use super::conversation::ConnectionConversation;
22use super::services::{ConnectionServices, ConnectionSubscription, PublishOutcome};
23use crate::ServerError;
24use crate::config::types::ServiceProfile;
25
26/// [`ConnectionServices`] for the worker front door: registration, correlated
27/// push/reply, and notifier-consumed reserved publishes only.
28///
29/// The type holds no state and constructs nothing, so [`Self::new`] is an
30/// infallible `const fn` — a structural signal that this path starts no scheduler
31/// and opens no store (both of which are fallible in full mode). Ordinary
32/// publish/subscribe/conversation operations return a typed [`ServerError`], which
33/// [`super::apply`] renders as the matching typed error frame
34/// (`PublishError`/`SubscribeError`/`ConversationError`); the connection stays
35/// healthy across the rejection.
36#[derive(Debug, Default, Clone, Copy)]
37pub struct WorkerFrontDoorServices;
38
39impl WorkerFrontDoorServices {
40    /// Creates the worker-front-door adapter. Constructs nothing.
41    #[must_use]
42    pub const fn new() -> Self {
43        Self
44    }
45
46    /// Builds the typed rejection returned for an unsupported channel/conversation
47    /// operation. Rendered by [`super::apply`] as the operation's typed error frame.
48    fn unsupported(operation: &str) -> ServerError {
49        ServerError::UnsupportedOperation {
50            operation: operation.to_owned(),
51            profile: ServiceProfile::WORKER_FRONT_DOOR,
52        }
53    }
54}
55
56impl ConnectionServices for WorkerFrontDoorServices {
57    fn publish(
58        &self,
59        channel: &str,
60        _envelope: &MessageEnvelope,
61        _idempotency_key: Option<&str>,
62    ) -> Result<PublishOutcome, ServerError> {
63        // Reserved observability publishes never reach here: `apply_frame` offers
64        // every publish to the notifier's observability tap FIRST and only falls
65        // through to `services.publish` when the tap did not consume it. So an
66        // ordinary (untapped) publish is the only thing that lands here, and the
67        // front door serves no channels — reject it explicitly.
68        Err(Self::unsupported(&format!(
69            "publish to channel '{channel}'"
70        )))
71    }
72
73    fn subscribe(
74        &self,
75        channel: &str,
76        _accepted_schemas: &[ProtocolSchemaId],
77        _install: Option<liminal::channel::InboxInstall>,
78    ) -> Result<ConnectionSubscription, ServerError> {
79        Err(Self::unsupported(&format!(
80            "subscribe to channel '{channel}'"
81        )))
82    }
83
84    fn unsubscribe(&self, subscription: ConnectionSubscription) -> Result<(), ServerError> {
85        // Unreachable in practice: `subscribe` never yields a `ConnectionSubscription`,
86        // so no owned subscription can exist to pass here, and `apply_frame` rejects
87        // an `Unsubscribe` frame before this method via `supports_channel_operations`.
88        // Release the resource and succeed rather than invent a second rejection
89        // surface for a value that cannot be constructed in this profile.
90        subscription.unsubscribe()
91    }
92
93    fn open_conversation(
94        &self,
95        _conversation_id: u64,
96        subject: &str,
97    ) -> Result<ConnectionConversation, ServerError> {
98        Err(Self::unsupported(&format!(
99            "opening conversation '{subject}'"
100        )))
101    }
102
103    fn conversation_message(
104        &self,
105        _conversation: &ConnectionConversation,
106        _envelope: &MessageEnvelope,
107    ) -> Result<(), ServerError> {
108        // Unreachable: no `ConnectionConversation` can exist because
109        // `open_conversation` always rejects. `apply_frame` rejects a
110        // `ConversationMessage` frame before this method via
111        // `supports_channel_operations`.
112        Err(Self::unsupported("conversation message"))
113    }
114
115    fn close_conversation(&self, conversation: ConnectionConversation) -> Result<(), ServerError> {
116        // Unreachable for the same reason as `unsubscribe`; release and succeed.
117        conversation.close()
118    }
119
120    fn flush_durable_state(&self) -> Result<(), ServerError> {
121        // No durable channels exist, so a graceful-shutdown flush is a total no-op —
122        // never an error, so shutting a worker-front-door server down stays clean.
123        Ok(())
124    }
125
126    fn supports_channel_operations(&self) -> bool {
127        false
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::WorkerFrontDoorServices;
134    use crate::server::connection::services::ConnectionServices;
135
136    /// Structural adjunct to the §9 D2 gate: the front-door adapter constructs
137    /// nothing, so its constructor is infallible and starts no scheduler. A
138    /// `Scheduler::new` (channel/conversation/haematite) is fallible and returns
139    /// `Result`; this returning a bare `Self` is the in-language signal that no
140    /// beamr scheduler is created by the adapter itself.
141    ///
142    /// The thread half of the §9 gate is the record-by-construction SEAM CENSUS,
143    /// not this test: the profile-aware construction path reaches every
144    /// scheduler-owning subsystem only through `services::SubsystemFactory`, whose
145    /// recording test implementation cannot skip a record without also failing to
146    /// construct (asserted with a full-profile positive control in
147    /// `services::durable_store_tests` and `supervisor::tests`). A true OS-level
148    /// thread census still belongs to the beamr composition lane's upcoming
149    /// scheduler-inventory API; the census assertions upgrade to it when it lands.
150    /// It is deliberately NOT faked with a platform-specific OS thread count.
151    #[test]
152    fn construction_is_infallible_and_starts_no_scheduler() {
153        let services = WorkerFrontDoorServices::new();
154        // The type carries no channel/conversation/store/dedup fields — that absence
155        // is compile-time enforced by the struct definition. This adapter reports it
156        // serves no channel/conversation operations.
157        assert!(
158            !services.supports_channel_operations(),
159            "the worker front door must report that it serves no channel operations"
160        );
161    }
162}