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