Skip to main content

lenso_kernel/
kernel.rs

1use super::{
2    ActivateContext, AppAdmission, AppReadyGate, BTreeMap, CancellationToken, Cell,
3    DeactivationReason, DriverControl, ExecutionAdapterCatalog, ManagedResourceScope,
4    ManagedTaskScope, NativeApp, NativeAppRuntime, NativeBindingTable, NativeEndpointBinding,
5    NativeEndpointState, NativeEndpointStateTable, NativeEventBindingTable,
6    NativeEventEndpointStateTable, NativeExecutionAdapter, NativePluginGeneration,
7    NativePluginRuntime, NativeStreamBindingTable, NativeStreamEndpointBinding,
8    NativeStreamEndpointState, NativeStreamEndpointStateTable, PlanResolutionError,
9    PluginDependencies, PluginDependency, PluginDependencyHandle, PluginEventDependencyHandle,
10    PluginStreamDependencyHandle, PrepareContext, PreparedBinding, PreparedEventBinding,
11    PreparedNativeApp, PreparedNativePlugin, PreparedStreamBinding, Rc, RefCell, RequestAdmission,
12    ResolvedAppPlan, RuntimeDiagnostics, RuntimeDriver, RuntimeFailure, ShutdownCoordinator, Weak,
13    begin_plugin_supervision, deactivate_in_reverse, event, handle_supervision_schedule_failure,
14    plugin_supervision, schedule_plugin_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 Plugin 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 plan
93            .plugin_instances()
94            .iter()
95            .any(|instance| instance.authoring_version() == 2)
96        {
97            return Self::start_controlled(
98                plan,
99                driver,
100                adapters,
101                diagnostics,
102                super::InvocationContext::new(0, None, CancellationToken::new()),
103                super::startup::DEFAULT_STARTUP_CLEANUP_TIMEOUT,
104            )
105            .await;
106        }
107        Self::start_owned(plan, driver, adapters, diagnostics, None, None).await
108    }
109
110    /// Starts through a Driver-owned attempt with explicit cancellation,
111    /// monotonic startup deadline, and one late-result cleanup budget.
112    pub async fn start_controlled<D: RuntimeDriver>(
113        plan: ResolvedAppPlan,
114        driver: D,
115        adapters: ExecutionAdapterCatalog,
116        diagnostics: RuntimeDiagnostics,
117        context: super::InvocationContext,
118        cleanup_timeout: std::time::Duration,
119    ) -> Result<NativeApp, RuntimeFailure> {
120        super::startup::start(
121            plan,
122            driver,
123            adapters,
124            diagnostics,
125            context,
126            cleanup_timeout,
127        )
128        .await
129    }
130
131    #[allow(
132        clippy::too_many_lines,
133        reason = "startup remains linear so validation, preparation and activation fail closed"
134    )]
135    pub(super) async fn start_owned<D: RuntimeDriver>(
136        plan: ResolvedAppPlan,
137        driver: D,
138        adapters: ExecutionAdapterCatalog,
139        diagnostics: RuntimeDiagnostics,
140        startup_context: Option<super::InvocationContext>,
141        startup_cleanup: Option<super::cleanup::StartupCleanupBudget>,
142    ) -> Result<NativeApp, RuntimeFailure> {
143        if let Err(error) = plan.validate() {
144            let error = runtime_plan_error(&error);
145            diagnostics.emit_runtime_failure(driver.now(), None, &error);
146            return Err(error);
147        }
148
149        let activation_order = match plan.activation_order() {
150            Ok(order) => order,
151            Err(error) => {
152                let error = runtime_plan_error(&error);
153                diagnostics.emit_runtime_failure(driver.now(), None, &error);
154                return Err(error);
155            }
156        };
157        let adapters = Rc::new(adapters);
158        let PreparedNativeApp {
159            bindings: prepared_bindings,
160            stream_bindings: prepared_stream_bindings,
161            event_bindings: prepared_event_bindings,
162            generations,
163        } = match adapters.prepare(&plan) {
164            Ok(prepared) => prepared,
165            Err(error) => {
166                diagnostics.emit_runtime_failure(driver.now(), None, &error);
167                return Err(error);
168            }
169        };
170        if let Err(error) = validate_prepared_native_app(
171            &plan,
172            &prepared_bindings,
173            &prepared_stream_bindings,
174            &prepared_event_bindings,
175            &generations,
176        ) {
177            diagnostics.emit_runtime_failure(driver.now(), None, &error);
178            return Err(error);
179        }
180        let (bindings, endpoint_states) = native_bindings(&plan, &prepared_bindings);
181        let (stream_bindings, stream_endpoint_states) =
182            native_stream_bindings(&plan, &prepared_stream_bindings);
183        let (event_bindings, event_endpoint_states) =
184            native_event_bindings(&plan, &prepared_event_bindings);
185        let runtime_link = Rc::new(RefCell::new(Weak::new()));
186        let dependencies = plugin_dependencies(
187            &plan,
188            &bindings,
189            &stream_bindings,
190            &event_bindings,
191            &runtime_link,
192        );
193        let driver_control = DriverControl::new(&driver);
194        let admission = AppAdmission::new();
195        let startup_cancellation = startup_context
196            .as_ref()
197            .map(super::InvocationContext::cancellation);
198        let plugin_runtimes =
199            native_plugin_runtimes(&plan, &driver, generations, startup_cancellation.as_ref());
200        let ready_gate = AppReadyGate::new();
201        let supervision = plugin_supervision(&plan);
202        let cleanup_timeout = startup_cleanup
203            .as_ref()
204            .map(super::cleanup::StartupCleanupBudget::timeout);
205        let runtime = Rc::new(NativeAppRuntime {
206            startup_context: RefCell::new(startup_context),
207            startup_cleanup,
208            cleanup_timeout,
209            executions: Rc::default(),
210            plan,
211            adapters,
212            plugins: plugin_runtimes,
213            dependencies,
214            endpoint_states,
215            stream_endpoint_states,
216            event_endpoint_states,
217            supervision: RefCell::new(supervision),
218            supervision_tasks: RefCell::new(BTreeMap::new()),
219            activation_order,
220            ready_gate,
221            admission,
222            driver: driver_control,
223            diagnostics: diagnostics.clone(),
224            request_ids: Rc::new(Cell::new(1)),
225            supervision_cancellation: CancellationToken::new(),
226            shutdown_started: Cell::new(false),
227            shutdown: ShutdownCoordinator::default(),
228            shutdown_task: RefCell::new(None),
229            terminal_failure: RefCell::new(None),
230        });
231        runtime_link.replace(Rc::downgrade(&runtime));
232        attach_managed_task_failure_handlers(&runtime);
233        runtime.diagnostics.emit(
234            super::DiagnosticSource::Lifecycle,
235            (runtime.driver.now)(),
236            |_| super::DiagnosticEvent::AppStarted {
237                plugin_count: runtime.plan.plugin_instances().len(),
238            },
239        );
240        let prepared_instances = prepare_native_plugins(&runtime).await?;
241        if let Err(error) = construct_native_plugins(&runtime).await {
242            let cleanup_error = deactivate_in_reverse(
243                &runtime.plugins,
244                &runtime.dependencies,
245                &prepared_instances,
246                DeactivationReason::StartupRollback,
247                &runtime.admission,
248                &runtime.diagnostics,
249                &runtime.driver,
250                runtime
251                    .startup_cleanup
252                    .as_ref()
253                    .map(super::cleanup::StartupCleanupBudget::establish),
254            )
255            .await;
256            retain_unsafe_startup(&runtime, cleanup_error.as_ref());
257            runtime
258                .diagnostics
259                .emit_runtime_failure((runtime.driver.now)(), None, &error);
260            return Err(error);
261        }
262        if let Err(error) = activate_native_plugins(&runtime).await {
263            let cleanup_error = deactivate_in_reverse(
264                &runtime.plugins,
265                &runtime.dependencies,
266                &prepared_instances,
267                DeactivationReason::StartupRollback,
268                &runtime.admission,
269                &runtime.diagnostics,
270                &runtime.driver,
271                runtime
272                    .startup_cleanup
273                    .as_ref()
274                    .map(super::cleanup::StartupCleanupBudget::establish),
275            )
276            .await;
277            retain_unsafe_startup(&runtime, cleanup_error.as_ref());
278            runtime
279                .diagnostics
280                .emit_runtime_failure((runtime.driver.now)(), None, &error);
281            return Err(error);
282        }
283        open_native_readiness(&runtime).await;
284        Ok(NativeApp {
285            bindings,
286            stream_bindings,
287            event_bindings,
288            diagnostics,
289            runtime,
290        })
291    }
292}
293
294pub(super) fn attach_managed_task_failure_handlers(runtime: &Rc<NativeAppRuntime>) {
295    for (instance_key, plugin) in &runtime.plugins {
296        let Some((_, tasks, _)) = plugin.generation_parts() else {
297            continue;
298        };
299        attach_managed_task_failure_handler(runtime, instance_key, &tasks);
300    }
301}
302
303fn startup_active(runtime: &NativeAppRuntime) -> Result<(), RuntimeFailure> {
304    let result = runtime
305        .startup_context
306        .borrow()
307        .as_ref()
308        .map_or(Ok(()), |context| {
309            super::ensure_context_active(&runtime.driver, context)
310        });
311    if result.is_err()
312        && let Some(cleanup) = &runtime.startup_cleanup
313    {
314        let now = (runtime.driver.now)();
315        let cleanup_started_at = runtime
316            .startup_context
317            .borrow()
318            .as_ref()
319            .and_then(super::InvocationContext::deadline)
320            .filter(|deadline| now >= *deadline)
321            .unwrap_or(now);
322        cleanup.establish_at(cleanup_started_at);
323    }
324    result
325}
326
327fn lifecycle_cancellation(
328    _runtime: &NativeAppRuntime,
329    tasks: &ManagedTaskScope,
330) -> CancellationToken {
331    tasks.cancellation()
332}
333
334pub(super) fn attach_managed_task_failure_handler(
335    runtime: &Rc<NativeAppRuntime>,
336    instance_key: &str,
337    tasks: &ManagedTaskScope,
338) {
339    let task_runtime = Rc::downgrade(runtime);
340    let task_instance_key = instance_key.to_owned();
341    let handler: Rc<dyn Fn()> = Rc::new(move || {
342        let Some(runtime) = task_runtime.upgrade() else {
343            return;
344        };
345        if begin_plugin_supervision(&runtime, &task_instance_key).unwrap_or(false)
346            && let Err(error) = schedule_plugin_supervision(&runtime, &task_instance_key)
347        {
348            let _ = handle_supervision_schedule_failure(&runtime, &task_instance_key, error);
349        }
350    });
351    tasks.set_failure_handler(&handler);
352}
353
354pub(super) fn runtime_plan_error(error: &PlanResolutionError) -> RuntimeFailure {
355    RuntimeFailure::InvalidResolvedPlan {
356        detail: error.to_string(),
357    }
358}
359
360#[allow(
361    clippy::too_many_lines,
362    reason = "one fail-closed pass keeps request, stream, event, and generation validation aligned"
363)]
364pub(super) fn validate_prepared_native_app(
365    plan: &ResolvedAppPlan,
366    bindings: &[PreparedBinding],
367    stream_bindings: &[PreparedStreamBinding],
368    event_bindings: &[PreparedEventBinding],
369    generations: &BTreeMap<String, PreparedNativePlugin>,
370) -> Result<(), RuntimeFailure> {
371    if generations.len() != plan.plugin_instances().len() {
372        return Err(RuntimeFailure::InvalidResolvedPlan {
373            detail: format!(
374                "Execution Adapters prepared {} Plugin generations; expected {}",
375                generations.len(),
376                plan.plugin_instances().len()
377            ),
378        });
379    }
380    for instance in plan.plugin_instances() {
381        let generation = generations.get(instance.instance_key()).ok_or_else(|| {
382            RuntimeFailure::InvalidResolvedPlan {
383                detail: format!(
384                    "Execution Adapters did not prepare Plugin Instance `{}`",
385                    instance.instance_key()
386                ),
387            }
388        })?;
389        validate_native_endpoint_set(
390            instance.instance_key(),
391            instance,
392            generation.endpoints(),
393            generation.stream_endpoints(),
394            generation.event_endpoints(),
395        )?;
396    }
397    if let Some(instance_key) = generations.keys().find(|instance_key| {
398        !plan
399            .plugin_instances()
400            .iter()
401            .any(|instance| instance.instance_key() == instance_key.as_str())
402    }) {
403        return Err(RuntimeFailure::InvalidResolvedPlan {
404            detail: format!("Execution Adapter prepared unknown Plugin Instance `{instance_key}`"),
405        });
406    }
407
408    let expected_request_bindings = plan
409        .capability_bindings()
410        .iter()
411        .filter(|binding| {
412            plan.plugin_instance(binding.provider_instance())
413                .and_then(|provider| {
414                    provider
415                        .provided_capabilities()
416                        .iter()
417                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
418                })
419                .is_some_and(|endpoint| !endpoint.request_operations().is_empty())
420        })
421        .count();
422    let expected_stream_bindings = plan
423        .capability_bindings()
424        .iter()
425        .filter(|binding| {
426            plan.plugin_instance(binding.provider_instance())
427                .and_then(|provider| {
428                    provider
429                        .provided_capabilities()
430                        .iter()
431                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
432                })
433                .is_some_and(|endpoint| !endpoint.stream_operations().is_empty())
434        })
435        .count();
436    let expected_event_bindings = plan
437        .capability_bindings()
438        .iter()
439        .filter(|binding| {
440            plan.plugin_instance(binding.provider_instance())
441                .and_then(|provider| {
442                    provider
443                        .provided_capabilities()
444                        .iter()
445                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
446                })
447                .is_some_and(|endpoint| !endpoint.event_operations().is_empty())
448        })
449        .count();
450    if bindings.len() != expected_request_bindings {
451        return Err(RuntimeFailure::InvalidResolvedPlan {
452            detail: if expected_stream_bindings == 0 && stream_bindings.is_empty() {
453                format!(
454                    "Execution Adapters prepared {} bindings; expected {}",
455                    bindings.len(),
456                    expected_request_bindings
457                )
458            } else {
459                format!(
460                    "Execution Adapters prepared {} request bindings; expected {}",
461                    bindings.len(),
462                    expected_request_bindings
463                )
464            },
465        });
466    }
467    if stream_bindings.len() != expected_stream_bindings {
468        return Err(RuntimeFailure::InvalidResolvedPlan {
469            detail: format!(
470                "Execution Adapters prepared {} stream bindings; expected {}",
471                stream_bindings.len(),
472                expected_stream_bindings
473            ),
474        });
475    }
476    if event_bindings.len() != expected_event_bindings {
477        return Err(RuntimeFailure::InvalidResolvedPlan {
478            detail: format!(
479                "Execution Adapters prepared {} Event bindings; expected {}",
480                event_bindings.len(),
481                expected_event_bindings
482            ),
483        });
484    }
485    for planned in plan.capability_bindings() {
486        let provider = generations
487            .get(planned.provider_instance())
488            .expect("the resolved Plan references one validated provider generation");
489        let descriptor = plan
490            .plugin_instance(planned.provider_instance())
491            .and_then(|provider| {
492                provider
493                    .provided_capabilities()
494                    .iter()
495                    .find(|endpoint| endpoint.capability_id() == planned.capability_id())
496            })
497            .expect("the resolved Plan references one validated provider endpoint");
498        if !descriptor.request_operations().is_empty() {
499            let matching: Vec<_> = bindings
500                .iter()
501                .filter(|prepared| {
502                    prepared.requirement_id() == planned.requirement_id()
503                        && prepared.consumer_instance == planned.consumer_instance()
504                        && prepared.provider_instance == planned.provider_instance()
505                        && prepared.endpoint.capability_id() == planned.capability_id()
506                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
507                })
508                .collect();
509            if matching.len() != 1 {
510                return Err(RuntimeFailure::InvalidResolvedPlan {
511                    detail: format!(
512                        "Execution Adapters prepared {} request bindings for `{}:{}:{}`; expected 1",
513                        matching.len(),
514                        planned.consumer_instance(),
515                        planned.capability_id(),
516                        planned.provider_instance()
517                    ),
518                });
519            }
520            if !provider
521                .endpoints()
522                .iter()
523                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
524            {
525                return Err(RuntimeFailure::InvalidResolvedPlan {
526                    detail: format!(
527                        "request binding `{}:{}:{}` does not reference its provider generation endpoint",
528                        planned.consumer_instance(),
529                        planned.capability_id(),
530                        planned.provider_instance()
531                    ),
532                });
533            }
534        }
535        if !descriptor.stream_operations().is_empty() {
536            let matching: Vec<_> = stream_bindings
537                .iter()
538                .filter(|prepared| {
539                    prepared.requirement_id() == planned.requirement_id()
540                        && prepared.consumer_instance == planned.consumer_instance()
541                        && prepared.provider_instance == planned.provider_instance()
542                        && prepared.endpoint.capability_id() == planned.capability_id()
543                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
544                })
545                .collect();
546            if matching.len() != 1 {
547                return Err(RuntimeFailure::InvalidResolvedPlan {
548                    detail: format!(
549                        "Execution Adapters prepared {} stream bindings for `{}:{}:{}`; expected 1",
550                        matching.len(),
551                        planned.consumer_instance(),
552                        planned.capability_id(),
553                        planned.provider_instance()
554                    ),
555                });
556            }
557            if !provider
558                .stream_endpoints()
559                .iter()
560                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
561            {
562                return Err(RuntimeFailure::InvalidResolvedPlan {
563                    detail: format!(
564                        "stream binding `{}:{}:{}` does not reference its provider generation endpoint",
565                        planned.consumer_instance(),
566                        planned.capability_id(),
567                        planned.provider_instance()
568                    ),
569                });
570            }
571        }
572        if !descriptor.event_operations().is_empty() {
573            let matching: Vec<_> = event_bindings
574                .iter()
575                .filter(|prepared| {
576                    prepared.requirement_id() == planned.requirement_id()
577                        && prepared.consumer_instance == planned.consumer_instance()
578                        && prepared.provider_instance == planned.provider_instance()
579                        && prepared.endpoint.capability_id() == planned.capability_id()
580                        && prepared.endpoint.descriptor_version() == planned.descriptor_version()
581                })
582                .collect();
583            if matching.len() != 1 {
584                return Err(RuntimeFailure::InvalidResolvedPlan {
585                    detail: format!(
586                        "Execution Adapters prepared {} Event bindings for `{}:{}:{}`; expected 1",
587                        matching.len(),
588                        planned.consumer_instance(),
589                        planned.capability_id(),
590                        planned.provider_instance()
591                    ),
592                });
593            }
594            if !provider
595                .event_endpoints()
596                .iter()
597                .any(|endpoint| Rc::ptr_eq(endpoint, &matching[0].endpoint))
598            {
599                return Err(RuntimeFailure::InvalidResolvedPlan {
600                    detail: format!(
601                        "Event binding `{}:{}:{}` does not reference its provider generation endpoint",
602                        planned.consumer_instance(),
603                        planned.capability_id(),
604                        planned.provider_instance()
605                    ),
606                });
607            }
608        }
609    }
610    Ok(())
611}
612
613pub(super) fn native_plugin_runtimes<D: RuntimeDriver>(
614    plan: &ResolvedAppPlan,
615    driver: &D,
616    mut generations: BTreeMap<String, PreparedNativePlugin>,
617    startup_cancellation: Option<&CancellationToken>,
618) -> BTreeMap<String, NativePluginRuntime> {
619    let mut runtimes = BTreeMap::new();
620    for instance in plan.plugin_instances() {
621        let lifecycle = generations
622            .remove(instance.instance_key())
623            .map(|generation| generation.lifecycle())
624            .expect("prepared App validation requires one generation per planned Instance");
625        let cancellation =
626            startup_cancellation.map_or_else(CancellationToken::new, CancellationToken::child);
627        runtimes.insert(
628            instance.instance_key().to_owned(),
629            NativePluginRuntime {
630                generation: RefCell::new(Some(NativePluginGeneration {
631                    lifecycle,
632                    tasks: ManagedTaskScope::new_with_cancellation(driver, cancellation),
633                    resources: ManagedResourceScope::new(),
634                    stop_attempted: false,
635                    cleanup_timed_out: false,
636                })),
637            },
638        );
639    }
640    runtimes
641}
642
643pub(super) async fn prepare_native_plugins(
644    runtime: &Rc<NativeAppRuntime>,
645) -> Result<Vec<String>, RuntimeFailure> {
646    let mut prepared_instances = Vec::with_capacity(runtime.activation_order.len());
647    for instance_key in &runtime.activation_order {
648        startup_active(runtime)?;
649        let instance = runtime
650            .plan
651            .plugin_instances()
652            .iter()
653            .find(|instance| instance.instance_key() == instance_key)
654            .expect("activation order only contains planned Plugin Instances");
655        let plugin = runtime
656            .plugins
657            .get(instance_key)
658            .expect("activation order only contains planned Plugin Instances");
659        let (lifecycle, tasks, resources) = plugin
660            .generation_parts()
661            .expect("every startup Plugin Instance has a generation");
662        let cancellation = lifecycle_cancellation(runtime, &tasks);
663        prepared_instances.push(instance_key.clone());
664        let started_at = (runtime.driver.now)();
665        runtime
666            .diagnostics
667            .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
668                super::DiagnosticEvent::LifecycleStarted {
669                    instance: instance_key.clone(),
670                    generation: 1,
671                    phase: super::PluginLifecyclePhase::Prepare,
672                }
673            });
674        let context = PrepareContext {
675            instance_key: instance_key.clone(),
676            entrypoint: instance.entrypoint().to_owned(),
677            configuration: instance.configuration().to_owned(),
678            dependencies: runtime
679                .dependencies
680                .get(instance_key)
681                .cloned()
682                .unwrap_or_default(),
683            resources,
684            cancellation,
685            admission: runtime.admission.clone(),
686        };
687        let result = lifecycle
688            .prepare(context)
689            .await
690            .and_then(|()| startup_active(runtime));
691        let outcome = result.as_ref().map_or_else(
692            |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
693            |()| super::DiagnosticOutcome::Succeeded,
694        );
695        runtime.diagnostics.emit(
696            super::DiagnosticSource::Lifecycle,
697            (runtime.driver.now)(),
698            |_| super::DiagnosticEvent::LifecycleCompleted {
699                instance: instance_key.clone(),
700                generation: 1,
701                phase: super::PluginLifecyclePhase::Prepare,
702                outcome,
703                elapsed: (runtime.driver.now)().saturating_sub(started_at),
704            },
705        );
706        if let Err(error) = result {
707            let cleanup_error = deactivate_in_reverse(
708                &runtime.plugins,
709                &runtime.dependencies,
710                &prepared_instances,
711                DeactivationReason::StartupRollback,
712                &runtime.admission,
713                &runtime.diagnostics,
714                &runtime.driver,
715                runtime
716                    .startup_cleanup
717                    .as_ref()
718                    .map(super::cleanup::StartupCleanupBudget::establish),
719            )
720            .await;
721            retain_unsafe_startup(runtime, cleanup_error.as_ref());
722            runtime.diagnostics.emit_runtime_failure(
723                (runtime.driver.now)(),
724                Some(instance_key),
725                &error,
726            );
727            return Err(error);
728        }
729    }
730    Ok(prepared_instances)
731}
732
733fn retain_unsafe_startup(runtime: &Rc<NativeAppRuntime>, cleanup_error: Option<&RuntimeFailure>) {
734    if matches!(cleanup_error, Some(RuntimeFailure::DeadlineExceeded { .. })) {
735        // Native code cannot be preempted safely. With no App handle to carry
736        // this failed startup generation, retain ownership until the embedding
737        // Host escalates by terminating the containing process.
738        std::mem::forget(runtime.clone());
739    }
740}
741
742pub(super) async fn activate_native_plugins(
743    runtime: &Rc<NativeAppRuntime>,
744) -> Result<(), RuntimeFailure> {
745    for instance_key in &runtime.activation_order {
746        startup_active(runtime)?;
747        let plugin = runtime
748            .plugins
749            .get(instance_key)
750            .expect("activation order only contains planned Plugin Instances");
751        let (lifecycle, tasks, resources) = plugin
752            .generation_parts()
753            .expect("every startup Plugin Instance has a generation");
754        let cancellation = lifecycle_cancellation(runtime, &tasks);
755        let started_at = (runtime.driver.now)();
756        runtime
757            .diagnostics
758            .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
759                super::DiagnosticEvent::LifecycleStarted {
760                    instance: instance_key.clone(),
761                    generation: 1,
762                    phase: super::PluginLifecyclePhase::Activate,
763                }
764            });
765        let context = ActivateContext {
766            instance_key: instance_key.clone(),
767            dependencies: runtime
768                .dependencies
769                .get(instance_key)
770                .cloned()
771                .unwrap_or_default(),
772            ready_gate: runtime.ready_gate.clone(),
773            tasks,
774            resources,
775            cancellation,
776            admission: runtime.admission.clone(),
777        };
778        let result = lifecycle
779            .activate(context)
780            .await
781            .and_then(|()| startup_active(runtime));
782        let outcome = result.as_ref().map_or_else(
783            |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
784            |()| super::DiagnosticOutcome::Succeeded,
785        );
786        runtime.diagnostics.emit(
787            super::DiagnosticSource::Lifecycle,
788            (runtime.driver.now)(),
789            |_| super::DiagnosticEvent::LifecycleCompleted {
790                instance: instance_key.clone(),
791                generation: 1,
792                phase: super::PluginLifecyclePhase::Activate,
793                outcome,
794                elapsed: (runtime.driver.now)().saturating_sub(started_at),
795            },
796        );
797        if let Err(error) = result {
798            runtime.diagnostics.emit_runtime_failure(
799                (runtime.driver.now)(),
800                Some(instance_key),
801                &error,
802            );
803            return Err(error);
804        }
805    }
806    Ok(())
807}
808
809pub(super) async fn construct_native_plugins(
810    runtime: &Rc<NativeAppRuntime>,
811) -> Result<(), RuntimeFailure> {
812    for instance_key in &runtime.activation_order {
813        let instance = runtime
814            .plan
815            .plugin_instance(instance_key)
816            .expect("construction order contains planned Instances");
817        if instance.authoring_version() == 1 {
818            continue;
819        }
820        startup_active(runtime)?;
821        let plugin = runtime
822            .plugins
823            .get(instance_key)
824            .expect("construction order contains planned Instances");
825        let (lifecycle, tasks, resources) = plugin
826            .generation_parts()
827            .expect("startup generation exists");
828        let started_at = (runtime.driver.now)();
829        runtime
830            .diagnostics
831            .emit(super::DiagnosticSource::Lifecycle, started_at, |_| {
832                super::DiagnosticEvent::LifecycleStarted {
833                    instance: instance_key.clone(),
834                    generation: 1,
835                    phase: super::PluginLifecyclePhase::Construct,
836                }
837            });
838        let result = lifecycle
839            .construct(ActivateContext {
840                instance_key: instance_key.clone(),
841                dependencies: runtime
842                    .dependencies
843                    .get(instance_key)
844                    .cloned()
845                    .unwrap_or_default(),
846                ready_gate: runtime.ready_gate.clone(),
847                tasks: tasks.clone(),
848                resources,
849                cancellation: lifecycle_cancellation(runtime, &tasks),
850                admission: runtime.admission.clone(),
851            })
852            .await
853            .and_then(|()| startup_active(runtime));
854        let outcome = result.as_ref().map_or_else(
855            |error| super::DiagnosticOutcome::RuntimeFailure(error.into()),
856            |()| super::DiagnosticOutcome::Succeeded,
857        );
858        runtime.diagnostics.emit(
859            super::DiagnosticSource::Lifecycle,
860            (runtime.driver.now)(),
861            |_| super::DiagnosticEvent::LifecycleCompleted {
862                instance: instance_key.clone(),
863                generation: 1,
864                phase: super::PluginLifecyclePhase::Construct,
865                outcome,
866                elapsed: (runtime.driver.now)().saturating_sub(started_at),
867            },
868        );
869        result?;
870    }
871    Ok(())
872}
873
874pub(super) async fn open_native_readiness(runtime: &Rc<NativeAppRuntime>) {
875    runtime.startup_context.borrow_mut().take();
876    runtime.ready_gate.open();
877    runtime.admission.open();
878    runtime.diagnostics.emit(
879        super::DiagnosticSource::Lifecycle,
880        (runtime.driver.now)(),
881        |_| super::DiagnosticEvent::AppReady,
882    );
883    (runtime.driver.yield_now)().await;
884}
885
886pub(super) fn native_bindings(
887    plan: &ResolvedAppPlan,
888    prepared: &[PreparedBinding],
889) -> (NativeBindingTable, NativeEndpointStateTable) {
890    let mut bindings = BTreeMap::new();
891    let mut endpoint_states = BTreeMap::new();
892    for binding in plan.capability_bindings() {
893        let Some(descriptor) =
894            plan.plugin_instance(binding.provider_instance())
895                .and_then(|provider| {
896                    provider
897                        .provided_capabilities()
898                        .iter()
899                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
900                })
901        else {
902            continue;
903        };
904        if descriptor.request_operations().is_empty() {
905            continue;
906        }
907        let Some(endpoint) = prepared.iter().find_map(|prepared| {
908            (prepared.requirement_id() == binding.requirement_id()
909                && prepared.consumer_instance == binding.consumer_instance()
910                && prepared.provider_instance == binding.provider_instance()
911                && prepared.endpoint.capability_id() == binding.capability_id())
912            .then_some(&prepared.endpoint)
913        }) else {
914            continue;
915        };
916        let state = endpoint_states
917            .entry((
918                binding.provider_instance().to_owned(),
919                endpoint.capability_id().to_owned(),
920            ))
921            .or_insert_with(|| Rc::new(NativeEndpointState::new(endpoint.clone(), 1)))
922            .clone();
923        let admissions = endpoint
924            .operations()
925            .iter()
926            .map(|operation| {
927                (
928                    (*operation).to_owned(),
929                    RequestAdmission::new(plan.request_admission_for(binding, operation)),
930                )
931            })
932            .collect();
933        bindings
934            .entry((
935                binding.consumer_instance().to_owned(),
936                endpoint.capability_id(),
937            ))
938            .or_insert_with(Vec::new)
939            .push(NativeEndpointBinding {
940                requirement_id: binding.requirement_id().to_owned(),
941                plugin_instance: binding.provider_instance().to_owned(),
942                state,
943                admissions,
944            });
945    }
946    (bindings, endpoint_states)
947}
948
949pub(super) fn native_stream_bindings(
950    plan: &ResolvedAppPlan,
951    prepared: &[PreparedStreamBinding],
952) -> (NativeStreamBindingTable, NativeStreamEndpointStateTable) {
953    let mut bindings = BTreeMap::new();
954    let mut endpoint_states = BTreeMap::new();
955    for binding in plan.capability_bindings() {
956        let Some(descriptor) =
957            plan.plugin_instance(binding.provider_instance())
958                .and_then(|provider| {
959                    provider
960                        .provided_capabilities()
961                        .iter()
962                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
963                })
964        else {
965            continue;
966        };
967        if descriptor.stream_operations().is_empty() {
968            continue;
969        }
970        let Some(endpoint) = prepared.iter().find_map(|prepared| {
971            (prepared.requirement_id() == binding.requirement_id()
972                && prepared.consumer_instance == binding.consumer_instance()
973                && prepared.provider_instance == binding.provider_instance()
974                && prepared.endpoint.capability_id() == binding.capability_id())
975            .then_some(&prepared.endpoint)
976        }) else {
977            continue;
978        };
979        let state = endpoint_states
980            .entry((
981                binding.provider_instance().to_owned(),
982                endpoint.capability_id().to_owned(),
983            ))
984            .or_insert_with(|| Rc::new(NativeStreamEndpointState::new(endpoint.clone(), 1)))
985            .clone();
986        let admissions = endpoint
987            .operations()
988            .iter()
989            .map(|operation| {
990                (
991                    (*operation).to_owned(),
992                    RequestAdmission::new(plan.request_admission_for(binding, operation)),
993                )
994            })
995            .collect();
996        bindings
997            .entry((
998                binding.consumer_instance().to_owned(),
999                endpoint.capability_id(),
1000            ))
1001            .or_insert_with(Vec::new)
1002            .push(NativeStreamEndpointBinding {
1003                requirement_id: binding.requirement_id().to_owned(),
1004                plugin_instance: binding.provider_instance().to_owned(),
1005                state,
1006                admissions,
1007            });
1008    }
1009    (bindings, endpoint_states)
1010}
1011
1012pub(super) fn native_event_bindings(
1013    plan: &ResolvedAppPlan,
1014    prepared: &[PreparedEventBinding],
1015) -> (NativeEventBindingTable, NativeEventEndpointStateTable) {
1016    let mut bindings = BTreeMap::new();
1017    let mut endpoint_states = BTreeMap::new();
1018    for binding in plan.capability_bindings() {
1019        let Some(descriptor) =
1020            plan.plugin_instance(binding.provider_instance())
1021                .and_then(|provider| {
1022                    provider
1023                        .provided_capabilities()
1024                        .iter()
1025                        .find(|endpoint| endpoint.capability_id() == binding.capability_id())
1026                })
1027        else {
1028            continue;
1029        };
1030        if descriptor.event_operations().is_empty() {
1031            continue;
1032        }
1033        let Some(endpoint) = prepared.iter().find_map(|prepared| {
1034            (prepared.requirement_id() == binding.requirement_id()
1035                && prepared.consumer_instance == binding.consumer_instance()
1036                && prepared.provider_instance == binding.provider_instance()
1037                && prepared.endpoint.capability_id() == binding.capability_id())
1038            .then_some(&prepared.endpoint)
1039        }) else {
1040            continue;
1041        };
1042        let state = endpoint_states
1043            .entry((
1044                binding.provider_instance().to_owned(),
1045                endpoint.capability_id().to_owned(),
1046            ))
1047            .or_insert_with(|| Rc::new(event::NativeEventEndpointState::new(endpoint.clone(), 1)))
1048            .clone();
1049        let queue = event::NativeEventQueue::new(plan.event_admission_for(binding));
1050        state.register_queue(&queue);
1051        bindings
1052            .entry((
1053                binding.consumer_instance().to_owned(),
1054                endpoint.capability_id(),
1055            ))
1056            .or_insert_with(Vec::new)
1057            .push(event::NativeEventEndpointBinding {
1058                requirement_id: binding.requirement_id().to_owned(),
1059                plugin_instance: binding.provider_instance().to_owned(),
1060                state,
1061                queue,
1062            });
1063    }
1064    (bindings, endpoint_states)
1065}
1066
1067pub(super) fn plugin_dependencies(
1068    plan: &ResolvedAppPlan,
1069    endpoints: &BTreeMap<(String, &'static str), Vec<NativeEndpointBinding>>,
1070    stream_endpoints: &NativeStreamBindingTable,
1071    event_endpoints: &NativeEventBindingTable,
1072    runtime: &Rc<RefCell<Weak<NativeAppRuntime>>>,
1073) -> BTreeMap<String, PluginDependencies> {
1074    let mut dependencies: BTreeMap<String, PluginDependencies> = plan
1075        .plugin_instances()
1076        .iter()
1077        .map(|instance| {
1078            (
1079                instance.instance_key().to_owned(),
1080                PluginDependencies::new(
1081                    instance.instance_key(),
1082                    runtime.clone(),
1083                    instance.required_capabilities().to_vec(),
1084                ),
1085            )
1086        })
1087        .collect();
1088    for binding in plan.capability_bindings() {
1089        dependencies
1090            .get_mut(binding.consumer_instance())
1091            .expect("every resolved binding consumer has Plugin dependencies")
1092            .bindings
1093            .push(PluginDependency::new(
1094                binding.requirement_id(),
1095                binding.capability_id(),
1096                binding.provider_instance(),
1097                binding.provider_order(),
1098                endpoints
1099                    .iter()
1100                    .find(|((consumer, capability), _)| {
1101                        consumer == binding.consumer_instance()
1102                            && *capability == binding.capability_id()
1103                    })
1104                    .and_then(|(_, endpoints)| {
1105                        endpoints.iter().find(|endpoint| {
1106                            endpoint.requirement_id == binding.requirement_id()
1107                                && endpoint.plugin_instance == binding.provider_instance()
1108                        })
1109                    })
1110                    .map(|endpoint| PluginDependencyHandle {
1111                        binding: endpoint.clone(),
1112                        caller_instance: binding.consumer_instance().to_owned(),
1113                        runtime: runtime.clone(),
1114                    }),
1115                stream_endpoints
1116                    .iter()
1117                    .find(|((consumer, capability), _)| {
1118                        consumer == binding.consumer_instance()
1119                            && *capability == binding.capability_id()
1120                    })
1121                    .and_then(|(_, endpoints)| {
1122                        endpoints.iter().find(|endpoint| {
1123                            endpoint.requirement_id == binding.requirement_id()
1124                                && endpoint.plugin_instance == binding.provider_instance()
1125                        })
1126                    })
1127                    .map(|endpoint| PluginStreamDependencyHandle {
1128                        binding: endpoint.clone(),
1129                        caller_instance: binding.consumer_instance().to_owned(),
1130                        runtime: runtime.clone(),
1131                    }),
1132                event_endpoints
1133                    .iter()
1134                    .find(|((consumer, capability), _)| {
1135                        consumer == binding.consumer_instance()
1136                            && *capability == binding.capability_id()
1137                    })
1138                    .and_then(|(_, endpoints)| {
1139                        endpoints.iter().find(|endpoint| {
1140                            endpoint.requirement_id == binding.requirement_id()
1141                                && endpoint.plugin_instance == binding.provider_instance()
1142                        })
1143                    })
1144                    .map(|endpoint| PluginEventDependencyHandle {
1145                        binding: endpoint.clone(),
1146                        caller_instance: binding.consumer_instance().to_owned(),
1147                        runtime: runtime.clone(),
1148                    }),
1149            ));
1150    }
1151    dependencies
1152}