Skip to main content

lenso_kernel/
prepared.rs

1use std::any::Any;
2
3use super::{
4    BTreeMap, BTreeSet, ErasedDomainResult, ErasedValue, ExecutionClassId, InvocationContext,
5    LocalBoxFuture, NativeEventEndpoint, NativeStreamEndpoint, PluginLifecycle, 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 Plugin 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 Plugin Instance generation returned by an Execution Adapter.
72#[derive(Debug)]
73pub struct PreparedNativePlugin {
74    pub(super) endpoints: NativeEndpointSet,
75    pub(super) lifecycle: Rc<dyn PluginLifecycle>,
76}
77
78impl PreparedNativePlugin {
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 PluginLifecycle,
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 PluginLifecycle>,
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 PluginLifecycle,
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 PluginLifecycle>,
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 PluginLifecycle,
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 PluginLifecycle,
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 PluginLifecycle,
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 PluginLifecycle> {
170        self.lifecycle.clone()
171    }
172
173    pub(super) fn into_parts(self) -> (NativeEndpointSet, Rc<dyn PluginLifecycle>) {
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) requirement_id: String,
182    pub(super) consumer_instance: String,
183    pub(super) provider_instance: String,
184    pub(super) endpoint: Rc<dyn NativeRequestEndpoint>,
185}
186
187/// One provider-specific bidirectional stream binding prepared by an Adapter.
188#[derive(Clone, Debug)]
189pub struct PreparedStreamBinding {
190    pub(super) requirement_id: String,
191    pub(super) consumer_instance: String,
192    pub(super) provider_instance: String,
193    pub(super) endpoint: Rc<dyn NativeStreamEndpoint>,
194}
195
196/// One provider-specific ephemeral Event binding prepared by an Adapter.
197#[derive(Clone, Debug)]
198pub struct PreparedEventBinding {
199    pub(super) requirement_id: String,
200    pub(super) consumer_instance: String,
201    pub(super) provider_instance: String,
202    pub(super) endpoint: Rc<dyn NativeEventEndpoint>,
203}
204
205impl PreparedEventBinding {
206    /// Binds one consumer to one exact Event endpoint and provider Instance.
207    pub fn new(
208        consumer_instance: impl Into<String>,
209        provider_instance: impl Into<String>,
210        endpoint: Rc<dyn NativeEventEndpoint>,
211    ) -> Self {
212        Self {
213            requirement_id: format!("~{}", endpoint.capability_id()),
214            consumer_instance: consumer_instance.into(),
215            provider_instance: provider_instance.into(),
216            endpoint,
217        }
218    }
219
220    /// Selects the consumer-local requirement prepared by this Adapter.
221    #[must_use]
222    pub fn with_requirement_id(mut self, id: impl Into<String>) -> Self {
223        self.requirement_id = id.into();
224        self
225    }
226
227    /// Returns the exact consumer-local requirement identity.
228    pub fn requirement_id(&self) -> &str {
229        &self.requirement_id
230    }
231
232    /// Returns the App-local consumer Instance selected by the Plan.
233    pub fn consumer_instance(&self) -> &str {
234        &self.consumer_instance
235    }
236
237    /// Returns the App-local provider Instance selected by the Plan.
238    pub fn provider_instance(&self) -> &str {
239        &self.provider_instance
240    }
241
242    /// Returns the exact prepared Event endpoint referenced by this binding.
243    pub fn endpoint(&self) -> Rc<dyn NativeEventEndpoint> {
244        self.endpoint.clone()
245    }
246
247    pub(super) fn same_identity(&self, other: &Self) -> bool {
248        self.requirement_id == other.requirement_id
249            && self.consumer_instance == other.consumer_instance
250            && self.provider_instance == other.provider_instance
251            && self.endpoint.capability_id() == other.endpoint.capability_id()
252    }
253}
254
255impl PreparedStreamBinding {
256    /// Binds one consumer to one exact stream endpoint and provider Instance.
257    pub fn new(
258        consumer_instance: impl Into<String>,
259        provider_instance: impl Into<String>,
260        endpoint: Rc<dyn NativeStreamEndpoint>,
261    ) -> Self {
262        Self {
263            requirement_id: format!("~{}", endpoint.capability_id()),
264            consumer_instance: consumer_instance.into(),
265            provider_instance: provider_instance.into(),
266            endpoint,
267        }
268    }
269
270    /// Selects the consumer-local requirement prepared by this Adapter.
271    #[must_use]
272    pub fn with_requirement_id(mut self, id: impl Into<String>) -> Self {
273        self.requirement_id = id.into();
274        self
275    }
276
277    /// Returns the exact consumer-local requirement identity.
278    pub fn requirement_id(&self) -> &str {
279        &self.requirement_id
280    }
281
282    /// Returns the App-local consumer Instance selected by the Plan.
283    pub fn consumer_instance(&self) -> &str {
284        &self.consumer_instance
285    }
286
287    /// Returns the App-local provider Instance selected by the Plan.
288    pub fn provider_instance(&self) -> &str {
289        &self.provider_instance
290    }
291
292    /// Returns the exact prepared stream endpoint referenced by this binding.
293    pub fn endpoint(&self) -> Rc<dyn NativeStreamEndpoint> {
294        self.endpoint.clone()
295    }
296
297    pub(super) fn same_identity(&self, other: &Self) -> bool {
298        self.requirement_id == other.requirement_id
299            && self.consumer_instance == other.consumer_instance
300            && self.provider_instance == other.provider_instance
301            && self.endpoint.capability_id() == other.endpoint.capability_id()
302    }
303}
304
305impl PreparedBinding {
306    /// Binds one consumer to the endpoint prepared for one exact provider Instance.
307    pub fn new(
308        consumer_instance: impl Into<String>,
309        provider_instance: impl Into<String>,
310        endpoint: Rc<dyn NativeRequestEndpoint>,
311    ) -> Self {
312        Self {
313            requirement_id: format!("~{}", endpoint.capability_id()),
314            consumer_instance: consumer_instance.into(),
315            provider_instance: provider_instance.into(),
316            endpoint,
317        }
318    }
319
320    /// Selects the consumer-local requirement prepared by this Adapter.
321    #[must_use]
322    pub fn with_requirement_id(mut self, id: impl Into<String>) -> Self {
323        self.requirement_id = id.into();
324        self
325    }
326
327    /// Returns the exact consumer-local requirement identity.
328    pub fn requirement_id(&self) -> &str {
329        &self.requirement_id
330    }
331
332    /// Returns the App-local consumer Instance selected by the Plan.
333    pub fn consumer_instance(&self) -> &str {
334        &self.consumer_instance
335    }
336
337    /// Returns the App-local provider Instance selected by the Plan.
338    pub fn provider_instance(&self) -> &str {
339        &self.provider_instance
340    }
341
342    /// Returns the exact prepared endpoint referenced by this binding.
343    pub fn endpoint(&self) -> Rc<dyn NativeRequestEndpoint> {
344        self.endpoint.clone()
345    }
346
347    pub(super) fn same_identity(&self, other: &Self) -> bool {
348        self.requirement_id == other.requirement_id
349            && self.consumer_instance == other.consumer_instance
350            && self.provider_instance == other.provider_instance
351            && self.endpoint.capability_id() == other.endpoint.capability_id()
352    }
353}
354
355/// Prepared native bindings returned by an Execution Adapter to Kernel.
356#[derive(Debug)]
357pub struct PreparedNativeApp {
358    pub(super) bindings: Vec<PreparedBinding>,
359    pub(super) stream_bindings: Vec<PreparedStreamBinding>,
360    pub(super) event_bindings: Vec<PreparedEventBinding>,
361    pub(super) generations: BTreeMap<String, PreparedNativePlugin>,
362}
363
364impl PreparedNativeApp {
365    /// Completes Adapter preparation with the full generation and binding tables.
366    pub fn new(
367        bindings: Vec<PreparedBinding>,
368        generations: BTreeMap<String, PreparedNativePlugin>,
369    ) -> Self {
370        Self {
371            bindings,
372            stream_bindings: Vec::new(),
373            event_bindings: Vec::new(),
374            generations,
375        }
376    }
377
378    /// Creates the complete Adapter result for an empty Plan.
379    pub fn empty() -> Self {
380        Self::new(Vec::new(), BTreeMap::new())
381    }
382
383    /// Adds the exact bidirectional stream bindings prepared by an Adapter.
384    #[must_use]
385    pub fn with_stream_bindings(mut self, stream_bindings: Vec<PreparedStreamBinding>) -> Self {
386        self.stream_bindings = stream_bindings;
387        self
388    }
389
390    /// Adds the exact ephemeral Event bindings prepared by an Adapter.
391    #[must_use]
392    pub fn with_event_bindings(mut self, event_bindings: Vec<PreparedEventBinding>) -> Self {
393        self.event_bindings = event_bindings;
394        self
395    }
396
397    pub(super) fn merge(&mut self, other: Self) -> Result<(), RuntimeFailure> {
398        for binding in other.bindings {
399            if self
400                .bindings
401                .iter()
402                .any(|existing| existing.same_identity(&binding))
403            {
404                return Err(RuntimeFailure::InvalidResolvedPlan {
405                    detail: format!(
406                        "multiple Execution Adapters prepared binding `{}:{}:{}`",
407                        binding.consumer_instance,
408                        binding.endpoint.capability_id(),
409                        binding.provider_instance
410                    ),
411                });
412            }
413            self.bindings.push(binding);
414        }
415        for binding in other.stream_bindings {
416            if self
417                .stream_bindings
418                .iter()
419                .any(|existing| existing.same_identity(&binding))
420            {
421                return Err(RuntimeFailure::InvalidResolvedPlan {
422                    detail: format!(
423                        "multiple Execution Adapters prepared stream binding `{}:{}:{}`",
424                        binding.consumer_instance,
425                        binding.endpoint.capability_id(),
426                        binding.provider_instance
427                    ),
428                });
429            }
430            self.stream_bindings.push(binding);
431        }
432        for binding in other.event_bindings {
433            if self
434                .event_bindings
435                .iter()
436                .any(|existing| existing.same_identity(&binding))
437            {
438                return Err(RuntimeFailure::InvalidResolvedPlan {
439                    detail: format!(
440                        "multiple Execution Adapters prepared Event binding `{}:{}:{}`",
441                        binding.consumer_instance,
442                        binding.endpoint.capability_id(),
443                        binding.provider_instance
444                    ),
445                });
446            }
447            self.event_bindings.push(binding);
448        }
449        for (instance_key, generation) in other.generations {
450            if self
451                .generations
452                .insert(instance_key.clone(), generation)
453                .is_some()
454            {
455                return Err(RuntimeFailure::InvalidResolvedPlan {
456                    detail: format!(
457                        "multiple Execution Adapters prepared Plugin Instance generation `{instance_key}`"
458                    ),
459                });
460            }
461        }
462        Ok(())
463    }
464}
465
466/// Host-specific seam that instantiates Plugin generations and prepares endpoints.
467pub trait ExecutionAdapter: std::fmt::Debug + 'static {
468    /// Confirms a versioned authoring/profile pair before any Plugin is prepared.
469    fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
470        authoring_version == 1
471            && (profile == self.execution_class().as_str()
472                || (self.execution_class() == ExecutionClassId::native_rust()
473                    && profile == "lenso.native-authoring@1"))
474    }
475    /// Returns the open execution class implemented by this Adapter package.
476    fn execution_class(&self) -> ExecutionClassId;
477
478    /// Instantiates the exact Plan and confirms its endpoint and binding tables.
479    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure>;
480
481    /// Creates a fresh generation for one selected Plugin Instance.
482    ///
483    /// Adapters that cannot truthfully recreate a generation retain the default
484    /// failure, which lets Kernel apply the selected finite policy without
485    /// pretending that an in-process fault boundary is recoverable.
486    fn recreate(
487        &self,
488        _plan: &ResolvedAppPlan,
489        instance_key: &str,
490    ) -> Result<PreparedNativePlugin, RuntimeFailure> {
491        Err(RuntimeFailure::Internal {
492            detail: format!("Execution Adapter cannot recreate Plugin Instance `{instance_key}`"),
493        })
494    }
495}
496
497/// Native Rust Adapter Interface for statically linked Plugin Releases.
498///
499/// The blanket implementation below contributes every native Adapter to the
500/// open catalog under the official native execution-class identity.
501pub trait NativeExecutionAdapter: std::fmt::Debug + 'static {
502    /// Old native Adapters must opt in explicitly before receiving version 2 contracts.
503    fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
504        authoring_version == 1
505            && matches!(profile, "lenso.native-authoring@1" | "lenso.native-rust@1")
506    }
507    /// Instantiates the exact Plan and confirms its endpoint and binding tables.
508    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure>;
509
510    /// Creates a fresh generation for one selected native Plugin Instance.
511    fn recreate(
512        &self,
513        _plan: &ResolvedAppPlan,
514        instance_key: &str,
515    ) -> Result<PreparedNativePlugin, RuntimeFailure> {
516        Err(RuntimeFailure::Internal {
517            detail: format!("Execution Adapter cannot recreate Plugin Instance `{instance_key}`"),
518        })
519    }
520}
521
522impl<T: NativeExecutionAdapter> ExecutionAdapter for T {
523    fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
524        NativeExecutionAdapter::supports_runtime_profile(self, authoring_version, profile)
525    }
526    fn execution_class(&self) -> ExecutionClassId {
527        ExecutionClassId::native_rust()
528    }
529
530    fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
531        NativeExecutionAdapter::prepare(self, plan)
532    }
533
534    fn recreate(
535        &self,
536        plan: &ResolvedAppPlan,
537        instance_key: &str,
538    ) -> Result<PreparedNativePlugin, RuntimeFailure> {
539        NativeExecutionAdapter::recreate(self, plan, instance_key)
540    }
541}
542
543/// The execution classes contributed by installed Adapter packages.
544#[derive(Clone, Debug, Default, Eq, PartialEq)]
545pub struct ExecutionClassSet(BTreeSet<ExecutionClassId>);
546
547impl ExecutionClassSet {
548    /// Returns whether an installed Adapter provides this execution class.
549    pub fn contains(&self, execution_class: &ExecutionClassId) -> bool {
550        self.0.contains(execution_class)
551    }
552
553    /// Iterates the execution classes in deterministic identity order.
554    pub fn iter(&self) -> impl Iterator<Item = &ExecutionClassId> {
555        self.0.iter()
556    }
557}
558
559/// A Runner could not assemble one unambiguous Adapter catalog.
560#[derive(Clone, Debug, Eq, PartialEq)]
561pub enum ExecutionAdapterCatalogError {
562    /// More than one installed Adapter claimed the same execution class.
563    DuplicateExecutionClass { execution_class: String },
564}
565
566impl std::fmt::Display for ExecutionAdapterCatalogError {
567    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568        match self {
569            Self::DuplicateExecutionClass { execution_class } => write!(
570                formatter,
571                "multiple Execution Adapters provide class `{execution_class}`"
572            ),
573        }
574    }
575}
576
577impl std::error::Error for ExecutionAdapterCatalogError {}
578
579/// Immutable Adapter catalog assembled by a Runner before Kernel boot.
580#[derive(Debug, Default)]
581pub struct ExecutionAdapterCatalog {
582    pub(super) adapters: BTreeMap<ExecutionClassId, Rc<dyn ExecutionAdapter>>,
583}
584
585impl ExecutionAdapterCatalog {
586    /// Creates an empty catalog for an App with no Plugin Instances.
587    pub fn new() -> Self {
588        Self::default()
589    }
590
591    /// Creates a catalog containing one Adapter package.
592    pub fn single(adapter: impl ExecutionAdapter) -> Self {
593        Self::new()
594            .with_adapter(adapter)
595            .expect("a new catalog cannot contain a duplicate execution class")
596    }
597
598    /// Installs one Adapter package under its open execution-class identity.
599    pub fn with_adapter(
600        self,
601        adapter: impl ExecutionAdapter,
602    ) -> Result<Self, ExecutionAdapterCatalogError> {
603        self.with_shared_adapter(Rc::new(adapter))
604    }
605
606    /// Installs an Adapter package discovered as a runtime trait object.
607    pub fn with_shared_adapter(
608        mut self,
609        adapter: Rc<dyn ExecutionAdapter>,
610    ) -> Result<Self, ExecutionAdapterCatalogError> {
611        let execution_class = adapter.execution_class();
612        if self.adapters.contains_key(&execution_class) {
613            return Err(ExecutionAdapterCatalogError::DuplicateExecutionClass {
614                execution_class: execution_class.to_string(),
615            });
616        }
617        self.adapters.insert(execution_class, adapter);
618        Ok(self)
619    }
620
621    /// Returns the effective execution classes contributed by installed packages.
622    pub fn execution_classes(&self) -> ExecutionClassSet {
623        ExecutionClassSet(self.adapters.keys().cloned().collect())
624    }
625
626    pub(super) fn adapter(
627        &self,
628        execution_class: &ExecutionClassId,
629    ) -> Option<Rc<dyn ExecutionAdapter>> {
630        self.adapters.get(execution_class).cloned()
631    }
632
633    pub(super) fn prepare(
634        &self,
635        plan: &ResolvedAppPlan,
636    ) -> Result<PreparedNativeApp, RuntimeFailure> {
637        let mut required_classes = BTreeSet::new();
638        for instance in plan.plugin_instances() {
639            if !self.adapters.contains_key(instance.execution_class()) {
640                return Err(RuntimeFailure::UnavailableExecutionClass {
641                    instance_key: instance.instance_key().to_owned(),
642                    execution_class: instance.execution_class().to_string(),
643                });
644            }
645            required_classes.insert(instance.execution_class().clone());
646            if !self.adapters[instance.execution_class()]
647                .supports_runtime_profile(instance.authoring_version(), instance.runtime_profile())
648            {
649                return Err(RuntimeFailure::InvalidResolvedPlan {
650                    detail: format!(
651                        "Adapter `{}` does not support authoring {} profile `{}` for `{}`",
652                        instance.execution_class(),
653                        instance.authoring_version(),
654                        instance.runtime_profile(),
655                        instance.instance_key()
656                    ),
657                });
658            }
659        }
660
661        let mut prepared = PreparedNativeApp::empty();
662        for execution_class in required_classes {
663            let adapter = self
664                .adapters
665                .get(&execution_class)
666                .expect("required execution classes were validated");
667            prepared.merge(adapter.prepare(plan)?)?;
668        }
669        Ok(prepared)
670    }
671}