Skip to main content

bb_runtime/engine/
bootstrap.rs

1//! Engine bootstrap state — consolidated owner of every bootstrap
2//! field the engine reads during install + poll.
3//!
4//! Host-driven: install records targets on `install_order` +
5//! `module_bootstraps` but leaves `pending` disarmed. The host arms
6//! the queue via [`crate::node::Node::run_bootstrap`] (empty slice)
7//! to drive every install-order target, or supplies one or more
8//! [`BootstrapInput`]s to fire named targets with staged inputs.
9//!
10//! ## Field roles
11//!
12//! - `install_order` — append-only target-name sequence the install
13//!   path stamps when a `module_phase = bootstrap` FunctionProto
14//!   lands. Drives the seeder front-to-back so multi-target installs
15//!   surface one BootstrapComplete per target in the order the host
16//!   supplied to `bytesandbrains::install`.
17//! - `module_bootstraps` — per-target metadata (function key) keyed
18//!   on the target name. Install stamps each entry as the
19//!   `module_phase = bootstrap` FunctionProto lands; the engine
20//!   reads it to resolve target → FunctionKey at seed time.
21//! - `current_exec_id` — `Some(ExecId)` while a bootstrap body is
22//!   in-flight; `None` otherwise. Single-target body gate — the
23//!   collapsed shape replaces the prior per-Component touch-set
24//!   conflict queue.
25//! - `next_idx` — seed pointer into `install_order`. Bumps each time
26//!   `Engine::maybe_complete_bootstrap` observes a phase drained.
27//!   `Engine::seed_bootstrap_call` reads it to pick the next target
28//!   once the host kicks the queue (empty-slice `run_bootstrap`).
29//! - `pending` — single body-op gate. Armed by
30//!   [`Self::arm_install_order`] (empty-slice host kick) or by the
31//!   engine's per-target staging path (non-empty `run_bootstrap`).
32//!   Cleared once every queued phase drains.
33
34use std::collections::HashMap;
35
36use crate::engine::dispatch_entry::FunctionKey;
37use crate::ids::ExecId;
38
39/// Per-target Module bootstrap metadata. Stamped into the engine
40/// bootstrap state when the install path sees a
41/// `module_phase = bootstrap` FunctionProto.
42#[derive(Clone, Debug)]
43pub struct ModuleBootstrap {
44    /// Canonical `(domain, name, overload)` key of the bootstrap
45    /// FunctionProto. Used to look up the GraphSlot at seed time.
46    pub function_key: FunctionKey,
47}
48
49/// Discriminator carried over from earlier shapes. Module is the
50/// only kind today; the enum is kept so external introspection that
51/// matched on `BootstrapKind::Module` keeps compiling.
52#[derive(Clone, Debug)]
53pub enum BootstrapKind {
54    /// Module bootstrap — `target` names the FunctionProto whose
55    /// body the engine seeds onto the frontier.
56    Module {
57        /// Target function name (matches an entry in the engine's
58        /// `install_order` queue).
59        target: String,
60    },
61}
62
63/// Host-facing bootstrap lifecycle status. Returned by
64/// `Node::bootstrap_status` so the caller can decide whether to keep
65/// polling or surface a "wait for input" prompt.
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum BootstrapStatus {
68    /// No bootstrap queued or in-flight — `Node::poll` runs the
69    /// body phase freely.
70    Idle,
71
72    /// Bootstrap is queued + executing. Body-phase ops park until
73    /// the queue drains.
74    Running,
75
76    /// Bootstrap is queued but waiting on host-supplied input
77    /// formals. The host must call `Node::run_bootstrap` with a
78    /// non-empty `BootstrapInput` slice to advance.
79    WaitingForInput,
80}
81
82/// Host-supplied bootstrap input staging request — borrowed shape.
83/// The host hands the engine a `target` name plus an ordered
84/// `(input_name, value_bytes)` slice; the engine validates against the
85/// target's declared formal input ports and runs the Principle 1a copy
86/// (cap-check → `try_reserve_exact` → `extend_from_slice`) so the
87/// caller's borrowed buffers can drop the moment
88/// [`crate::node::Node::run_bootstrap`] returns.
89pub struct BootstrapInput<'a> {
90    /// Target function name. Must match an entry in the engine's
91    /// `install_order` queue (i.e. a Module whose `bootstrap`
92    /// FunctionProto landed via `install_function_library`).
93    pub target: &'a str,
94
95    /// Ordered `(input_name, value_bytes)` pairs. Validated against
96    /// the target's declared input formals; missing required inputs
97    /// surface as `BootstrapError::MissingInput` and unknown ones as
98    /// `BootstrapError::UnknownInput` before any staging happens.
99    pub inputs: &'a [(&'a str, &'a [u8])],
100}
101
102/// Owned-form mirror of [`BootstrapInput`]. Kept around because
103/// callers (and tests) may want an owned shape, but the new flat
104/// `run_bootstrap` path stages each entry directly without parking
105/// it on a queue.
106#[derive(Clone, Debug)]
107pub struct OwnedBootstrapInput {
108    /// Target function name. Same semantics as
109    /// [`BootstrapInput::target`].
110    pub target: String,
111
112    /// Ordered `(input_name, value_bytes)` pairs. Same semantics as
113    /// [`BootstrapInput::inputs`] but with owned strings + buffers.
114    pub inputs: Vec<(String, Vec<u8>)>,
115}
116
117/// Engine-owned bootstrap state.
118pub(crate) struct BootstrapState {
119    /// Per-target Module bootstrap metadata. Populated by the
120    /// install path when a `module_phase = bootstrap` FunctionProto
121    /// lands.
122    pub(crate) module_bootstraps: HashMap<String, ModuleBootstrap>,
123
124    /// Append-only sequence of Module bootstrap target names in
125    /// install order. The seeder walks front-to-back so multi-
126    /// target installs surface one BootstrapComplete per target.
127    /// Append-only across a Node's lifetime; the seeder advances
128    /// `next_idx` rather than mutating the Vec so introspection
129    /// keeps reporting every queued target across phases.
130    pub(crate) install_order: Vec<String>,
131
132    /// Currently in-flight bootstrap body ExecId. Single-slot;
133    /// the post-collapse design fires bootstraps sequentially so
134    /// at most one body is alive at a time.
135    pub(crate) current_exec_id: Option<ExecId>,
136
137    /// Seed pointer into `install_order`. Bumps each time
138    /// [`crate::engine::core::Engine::maybe_complete_bootstrap`]
139    /// observes a phase drained. The host kick via
140    /// [`Self::arm_install_order`] picks up at this offset so a
141    /// partially-drained queue continues from the next unseeded
142    /// target.
143    pub(crate) next_idx: usize,
144
145    /// Coarse "queue still has work" flag. Armed by
146    /// [`Self::arm_install_order`] (host kick) or by the engine's
147    /// per-target staging path; cleared by `maybe_complete_bootstrap`
148    /// after the last queued phase drains. `Engine::poll` consults
149    /// it to gate body-phase ops.
150    pub(crate) pending: bool,
151}
152
153impl BootstrapState {
154    /// Construct an empty bootstrap state — every map / Vec empty,
155    /// `next_idx = 0`, `pending = false`.
156    pub(crate) fn new() -> Self {
157        Self {
158            module_bootstraps: HashMap::new(),
159            install_order: Vec::new(),
160            current_exec_id: None,
161            next_idx: 0,
162            pending: false,
163        }
164    }
165
166    /// Reset transient fields ahead of a `Node::restore` call.
167    /// `install_order` and `module_bootstraps` stay populated (the
168    /// install path stamps both at register time, and restore
169    /// preserves install metadata); `pending`, `current_exec_id`,
170    /// and `next_idx` reset so the restored Node does not re-fire
171    /// bootstraps it already ran.
172    pub(crate) fn clear_for_restore(&mut self) {
173        self.pending = false;
174        self.current_exec_id = None;
175        self.next_idx = self.install_order.len();
176    }
177
178    /// First queued Module bootstrap's function key, or `None` when
179    /// the queue is empty. Mirrors the prior `bootstrap_function_key()`
180    /// accessor.
181    pub(crate) fn first_function_key(&self) -> Option<&FunctionKey> {
182        let name = self.install_order.first()?;
183        self.module_bootstraps.get(name).map(|m| &m.function_key)
184    }
185
186    /// All queued Module bootstrap function keys in install order.
187    /// Mirrors the prior `bootstrap_function_keys()` accessor.
188    /// Allocates a fresh Vec each call — the caller uses this for
189    /// introspection (snapshot dumps, host-side asserts), not the
190    /// hot path.
191    pub(crate) fn function_keys(&self) -> Vec<FunctionKey> {
192        self.install_order
193            .iter()
194            .filter_map(|name| {
195                self.module_bootstraps
196                    .get(name)
197                    .map(|m| m.function_key.clone())
198            })
199            .collect()
200    }
201
202    /// Record a Module bootstrap target. Appends to `install_order`,
203    /// inserts `module_bootstraps[name]`. Idempotent per target name —
204    /// re-registering the same name updates the metadata in place
205    /// without re-appending to `install_order`.
206    ///
207    /// Does not arm `pending`. Host-driven model: install records
208    /// the bootstrap, the host calls [`crate::node::Node::run_bootstrap`]
209    /// to actually fire it. `Engine::poll` no longer auto-seeds the
210    /// queue on first call.
211    pub(crate) fn register_module(&mut self, function_key: FunctionKey) {
212        let name = function_key.1.clone();
213        let entry = self
214            .module_bootstraps
215            .entry(name.clone())
216            .or_insert_with(|| ModuleBootstrap {
217                function_key: function_key.clone(),
218            });
219        // Refresh the function_key in case install path passes a
220        // different (domain, overload) for the same name — the most
221        // recent install wins.
222        entry.function_key = function_key;
223        if !self.install_order.iter().any(|n| n == &name) {
224            self.install_order.push(name);
225        }
226    }
227
228    /// Arm `pending` so [`crate::engine::core::Engine::seed_bootstrap_call`]
229    /// picks up where the previous drain left off. The host calls
230    /// this through `Node::run_bootstrap` (empty slice) to kick the
231    /// install-time bootstrap queue; returns `false` when no
232    /// install-order target remains (idempotent on a fully drained
233    /// Node).
234    pub(crate) fn arm_install_order(&mut self) -> bool {
235        if self.next_idx >= self.install_order.len() {
236            return false;
237        }
238        self.pending = true;
239        true
240    }
241
242    /// Mark a Module bootstrap target as in-flight by recording its
243    /// body ExecId in `current_exec_id`. The body-op gate consults
244    /// this single ExecId via the descendant-chain walk.
245    pub(crate) fn mark_module_in_flight(&mut self, _target: String, exec_id: ExecId) {
246        self.current_exec_id = Some(exec_id);
247    }
248
249    /// Drop the currently in-flight bootstrap body ExecId.
250    /// `maybe_complete_bootstrap` calls this once the phase drains.
251    pub(crate) fn clear_in_flight(&mut self) {
252        self.current_exec_id = None;
253    }
254}
255