1pub mod agents;
4pub(crate) mod array_storage;
5pub mod blocking;
6pub mod call_log;
7pub mod decision;
8pub mod disk_quota;
9pub mod embedding;
10pub mod exec;
11pub mod fs;
12pub mod fuel;
13pub mod gc_singleton;
14pub mod host;
15pub mod intrinsic_types;
16pub mod json;
17pub(crate) mod json_text;
18pub mod limits;
19pub mod llm;
20pub mod mcp;
21pub mod metrics;
22pub mod number;
23pub mod prelude;
24pub mod secrets;
25pub mod security;
26pub mod session_kv;
27pub mod skills;
28pub mod token_ledger;
29pub mod vfs;
30pub mod watchdog;
31
32pub use agents::{
33 AGENTS_MODULE_NAME, AgentCallError, AgentInfo, AgentOutcome, AgentProvider, AgentRequest,
34 AgentUsage,
35};
36pub use call_log::{BodyCopy, CallOutcome, CallRecord, ModelUsage, PayloadRecord};
37pub use decision::{
38 CallSite, CallTicket, DecisionAction, DecisionCause, DecisionExplanation, DecisionLog,
39 DecisionLogConfig, DecisionLogOutput, DecisionRecord, DecisionRecorder, EntryPath,
40 FailureReasonRecord, FailureRecord, NearMissRecord, RecordObserver, RuleCitation, SourceLine,
41};
42pub use disk_quota::{DiskQuota, Holder, OpenFileGuard, QuotaCharge, QuotaExceeded};
43pub use embedding::{
44 DEFAULT_MAX_EMBEDDING_REQUESTS, DEFAULT_MAX_EXECUTION_EMBEDDING_TOKENS, EMBEDDING_MODULE_NAME,
45 EmbeddingBatch, EmbeddingBoundKind, EmbeddingError, EmbeddingFailureReason, EmbeddingLimitKind,
46 EmbeddingLimits, EmbeddingMalformedReason, EmbeddingModel, EmbeddingProvider,
47 EmbeddingShapeError, EmbeddingTokenBudget, Purpose, SubBatchSettlement,
48 estimate_embedding_tokens,
49};
50pub use exec::{RunResult, dispatch_main_async, instantiate_program_async};
51pub use host::{
52 INTERNAL_MODULE_NAME, NUMBER_MODULE_NAME, host_package_declarations,
53 install_async as install_runtime_async, install_async_for as install_runtime_async_for,
54 install_host_functions as install_runtime_host_functions,
55 install_host_functions_for as install_runtime_host_functions_for,
56 install_store_bound as install_runtime_store_bound, internal_host_package_declarations,
57 stdlib_package_declarations,
58};
59pub use json::JSON_MODULE_NAME;
60pub use limits::{
61 DEFAULT_MAX_STORE_BYTES, MemoryCapExceeded, MemoryExhausted, TenantLimits,
62 install_tenant_limits, is_memory_exhausted,
63};
64pub use llm::{
65 DEFAULT_MAX_ALL_EXECUTIONS_TOKENS, DEFAULT_MAX_EXECUTION_TOKENS, ExecutionTokenBudget,
66 FailureReason, LLM_MODULE_NAME, LlmCallError, LlmFailure, LlmLimitKind, LlmLimits, LlmModel,
67 LlmOutcome, LlmProvider, PromptBoundKind, SharedTokenBudget,
68};
69pub use mcp::{
70 MCP_MODULE_NAME, McpCallError, McpOutcome, McpResponse, McpTransport, install_mcp_async,
71 mcp_call_package_declaration,
72};
73pub use metrics::{HttpMetric, MetricsSink, NoopMetricsSink};
74pub use prelude::bigint::ops::BIGINT_MODULE_NAME;
75pub use prelude::temporal::shared::TEMPORAL_MODULE_NAME;
76pub use secrets::{NoopSecretProvider, SecretProvider};
77pub use security::{AllowAllCheck, AuditDecision, CheckOutcome, SecurityCheck};
78pub use session_kv::{
79 InMemorySessionKv, SessionKvEntry, SessionKvError, SessionKvLimitKind, SessionKvLimits,
80 SessionKvPage, SessionKvStore, SharedKvBudget,
81};
82pub use skills::{SKILLS_MODULE_NAME, Skill, SkillError, SkillInfo, SkillProvider};
83pub use vfs::{
84 Access, CopyDirError, MAX_MEASURED_DEPTH, MountError, MountSpec, Vfs, VfsMode, copy_host_dir,
85 copy_host_subdir, measure_dir, measure_host_dir, measure_host_dir_skipping_vanished,
86 measure_host_subdir_skipping_vanished, open_host_subdir, regular_files,
87};
88pub use watchdog::Watchdog;
89
90pub use crate::stdlib::http::{
91 AuthProxy, AuthProxyError, HttpClient, HttpError, HttpRequest, HttpResponse, NetworkPolicy,
92 NoopAuthProxy, ReqwestHttpClient,
93};
94
95use std::cell::RefCell;
96use std::io::Write;
97use std::sync::{Arc, Mutex};
98use std::time::Duration;
99
100use wasmtime::{
101 ArrayRef, AsContextMut, Config, Engine, Instance, Linker, Module, OptLevel, Rooted, Store,
102 WasmBacktraceDetails,
103};
104
105use crate::{PackageDeclaration, TypeInfoTable};
106
107#[derive(Debug, Clone)]
111pub struct VfsInfo {
112 pub mode: VfsMode,
113 pub size_limit: Option<u64>,
114}
115
116pub struct StoreData {
117 #[cfg(test)]
118 pub(crate) array_growth: array_storage::GrowthStats,
119 pub blocking_work: blocking::BlockingWork,
122 pub git: Option<crate::stdlib::git::GitConfig>,
123 pub(crate) git_history: Option<Arc<crate::stdlib::git::log_cache::Cache>>,
124 pub console: Box<dyn Write + Send>,
125 pub vfs: Vfs,
126 pub vfs_info: VfsInfo,
127 pub security_check: Arc<dyn SecurityCheck>,
128 pub fs_max_read_size: u64,
129 pub http_client: Arc<dyn HttpClient>,
130 pub http_max_response_size: u64,
131 pub http_max_download_timeout_ms: u64,
133 pub auth_proxy: Arc<dyn AuthProxy>,
134 pub secret_provider: Arc<dyn SecretProvider>,
135 pub mcp_transport: Option<Arc<dyn McpTransport>>,
139 pub llm_provider: Option<Arc<dyn LlmProvider>>,
145 pub llm_budget: Option<Arc<ExecutionTokenBudget>>,
152 pub embedding_provider: Option<Arc<dyn EmbeddingProvider>>,
156 pub embedding_budget: Option<Arc<EmbeddingTokenBudget>>,
159 pub session_kv: Option<Arc<dyn SessionKvStore>>,
164 pub agent_provider: Option<Arc<dyn AgentProvider>>,
168 pub skill_provider: Option<Arc<dyn SkillProvider>>,
172 pub metrics: Arc<dyn metrics::MetricsSink>,
175 pub tenant_limits: TenantLimits,
176 pub host_fuel: u64,
179 pub(crate) next_identity_hash: u64,
180 #[cfg(test)]
181 pub(crate) collection_index_reads: u64,
182 #[cfg(test)]
183 pub(crate) object_index_probes: u64,
184 pub host_fuel_pending: u64,
187 pub host_fuel_applied_at: Option<u64>,
190 pub test_labels: RefCell<Vec<String>>,
195 pub host_abi: Option<crate::runtime::host::HostAbi>,
200 pub(crate) intrinsic_types: Option<Arc<crate::runtime::intrinsic_types::IntrinsicTypes>>,
203 pub(crate) undefined_value: Option<wasmtime::Global>,
205 pub(crate) closure_receiver_type: Option<wasmtime::StructType>,
208 pub(crate) call_metadata_type: Option<wasmtime::StructType>,
211 pub(crate) iterator_functions: [Option<wasmtime::Func>; 10],
212 pub(crate) iterator_constants: [Option<wasmtime::Global>; 6],
213 pub(crate) static_values: std::collections::BTreeMap<Vec<u16>, wasmtime::Global>,
216 pub(crate) parameter_cache: prelude::arguments::ParameterCache,
217 pub(crate) regex_input: Option<prelude::regex::input::InputCache>,
218 pub type_info: std::collections::BTreeMap<String, TypeInfoTable>,
220 pub vtable_walk_depth: u32,
223 pub(crate) vtable_walk_nodes: u32,
226 pub(crate) settling_host_result: bool,
228 pub(crate) thrown_denials: host::ThrownDenials,
230}
231
232pub(crate) const MAX_VTABLE_WALK_DEPTH: u32 = 128;
241
242pub(crate) const MAX_STRUCTURAL_WALK_NODES: u32 = 100_000;
244
245pub const DEFAULT_FS_MAX_READ_SIZE: u64 = 50 * 1024 * 1024;
246
247pub const DEFAULT_HTTP_MAX_RESPONSE_SIZE: u64 = 50 * 1024 * 1024;
248
249#[derive(Clone, Copy)]
250pub struct LinkedPackageModule<'a> {
251 pub module: &'a Module,
252 pub declaration: &'a PackageDeclaration,
253 pub type_info: &'a TypeInfoTable,
254}
255
256impl StoreData {
257 pub fn with_vfs(vfs: Vfs) -> Self {
258 Self::with_vfs_and_cap(vfs, DEFAULT_MAX_STORE_BYTES)
259 }
260
261 pub fn with_vfs_and_cap(vfs: Vfs, max_store_bytes: u64) -> Self {
262 let vfs_info = VfsInfo {
263 mode: vfs.mode(),
264 size_limit: None,
265 };
266 Self {
267 #[cfg(test)]
268 array_growth: array_storage::GrowthStats::default(),
269 console: Box::new(std::io::stderr()),
270 vfs,
271 vfs_info,
272 security_check: security::default_check(),
273 git: None,
274 git_history: None,
275 blocking_work: blocking::BlockingWork::default(),
276 fs_max_read_size: DEFAULT_FS_MAX_READ_SIZE,
277 http_client: crate::stdlib::http::default_http_client(),
278 http_max_response_size: DEFAULT_HTTP_MAX_RESPONSE_SIZE,
279 http_max_download_timeout_ms: 60_000,
280 auth_proxy: crate::stdlib::http::default_auth_proxy(),
281 secret_provider: Arc::new(secrets::NoopSecretProvider),
282 mcp_transport: None,
283 llm_provider: None,
284 llm_budget: None,
285 embedding_provider: None,
286 embedding_budget: None,
287 session_kv: None,
288 agent_provider: None,
289 skill_provider: None,
290 metrics: Arc::new(metrics::NoopMetricsSink),
291 tenant_limits: TenantLimits::new(max_store_bytes),
292 host_fuel: 0,
293 #[cfg(test)]
294 collection_index_reads: 0,
295 #[cfg(test)]
296 object_index_probes: 0,
297 next_identity_hash: 0,
298 host_fuel_pending: 0,
299 host_fuel_applied_at: None,
300 test_labels: RefCell::new(Vec::new()),
301 host_abi: None,
302 intrinsic_types: None,
303 undefined_value: None,
304 closure_receiver_type: None,
305 call_metadata_type: None,
306 iterator_functions: [None; 10],
307 iterator_constants: [None; 6],
308 static_values: std::collections::BTreeMap::new(),
309 parameter_cache: Default::default(),
310 regex_input: None,
311 type_info: std::collections::BTreeMap::new(),
312 vtable_walk_depth: 0,
313 vtable_walk_nodes: 0,
314 settling_host_result: false,
315 thrown_denials: host::ThrownDenials::default(),
316 }
317 }
318
319 pub fn install_type_info(&mut self, table: TypeInfoTable) {
320 self.type_info.insert(table.package_name.clone(), table);
321 }
322
323 pub fn with_tempdir() -> std::io::Result<Self> {
324 Ok(Self::with_vfs(Vfs::tempdir()?))
325 }
326}
327
328pub async fn install_package_modules_async(
329 linker: &mut Linker<StoreData>,
330 store: &mut Store<StoreData>,
331 packages: &[LinkedPackageModule<'_>],
332) -> wasmtime::Result<()> {
333 for package in packages {
334 let name = package.declaration.package_name.as_str();
335 if store.data().git.is_none()
336 && package
337 .module
338 .imports()
339 .any(|import| import.module() == "submilli:git")
340 {
341 wasmtime::bail!(
342 "package `{name}` imports submilli:git; configure the blueprint git block"
343 );
344 }
345 match package.module.name() {
352 Some(declared) if declared == name => {}
353 Some(declared) => wasmtime::bail!(
354 "package `{name}`: its module declares the name `{declared}`, which would give \
355 it that principal's permissions. Rebuild the package."
356 ),
357 None => wasmtime::bail!(
358 "package `{name}`: its module declares no name, so its gated calls cannot be \
359 attributed. Rebuild the package."
360 ),
361 }
362 store
363 .data_mut()
364 .install_type_info(package.type_info.clone());
365 let instance = {
369 let outcome = linker.instantiate_async(&mut *store, package.module).await;
370 name_the_failing_initializer(&mut *store, name, outcome)?
371 };
372 linker.instance(&mut *store, name, instance)?;
373 }
374 Ok(())
375}
376
377fn name_the_failing_initializer(
386 store: &mut Store<StoreData>,
387 package: &str,
388 outcome: wasmtime::Result<Instance>,
389) -> wasmtime::Result<Instance> {
390 let err = match outcome {
391 Ok(instance) => return Ok(instance),
392 Err(err) => err,
393 };
394 let recovered = exec::uncaught_error(store, err);
395 let Some(thrown) = recovered.downcast_ref::<crate::backtrace::ThrownError>() else {
396 return Err(recovered);
397 };
398 Err(wasmtime::Error::new(crate::backtrace::ThrownError {
399 message: format!(
400 "package `{package}` failed to initialize: {}",
401 thrown.message
402 ),
403 backtrace: thrown.backtrace.clone(),
404 denial: thrown.denial.clone(),
405 }))
406}
407
408const NATIVE_STACK_PER_WASM_BYTE: usize = 32;
411const MIN_NATIVE_STACK: usize = 8 * 1024 * 1024;
412
413#[derive(Clone, Debug)]
414pub struct RuntimeConfig {
415 pub fuel: u64,
416 pub max_wasm_stack: usize,
417 pub memory_reservation: u64,
418 pub memory_guard_size: u64,
419 pub memory_reservation_for_growth: u64,
420 pub max_store_bytes: u64,
421 pub timeout: Option<Duration>,
422 pub async_yield_fuel: Option<u64>,
423}
424
425impl Default for RuntimeConfig {
426 fn default() -> Self {
427 Self {
428 fuel: 1_000_000_000_000,
434 max_wasm_stack: 512 * 1024,
435 memory_reservation: 3 * 1024 * 1024,
436 memory_guard_size: 64 * 1024,
437 memory_reservation_for_growth: 16 * 1024 * 1024,
438 max_store_bytes: DEFAULT_MAX_STORE_BYTES,
439 timeout: None,
440 async_yield_fuel: Some(10_000),
445 }
446 }
447}
448
449impl RuntimeConfig {
450 pub fn native_stack_size(&self) -> usize {
459 self.max_wasm_stack
460 .saturating_mul(NATIVE_STACK_PER_WASM_BYTE)
461 .max(MIN_NATIVE_STACK)
462 }
463
464 pub fn engine(&self) -> wasmtime::Result<Engine> {
465 Engine::new(&self.wasmtime_config())
466 }
467
468 pub fn engine_async(&self) -> wasmtime::Result<Engine> {
472 Engine::new(&self.wasmtime_config())
473 }
474
475 pub fn wasmtime_config(&self) -> Config {
476 let mut config = Config::new();
477 config.cranelift_opt_level(OptLevel::None);
488 config.consume_fuel(true);
489 config.max_wasm_stack(self.max_wasm_stack);
490
491 config.epoch_interruption(true);
492
493 config.memory_reservation(self.memory_reservation);
494 config.memory_guard_size(self.memory_guard_size);
495 config.memory_reservation_for_growth(self.memory_reservation_for_growth);
496 config.memory_may_move(true);
497 config.memory_init_cow(true);
498
499 config.gc_heap_reservation(0);
503 config.gc_heap_guard_size(self.memory_guard_size);
504 config.gc_heap_reservation_for_growth(self.memory_reservation_for_growth);
505 config.gc_heap_may_move(true);
506
507 config.wasm_gc(true);
508 config.wasm_function_references(true);
509 config.wasm_exceptions(true);
510
511 config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
512
513 config
514 }
515
516 pub fn store<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
517 let mut store = Store::new(engine, data);
518 store.set_fuel(self.fuel)?;
519 let delta = if self.timeout.is_some() { 1 } else { u64::MAX };
522 store.set_epoch_deadline(delta);
523 store.epoch_deadline_trap();
524 Ok(store)
525 }
526
527 pub fn store_async<T>(&self, engine: &Engine, data: T) -> wasmtime::Result<Store<T>> {
528 let mut store = self.store(engine, data)?;
529 store.fuel_async_yield_interval(self.async_yield_fuel)?;
530 Ok(store)
531 }
532
533 pub fn arm_timeout(&self, engine: &Engine) -> wasmtime::Result<Option<Watchdog>> {
536 self.timeout
537 .map(|duration| watchdog::arm(engine, duration))
538 .transpose()
539 .map_err(|error| {
540 wasmtime::Error::from(error).context("starting execution timeout watchdog")
541 })
542 }
543
544 pub async fn run(&self, wasm_bytes: &[u8]) -> wasmtime::Result<RunResult> {
551 self.run_with_type_info(wasm_bytes, None).await
552 }
553
554 pub async fn run_compiled(
555 &self,
556 compiled: &crate::compile::CompiledScript,
557 ) -> wasmtime::Result<RunResult> {
558 self.run_with_type_info(&compiled.wasm, Some(compiled.type_info.clone()))
559 .await
560 }
561
562 async fn run_with_type_info(
563 &self,
564 wasm_bytes: &[u8],
565 type_info: Option<TypeInfoTable>,
566 ) -> wasmtime::Result<RunResult> {
567 let buf = Arc::new(Mutex::new(Vec::new()));
568 let mut data = StoreData::with_vfs_and_cap(Vfs::tempdir()?, self.max_store_bytes);
569 data.console = Box::new(ConsoleSink(Arc::clone(&buf)));
570 if let Some(type_info) = type_info {
571 data.install_type_info(type_info);
572 }
573 let engine = self.engine()?;
574 let mut store = self.store_async(&engine, data)?;
575 install_tenant_limits(&mut store);
576 let module = Module::new(&engine, wasm_bytes)?;
577 let mut linker = Linker::<StoreData>::new(&engine);
578 install_runtime_async(&mut linker, &mut store).await?;
579 let _watchdog = self.arm_timeout(&engine)?;
580 let inst = instantiate_program_async(&linker, &mut store, &module).await?;
581 let value = dispatch_main_async(&mut store, &inst).await?;
582 let captured = buf.lock().expect("console buffer lock poisoned").clone();
586 let console = String::from_utf8(captured)
587 .map_err(|e| wasmtime::Error::msg(format!("console output not utf-8: {e}")))?;
588 Ok(RunResult { value, console })
589 }
590}
591
592struct ConsoleSink(Arc<Mutex<Vec<u8>>>);
596
597impl Write for ConsoleSink {
598 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
599 self.0
600 .lock()
601 .expect("console buffer lock poisoned")
602 .write(buf)
603 }
604
605 fn flush(&mut self) -> std::io::Result<()> {
606 Ok(())
607 }
608}
609
610pub(crate) fn read_submilli_string(
612 mut ctx: impl AsContextMut<Data = StoreData>,
613 msg: Rooted<ArrayRef>,
614) -> wasmtime::Result<String> {
615 let units = host::read_code_units(&mut ctx, msg, "string")?;
616 fuel::charge(ctx, fuel::SCAN, units.len() as u64)?;
617 Ok(String::from_utf16_lossy(&units))
618}