Skip to main content

lenso_kernel/
kernel.rs

1use super::{
2    ActivateContext, AppAdmission, AppReadyGate, BTreeMap, CancellationToken, Cell,
3    DeactivationReason, DriverControl, ExecutionAdapterCatalog, ManagedResourceScope,
4    ManagedTaskScope, ModuleDependencies, ModuleDependency, ModuleDependencyHandle,
5    ModuleEventDependencyHandle, ModuleStreamDependencyHandle, NativeApp, NativeAppRuntime,
6    NativeBindingTable, NativeEndpointBinding, NativeEndpointState, NativeEndpointStateTable,
7    NativeEventBindingTable, NativeEventEndpointStateTable, NativeExecutionAdapter,
8    NativeModuleGeneration, NativeModuleRuntime, NativeStreamBindingTable,
9    NativeStreamEndpointBinding, NativeStreamEndpointState, NativeStreamEndpointStateTable,
10    PlanResolutionError, PrepareContext, PreparedBinding, PreparedEventBinding, PreparedNativeApp,
11    PreparedNativeModule, PreparedStreamBinding, Rc, RefCell, RequestAdmission, ResolvedAppPlan,
12    RuntimeDiagnostics, RuntimeDriver, RuntimeFailure, ShutdownCoordinator, Weak,
13    begin_module_supervision, deactivate_in_reverse, event, handle_supervision_schedule_failure,
14    module_supervision, schedule_module_supervision, validate_native_endpoint_set,
15};
16
17/// A reason the Kernel rejected a Resolved App Plan before boot.
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum PlanValidationError {
20    /// The Plan schema cannot be executed by this Kernel version.
21    UnsupportedSchemaVersion { expected: u32, actual: u32 },
22    /// The Plan graph is structurally invalid and cannot be booted.
23    InvalidResolvedPlan { detail: String },
24}
25
26impl std::fmt::Display for PlanValidationError {
27    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::UnsupportedSchemaVersion { expected, actual } => write!(
30                formatter,
31                "unsupported Resolved App Plan schema {actual}; expected {expected}"
32            ),
33            Self::InvalidResolvedPlan { detail } => {
34                write!(formatter, "invalid Resolved App Plan: {detail}")
35            }
36        }
37    }
38}
39
40impl std::error::Error for PlanValidationError {}
41
42/// The portable App execution engine.
43#[derive(Debug)]
44pub struct Kernel;
45
46impl Kernel {
47    /// Starts one App backed by a single statically linked native Adapter package.
48    pub async fn start_native<D: RuntimeDriver, A: NativeExecutionAdapter>(
49        plan: ResolvedAppPlan,
50        driver: D,
51        adapter: A,
52    ) -> Result<NativeApp, RuntimeFailure> {
53        Self::start_native_with_diagnostics(plan, driver, adapter, RuntimeDiagnostics::new()).await
54    }
55
56    /// Starts one native Adapter package with an opt-in Runtime Diagnostics port.
57    pub async fn start_native_with_diagnostics<D: RuntimeDriver, A: NativeExecutionAdapter>(
58        plan: ResolvedAppPlan,
59        driver: D,
60        adapter: A,
61        diagnostics: RuntimeDiagnostics,
62    ) -> Result<NativeApp, RuntimeFailure> {
63        Self::start_with_diagnostics(
64            plan,
65            driver,
66            ExecutionAdapterCatalog::single(adapter),
67            diagnostics,
68        )
69        .await
70    }
71
72    /// Starts Module Instances through the Adapter catalog assembled by the Runner.
73    pub async fn start<D: RuntimeDriver>(
74        plan: ResolvedAppPlan,
75        driver: D,
76        adapters: ExecutionAdapterCatalog,
77    ) -> Result<NativeApp, RuntimeFailure> {
78        Self::start_with_diagnostics(plan, driver, adapters, RuntimeDiagnostics::new()).await
79    }
80
81    /// Starts an App with an opt-in Runtime Diagnostics port.
82    #[allow(
83        clippy::too_many_lines,
84        reason = "startup remains linear so validation, preparation, and activation fail closed in order"
85    )]
86    pub async fn start_with_diagnostics<D: RuntimeDriver>(
87        plan: ResolvedAppPlan,
88        driver: D,
89        adapters: ExecutionAdapterCatalog,
90        diagnostics: RuntimeDiagnostics,
91    ) -> Result<NativeApp, RuntimeFailure> {
92        if let Err(error) = plan.validate() {
93            let error = runtime_plan_error(&error);
94            diagnostics.emit_runtime_failure(driver.now(), None, &error);
95            return Err(error);
96        }
97
98        let activation_order = match plan.activation_order() {
99            Ok(order) => order,
100            Err(error) => {
101                let error = runtime_plan_error(&error);
102                diagnostics.emit_runtime_failure(driver.now(), None, &error);
103                return Err(error);
104            }
105        };
106        let adapters = Rc::new(adapters);
107        let PreparedNativeApp {
108            bindings: prepared_bindings,
109            stream_bindings: prepared_stream_bindings,
110            event_bindings: prepared_event_bindings,
111            generations,
112        } = match adapters.prepare(&plan) {
113            Ok(prepared) => prepared,
114            Err(error) => {
115                diagnostics.emit_runtime_failure(driver.now(), None, &error);
116                return Err(error);
117            }
118        };
119        if let Err(error) = validate_prepared_native_app(
120            &plan,
121            &prepared_bindings,
122            &prepared_stream_bindings,
123            &prepared_event_bindings,
124            &generations,
125        ) {
126            diagnostics.emit_runtime_failure(driver.now(), None, &error);
127            return Err(error);
128        }
129        let (bindings, endpoint_states) = native_bindings(&plan, &prepared_bindings);
130        let (stream_bindings, stream_endpoint_states) =
131            native_stream_bindings(&plan, &prepared_stream_bindings);
132        let (event_bindings, event_endpoint_states) =
133            native_event_bindings(&plan, &prepared_event_bindings);
134        let runtime_link = Rc::new(RefCell::new(Weak::new()));
135        let dependencies = module_dependencies(
136            &plan,
137            &bindings,
138            &stream_bindings,
139            &event_bindings,
140            &runtime_link,
141        );
142        let driver_control = DriverControl::new(&driver);
143        let admission = AppAdmission::new();
144        let module_runtimes = native_module_runtimes(&plan, &driver, generations);
145        let ready_gate = AppReadyGate::new();
146        let supervision = module_supervision(&plan);
147        let runtime = Rc::new(NativeAppRuntime {
148            plan,
149            adapters,
150            modules: module_runtimes,
151            dependencies,
152            endpoint_states,
153            stream_endpoint_states,
154            event_endpoint_states,
155            supervision: RefCell::new(supervision),
156            supervision_tasks: RefCell::new(BTreeMap::new()),
157            activation_order,
158            ready_gate,
159            admission,
160            driver: driver_control,
161            diagnostics: diagnostics.clone(),
162            request_ids: Rc::new(Cell::new(1)),
163            supervision_cancellation: CancellationToken::new(),
164            shutdown_started: Cell::new(false),
165            shutdown: ShutdownCoordinator::default(),
166            shutdown_task: RefCell::new(None),
167            terminal_failure: RefCell::new(None),
168        });
169        runtime_link.replace(Rc::downgrade(&runtime));
170        attach_managed_task_failure_handlers(&runtime);
171        runtime.diagnostics.emit(
172            super::DiagnosticSource::Lifecycle,
173            (runtime.driver.now)(),
174            |_| super::DiagnosticEvent::AppStarted {
175                module_count: runtime.plan.module_instances().len(),
176            },
177        );
178        let prepared_instances = prepare_native_modules(&runtime).await?;
179        if let Err(error) = activate_native_modules(&runtime).await {
180            let _ = deactivate_in_reverse(
181                &runtime.modules,
182                &runtime.dependencies,
183                &prepared_instances,
184                DeactivationReason::StartupRollback,
185                &runtime.admission,
186                &runtime.diagnostics,
187                &runtime.driver,
188            )
189            .await;
190            runtime
191                .diagnostics
192                .emit_runtime_failure((runtime.driver.now)(), None, &error);
193            return Err(error);
194        }
195        open_native_readiness(&runtime).await;
196        Ok(NativeApp {
197            bindings,
198            stream_bindings,
199            event_bindings,
200            diagnostics,
201            runtime,
202        })
203    }
204}
205
206pub(super) fn attach_managed_task_failure_handlers(runtime: &Rc<NativeAppRuntime>) {
207    for (instance_key, module) in &runtime.modules {
208        let Some((_, tasks, _)) = module.generation_parts() else {
209            continue;
210        };
211        attach_managed_task_failure_handler(runtime, instance_key, &tasks);
212    }
213}
214
215pub(super) fn attach_managed_task_failure_handler(
216    runtime: &Rc<NativeAppRuntime>,
217    instance_key: &str,
218    tasks: &ManagedTaskScope,
219) {
220    let task_runtime = Rc::downgrade(runtime);
221    let task_instance_key = instance_key.to_owned();
222    let handler: Rc<dyn Fn()> = Rc::new(move || {
223        let Some(runtime) = task_runtime.upgrade() else {
224            return;
225        };
226        if begin_module_supervision(&runtime, &task_instance_key).unwrap_or(false)
227            && let Err(error) = schedule_module_supervision(&runtime, &task_instance_key)
228        {
229            let _ = handle_supervision_schedule_failure(&runtime, &task_instance_key, error);
230        }
231    });
232    tasks.set_failure_handler(&handler);
233}
234
235pub(super) fn runtime_plan_error(error: &PlanResolutionError) -> RuntimeFailure {
236    RuntimeFailure::InvalidResolvedPlan {
237        detail: error.to_string(),
238    }
239}
240
241#[allow(
242    clippy::too_many_lines,
243    reason = "one fail-closed pass keeps request, stream, event, and generation validation aligned"
244)]
245pub(super) fn validate_prepared_native_app(
246    plan: &ResolvedAppPlan,
247    bindings: &[PreparedBinding],
248    stream_bindings: &[PreparedStreamBinding],
249    event_bindings: &[PreparedEventBinding],
250    generations: &BTreeMap<String, PreparedNativeModule>,
251) -> Result<(), RuntimeFailure> {
252    if generations.len() != plan.module_instances().len() {
253        return Err(RuntimeFailure::InvalidResolvedPlan {
254            detail: format!(
255                "Execution Adapters prepared {} Module generations; expected {}",
256                generations.len(),
257                plan.module_instances().len()
258            ),
259        });
260    }
261    for instance in plan.module_instances() {
262        let generation = generations.get(instance.instance_key()).ok_or_else(|| {
263            RuntimeFailure::InvalidResolvedPlan {
264                detail: format!(
265                    "Execution Adapters did not prepare Module Instance `{}`",
266                    instance.instance_key()
267                ),
268            }
269        })?;
270        validate_native_endpoint_set(
271            instance.instance_key(),
272            instance,
273            generation.endpoints(),
274            generation.stream_endpoints(),
275            generation.event_endpoints(),
276        )?;
277    }
278    if let Some(instance_key) = generations.keys().find(|instance_key| {
279        !plan
280            .module_instances()
281            .iter()
282            .any(|instance| instance.instance_key() == instance_key.as_str())
283    }) {
284        return Err(RuntimeFailure::InvalidResolvedPlan {
285            detail: format!("Execution Adapter prepared unknown Module Instance `{instance_key}`"),
286        });
287    }
288
289    let expected_request_bindings = plan
290        .capability_bindings()
291        .iter()
292        .filter(|binding| {
293            plan.module_instance(binding.provider_instance())
294                .and_then(|provider| {
295                    provider
296                        .provided_capabilities()
297                        .iter()
298                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
299                })
300                .is_some_and(|endpoint| !endpoint.request_operations().is_empty())
301        })
302        .count();
303    let expected_stream_bindings = plan
304        .capability_bindings()
305        .iter()
306        .filter(|binding| {
307            plan.module_instance(binding.provider_instance())
308                .and_then(|provider| {
309                    provider
310                        .provided_capabilities()
311                        .iter()
312                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
313                })
314                .is_some_and(|endpoint| !endpoint.stream_operations().is_empty())
315        })
316        .count();
317    let expected_event_bindings = plan
318        .capability_bindings()
319        .iter()
320        .filter(|binding| {
321            plan.module_instance(binding.provider_instance())
322                .and_then(|provider| {
323                    provider
324                        .provided_capabilities()
325                        .iter()
326                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
327                })
328                .is_some_and(|endpoint| !endpoint.event_operations().is_empty())
329        })
330        .count();
331    if bindings.len() != expected_request_bindings {
332        return Err(RuntimeFailure::InvalidResolvedPlan {
333            detail: if expected_stream_bindings == 0 && stream_bindings.is_empty() {
334                format!(
335                    "Execution Adapters prepared {} bindings; expected {}",
336                    bindings.len(),
337                    expected_request_bindings
338                )
339            } else {
340                format!(
341                    "Execution Adapters prepared {} request bindings; expected {}",
342                    bindings.len(),
343                    expected_request_bindings
344                )
345            },
346        });
347    }
348    if stream_bindings.len() != expected_stream_bindings {
349        return Err(RuntimeFailure::InvalidResolvedPlan {
350            detail: format!(
351                "Execution Adapters prepared {} stream bindings; expected {}",
352                stream_bindings.len(),
353                expected_stream_bindings
354            ),
355        });
356    }
357    if event_bindings.len() != expected_event_bindings {
358        return Err(RuntimeFailure::InvalidResolvedPlan {
359            detail: format!(
360                "Execution Adapters prepared {} Event bindings; expected {}",
361                event_bindings.len(),
362                expected_event_bindings
363            ),
364        });
365    }
366    for planned in plan.capability_bindings() {
367        let provider = generations
368            .get(planned.provider_instance())
369            .expect("the resolved Plan references one validated provider generation");
370        let descriptor = plan
371            .module_instance(planned.provider_instance())
372            .and_then(|provider| {
373                provider
374                    .provided_capabilities()
375                    .iter()
376                    .find(|endpoint| endpoint.capability_id() == planned.capability_id())
377            })
378            .expect("the resolved Plan references one validated provider endpoint");
379        if !descriptor.request_operations().is_empty() {
380            let matching: Vec<_> = bindings
381                .iter()
382                .filter(|prepared| {
383                    prepared.consumer_instance == planned.consumer_instance()
384                        && prepared.provider_instance == planned.provider_instance()
385                        && prepared.endpoint.capability_id() == planned.capability_id()
386                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
387                })
388                .collect();
389            if matching.len() != 1 {
390                return Err(RuntimeFailure::InvalidResolvedPlan {
391                    detail: format!(
392                        "Execution Adapters prepared {} request bindings for `{}:{}:{}`; expected 1",
393                        matching.len(),
394                        planned.consumer_instance(),
395                        planned.capability_id(),
396                        planned.provider_instance()
397                    ),
398                });
399            }
400            if !provider
401                .endpoints()
402                .iter()
403                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
404            {
405                return Err(RuntimeFailure::InvalidResolvedPlan {
406                    detail: format!(
407                        "request binding `{}:{}:{}` does not reference its provider generation endpoint",
408                        planned.consumer_instance(),
409                        planned.capability_id(),
410                        planned.provider_instance()
411                    ),
412                });
413            }
414        }
415        if !descriptor.stream_operations().is_empty() {
416            let matching: Vec<_> = stream_bindings
417                .iter()
418                .filter(|prepared| {
419                    prepared.consumer_instance == planned.consumer_instance()
420                        && prepared.provider_instance == planned.provider_instance()
421                        && prepared.endpoint.capability_id() == planned.capability_id()
422                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
423                })
424                .collect();
425            if matching.len() != 1 {
426                return Err(RuntimeFailure::InvalidResolvedPlan {
427                    detail: format!(
428                        "Execution Adapters prepared {} stream bindings for `{}:{}:{}`; expected 1",
429                        matching.len(),
430                        planned.consumer_instance(),
431                        planned.capability_id(),
432                        planned.provider_instance()
433                    ),
434                });
435            }
436            if !provider
437                .stream_endpoints()
438                .iter()
439                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
440            {
441                return Err(RuntimeFailure::InvalidResolvedPlan {
442                    detail: format!(
443                        "stream binding `{}:{}:{}` does not reference its provider generation endpoint",
444                        planned.consumer_instance(),
445                        planned.capability_id(),
446                        planned.provider_instance()
447                    ),
448                });
449            }
450        }
451        if !descriptor.event_operations().is_empty() {
452            let matching: Vec<_> = event_bindings
453                .iter()
454                .filter(|prepared| {
455                    prepared.consumer_instance == planned.consumer_instance()
456                        && prepared.provider_instance == planned.provider_instance()
457                        && prepared.endpoint.capability_id() == planned.capability_id()
458                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
459                })
460                .collect();
461            if matching.len() != 1 {
462                return Err(RuntimeFailure::InvalidResolvedPlan {
463                    detail: format!(
464                        "Execution Adapters prepared {} Event bindings for `{}:{}:{}`; expected 1",
465                        matching.len(),
466                        planned.consumer_instance(),
467                        planned.capability_id(),
468                        planned.provider_instance()
469                    ),
470                });
471            }
472            if !provider
473                .event_endpoints()
474                .iter()
475                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
476            {
477                return Err(RuntimeFailure::InvalidResolvedPlan {
478                    detail: format!(
479                        "Event binding `{}:{}:{}` does not reference its provider generation endpoint",
480                        planned.consumer_instance(),
481                        planned.capability_id(),
482                        planned.provider_instance()
483                    ),
484                });
485            }
486        }
487    }
488    Ok(())
489}
490
491pub(super) fn native_module_runtimes<D: RuntimeDriver>(
492    plan: &ResolvedAppPlan,
493    driver: &D,
494    mut generations: BTreeMap<String, PreparedNativeModule>,
495) -> BTreeMap<String, NativeModuleRuntime> {
496    let mut runtimes = BTreeMap::new();
497    for instance in plan.module_instances() {
498        let lifecycle = generations
499            .remove(instance.instance_key())
500            .map(|generation| generation.lifecycle())
501            .expect("prepared App validation requires one generation per planned Instance");
502        runtimes.insert(
503            instance.instance_key().to_owned(),
504            NativeModuleRuntime {
505                generation: RefCell::new(Some(NativeModuleGeneration {
506                    lifecycle,
507                    tasks: ManagedTaskScope::new(driver),
508                    resources: ManagedResourceScope::new(),
509                })),
510            },
511        );
512    }
513    runtimes
514}
515
516pub(super) async fn prepare_native_modules(
517    runtime: &Rc<NativeAppRuntime>,
518) -> Result<Vec<String>, RuntimeFailure> {
519    let mut prepared_instances = Vec::with_capacity(runtime.activation_order.len());
520    for instance_key in &runtime.activation_order {
521        let instance = runtime
522            .plan
523            .module_instances()
524            .iter()
525            .find(|instance| instance.instance_key() == instance_key)
526            .expect("activation order only contains planned Module Instances");
527        let module = runtime
528            .modules
529            .get(instance_key)
530            .expect("activation order only contains planned Module Instances");
531        let (lifecycle, tasks, resources) = module
532            .generation_parts()
533            .expect("every startup Module Instance has a generation");
534        let cancellation = tasks.cancellation();
535        prepared_instances.push(instance_key.clone());
536        let started_at = (runtime.driver.now)();
537        runtime
538            .diagnostics
539            .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
540                super::DiagnosticEvent::LifecycleStarted {
541                    instance: instance_key.clone(),
542                    generation: 1,
543                    phase: super::ModuleLifecyclePhase::Prepare,
544                }
545            });
546        let context = PrepareContext {
547            instance_key: instance_key.clone(),
548            entrypoint: instance.entrypoint().to_owned(),
549            configuration: instance.configuration().to_owned(),
550            dependencies: runtime
551                .dependencies
552                .get(instance_key)
553                .cloned()
554                .unwrap_or_default(),
555            resources,
556            cancellation,
557            admission: runtime.admission.clone(),
558        };
559        let result = lifecycle.prepare(context).await;
560        let outcome = result.as_ref().map_or_else(
561            |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
562            |()| super::DiagnosticOutcome::Succeeded,
563        );
564        runtime.diagnostics.emit(
565            super::DiagnosticSource::Lifecycle,
566            (runtime.driver.now)(),
567            |_| super::DiagnosticEvent::LifecycleCompleted {
568                instance: instance_key.clone(),
569                generation: 1,
570                phase: super::ModuleLifecyclePhase::Prepare,
571                outcome,
572                elapsed: (runtime.driver.now)().saturating_sub(started_at),
573            },
574        );
575        if let Err(error) = result {
576            let _ = deactivate_in_reverse(
577                &runtime.modules,
578                &runtime.dependencies,
579                &prepared_instances,
580                DeactivationReason::StartupRollback,
581                &runtime.admission,
582                &runtime.diagnostics,
583                &runtime.driver,
584            )
585            .await;
586            runtime.diagnostics.emit_runtime_failure(
587                (runtime.driver.now)(),
588                Some(instance_key),
589                &error,
590            );
591            return Err(error);
592        }
593    }
594    Ok(prepared_instances)
595}
596
597pub(super) async fn activate_native_modules(
598    runtime: &Rc<NativeAppRuntime>,
599) -> Result<(), RuntimeFailure> {
600    for instance_key in &runtime.activation_order {
601        let module = runtime
602            .modules
603            .get(instance_key)
604            .expect("activation order only contains planned Module Instances");
605        let (lifecycle, tasks, resources) = module
606            .generation_parts()
607            .expect("every startup Module Instance has a generation");
608        let cancellation = tasks.cancellation();
609        let started_at = (runtime.driver.now)();
610        runtime
611            .diagnostics
612            .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
613                super::DiagnosticEvent::LifecycleStarted {
614                    instance: instance_key.clone(),
615                    generation: 1,
616                    phase: super::ModuleLifecyclePhase::Activate,
617                }
618            });
619        let context = ActivateContext {
620            instance_key: instance_key.clone(),
621            dependencies: runtime
622                .dependencies
623                .get(instance_key)
624                .cloned()
625                .unwrap_or_default(),
626            ready_gate: runtime.ready_gate.clone(),
627            tasks,
628            resources,
629            cancellation,
630            admission: runtime.admission.clone(),
631        };
632        let result = lifecycle.activate(context).await;
633        let outcome = result.as_ref().map_or_else(
634            |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
635            |()| super::DiagnosticOutcome::Succeeded,
636        );
637        runtime.diagnostics.emit(
638            super::DiagnosticSource::Lifecycle,
639            (runtime.driver.now)(),
640            |_| super::DiagnosticEvent::LifecycleCompleted {
641                instance: instance_key.clone(),
642                generation: 1,
643                phase: super::ModuleLifecyclePhase::Activate,
644                outcome,
645                elapsed: (runtime.driver.now)().saturating_sub(started_at),
646            },
647        );
648        if let Err(error) = result {
649            runtime.diagnostics.emit_runtime_failure(
650                (runtime.driver.now)(),
651                Some(instance_key),
652                &error,
653            );
654            return Err(error);
655        }
656    }
657    Ok(())
658}
659
660pub(super) async fn open_native_readiness(runtime: &Rc<NativeAppRuntime>) {
661    runtime.ready_gate.open();
662    runtime.admission.open();
663    runtime.diagnostics.emit(
664        super::DiagnosticSource::Lifecycle,
665        (runtime.driver.now)(),
666        |_| super::DiagnosticEvent::AppReady,
667    );
668    (runtime.driver.yield_now)().await;
669}
670
671pub(super) fn native_bindings(
672    plan: &ResolvedAppPlan,
673    prepared: &[PreparedBinding],
674) -> (NativeBindingTable, NativeEndpointStateTable) {
675    let mut bindings = BTreeMap::new();
676    let mut endpoint_states = BTreeMap::new();
677    for binding in plan.capability_bindings() {
678        let Some(descriptor) =
679            plan.module_instance(binding.provider_instance())
680                .and_then(|provider| {
681                    provider
682                        .provided_capabilities()
683                        .iter()
684                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
685                })
686        else {
687            continue;
688        };
689        if descriptor.request_operations().is_empty() {
690            continue;
691        }
692        let Some(endpoint) = prepared.iter().find_map(|prepared| {
693            (prepared.consumer_instance == binding.consumer_instance()
694                && prepared.provider_instance == binding.provider_instance()
695                && prepared.endpoint.capability_id() == binding.capability_id())
696            .then_some(&prepared.endpoint)
697        }) else {
698            continue;
699        };
700        let state = endpoint_states
701            .entry((
702                binding.provider_instance().to_owned(),
703                endpoint.capability_id().to_owned(),
704            ))
705            .or_insert_with(|| Rc::new(NativeEndpointState::new(endpoint.clone(), 1)))
706            .clone();
707        let admissions = endpoint
708            .operations()
709            .iter()
710            .map(|operation| {
711                (
712                    (*operation).to_owned(),
713                    RequestAdmission::new(plan.request_admission_for(binding, operation)),
714                )
715            })
716            .collect();
717        bindings
718            .entry((
719                binding.consumer_instance().to_owned(),
720                endpoint.capability_id(),
721            ))
722            .or_insert_with(Vec::new)
723            .push(NativeEndpointBinding {
724                module_instance: binding.provider_instance().to_owned(),
725                state,
726                admissions,
727            });
728    }
729    (bindings, endpoint_states)
730}
731
732pub(super) fn native_stream_bindings(
733    plan: &ResolvedAppPlan,
734    prepared: &[PreparedStreamBinding],
735) -> (NativeStreamBindingTable, NativeStreamEndpointStateTable) {
736    let mut bindings = BTreeMap::new();
737    let mut endpoint_states = BTreeMap::new();
738    for binding in plan.capability_bindings() {
739        let Some(descriptor) =
740            plan.module_instance(binding.provider_instance())
741                .and_then(|provider| {
742                    provider
743                        .provided_capabilities()
744                        .iter()
745                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
746                })
747        else {
748            continue;
749        };
750        if descriptor.stream_operations().is_empty() {
751            continue;
752        }
753        let Some(endpoint) = prepared.iter().find_map(|prepared| {
754            (prepared.consumer_instance == binding.consumer_instance()
755                && prepared.provider_instance == binding.provider_instance()
756                && prepared.endpoint.capability_id() == binding.capability_id())
757            .then_some(&prepared.endpoint)
758        }) else {
759            continue;
760        };
761        let state = endpoint_states
762            .entry((
763                binding.provider_instance().to_owned(),
764                endpoint.capability_id().to_owned(),
765            ))
766            .or_insert_with(|| Rc::new(NativeStreamEndpointState::new(endpoint.clone(), 1)))
767            .clone();
768        let admissions = endpoint
769            .operations()
770            .iter()
771            .map(|operation| {
772                (
773                    (*operation).to_owned(),
774                    RequestAdmission::new(plan.request_admission_for(binding, operation)),
775                )
776            })
777            .collect();
778        bindings
779            .entry((
780                binding.consumer_instance().to_owned(),
781                endpoint.capability_id(),
782            ))
783            .or_insert_with(Vec::new)
784            .push(NativeStreamEndpointBinding {
785                module_instance: binding.provider_instance().to_owned(),
786                state,
787                admissions,
788            });
789    }
790    (bindings, endpoint_states)
791}
792
793pub(super) fn native_event_bindings(
794    plan: &ResolvedAppPlan,
795    prepared: &[PreparedEventBinding],
796) -> (NativeEventBindingTable, NativeEventEndpointStateTable) {
797    let mut bindings = BTreeMap::new();
798    let mut endpoint_states = BTreeMap::new();
799    for binding in plan.capability_bindings() {
800        let Some(descriptor) =
801            plan.module_instance(binding.provider_instance())
802                .and_then(|provider| {
803                    provider
804                        .provided_capabilities()
805                        .iter()
806                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
807                })
808        else {
809            continue;
810        };
811        if descriptor.event_operations().is_empty() {
812            continue;
813        }
814        let Some(endpoint) = prepared.iter().find_map(|prepared| {
815            (prepared.consumer_instance == binding.consumer_instance()
816                && prepared.provider_instance == binding.provider_instance()
817                && prepared.endpoint.capability_id() == binding.capability_id())
818            .then_some(&prepared.endpoint)
819        }) else {
820            continue;
821        };
822        let state = endpoint_states
823            .entry((
824                binding.provider_instance().to_owned(),
825                endpoint.capability_id().to_owned(),
826            ))
827            .or_insert_with(|| Rc::new(event::NativeEventEndpointState::new(endpoint.clone(), 1)))
828            .clone();
829        let queue = event::NativeEventQueue::new(plan.event_admission_for(binding));
830        state.register_queue(&queue);
831        bindings
832            .entry((
833                binding.consumer_instance().to_owned(),
834                endpoint.capability_id(),
835            ))
836            .or_insert_with(Vec::new)
837            .push(event::NativeEventEndpointBinding {
838                module_instance: binding.provider_instance().to_owned(),
839                state,
840                queue,
841            });
842    }
843    (bindings, endpoint_states)
844}
845
846pub(super) fn module_dependencies(
847    plan: &ResolvedAppPlan,
848    endpoints: &BTreeMap<(String, &'static str), Vec<NativeEndpointBinding>>,
849    stream_endpoints: &NativeStreamBindingTable,
850    event_endpoints: &NativeEventBindingTable,
851    runtime: &Rc<RefCell<Weak<NativeAppRuntime>>>,
852) -> BTreeMap<String, ModuleDependencies> {
853    let mut dependencies: BTreeMap<String, ModuleDependencies> = plan
854        .module_instances()
855        .iter()
856        .map(|instance| {
857            (
858                instance.instance_key().to_owned(),
859                ModuleDependencies::new(instance.instance_key(), runtime.clone()),
860            )
861        })
862        .collect();
863    for binding in plan.capability_bindings() {
864        dependencies
865            .get_mut(binding.consumer_instance())
866            .expect("every resolved binding consumer has Module dependencies")
867            .bindings
868            .push(ModuleDependency::new(
869                binding.capability_id(),
870                binding.provider_instance(),
871                binding.provider_order(),
872                endpoints
873                    .iter()
874                    .find(|((consumer, capability), _)| {
875                        consumer == binding.consumer_instance()
876                            && *capability == binding.capability_id()
877                    })
878                    .and_then(|(_, endpoints)| endpoints.get(binding.provider_order()))
879                    .map(|endpoint| ModuleDependencyHandle {
880                        binding: endpoint.clone(),
881                        caller_instance: binding.consumer_instance().to_owned(),
882                        runtime: runtime.clone(),
883                    }),
884                stream_endpoints
885                    .iter()
886                    .find(|((consumer, capability), _)| {
887                        consumer == binding.consumer_instance()
888                            && *capability == binding.capability_id()
889                    })
890                    .and_then(|(_, endpoints)| endpoints.get(binding.provider_order()))
891                    .map(|endpoint| ModuleStreamDependencyHandle {
892                        binding: endpoint.clone(),
893                        caller_instance: binding.consumer_instance().to_owned(),
894                        runtime: runtime.clone(),
895                    }),
896                event_endpoints
897                    .iter()
898                    .find(|((consumer, capability), _)| {
899                        consumer == binding.consumer_instance()
900                            && *capability == binding.capability_id()
901                    })
902                    .and_then(|(_, endpoints)| endpoints.get(binding.provider_order()))
903                    .map(|endpoint| ModuleEventDependencyHandle {
904                        binding: endpoint.clone(),
905                        caller_instance: binding.consumer_instance().to_owned(),
906                        runtime: runtime.clone(),
907                    }),
908            ));
909    }
910    dependencies
911}