Skip to main content

lenso_kernel/
prepared.rs

1use std::any::Any;
2
3use super::{
4    BTreeMap, BTreeSet, ErasedDomainResult, ErasedValue, ExecutionClassId, InvocationContext,
5    LocalBoxFuture, ModuleLifecycle, NativeEventEndpoint, NativeStreamEndpoint, Rc,
6    ResolvedAppPlan, RuntimeFailure,
7};
8
9/// Type-erased native endpoint used only while Kernel constructs and dispatches the graph.
10pub trait NativeRequestEndpoint: std::fmt::Debug {
11    /// Stable Capability series identity.
12    fn capability_id(&self) -> &'static str;
13    /// Exact Descriptor version implemented by this endpoint.
14    fn descriptor_version(&self) -> &'static str;
15    /// Exact stable Operation names implemented by this endpoint.
16    fn operations(&self) -> &'static [&'static str];
17    /// Exposes a generated endpoint to its matching typed Capability binding.
18    ///
19    /// Hand-written and older generated endpoints use the default erased path.
20    #[doc(hidden)]
21    fn typed_endpoint(&self) -> Option<&dyn Any> {
22        None
23    }
24    /// Dispatches one operation without serializing its typed Rust payload.
25    fn invoke(
26        &self,
27        operation: &str,
28        request: ErasedValue,
29        context: InvocationContext,
30    ) -> LocalBoxFuture<'static, Result<ErasedDomainResult, RuntimeFailure>>;
31}
32
33/// The complete native endpoint set owned by one Module generation.
34#[derive(Clone, Debug, Default)]
35pub struct NativeEndpointSet {
36    request: Vec<Rc<dyn NativeRequestEndpoint>>,
37    stream: Vec<Rc<dyn NativeStreamEndpoint>>,
38    event: Vec<Rc<dyn NativeEventEndpoint>>,
39}
40
41impl NativeEndpointSet {
42    /// Creates an endpoint set containing every native interaction kind.
43    pub fn new(
44        request: Vec<Rc<dyn NativeRequestEndpoint>>,
45        stream: Vec<Rc<dyn NativeStreamEndpoint>>,
46        event: Vec<Rc<dyn NativeEventEndpoint>>,
47    ) -> Self {
48        Self {
49            request,
50            stream,
51            event,
52        }
53    }
54
55    /// Returns the request endpoints in this generation.
56    pub fn request(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
57        &self.request
58    }
59
60    /// Returns the stream endpoints in this generation.
61    pub fn stream(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
62        &self.stream
63    }
64
65    /// Returns the Event endpoints in this generation.
66    pub fn event(&self) -> &[Rc<dyn NativeEventEndpoint>] {
67        &self.event
68    }
69}
70
71/// One freshly prepared Module Instance generation returned by an Execution Adapter.
72#[derive(Debug)]
73pub struct PreparedNativeModule {
74    pub(super) endpoints: NativeEndpointSet,
75    pub(super) lifecycle: Rc<dyn ModuleLifecycle>,
76}
77
78impl PreparedNativeModule {
79    /// Creates one generation from its exact endpoint set and lifecycle Interface.
80    pub fn new(
81        endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
82        lifecycle: impl ModuleLifecycle,
83    ) -> Self {
84        Self {
85            endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
86            lifecycle: Rc::new(lifecycle),
87        }
88    }
89
90    /// Creates one generation from an already shared lifecycle implementation.
91    pub fn with_lifecycle(
92        endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
93        lifecycle: Rc<dyn ModuleLifecycle>,
94    ) -> Self {
95        Self {
96            endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
97            lifecycle,
98        }
99    }
100
101    /// Creates one generation with request and bidirectional stream endpoints.
102    pub fn with_endpoints(
103        endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
104        stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
105        lifecycle: impl ModuleLifecycle,
106    ) -> Self {
107        Self {
108            endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
109            lifecycle: Rc::new(lifecycle),
110        }
111    }
112
113    /// Creates a generation from one complete endpoint set and shared lifecycle.
114    pub fn with_endpoint_set_lifecycle(
115        endpoints: NativeEndpointSet,
116        lifecycle: Rc<dyn ModuleLifecycle>,
117    ) -> Self {
118        Self {
119            endpoints,
120            lifecycle,
121        }
122    }
123
124    /// Creates one generation containing only bidirectional stream endpoints.
125    pub fn with_stream_endpoints(
126        stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
127        lifecycle: impl ModuleLifecycle,
128    ) -> Self {
129        Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
130    }
131
132    /// Creates one generation containing only ephemeral Event endpoints.
133    pub fn with_event_endpoints(
134        event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
135        lifecycle: impl ModuleLifecycle,
136    ) -> Self {
137        Self::with_all_endpoints(Vec::new(), Vec::new(), event_endpoints, lifecycle)
138    }
139
140    /// Creates one generation with request, stream, and ephemeral Event endpoints.
141    pub fn with_all_endpoints(
142        endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
143        stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
144        event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
145        lifecycle: impl ModuleLifecycle,
146    ) -> Self {
147        Self {
148            endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
149            lifecycle: Rc::new(lifecycle),
150        }
151    }
152
153    /// Returns the exact endpoints prepared for this generation.
154    pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
155        self.endpoints.request()
156    }
157
158    /// Returns the exact stream endpoints prepared for this generation.
159    pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
160        self.endpoints.stream()
161    }
162
163    /// Returns the exact Event endpoints prepared for this generation.
164    pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
165        self.endpoints.event()
166    }
167
168    /// Returns the lifecycle Interface prepared for this generation.
169    pub fn lifecycle(&self) -> Rc<dyn ModuleLifecycle> {
170        self.lifecycle.clone()
171    }
172
173    pub(super) fn into_parts(self) -> (NativeEndpointSet, Rc<dyn ModuleLifecycle>) {
174        (self.endpoints, self.lifecycle)
175    }
176}
177
178/// One provider-specific binding prepared by an Execution Adapter.
179#[derive(Clone, Debug)]
180pub struct PreparedBinding {
181    pub(super) consumer_instance: String,
182    pub(super) provider_instance: String,
183    pub(super) endpoint: Rc<dyn NativeRequestEndpoint>,
184}
185
186/// One provider-specific bidirectional stream binding prepared by an Adapter.
187#[derive(Clone, Debug)]
188pub struct PreparedStreamBinding {
189    pub(super) consumer_instance: String,
190    pub(super) provider_instance: String,
191    pub(super) endpoint: Rc<dyn NativeStreamEndpoint>,
192}
193
194/// One provider-specific ephemeral Event binding prepared by an Adapter.
195#[derive(Clone, Debug)]
196pub struct PreparedEventBinding {
197    pub(super) consumer_instance: String,
198    pub(super) provider_instance: String,
199    pub(super) endpoint: Rc<dyn NativeEventEndpoint>,
200}
201
202impl PreparedEventBinding {
203    /// Binds one consumer to one exact Event endpoint and provider Instance.
204    pub fn new(
205        consumer_instance: impl Into<String>,
206        provider_instance: impl Into<String>,
207        endpoint: Rc<dyn NativeEventEndpoint>,
208    ) -> Self {
209        Self {
210            consumer_instance: consumer_instance.into(),
211            provider_instance: provider_instance.into(),
212            endpoint,
213        }
214    }
215
216    /// Returns the App-local consumer Instance selected by the Plan.
217    pub fn consumer_instance(&self) -> &str {
218        &self.consumer_instance
219    }
220
221    /// Returns the App-local provider Instance selected by the Plan.
222    pub fn provider_instance(&self) -> &str {
223        &self.provider_instance
224    }
225
226    /// Returns the exact prepared Event endpoint referenced by this binding.
227    pub fn endpoint(&self) -> Rc<dyn NativeEventEndpoint> {
228        self.endpoint.clone()
229    }
230
231    pub(super) fn same_identity(&self, other: &Self) -> bool {
232        self.consumer_instance == other.consumer_instance
233            && self.provider_instance == other.provider_instance
234            && self.endpoint.capability_id() == other.endpoint.capability_id()
235    }
236}
237
238impl PreparedStreamBinding {
239    /// Binds one consumer to one exact stream endpoint and provider Instance.
240    pub fn new(
241        consumer_instance: impl Into<String>,
242        provider_instance: impl Into<String>,
243        endpoint: Rc<dyn NativeStreamEndpoint>,
244    ) -> Self {
245        Self {
246            consumer_instance: consumer_instance.into(),
247            provider_instance: provider_instance.into(),
248            endpoint,
249        }
250    }
251
252    /// Returns the App-local consumer Instance selected by the Plan.
253    pub fn consumer_instance(&self) -> &str {
254        &self.consumer_instance
255    }
256
257    /// Returns the App-local provider Instance selected by the Plan.
258    pub fn provider_instance(&self) -> &str {
259        &self.provider_instance
260    }
261
262    /// Returns the exact prepared stream endpoint referenced by this binding.
263    pub fn endpoint(&self) -> Rc<dyn NativeStreamEndpoint> {
264        self.endpoint.clone()
265    }
266
267    pub(super) fn same_identity(&self, other: &Self) -> bool {
268        self.consumer_instance == other.consumer_instance
269            && self.provider_instance == other.provider_instance
270            && self.endpoint.capability_id() == other.endpoint.capability_id()
271    }
272}
273
274impl PreparedBinding {
275    /// Binds one consumer to the endpoint prepared for one exact provider Instance.
276    pub fn new(
277        consumer_instance: impl Into<String>,
278        provider_instance: impl Into<String>,
279        endpoint: Rc<dyn NativeRequestEndpoint>,
280    ) -> Self {
281        Self {
282            consumer_instance: consumer_instance.into(),
283            provider_instance: provider_instance.into(),
284            endpoint,
285        }
286    }
287
288    /// Returns the App-local consumer Instance selected by the Plan.
289    pub fn consumer_instance(&self) -> &str {
290        &self.consumer_instance
291    }
292
293    /// Returns the App-local provider Instance selected by the Plan.
294    pub fn provider_instance(&self) -> &str {
295        &self.provider_instance
296    }
297
298    /// Returns the exact prepared endpoint referenced by this binding.
299    pub fn endpoint(&self) -> Rc<dyn NativeRequestEndpoint> {
300        self.endpoint.clone()
301    }
302
303    pub(super) fn same_identity(&self, other: &Self) -> bool {
304        self.consumer_instance == other.consumer_instance
305            && self.provider_instance == other.provider_instance
306            && self.endpoint.capability_id() == other.endpoint.capability_id()
307    }
308}
309
310/// Prepared native bindings returned by an Execution Adapter to Kernel.
311#[derive(Debug)]
312pub struct PreparedNativeApp {
313    pub(super) bindings: Vec<PreparedBinding>,
314    pub(super) stream_bindings: Vec<PreparedStreamBinding>,
315    pub(super) event_bindings: Vec<PreparedEventBinding>,
316    pub(super) generations: BTreeMap<String, PreparedNativeModule>,
317}
318
319impl PreparedNativeApp {
320    /// Completes Adapter preparation with the full generation and binding tables.
321    pub fn new(
322        bindings: Vec<PreparedBinding>,
323        generations: BTreeMap<String, PreparedNativeModule>,
324    ) -> Self {
325        Self {
326            bindings,
327            stream_bindings: Vec::new(),
328            event_bindings: Vec::new(),
329            generations,
330        }
331    }
332
333    /// Creates the complete Adapter result for an empty Plan.
334    pub fn empty() -> Self {
335        Self::new(Vec::new(), BTreeMap::new())
336    }
337
338    /// Adds the exact bidirectional stream bindings prepared by an Adapter.
339    #[must_use]
340    pub fn with_stream_bindings(mut self, stream_bindings: Vec<PreparedStreamBinding>) -> Self {
341        self.stream_bindings = stream_bindings;
342        self
343    }
344
345    /// Adds the exact ephemeral Event bindings prepared by an Adapter.
346    #[must_use]
347    pub fn with_event_bindings(mut self, event_bindings: Vec<PreparedEventBinding>) -> Self {
348        self.event_bindings = event_bindings;
349        self
350    }
351
352    pub(super) fn merge(&mut self, other: Self) -> Result<(), RuntimeFailure> {
353        for binding in other.bindings {
354            if self
355                .bindings
356                .iter()
357                .any(|existing| existing.same_identity(&binding))
358            {
359                return Err(RuntimeFailure::InvalidResolvedPlan {
360                    detail: format!(
361                        "multiple Execution Adapters prepared binding `{}:{}:{}`",
362                        binding.consumer_instance,
363                        binding.endpoint.capability_id(),
364                        binding.provider_instance
365                    ),
366                });
367            }
368            self.bindings.push(binding);
369        }
370        for binding in other.stream_bindings {
371            if self
372                .stream_bindings
373                .iter()
374                .any(|existing| existing.same_identity(&binding))
375            {
376                return Err(RuntimeFailure::InvalidResolvedPlan {
377                    detail: format!(
378                        "multiple Execution Adapters prepared stream binding `{}:{}:{}`",
379                        binding.consumer_instance,
380                        binding.endpoint.capability_id(),
381                        binding.provider_instance
382                    ),
383                });
384            }
385            self.stream_bindings.push(binding);
386        }
387        for binding in other.event_bindings {
388            if self
389                .event_bindings
390                .iter()
391                .any(|existing| existing.same_identity(&binding))
392            {
393                return Err(RuntimeFailure::InvalidResolvedPlan {
394                    detail: format!(
395                        "multiple Execution Adapters prepared Event binding `{}:{}:{}`",
396                        binding.consumer_instance,
397                        binding.endpoint.capability_id(),
398                        binding.provider_instance
399                    ),
400                });
401            }
402            self.event_bindings.push(binding);
403        }
404        for (instance_key, generation) in other.generations {
405            if self
406                .generations
407                .insert(instance_key.clone(), generation)
408                .is_some()
409            {
410                return Err(RuntimeFailure::InvalidResolvedPlan {
411                    detail: format!(
412                        "multiple Execution Adapters prepared Module Instance generation `{instance_key}`"
413                    ),
414                });
415            }
416        }
417        Ok(())
418    }
419}
420
421/// Host-specific seam that instantiates Module generations and prepares endpoints.
422pub trait ExecutionAdapter: std::fmt::Debug + 'static {
423    /// Returns the open execution class implemented by this Adapter package.
424    fn execution_class(&self) -> ExecutionClassId;
425
426    /// Instantiates the exact Plan and confirms its endpoint and binding tables.
427    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure>;
428
429    /// Creates a fresh generation for one selected Module Instance.
430    ///
431    /// Adapters that cannot truthfully recreate a generation retain the default
432    /// failure, which lets Kernel apply the selected finite policy without
433    /// pretending that an in-process fault boundary is recoverable.
434    fn recreate(
435        &self,
436        _plan: &ResolvedAppPlan,
437        instance_key: &str,
438    ) -> Result<PreparedNativeModule, RuntimeFailure> {
439        Err(RuntimeFailure::Internal {
440            detail: format!("Execution Adapter cannot recreate Module Instance `{instance_key}`"),
441        })
442    }
443}
444
445/// Native Rust Adapter Interface for statically linked Module packages.
446///
447/// The blanket implementation below contributes every native Adapter to the
448/// open catalog under the official native execution-class identity.
449pub trait NativeExecutionAdapter: std::fmt::Debug + 'static {
450    /// Instantiates the exact Plan and confirms its endpoint and binding tables.
451    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure>;
452
453    /// Creates a fresh generation for one selected native Module Instance.
454    fn recreate(
455        &self,
456        _plan: &ResolvedAppPlan,
457        instance_key: &str,
458    ) -> Result<PreparedNativeModule, RuntimeFailure> {
459        Err(RuntimeFailure::Internal {
460            detail: format!("Execution Adapter cannot recreate Module Instance `{instance_key}`"),
461        })
462    }
463}
464
465impl<T: NativeExecutionAdapter> ExecutionAdapter for T {
466    fn execution_class(&self) -> ExecutionClassId {
467        ExecutionClassId::native_rust()
468    }
469
470    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
471        NativeExecutionAdapter::prepare(self, plan)
472    }
473
474    fn recreate(
475        &self,
476        plan: &ResolvedAppPlan,
477        instance_key: &str,
478    ) -> Result<PreparedNativeModule, RuntimeFailure> {
479        NativeExecutionAdapter::recreate(self, plan, instance_key)
480    }
481}
482
483/// The execution classes contributed by installed Adapter packages.
484#[derive(Clone, Debug, Default, Eq, PartialEq)]
485pub struct ExecutionClassSet(BTreeSet<ExecutionClassId>);
486
487impl ExecutionClassSet {
488    /// Returns whether an installed Adapter provides this execution class.
489    pub fn contains(&self, execution_class: &ExecutionClassId) -> bool {
490        self.0.contains(execution_class)
491    }
492
493    /// Iterates the execution classes in deterministic identity order.
494    pub fn iter(&self) -> impl Iterator<Item = &ExecutionClassId> {
495        self.0.iter()
496    }
497}
498
499/// A Runner could not assemble one unambiguous Adapter catalog.
500#[derive(Clone, Debug, Eq, PartialEq)]
501pub enum ExecutionAdapterCatalogError {
502    /// More than one installed Adapter claimed the same execution class.
503    DuplicateExecutionClass { execution_class: String },
504}
505
506impl std::fmt::Display for ExecutionAdapterCatalogError {
507    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508        match self {
509            Self::DuplicateExecutionClass { execution_class } => write!(
510                formatter,
511                "multiple Execution Adapters provide class `{execution_class}`"
512            ),
513        }
514    }
515}
516
517impl std::error::Error for ExecutionAdapterCatalogError {}
518
519/// Immutable Adapter catalog assembled by a Runner before Kernel boot.
520#[derive(Debug, Default)]
521pub struct ExecutionAdapterCatalog {
522    pub(super) adapters: BTreeMap<ExecutionClassId, Rc<dyn ExecutionAdapter>>,
523}
524
525impl ExecutionAdapterCatalog {
526    /// Creates an empty catalog for an App with no Module Instances.
527    pub fn new() -> Self {
528        Self::default()
529    }
530
531    /// Creates a catalog containing one Adapter package.
532    pub fn single(adapter: impl ExecutionAdapter) -> Self {
533        Self::new()
534            .with_adapter(adapter)
535            .expect("a new catalog cannot contain a duplicate execution class")
536    }
537
538    /// Installs one Adapter package under its open execution-class identity.
539    pub fn with_adapter(
540        self,
541        adapter: impl ExecutionAdapter,
542    ) -> Result<Self, ExecutionAdapterCatalogError> {
543        self.with_shared_adapter(Rc::new(adapter))
544    }
545
546    /// Installs an Adapter package discovered as a runtime trait object.
547    pub fn with_shared_adapter(
548        mut self,
549        adapter: Rc<dyn ExecutionAdapter>,
550    ) -> Result<Self, ExecutionAdapterCatalogError> {
551        let execution_class = adapter.execution_class();
552        if self.adapters.contains_key(&execution_class) {
553            return Err(ExecutionAdapterCatalogError::DuplicateExecutionClass {
554                execution_class: execution_class.to_string(),
555            });
556        }
557        self.adapters.insert(execution_class, adapter);
558        Ok(self)
559    }
560
561    /// Returns the effective execution classes contributed by installed packages.
562    pub fn execution_classes(&self) -> ExecutionClassSet {
563        ExecutionClassSet(self.adapters.keys().cloned().collect())
564    }
565
566    pub(super) fn adapter(
567        &self,
568        execution_class: &ExecutionClassId,
569    ) -> Option<Rc<dyn ExecutionAdapter>> {
570        self.adapters.get(execution_class).cloned()
571    }
572
573    pub(super) fn prepare(
574        &self,
575        plan: &ResolvedAppPlan,
576    ) -> Result<PreparedNativeApp, RuntimeFailure> {
577        let mut required_classes = BTreeSet::new();
578        for instance in plan.module_instances() {
579            if !self.adapters.contains_key(instance.execution_class()) {
580                return Err(RuntimeFailure::UnavailableExecutionClass {
581                    instance_key: instance.instance_key().to_owned(),
582                    execution_class: instance.execution_class().to_string(),
583                });
584            }
585            required_classes.insert(instance.execution_class().clone());
586        }
587
588        let mut prepared = PreparedNativeApp::empty();
589        for execution_class in required_classes {
590            let adapter = self
591                .adapters
592                .get(&execution_class)
593                .expect("required execution classes were validated");
594            prepared.merge(adapter.prepare(plan)?)?;
595        }
596        Ok(prepared)
597    }
598}