//! Engine injection: the one step that turns an uninjected section VM into //! an injected one, installing `args`, `argv`, `sys`, `var`, the `tools`, //! `models`, and `compactors` tables, and the VM's `messages` list. use std::sync::{Arc, Mutex}; use runvil_model_client::model::ModelBinding; use runvil_types::emitter::Emitter; use runvil_vfs::Access; use serde_json::Value as Json; use super::{Injected, SectionVm, Uninjected}; use crate::argv::Argv; use crate::compactors::install_compactors; use crate::error::{self, Error, Result}; use crate::messages::install_messages; use crate::models::install_models; use crate::sys::seal_sys; use crate::tools::install_tools; use crate::var::guarded_var; /// A refused Engine injection: the error, beside the uninjected VM it was /// refused on, which the caller tears down at its own boundary. #[derive(Debug)] pub struct InjectFailure { vm: Box>, error: Box, } impl InjectFailure { /// Why the injection failed. #[cfg(test)] #[must_use] pub(crate) fn error(&self) -> &Error { &self.error } /// Tears the refused VM down under `section` and returns why the /// injection failed. #[must_use] pub fn teardown(self, emitter: &Emitter, section: &str) -> Error { self.vm.teardown(emitter, section); *self.error } } impl SectionVm { /// Installs Engine values while seeding `var` from an earlier VM, and /// gives back the injected VM. /// /// Injection consumes the uninjected VM, so it runs once. /// /// The `var` global is a guarded proxy (see `guarded_var`): writes are /// validated for JSON-representability at the assigning line, and the /// hidden data table behind it is what [`var`](SectionVm::var) reads /// back. `access` is the chain step's VFS capability: the `store` /// table's closures share it, so a fanout arm's store ops are attributed /// to the arm's spawned identity, and a conflicting access from an /// identity not ordered after the arm's surfaces as a write race. /// /// `argv` is the parsed form of the args string, installed per its /// [`Argv`] mode: writable for the H1 pass (whose repaired value the /// executor reads back with [`argv_json`](SectionVm::argv_json)), frozen /// for every other section. /// /// `selection` holds the current model: `models.use` writes it, and /// [`current_model`](SectionVm::current_model) reads it. /// /// # Errors /// Returns the [`InjectFailure`] holding the bridging failure if Engine /// values cannot be bridged. #[expect( clippy::similar_names, reason = "args and argv are the spec'd global names; the pair is intentional" )] pub fn inject_values( self, args: &str, sys: &Json, access: &Arc, initial_var: Option<&Json>, argv: Argv<'_>, selection: &Arc>>, ) -> std::result::Result { if let Err(error) = self.install_values(args, sys, initial_var, argv, selection) { return Err(InjectFailure { vm: Box::new(self), error: Box::new(error), }); } Ok(self.injected(Injected { access: Arc::clone(access), sys_live: Arc::new(Mutex::new(sys.clone())), selection: Arc::clone(selection), })) } #[expect( clippy::similar_names, reason = "args and argv are the spec'd global names; the pair is intentional" )] fn install_values( &self, args: &str, sys: &Json, initial_var: Option<&Json>, argv: Argv<'_>, selection: &Arc>>, ) -> Result<()> { let globals = self.lua.globals(); globals .raw_set("args", args) .map_err(error::make_lua_error)?; match argv { Argv::Writable(value) => crate::argv::install_writable(&self.lua, value)?, Argv::Frozen(value) => crate::argv::install_frozen(&self.lua, value)?, } let sys_table = seal_sys(&self.lua, sys)?; globals .raw_set("sys", sys_table) .map_err(error::make_lua_error)?; let var = guarded_var(&self.lua, initial_var)?; globals.raw_set("var", var).map_err(error::make_lua_error)?; install_tools( &self.lua, &globals, &self.tool_set, &self.offers, &self.adhoc_tools, &self.messages, )?; install_models( &self.lua, &globals, &self.bound_models, selection, self.raw_model_ids, )?; install_messages(&self.lua, &self.messages)?; install_compactors(&self.lua, &globals, &self.messages) } /// The same VM at the injected stage, holding `state`. fn injected(self, state: Injected) -> SectionVm { let SectionVm { lua, tool_set, bound_models, offers, jump_slot, adhoc_handler_depth, loop_running, library_load_depth, library_programs, store_conflict, log_budget, log_byte_budget, adhoc_tools, cancel_hook, raw_model_ids, messages, state: Uninjected, } = self; SectionVm { lua, tool_set, bound_models, offers, jump_slot, adhoc_handler_depth, loop_running, library_load_depth, library_programs, store_conflict, log_budget, log_byte_budget, adhoc_tools, cancel_hook, raw_model_ids, messages, state, } } }