Skip to main content

frame_host/dev/
node.rs

1//! The real [`NodeControl`]: the barrier's steps executed against the
2//! live frame-core registry and runtime.
3//!
4//! On the SUCCESS path the reload is remove-free (constraint 5): the same
5//! registration is stopped, re-staged through the registry's purge-aware
6//! seam, and restarted — identity, metadata, grants, and the durable
7//! branch are the same objects across incarnations. The ONE exceptional
8//! path is rollback out of `Failed`: the staging seam legally refuses a
9//! Failed component, so recovery goes remove → re-register with the SAME
10//! identity and metadata — the branch and the per-identity incarnation
11//! history both survive remove by design (F-5a), so even this path keeps
12//! the fence and the freshness witnesses intact.
13
14use std::sync::Arc;
15
16use frame_core::component::{ComponentId, ComponentMeta};
17use frame_core::error::RegistryError;
18use frame_core::event::LifecycleState;
19use frame_core::registry::ComponentRegistry;
20use frame_core::runtime::{SupportModuleRef, SupportSwapper};
21
22use super::{CandidateBytes, GenerationReport, NodeControl, StartFailure};
23
24/// One liveness witness against the live registry — the application
25/// supplies its own (the generated host's mailbox probe and entity
26/// round-trip), frame-host never invents one.
27pub type Witness = Box<dyn FnMut(&ComponentRegistry) -> Result<(), String> + Send>;
28
29/// The barrier's node seam over the real registry and runtime.
30pub struct DevNodeControl {
31    registry: Arc<ComponentRegistry>,
32    swapper: SupportSwapper,
33    component: ComponentId,
34    meta: ComponentMeta,
35    support: SupportModuleRef,
36    witness_mailbox: Witness,
37    witness_content: Witness,
38}
39
40impl DevNodeControl {
41    /// A control seam for one component on one node.
42    #[must_use]
43    pub fn new(
44        registry: Arc<ComponentRegistry>,
45        swapper: SupportSwapper,
46        meta: ComponentMeta,
47        support: SupportModuleRef,
48        witness_mailbox: Witness,
49        witness_content: Witness,
50    ) -> Self {
51        Self {
52            registry,
53            swapper,
54            component: meta.id,
55            meta,
56            support,
57            witness_mailbox,
58            witness_content,
59        }
60    }
61
62    fn state(&self) -> Result<LifecycleState, String> {
63        self.registry
64            .status(self.component)
65            .map_err(|error| error.to_string())?
66            .map(|status| status.state)
67            .ok_or_else(|| "component is not registered".to_owned())
68    }
69}
70
71impl NodeControl for DevNodeControl {
72    fn stop_component(&mut self) -> Result<(), String> {
73        self.registry
74            .stop(self.component)
75            .map(|_| ())
76            .map_err(|error| error.to_string())
77    }
78
79    fn stage(&mut self, candidate: &CandidateBytes) -> Result<(), String> {
80        match self.state()? {
81            // Rollback out of Failed: the staging seam refuses Failed by
82            // law, so recovery re-registers the SAME identity and meta.
83            // Branch and incarnation history survive remove (F-5a).
84            LifecycleState::Failed => {
85                self.registry
86                    .remove(self.component)
87                    .map_err(|error| format!("recovering failed registration: {error}"))?;
88                self.registry
89                    .register(self.meta.clone(), candidate.component_beam.clone())
90                    .map_err(|error| format!("re-registering after failure: {error}"))?;
91            }
92            _ => {
93                self.registry
94                    .upgrade_bytecode(self.component, candidate.component_beam.clone())
95                    .map_err(|error| error.to_string())?;
96            }
97        }
98        self.support = self
99            .swapper
100            .swap(&self.support, &candidate.ffi_beam)
101            .map_err(|error| format!("support module swap: {error}"))?;
102        Ok(())
103    }
104
105    fn start_component(&mut self) -> Result<(), StartFailure> {
106        self.registry
107            .start(self.component)
108            .map_err(|error| match error {
109                RegistryError::ModuleLoad { .. } | RegistryError::DeferredBifImports { .. } => {
110                    StartFailure::Load(error.to_string())
111                }
112                other => StartFailure::Tree(other.to_string()),
113            })
114    }
115
116    fn witness_mailbox(&mut self) -> Result<(), String> {
117        (self.witness_mailbox)(&self.registry)
118    }
119
120    fn witness_content(&mut self) -> Result<(), String> {
121        (self.witness_content)(&self.registry)
122    }
123
124    fn report(
125        &mut self,
126        build_generation: u64,
127        content_digest: [u8; 32],
128    ) -> Result<GenerationReport, String> {
129        Ok(GenerationReport {
130            build_generation,
131            content_digest,
132            component_incarnation: self
133                .registry
134                .component_incarnation(self.component)
135                .map_err(|error| format!("incarnation witness unreadable: {error}"))?,
136            module_generation: self
137                .registry
138                .component_module_generation(self.component)
139                .map_err(|error| format!("module generation witness unreadable: {error}"))?,
140        })
141    }
142}