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`] (which calls
7//! [`Self::arm_install_order`] + `Engine::seed_bootstrap_call`) or
8//! by staging a `BootstrapRequest` (which arms via
9//! [`Self::enqueue_request`]).
10//!
11//! ## Field roles
12//!
13//! - `install_order` — append-only target-name sequence the install
14//! path stamps when a `module_phase = bootstrap` FunctionProto
15//! lands. Drives the seeder front-to-back so multi-target installs
16//! surface one BootstrapComplete per target in the order the host
17//! supplied to `bytesandbrains::install`.
18//! - `module_bootstraps` — per-target metadata (function key + touch
19//! set) keyed on the target name. The touch set is the closure of
20//! every `ComponentRef` referenced by the bootstrap function body
21//! (slot-id NodeProtos + transitive FunctionCalls); the install
22//! path stamps it via `Engine::compute_touch_set` so the
23//! host-driven driver can pre-acquire bound components without
24//! re-walking the program at run time.
25//! - `component_bootstraps` — per-slot Component bootstrap registry.
26//! - `pending_requests` — host-supplied `BootstrapRequest`s awaiting
27//! validation + staging.
28//! - `in_flight` — currently executing bootstraps (Module or
29//! Component). Today at most one entry — the currently seeded
30//! module bootstrap. The Vec shape readies the host-driven path
31//! for concurrent Component bootstraps.
32//! - `waiting` — validated + staged `QueuedBootstrap`s ready to fire
33//! once the in-flight set drains. Empty today; F4 populates.
34//! - `next_idx` — seed pointer into `install_order`. Bumps each time
35//! `Engine::maybe_complete_bootstrap` observes a phase drained.
36//! `Engine::seed_bootstrap_call` reads it to pick the next target
37//! once the host kicks the queue.
38//! - `pending` — coarse "queue still has work" flag the body-op gate
39//! + `maybe_complete_bootstrap` consult. Armed by
40//! `arm_install_order` (host kick) or `enqueue_request` (staged
41//! `BootstrapRequest`), cleared once every queued phase drains.
42
43use std::collections::{HashMap, HashSet, VecDeque};
44
45use crate::engine::dispatch_entry::FunctionKey;
46use crate::ids::{ComponentRef, ExecId};
47
48/// Per-target Module bootstrap metadata. Stamped into the engine
49/// bootstrap state when the install path sees a
50/// `module_phase = bootstrap` FunctionProto.
51#[derive(Clone, Debug)]
52pub struct ModuleBootstrap {
53 /// Canonical `(domain, name, overload)` key of the bootstrap
54 /// FunctionProto. Used to look up the GraphSlot at seed time.
55 pub function_key: FunctionKey,
56
57 /// Closure of every `ComponentRef` referenced by the bootstrap
58 /// body (slot-id NodeProtos + every transitively-called
59 /// FunctionProto's slot-id NodeProtos). Computed by the engine
60 /// at install time; the host-driven driver consults it to pre-
61 /// acquire bound components without re-walking the program.
62 pub touch_set: HashSet<ComponentRef>,
63}
64
65/// Per-slot Component bootstrap metadata. Stamped into the engine
66/// bootstrap state when a Component registers a `Bootstrap`
67/// Contract impl. Empty today; F5 wires the registration path.
68#[derive(Clone, Debug)]
69pub struct ComponentBootstrap {
70 /// Component reference the bootstrap dispatches against.
71 pub cref: ComponentRef,
72}
73
74/// Discriminator for the two bootstrap dispatch kinds the engine
75/// drives. Module bootstraps splice into the FunctionCall path under
76/// a fresh ExecId; Component bootstraps invoke a Contract method on
77/// the bound runtime impl directly.
78#[derive(Clone, Debug)]
79pub enum BootstrapKind {
80 /// Module bootstrap — `target` names the FunctionProto whose
81 /// body the engine seeds onto the frontier.
82 Module {
83 /// Target function name (matches an entry in the engine's
84 /// `install_order` queue).
85 target: String,
86 },
87
88 /// Component bootstrap — `slot` names the binding slot whose
89 /// bound Component the engine invokes.
90 Component {
91 /// Slot name (matches a key in the engine's
92 /// `component_bootstraps` registry).
93 slot: String,
94 },
95}
96
97/// Host-facing bootstrap lifecycle status. Returned by
98/// `Node::bootstrap_status` so the caller can decide whether to keep
99/// polling or surface a "wait for input" prompt. F3 fills the
100/// observable surface.
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum BootstrapStatus {
103 /// No bootstrap queued or in-flight — `Node::poll` runs the
104 /// body phase freely.
105 Idle,
106
107 /// Bootstrap is queued + executing. Body-phase ops park until
108 /// the queue drains.
109 Running,
110
111 /// Bootstrap is queued but waiting on host-supplied input
112 /// formals. The host must call `Node::run_bootstrap`
113 /// (`BootstrapTarget::ModuleRequests` or `Slots`) to advance.
114 WaitingForInput,
115}
116
117/// Host-supplied bootstrap input staging request — borrowed shape per
118/// the F5 host-driven bootstrap spec
119/// (`docs/internal/superpowers/specs/2026-06-25-host-driven-bootstrap.md`
120/// §3.1). The host hands the engine a `target` name plus an ordered
121/// `(input_name, value_bytes)` slice; the engine validates against the
122/// target's declared formal input ports and runs the Principle 1a copy
123/// (cap-check → `try_reserve_exact` → `extend_from_slice`) so the
124/// caller's borrowed buffers can drop the moment
125/// [`crate::engine::core::Engine::enqueue_bootstrap_request`] returns.
126///
127/// Owned-form storage on the engine's internal conflict queue lives in
128/// [`OwnedBootstrapRequest`]; the borrowed form here is the host-facing
129/// request shape only.
130pub struct BootstrapRequest<'a> {
131 /// Target function name. Must match an entry in the engine's
132 /// `install_order` queue (i.e. a Module whose `bootstrap`
133 /// FunctionProto landed via `install_function_library`).
134 pub target: &'a str,
135
136 /// Ordered `(input_name, value_bytes)` pairs. Validated against
137 /// the target's declared input formals; missing required inputs
138 /// surface as `BootstrapError::MissingInput` and unknown ones as
139 /// `BootstrapError::UnknownInput` before any staging happens.
140 pub inputs: &'a [(&'a str, &'a [u8])],
141}
142
143/// Owned-form bootstrap input request used by the engine's internal
144/// conflict-queue path (`BootstrapState::pending_requests`). The host-
145/// facing borrowed [`BootstrapRequest`] is the canonical staging shape;
146/// this owned mirror exists so the conflict queue can keep parked
147/// requests alive across poll cycles. Kept internal-ish to the engine —
148/// the [`BootstrapState::enqueue_request`] consumer is exercised by
149/// sibling tests only.
150#[derive(Clone, Debug)]
151pub struct OwnedBootstrapRequest {
152 /// Target function name. Same semantics as
153 /// [`BootstrapRequest::target`].
154 pub target_name: String,
155
156 /// Ordered `(input_name, value_bytes)` pairs. Same semantics as
157 /// [`BootstrapRequest::inputs`] but with owned strings + buffers
158 /// so the queue survives poll cycles.
159 pub inputs: Vec<(String, Vec<u8>)>,
160}
161
162/// Currently executing bootstrap. The host-driven seeder
163/// (`Engine::seed_bootstrap_call`) pops one Module target per phase
164/// and pushes it here; the Vec shape supports concurrent disjoint
165/// Component bootstraps fired through the conflict-queue path.
166#[derive(Clone, Debug)]
167pub struct InFlightBootstrap {
168 /// Kind discriminator — Module vs Component.
169 pub kind: BootstrapKind,
170
171 /// ExecId allocated for the bootstrap's body. The body-op gate
172 /// walks the `parent_exec_id` chain on `pending_calls` and
173 /// fires an op when the chain terminates at this ExecId.
174 pub exec_id: ExecId,
175
176 /// `ComponentRef` closure this bootstrap locks against body-phase
177 /// ops. Populated from `ModuleBootstrap.touch_set` (Module) or the
178 /// single bound `ComponentRef` (Component) at fire time. The
179 /// body-op gate (`Engine::is_op_locked`) parks any body op whose
180 /// touched `ComponentRef` falls in this set so disjoint Components
181 /// can keep firing while bootstrap runs.
182 pub touch_set: HashSet<ComponentRef>,
183
184 /// Names already covered by staged input values. Empty today;
185 /// F4 populates from `BootstrapRequest::inputs` as the host
186 /// supplies formals.
187 pub staged_inputs: HashSet<String>,
188}
189
190/// Validated + staged bootstrap that cleared the touch-set conflict
191/// check and is ready for the engine to assign an ExecId + push its
192/// body onto the frontier. The conflict queue
193/// (`BootstrapState::process_pending_requests` /
194/// `on_bootstrap_drained`) emits `ReadyBootstrap`s back to the engine
195/// instead of mutating in-flight state directly so the seed step
196/// (ExecId allocation, frontier population) stays on the engine
197/// side where the OpRef tables live.
198#[derive(Clone, Debug)]
199pub struct ReadyBootstrap {
200 /// Kind discriminator — Module vs Component.
201 pub kind: BootstrapKind,
202
203 /// `ComponentRef` closure the engine should record on the new
204 /// `InFlightBootstrap` so the gate locks the right slots.
205 pub touch_set: HashSet<ComponentRef>,
206
207 /// Names already covered by staged input values (carried over
208 /// from the originating `QueuedBootstrap`). Empty today;
209 /// `Node::run_bootstrap` (`BootstrapTarget::ModuleRequests`) populates once F4 lands.
210 pub staged_inputs: HashSet<String>,
211}
212
213/// Validated + staged bootstrap awaiting an in-flight slot. F4
214/// drains `waiting` once `in_flight` clears. Empty today.
215#[derive(Clone, Debug)]
216pub struct QueuedBootstrap {
217 /// Kind discriminator — Module vs Component.
218 pub kind: BootstrapKind,
219
220 /// `ComponentRef` closure this queued bootstrap will lock when it
221 /// promotes to in-flight. Mirrors `InFlightBootstrap.touch_set`
222 /// so the conflict-queue check (F3 Commit 2) can compare a
223 /// waiter's touch set against currently-in-flight touch sets.
224 pub touch_set: HashSet<ComponentRef>,
225
226 /// Names already covered by staged input values. Filled by
227 /// `Node::run_bootstrap` (`BootstrapTarget::ModuleRequests`) once F4 lands.
228 pub staged_inputs: HashSet<String>,
229}
230
231/// Engine-owned bootstrap state. One field on `Engine`, replacing
232/// the four `bootstrap_*` fields the prior shape carried.
233pub(crate) struct BootstrapState {
234 /// Per-target Module bootstrap metadata. Populated by the
235 /// install path when a `module_phase = bootstrap` FunctionProto
236 /// lands.
237 pub(crate) module_bootstraps: HashMap<String, ModuleBootstrap>,
238
239 /// Per-slot Component bootstrap metadata. Empty today; F5 wires
240 /// the registration path.
241 pub(crate) component_bootstraps: HashMap<String, ComponentBootstrap>,
242
243 /// Append-only sequence of Module bootstrap target names in
244 /// install order. The seeder walks front-to-back so multi-
245 /// target installs surface one BootstrapComplete per target.
246 /// Append-only across a Node's lifetime; the seeder advances
247 /// `next_idx` rather than mutating the Vec so introspection
248 /// keeps reporting every queued target across phases.
249 pub(crate) install_order: Vec<String>,
250
251 /// Host-supplied bootstrap input staging requests awaiting
252 /// validation + staging. Empty today; F3 populates from
253 /// `Node::run_bootstrap` (`BootstrapTarget::ModuleRequests`). The owned-form mirror of
254 /// [`BootstrapRequest`] keeps parked entries alive across poll
255 /// cycles; the engine's F5 immediate-fire entry point
256 /// ([`crate::engine::core::Engine::enqueue_bootstrap_request`])
257 /// bypasses this queue and stages directly.
258 pub(crate) pending_requests: VecDeque<OwnedBootstrapRequest>,
259
260 /// Currently executing bootstraps. The host-driven seeder
261 /// (`Engine::seed_bootstrap_call`) records one Module target per
262 /// phase; the conflict-queue path can fire additional disjoint
263 /// Component bootstraps so multiple entries coexist when
264 /// `pending_requests` fans out.
265 pub(crate) in_flight: Vec<InFlightBootstrap>,
266
267 /// Validated + staged bootstraps ready to fire once `in_flight`
268 /// drains. Empty today; F4 populates.
269 pub(crate) waiting: VecDeque<QueuedBootstrap>,
270
271 /// Seed pointer into `install_order`. Bumps each time
272 /// [`crate::engine::core::Engine::maybe_complete_bootstrap`]
273 /// observes a phase drained. The host kick via
274 /// [`Self::arm_install_order`] rewinds — or rather, picks up at —
275 /// this offset so re-arming a partially-drained queue continues
276 /// from the next unseeded target.
277 pub(crate) next_idx: usize,
278
279 /// Coarse "queue still has work" flag. Armed by
280 /// [`Self::arm_install_order`] (host kick) or
281 /// [`Self::enqueue_request`] (staged input), cleared by
282 /// `maybe_complete_bootstrap` after the last queued phase drains.
283 /// `Engine::poll` consults it to skip the bootstrap path entirely
284 /// on cycles with no queued work.
285 pub(crate) pending: bool,
286}
287
288impl BootstrapState {
289 /// Construct an empty bootstrap state — every map / Vec /
290 /// VecDeque empty, `next_idx = 0`, `pending = false`.
291 pub(crate) fn new() -> Self {
292 Self {
293 module_bootstraps: HashMap::new(),
294 component_bootstraps: HashMap::new(),
295 install_order: Vec::new(),
296 pending_requests: VecDeque::new(),
297 in_flight: Vec::new(),
298 waiting: VecDeque::new(),
299 next_idx: 0,
300 pending: false,
301 }
302 }
303
304 /// Reset transient fields ahead of a `Node::restore` call.
305 /// `install_order` and `module_bootstraps` stay populated (the
306 /// install path stamps both at register time, and restore
307 /// preserves install metadata); `pending`, `in_flight`,
308 /// `pending_requests`, `waiting`, and `next_idx` reset so the
309 /// restored Node does not re-fire bootstraps it already ran.
310 pub(crate) fn clear_for_restore(&mut self) {
311 self.pending = false;
312 self.in_flight.clear();
313 self.pending_requests.clear();
314 self.waiting.clear();
315 self.next_idx = self.install_order.len();
316 }
317
318 /// First queued Module bootstrap's function key, or `None` when
319 /// the queue is empty. Mirrors the prior `bootstrap_function_key()`
320 /// accessor.
321 pub(crate) fn first_function_key(&self) -> Option<&FunctionKey> {
322 let name = self.install_order.first()?;
323 self.module_bootstraps.get(name).map(|m| &m.function_key)
324 }
325
326 /// All queued Module bootstrap function keys in install order.
327 /// Mirrors the prior `bootstrap_function_keys()` accessor.
328 /// Allocates a fresh Vec each call — the caller uses this for
329 /// introspection (snapshot dumps, host-side asserts), not the
330 /// hot path.
331 pub(crate) fn function_keys(&self) -> Vec<FunctionKey> {
332 self.install_order
333 .iter()
334 .filter_map(|name| {
335 self.module_bootstraps
336 .get(name)
337 .map(|m| m.function_key.clone())
338 })
339 .collect()
340 }
341
342 /// ExecId of the currently in-flight Module bootstrap. Mirrors
343 /// the prior `bootstrap_exec_id` field. Returns `None` when no
344 /// Module bootstrap is in-flight; if multiple in-flight entries
345 /// exist (future Component bootstrap path), returns the first
346 /// Module-kind ExecId.
347 pub(crate) fn module_exec_id(&self) -> Option<ExecId> {
348 self.in_flight.iter().find_map(|b| match b.kind {
349 BootstrapKind::Module { .. } => Some(b.exec_id),
350 BootstrapKind::Component { .. } => None,
351 })
352 }
353
354 /// Record a Module bootstrap target. Appends to `install_order`,
355 /// inserts `module_bootstraps[name]`. Idempotent per target name —
356 /// re-registering the same name updates the metadata in place
357 /// without re-appending to `install_order`.
358 ///
359 /// Does not arm `pending`. Host-driven F4 model: install records
360 /// the bootstrap, the host calls [`crate::node::Node::run_bootstrap`]
361 /// (or stages a `BootstrapRequest`) to actually fire it. `Engine::poll`
362 /// no longer auto-seeds the queue on first call.
363 pub(crate) fn register_module(&mut self, function_key: FunctionKey) {
364 let name = function_key.1.clone();
365 let entry = self
366 .module_bootstraps
367 .entry(name.clone())
368 .or_insert_with(|| ModuleBootstrap {
369 function_key: function_key.clone(),
370 touch_set: HashSet::new(),
371 });
372 // Refresh the function_key in case install path passes a
373 // different (domain, overload) for the same name — the most
374 // recent install wins.
375 entry.function_key = function_key;
376 if !self.install_order.iter().any(|n| n == &name) {
377 self.install_order.push(name);
378 }
379 }
380
381 /// Arm `pending` and rewind `next_idx` to the first unseeded target
382 /// so [`crate::engine::core::Engine::seed_bootstrap_call`] picks up
383 /// where the previous drain left off. The host calls this through
384 /// `Node::run_bootstrap` to kick the install-time bootstrap queue;
385 /// returns `false` when no install-order target remains (idempotent
386 /// on a fully drained Node).
387 pub(crate) fn arm_install_order(&mut self) -> bool {
388 if self.next_idx >= self.install_order.len() {
389 return false;
390 }
391 self.pending = true;
392 true
393 }
394
395 /// Mark a Module bootstrap target as in-flight. Pushes an
396 /// `InFlightBootstrap` with kind `Module { target }` carrying
397 /// the supplied ExecId + the `ComponentRef` closure the body-op
398 /// gate consults to park overlapping body ops.
399 pub(crate) fn mark_module_in_flight(
400 &mut self,
401 target: String,
402 exec_id: ExecId,
403 touch_set: HashSet<ComponentRef>,
404 ) {
405 self.in_flight.push(InFlightBootstrap {
406 kind: BootstrapKind::Module { target },
407 exec_id,
408 touch_set,
409 staged_inputs: HashSet::new(),
410 });
411 }
412
413 /// Look up a Component bootstrap by slot name. F5 populates
414 /// `component_bootstraps` from the Bootstrap Contract
415 /// registration path; today the map stays empty so this lookup
416 /// always returns `None`. Wired now so the field has a non-test
417 /// reader and the F5 dispatch path doesn't need to expand the
418 /// accessor surface — it just calls this method.
419 pub(crate) fn component_bootstrap(&self, slot: &str) -> Option<&ComponentBootstrap> {
420 self.component_bootstraps.get(slot)
421 }
422
423 /// Push a host-supplied [`OwnedBootstrapRequest`] onto the pending
424 /// queue + arm `pending` so the body gate stays parked until the
425 /// staged work drains. Direct entry-point exists for the engine's
426 /// internal conflict-queue tests; production callers use
427 /// [`crate::engine::core::Engine::enqueue_bootstrap_request`]
428 /// (F5 immediate-fire) which validates + stages without going
429 /// through the parked queue.
430 #[cfg(test)]
431 pub(crate) fn enqueue_request(&mut self, req: OwnedBootstrapRequest) {
432 self.pending_requests.push_back(req);
433 self.pending = true;
434 }
435
436 /// Drain `pending_requests` once. For each request, resolve its
437 /// target's `ComponentRef` touch set (Module bootstraps look it
438 /// up via `module_bootstraps`); compare against every currently
439 /// in-flight bootstrap's touch set. On disjoint → return a
440 /// [`ReadyBootstrap`] for the engine to seed. On overlap →
441 /// enqueue as [`QueuedBootstrap`] in `waiting` and let
442 /// [`Self::on_bootstrap_drained`] promote it later. Requests
443 /// whose target is unknown drop silently — caller-side
444 /// validation is responsible for surfacing the error before the
445 /// request reaches the queue.
446 pub(crate) fn process_pending_requests(&mut self) -> Vec<ReadyBootstrap> {
447 let mut ready = Vec::new();
448 while let Some(req) = self.pending_requests.pop_front() {
449 // Look up the touch set for this target. Module
450 // bootstraps cache it on `module_bootstraps`. Unknown
451 // targets drop here — the host-facing API
452 // (`Node::run_bootstrap` (`BootstrapTarget::ModuleRequests`)) returns
453 // `BootstrapError::UnknownTarget` before enqueueing, so
454 // reaching this branch indicates a stale request and we
455 // skip rather than wedge.
456 let Some(meta) = self.module_bootstraps.get(&req.target_name) else {
457 continue;
458 };
459 let touch_set = meta.touch_set.clone();
460 let kind = BootstrapKind::Module {
461 target: req.target_name.clone(),
462 };
463 // Staged-inputs placeholder — F4 populates from
464 // `req.inputs` once input staging lands. For Commit 2
465 // the conflict-queue path is the only consumer and it
466 // ignores the names.
467 let staged_inputs: HashSet<String> =
468 req.inputs.iter().map(|(name, _)| name.clone()).collect();
469 if Self::overlaps_any_in_flight(&self.in_flight, &touch_set) {
470 self.waiting.push_back(QueuedBootstrap {
471 kind,
472 touch_set,
473 staged_inputs,
474 });
475 } else {
476 ready.push(ReadyBootstrap {
477 kind,
478 touch_set,
479 staged_inputs,
480 });
481 }
482 }
483 ready
484 }
485
486 /// Drop the in-flight bootstrap whose body ExecId matches
487 /// `exec_id`, then walk `waiting` once and promote any waiter
488 /// whose touch set no longer conflicts with the remaining
489 /// in-flight set. Returns promoted waiters in queue order so
490 /// the engine can seed them. The engine calls this from
491 /// `maybe_complete_bootstrap` after a phase drains.
492 pub(crate) fn on_bootstrap_drained(&mut self, exec_id: ExecId) -> Vec<ReadyBootstrap> {
493 self.in_flight.retain(|b| b.exec_id != exec_id);
494 let mut promoted = Vec::new();
495 let mut remaining = VecDeque::new();
496 while let Some(waiter) = self.waiting.pop_front() {
497 if Self::overlaps_any_in_flight(&self.in_flight, &waiter.touch_set) {
498 remaining.push_back(waiter);
499 } else {
500 promoted.push(ReadyBootstrap {
501 kind: waiter.kind,
502 touch_set: waiter.touch_set,
503 staged_inputs: waiter.staged_inputs,
504 });
505 }
506 }
507 self.waiting = remaining;
508 promoted
509 }
510
511 /// `true` when `touch_set` overlaps any in-flight bootstrap's
512 /// touch set. Empty touch sets never conflict — Module
513 /// bootstraps whose body touches no Component can fan out
514 /// alongside any in-flight target.
515 fn overlaps_any_in_flight(
516 in_flight: &[InFlightBootstrap],
517 touch_set: &HashSet<ComponentRef>,
518 ) -> bool {
519 if touch_set.is_empty() {
520 return false;
521 }
522 in_flight
523 .iter()
524 .any(|b| !b.touch_set.is_disjoint(touch_set))
525 }
526}
527