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        _connection: u64,
60        channel: &str,
61        _envelope: &MessageEnvelope,
62        _idempotency_key: Option<&str>,
63    ) -> Result<PublishOutcome, ServerError> {
64        // Reserved observability publishes never reach here: `apply_frame` offers
65        // every publish to the notifier's observability tap FIRST and only falls
66        // through to `services.publish` when the tap did not consume it. So an
67        // ordinary (untapped) publish is the only thing that lands here, and the
68        // front door serves no channels — reject it explicitly.
69        Err(Self::unsupported(&format!(
70            "publish to channel '{channel}'"
71        )))
72    }
73
74    fn subscribe(
75        &self,
76        channel: &str,
77        _accepted_schemas: &[ProtocolSchemaId],
78        _install: Option<liminal::channel::InboxInstall>,
79    ) -> Result<ConnectionSubscription, ServerError> {
80        Err(Self::unsupported(&format!(
81            "subscribe to channel '{channel}'"
82        )))
83    }
84
85    fn unsubscribe(&self, subscription: ConnectionSubscription) -> Result<(), ServerError> {
86        // Unreachable in practice: `subscribe` never yields a `ConnectionSubscription`,
87        // so no owned subscription can exist to pass here, and `apply_frame` rejects
88        // an `Unsubscribe` frame before this method via `supports_channel_operations`.
89        // Release the resource and succeed rather than invent a second rejection
90        // surface for a value that cannot be constructed in this profile.
91        subscription.unsubscribe()
92    }
93
94    fn open_conversation(
95        &self,
96        _conversation_id: u64,
97        subject: &str,
98    ) -> Result<ConnectionConversation, ServerError> {
99        Err(Self::unsupported(&format!(
100            "opening conversation '{subject}'"
101        )))
102    }
103
104    fn conversation_message(
105        &self,
106        _conversation: &ConnectionConversation,
107        _envelope: &MessageEnvelope,
108        _op_id: Option<u64>,
109    ) -> Result<(), ServerError> {
110        // Unreachable: no `ConnectionConversation` can exist because
111        // `open_conversation` always rejects. `apply_frame` rejects a
112        // `ConversationMessage` frame before this method via
113        // `supports_channel_operations`.
114        Err(Self::unsupported("conversation message"))
115    }
116
117    fn close_conversation(&self, conversation: ConnectionConversation) -> Result<(), ServerError> {
118        // Unreachable for the same reason as `unsubscribe`; release and succeed.
119        conversation.close()
120    }
121
122    fn flush_durable_state(&self) -> Result<(), ServerError> {
123        // No durable channels exist, so a graceful-shutdown flush is a total no-op —
124        // never an error, so shutting a worker-front-door server down stays clean.
125        Ok(())
126    }
127
128    fn supports_channel_operations(&self) -> bool {
129        false
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::WorkerFrontDoorServices;
136    use crate::server::connection::services::ConnectionServices;
137
138    /// Structural adjunct to the §9 D2 gate: the front-door adapter constructs
139    /// nothing, so its constructor is infallible and starts no scheduler. A
140    /// `Scheduler::new` (channel/conversation/haematite) is fallible and returns
141    /// `Result`; this returning a bare `Self` is the in-language signal that no
142    /// beamr scheduler is created by the adapter itself.
143    ///
144    /// The thread half of the §9 gate is the record-by-construction SEAM CENSUS,
145    /// not this test: the profile-aware construction path reaches every
146    /// scheduler-owning subsystem only through `services::SubsystemFactory`, whose
147    /// recording test implementation cannot skip a record without also failing to
148    /// construct (asserted with a full-profile positive control in
149    /// `services::durable_store_tests` and `supervisor::tests`). A true OS-level
150    /// thread census still belongs to the beamr composition lane's upcoming
151    /// scheduler-inventory API; the census assertions upgrade to it when it lands.
152    /// It is deliberately NOT faked with a platform-specific OS thread count.
153    #[test]
154    fn construction_is_infallible_and_starts_no_scheduler() {
155        let services = WorkerFrontDoorServices::new();
156        // The type carries no channel/conversation/store/dedup fields — that absence
157        // is compile-time enforced by the struct definition. This adapter reports it
158        // serves no channel/conversation operations.
159        assert!(
160            !services.supports_channel_operations(),
161            "the worker front door must report that it serves no channel operations"
162        );
163    }
164}