From ce07b1d71d73e4abb314c1dd5b2f6404490e844d Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 00:11:13 +0100 Subject: [PATCH 01/22] Init capsule-core crate and add database file for wal system --- .vscode/settings.json | 5 + crates/capsule-core/Cargo.lock | 190 ++++++++++ crates/capsule-core/Cargo.toml | 8 + crates/capsule-core/src/lib.rs | 1 + crates/capsule-core/src/storage/database.rs | 362 ++++++++++++++++++++ crates/capsule-core/src/storage/mod.rs | 1 + 6 files changed, 567 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 crates/capsule-core/Cargo.lock create mode 100644 crates/capsule-core/Cargo.toml create mode 100644 crates/capsule-core/src/lib.rs create mode 100644 crates/capsule-core/src/storage/database.rs create mode 100644 crates/capsule-core/src/storage/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d3f9614 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + "crates/capsule-core/Cargo.toml" + ] +} diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock new file mode 100644 index 0000000..d061f9e --- /dev/null +++ b/crates/capsule-core/Cargo.lock @@ -0,0 +1,190 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "capsule-core" +version = "0.1.0" +dependencies = [ + "rusqlite", + "serde_json", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml new file mode 100644 index 0000000..afb45c6 --- /dev/null +++ b/crates/capsule-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "capsule-core" +version = "0.1.0" +edition = "2024" + +[dependencies] +rusqlite = "0.37.0" +serde_json = "1.0.145" diff --git a/crates/capsule-core/src/lib.rs b/crates/capsule-core/src/lib.rs new file mode 100644 index 0000000..30f61eb --- /dev/null +++ b/crates/capsule-core/src/lib.rs @@ -0,0 +1 @@ +pub mod storage; diff --git a/crates/capsule-core/src/storage/database.rs b/crates/capsule-core/src/storage/database.rs new file mode 100644 index 0000000..505b8d9 --- /dev/null +++ b/crates/capsule-core/src/storage/database.rs @@ -0,0 +1,362 @@ +use rusqlite::{Connection, Error as SqliteError, Result as SqliteResult, Row}; +use std::fmt; +use std::sync::{Arc, Mutex}; +use std::path::PathBuf; + +#[derive(Debug)] +pub enum DatabaseError { + SqliteError(String), + FsError(String), + InvalidQuery(String), +} + +impl fmt::Display for DatabaseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DatabaseError::SqliteError(msg) => write!(f, "SQLite error: {}", msg), + DatabaseError::FsError(msg) => write!(f, "File system error: {}", msg), + DatabaseError::InvalidQuery(msg) => write!(f, "Invalid query: {}", msg), + } + } +} + +impl std::error::Error for DatabaseError {} + +impl From for DatabaseError { + fn from(err: SqliteError) -> Self { + DatabaseError::SqliteError(err.to_string()) + } +} + +impl From for DatabaseError { + fn from(err: serde_json::Error) -> Self { + DatabaseError::FsError(err.to_string()) + } +} + +impl From for DatabaseError { + fn from(err: std::io::Error) -> Self { + DatabaseError::FsError(err.to_string()) + } +} + +#[derive(Clone)] +pub struct Database { + pub conn: Arc>, +} + +impl Database { + pub fn new(path: Option<&PathBuf>, database_name: &str) -> Result { + let conn = match path { + Some(path) => { + std::fs::create_dir_all(&path)?; + + let db_path = path.join(database_name); + Connection::open(&db_path)? + } + None => Connection::open(":memory:")?, + }; + + conn.execute_batch( + " + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + PRAGMA cache_size = -64000; + PRAGMA foreign_keys = ON; + PRAGMA temp_store = MEMORY; + PRAGMA mmap_size = 30000000000; + ", + )?; + + let db = Self { + conn: Arc::new(Mutex::new(conn)), + }; + + Ok(db) + } + + pub fn create_table(&self, table: &str, columns: &[&str], constraints: &[&str]) -> SqliteResult<()> { + self.validate_table_name(table)?; + + let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + + let mut all_columns = vec!["id INTEGER PRIMARY KEY AUTOINCREMENT"]; + + all_columns.extend_from_slice(columns); + all_columns.push("created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"); + all_columns.push("updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"); + + let mut definitions = all_columns.iter().map(|s| s.to_string()).collect::>(); + + definitions.extend(constraints.iter().map(|s| s.to_string())); + + let sql = format!( + "CREATE TABLE IF NOT EXISTS {} (\n {}\n )", + table, + definitions.join(",\n ") + ); + + conn.execute(&sql, [])?; + + Ok(()) + } + + pub fn execute

(&self, query: &str, params: P) -> SqliteResult + where + P: rusqlite::Params, + { + let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + + let result = conn.execute(query, params)?; + + Ok(result) + } + + pub fn query(&self, query: &str, params: P, mut mapper: F) -> Result, DatabaseError> + where + P: rusqlite::Params, + F: FnMut(&Row) -> Result, + { + let conn = self + .conn + .lock() + .map_err(|_| DatabaseError::SqliteError("Failed to acquire mutex lock".to_string()))?; + + let mut stmt = conn + .prepare(query) + .map_err(|e| DatabaseError::SqliteError(e.to_string()))?; + + let rows = stmt.query_map(params, |row| mapper(row).map_err(|_| SqliteError::InvalidQuery))?; + + let mut results = Vec::new(); + for row in rows { + let value = row.map_err(|e| DatabaseError::SqliteError(e.to_string()))?; + results.push(value); + } + + Ok(results) + } + + pub fn table_exists(&self, table: &str) -> SqliteResult { + let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + + let count: i64 = conn.query_row( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", + [table], + |row| row.get(0), + )?; + + Ok(count > 0) + } + + pub fn validate_table_name(&self, table: &str) -> SqliteResult<()> { + if table.is_empty() || table.len() > 64 { + return Err(SqliteError::InvalidParameterName( + "Table name must be between 1-64 characters".to_string(), + )); + } + + if !table.chars().all(|c| c.is_alphanumeric() || c == '_') { + return Err(SqliteError::InvalidParameterName(format!( + "Table name can only contain alphanumeric characters and underscores for {}", + table + ))); + } + + if table.chars().next().unwrap().is_numeric() { + return Err(SqliteError::InvalidParameterName( + "Table name cannot start with a number".to_string(), + )); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod creation { + use super::*; + + #[test] + fn test_new_database_creates_in_memory_database() { + let db = Database::new(None, "state.db-wal"); + assert!(db.is_ok(), "Failed to create in-memory database"); + } + + #[test] + fn test_create_table() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + db.create_table("test_table", &["name TEXT NOT NULL", "path TEXT NOT NULL"], &[]) + .expect("Failed to create test table"); + + let conn = db.conn.lock().unwrap(); + + let mut test_table_stmt = conn + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='test_table'") + .expect("Failed to prepare query"); + + let test_table_exists: bool = test_table_stmt.exists([]).expect("Failed to check if table exists"); + + assert!(test_table_exists, "test table was not created"); + } + + #[test] + fn test_create_table_with_constraints() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + db.create_table( + "test_table", + &["name TEXT NOT NULL", "path TEXT NOT NULL"], + &["UNIQUE (path)"], + ) + .expect("Failed to create test table"); + + let conn = db.conn.lock().unwrap(); + + conn.execute("INSERT INTO test_table (name, path) VALUES (?, ?)", ["test", "test"]) + .expect("Failed to insert test"); + assert!( + conn.execute("INSERT INTO test_table (name, path) VALUES (?, ?)", ["test", "test"]) + .is_err(), + "Second insert should have failed" + ); + } + } + + mod execution { + use super::*; + + #[test] + fn test_execute() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS test_table (name TEXT NOT NULL, description TEXT NOT NULL)", + [], + ) + .expect("Failed to create test table"); + } + + let result = db + .execute( + "INSERT INTO test_table (name, description) VALUES (?, ?)", + ["test", "test"], + ) + .expect("Failed to execute test"); + assert!(result > 0, "Failed to execute test"); + } + } + + mod queries { + use super::*; + + #[test] + fn test_query() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS test_table (name TEXT NOT NULL, description TEXT NOT NULL)", + [], + ) + .expect("Failed to create test table"); + + conn.execute( + "INSERT INTO test_table (name, description) VALUES (?, ?)", + ["test1", "desc1"], + ) + .expect("Failed to insert test"); + } + + let result = db + .query("SELECT * FROM test_table", [], |row| Ok((row.get::<_, String>(0)?,))) + .expect("Failed to query test"); + + assert!(result.len() > 0, "Failed to execute test"); + } + + #[test] + fn test_query_multiple_rows() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS test_table (name TEXT NOT NULL, description TEXT NOT NULL)", + [], + ) + .expect("Failed to create test table"); + + conn.execute( + "INSERT INTO test_table (name, description) VALUES (?, ?)", + ["test1", "desc1"], + ) + .expect("Failed to insert test"); + conn.execute( + "INSERT INTO test_table (name, description) VALUES (?, ?)", + ["test2", "desc2"], + ) + .expect("Failed to insert test"); + conn.execute( + "INSERT INTO test_table (name, description) VALUES (?, ?)", + ["test3", "desc3"], + ) + .expect("Failed to insert test"); + } + + let result = db + .query("SELECT * FROM test_table", [], |row| Ok((row.get::<_, String>(0)?,))) + .expect("Failed to query test"); + + assert!(result.len() == 3, "Failed to execute test"); + } + } + + mod utilities { + use super::super::*; + + #[test] + fn test_table_exists() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "CREATE TABLE IF NOT EXISTS test_table (name TEXT NOT NULL, description TEXT NOT NULL)", + [], + ) + .expect("Failed to create test table"); + } + + let table_exists: bool = db.table_exists("test_table").expect("Failed to check if table exists"); + + assert!(table_exists, "test table was not created"); + } + + #[test] + fn test_validate_table_name() { + let db = Database::new(None, "state.db-wal").expect("Failed to create database"); + + let test_simple_name: Result<(), rusqlite::Error> = db.validate_table_name("test_table"); + assert!(test_simple_name.is_ok(), "Failed to validate table name"); + + let test_start_by_number = db.validate_table_name("123test_table"); + assert!(test_start_by_number.is_err(), "Failed to validate table name"); + + let test_start_by_number_and_special_characters = db.validate_table_name("test; dede"); + assert!( + test_start_by_number_and_special_characters.is_err(), + "Failed to validate table name" + ); + + let test_long_name = + db.validate_table_name("A_long_naaaaaaaaaaaaaaammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmme"); + assert!(test_long_name.is_err(), "Failed to validate table name"); + } + } +} diff --git a/crates/capsule-core/src/storage/mod.rs b/crates/capsule-core/src/storage/mod.rs new file mode 100644 index 0000000..8fd0a6b --- /dev/null +++ b/crates/capsule-core/src/storage/mod.rs @@ -0,0 +1 @@ +pub mod database; From a4887a94330098c69f16561263db5bab56cdb041 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 00:41:18 +0100 Subject: [PATCH 02/22] add log file for wal (working progress) --- crates/capsule-core/Cargo.lock | 133 ++++++++++++++++++++ crates/capsule-core/Cargo.toml | 2 + crates/capsule-core/src/storage/database.rs | 18 +-- crates/capsule-core/src/storage/log.rs | 75 +++++++++++ crates/capsule-core/src/storage/mod.rs | 2 + 5 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 crates/capsule-core/src/storage/log.rs diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock index d061f9e..f7a29b8 100644 --- a/crates/capsule-core/Cargo.lock +++ b/crates/capsule-core/Cargo.lock @@ -8,14 +8,28 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "capsule-core" version = "0.1.0" dependencies = [ "rusqlite", + "serde", "serde_json", + "uuid", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -34,6 +48,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -58,6 +84,22 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + [[package]] name = "libsqlite3-sys" version = "0.35.0" @@ -74,6 +116,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "pkg-config" version = "0.3.32" @@ -98,6 +146,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rusqlite" version = "0.37.0" @@ -112,6 +166,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.20" @@ -125,6 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -183,8 +244,80 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index afb45c6..0e01406 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -5,4 +5,6 @@ edition = "2024" [dependencies] rusqlite = "0.37.0" +serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" +uuid = { version = "1.19.0", features = ["v4", "serde"] } diff --git a/crates/capsule-core/src/storage/database.rs b/crates/capsule-core/src/storage/database.rs index 505b8d9..77e5056 100644 --- a/crates/capsule-core/src/storage/database.rs +++ b/crates/capsule-core/src/storage/database.rs @@ -1,7 +1,7 @@ use rusqlite::{Connection, Error as SqliteError, Result as SqliteResult, Row}; use std::fmt; use std::sync::{Arc, Mutex}; -use std::path::PathBuf; +use std::path::Path; #[derive(Debug)] pub enum DatabaseError { @@ -13,9 +13,9 @@ pub enum DatabaseError { impl fmt::Display for DatabaseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DatabaseError::SqliteError(msg) => write!(f, "SQLite error: {}", msg), - DatabaseError::FsError(msg) => write!(f, "File system error: {}", msg), - DatabaseError::InvalidQuery(msg) => write!(f, "Invalid query: {}", msg), + DatabaseError::SqliteError(msg) => write!(f, "SQLite error > {}", msg), + DatabaseError::FsError(msg) => write!(f, "File system error > {}", msg), + DatabaseError::InvalidQuery(msg) => write!(f, "Invalid query > {}", msg), } } } @@ -46,13 +46,15 @@ pub struct Database { } impl Database { - pub fn new(path: Option<&PathBuf>, database_name: &str) -> Result { + pub fn new(path: Option<&str>, database_name: &str) -> Result { let conn = match path { Some(path) => { - std::fs::create_dir_all(&path)?; + let database_path = &format!("{}/{}", path, database_name); + if !Path::new(&database_path).exists() { + std::fs::create_dir_all(&path)?; + } - let db_path = path.join(database_name); - Connection::open(&db_path)? + Connection::open(&database_path)? } None => Connection::open(":memory:")?, }; diff --git a/crates/capsule-core/src/storage/log.rs b/crates/capsule-core/src/storage/log.rs new file mode 100644 index 0000000..53a4315 --- /dev/null +++ b/crates/capsule-core/src/storage/log.rs @@ -0,0 +1,75 @@ +use serde::{Serialize, Deserialize}; +use uuid::Uuid; +use std::fmt; + +use crate::storage::database::{Database, DatabaseError}; + + +#[derive(Debug)] +pub enum LogError { + DatabaseError(String), +} + +impl fmt::Display for LogError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LogError::DatabaseError(msg) => write!(f, "Log error > {}", msg), + } + } +} + +impl From for LogError { + fn from(err: DatabaseError) -> Self { + LogError::DatabaseError(err.to_string()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum InstanceState { + Created, + Running, + Completed, + Failed, + Crashed, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstanceLog { + pub id: Uuid, + pub namespace: String, + pub task_name: String, + pub state: InstanceState, + pub fuel_limit: u64, + pub fuel_consumed: u64, + pub gpu_device: Option, + pub created_at: i64, + pub updated_at: i64, +} + +#[derive(Clone)] +pub struct Log { + db: Database, +} + +impl Log { + pub fn new(path: Option<&str>, database_name: &str) -> Result { + let db = Database::new(path, database_name)?; + + Ok(Self { + db, + }) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_log() { + let log = Log::new(None, "state.db-wal"); + assert!(log.is_ok(), "Failed to create log"); + } +} diff --git a/crates/capsule-core/src/storage/mod.rs b/crates/capsule-core/src/storage/mod.rs index 8fd0a6b..5c05f94 100644 --- a/crates/capsule-core/src/storage/mod.rs +++ b/crates/capsule-core/src/storage/mod.rs @@ -1 +1,3 @@ pub mod database; +pub mod log; + From d90a9ec7809dd9ba755503d52a2db0d5a45cacd4 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 00:46:22 +0100 Subject: [PATCH 03/22] Rename storage folder to config folder --- crates/capsule-core/src/{storage => config}/database.rs | 0 crates/capsule-core/src/{storage => config}/log.rs | 2 -- crates/capsule-core/src/{storage => config}/mod.rs | 0 3 files changed, 2 deletions(-) rename crates/capsule-core/src/{storage => config}/database.rs (100%) rename crates/capsule-core/src/{storage => config}/log.rs (99%) rename crates/capsule-core/src/{storage => config}/mod.rs (100%) diff --git a/crates/capsule-core/src/storage/database.rs b/crates/capsule-core/src/config/database.rs similarity index 100% rename from crates/capsule-core/src/storage/database.rs rename to crates/capsule-core/src/config/database.rs diff --git a/crates/capsule-core/src/storage/log.rs b/crates/capsule-core/src/config/log.rs similarity index 99% rename from crates/capsule-core/src/storage/log.rs rename to crates/capsule-core/src/config/log.rs index 53a4315..d13ab4c 100644 --- a/crates/capsule-core/src/storage/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -4,7 +4,6 @@ use std::fmt; use crate::storage::database::{Database, DatabaseError}; - #[derive(Debug)] pub enum LogError { DatabaseError(String), @@ -62,7 +61,6 @@ impl Log { } } - #[cfg(test)] mod tests { use super::*; diff --git a/crates/capsule-core/src/storage/mod.rs b/crates/capsule-core/src/config/mod.rs similarity index 100% rename from crates/capsule-core/src/storage/mod.rs rename to crates/capsule-core/src/config/mod.rs From 95c1e1106d38dbd5780f9cc5c0d41155dbb1c283 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 00:47:43 +0100 Subject: [PATCH 04/22] Updates mods rs --- crates/capsule-core/src/config/log.rs | 2 +- crates/capsule-core/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index d13ab4c..769820f 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize}; use uuid::Uuid; use std::fmt; -use crate::storage::database::{Database, DatabaseError}; +use crate::config::database::{Database, DatabaseError}; #[derive(Debug)] pub enum LogError { diff --git a/crates/capsule-core/src/lib.rs b/crates/capsule-core/src/lib.rs index 30f61eb..ef68c36 100644 --- a/crates/capsule-core/src/lib.rs +++ b/crates/capsule-core/src/lib.rs @@ -1 +1 @@ -pub mod storage; +pub mod config; From 664ed18b2d01bc12dcd1e9418caa414335ffadab Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 12:48:19 +0100 Subject: [PATCH 05/22] improve database and log --- crates/capsule-core/src/config/database.rs | 47 ++++++++++++---------- crates/capsule-core/src/config/log.rs | 47 ++++++++++++++++++++-- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/crates/capsule-core/src/config/database.rs b/crates/capsule-core/src/config/database.rs index 77e5056..a904e40 100644 --- a/crates/capsule-core/src/config/database.rs +++ b/crates/capsule-core/src/config/database.rs @@ -1,13 +1,13 @@ -use rusqlite::{Connection, Error as SqliteError, Result as SqliteResult, Row}; +use rusqlite::{Connection, Error as SqliteError, Row}; use std::fmt; use std::sync::{Arc, Mutex}; -use std::path::Path; #[derive(Debug)] pub enum DatabaseError { SqliteError(String), FsError(String), InvalidQuery(String), + LockError(String), } impl fmt::Display for DatabaseError { @@ -16,6 +16,7 @@ impl fmt::Display for DatabaseError { DatabaseError::SqliteError(msg) => write!(f, "SQLite error > {}", msg), DatabaseError::FsError(msg) => write!(f, "File system error > {}", msg), DatabaseError::InvalidQuery(msg) => write!(f, "Invalid query > {}", msg), + DatabaseError::LockError(msg) => write!(f, "Lock error > {}", msg), } } } @@ -28,6 +29,13 @@ impl From for DatabaseError { } } +impl From>> for DatabaseError { + fn from(err: std::sync::PoisonError>) -> Self { + DatabaseError::LockError(err.to_string()) + } +} + + impl From for DatabaseError { fn from(err: serde_json::Error) -> Self { DatabaseError::FsError(err.to_string()) @@ -50,9 +58,8 @@ impl Database { let conn = match path { Some(path) => { let database_path = &format!("{}/{}", path, database_name); - if !Path::new(&database_path).exists() { - std::fs::create_dir_all(&path)?; - } + + std::fs::create_dir_all(&path)?; Connection::open(&database_path)? } @@ -77,10 +84,10 @@ impl Database { Ok(db) } - pub fn create_table(&self, table: &str, columns: &[&str], constraints: &[&str]) -> SqliteResult<()> { + pub fn create_table(&self, table: &str, columns: &[&str], constraints: &[&str]) -> Result<(), DatabaseError> { self.validate_table_name(table)?; - let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + let conn = self.conn.lock()?; let mut all_columns = vec!["id INTEGER PRIMARY KEY AUTOINCREMENT"]; @@ -103,11 +110,11 @@ impl Database { Ok(()) } - pub fn execute

(&self, query: &str, params: P) -> SqliteResult + pub fn execute

(&self, query: &str, params: P) -> Result where P: rusqlite::Params, { - let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + let conn = self.conn.lock()?; let result = conn.execute(query, params)?; @@ -121,26 +128,24 @@ impl Database { { let conn = self .conn - .lock() - .map_err(|_| DatabaseError::SqliteError("Failed to acquire mutex lock".to_string()))?; + .lock()?; let mut stmt = conn - .prepare(query) - .map_err(|e| DatabaseError::SqliteError(e.to_string()))?; + .prepare(query)?; let rows = stmt.query_map(params, |row| mapper(row).map_err(|_| SqliteError::InvalidQuery))?; let mut results = Vec::new(); for row in rows { - let value = row.map_err(|e| DatabaseError::SqliteError(e.to_string()))?; + let value = row?; results.push(value); } Ok(results) } - pub fn table_exists(&self, table: &str) -> SqliteResult { - let conn = self.conn.lock().map_err(|_| SqliteError::InvalidQuery)?; + pub fn table_exists(&self, table: &str) -> Result { + let conn = self.conn.lock()?; let count: i64 = conn.query_row( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?", @@ -151,22 +156,22 @@ impl Database { Ok(count > 0) } - pub fn validate_table_name(&self, table: &str) -> SqliteResult<()> { + pub fn validate_table_name(&self, table: &str) -> Result<(), DatabaseError> { if table.is_empty() || table.len() > 64 { - return Err(SqliteError::InvalidParameterName( + return Err(DatabaseError::InvalidQuery( "Table name must be between 1-64 characters".to_string(), )); } if !table.chars().all(|c| c.is_alphanumeric() || c == '_') { - return Err(SqliteError::InvalidParameterName(format!( + return Err(DatabaseError::InvalidQuery(format!( "Table name can only contain alphanumeric characters and underscores for {}", table ))); } if table.chars().next().unwrap().is_numeric() { - return Err(SqliteError::InvalidParameterName( + return Err(DatabaseError::InvalidQuery( "Table name cannot start with a number".to_string(), )); } @@ -344,7 +349,7 @@ mod tests { fn test_validate_table_name() { let db = Database::new(None, "state.db-wal").expect("Failed to create database"); - let test_simple_name: Result<(), rusqlite::Error> = db.validate_table_name("test_table"); + let test_simple_name = db.validate_table_name("test_table"); assert!(test_simple_name.is_ok(), "Failed to validate table name"); let test_start_by_number = db.validate_table_name("123test_table"); diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index 769820f..5300a5e 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -36,7 +36,9 @@ pub enum InstanceState { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceLog { pub id: Uuid, - pub namespace: String, + pub agent_name: String, + pub agent_version: String, + pub task_id: String, pub task_name: String, pub state: InstanceState, pub fuel_limit: u64, @@ -48,13 +50,31 @@ pub struct InstanceLog { #[derive(Clone)] pub struct Log { - db: Database, + pub db: Database, } impl Log { pub fn new(path: Option<&str>, database_name: &str) -> Result { + let db = Database::new(path, database_name)?; + let table_exists = db.table_exists("instance_log")?; + + if !table_exists { + db.create_table("instance_log", &[ + "agent_name TEXT NOT NULL", + "agent_version TEXT NOT NULL", + "task_id TEXT NOT NULL", + "task_name TEXT NOT NULL", + "state TEXT NOT NULL", + "fuel_limit INTEGER NOT NULL", + "fuel_consumed INTEGER NOT NULL", + "gpu_device INTEGER", + ], &[])?; + + db.execute("CREATE INDEX IF NOT EXISTS idx_instance_log_task_id ON instance_log(task_id)", [])?; + } + Ok(Self { db, }) @@ -67,7 +87,26 @@ mod tests { #[test] fn test_new_log() { - let log = Log::new(None, "state.db-wal"); - assert!(log.is_ok(), "Failed to create log"); + let log = Log::new(None, "state.db-wal").unwrap(); + + let conn = log.db.conn.lock().unwrap(); + + let mut stmt = conn + .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='instance_log'") + .expect("Failed to prepare query"); + + let mut index_stmt = conn + .prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_instance_log_task_id'") + .expect("Failed to prepare query"); + + let exists = stmt.exists([]) + .expect("Failed to check if table exists"); + + let index_exists: bool = index_stmt + .exists([]) + .expect("Failed to check if index exists"); + + assert!(exists, "Table instance_log does not exist"); + assert!(index_exists, "Index idx_instance_log_task_id does not exist"); } } From 77b83655351403673be7640f99ec3fcc02404392 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 18:54:22 +0100 Subject: [PATCH 06/22] add test for ci --- .github/workflows/test.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4351960 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,21 @@ +name: Rust Tests + +on: + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Run cargo test in crates/capsule-core + working-directory: crates/capsule-core + run: cargo test --all --verbose From a420b8b5e6acc4e32650f8756083f5be794d8c09 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 19:21:51 +0100 Subject: [PATCH 07/22] Add rustfmt and complete log rs --- crates/capsule-core/Cargo.lock | 477 ++++++++++++++++++++- crates/capsule-core/Cargo.toml | 6 +- crates/capsule-core/src/config/database.rs | 81 ++-- crates/capsule-core/src/config/log.rs | 435 +++++++++++++++++-- crates/capsule-core/src/config/mod.rs | 1 - 5 files changed, 936 insertions(+), 64 deletions(-) diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock index f7a29b8..934683a 100644 --- a/crates/capsule-core/Cargo.lock +++ b/crates/capsule-core/Cargo.lock @@ -2,6 +2,42 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.5.0", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -19,8 +55,10 @@ name = "capsule-core" version = "0.1.0" dependencies = [ "rusqlite", + "rustfmt", "serde", "serde_json", + "tokio", "uuid", ] @@ -30,6 +68,44 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "env_logger" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +dependencies = [ + "log 0.3.9", + "regex", +] + +[[package]] +name = "extprim" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b1a357c911c352439b460d7b375b5c85977b9db395b703dfee5a94dfb4d66a2" +dependencies = [ + "num-traits", + "rand", + "rustc_version", + "semver", + "serde", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -48,6 +124,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -94,6 +185,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.177" @@ -110,18 +217,48 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.29", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg 1.5.0", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pkg-config" version = "0.3.32" @@ -152,18 +289,190 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "regex" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", + "utf8-ranges", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util", +] + [[package]] name = "rusqlite" version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags", + "bitflags 2.10.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", + "uuid", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfmt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec940eed814db0fb7ab928c5f5025f97dc55d1c0e345e39dda2ce9f945557500" +dependencies = [ + "diff", + "env_logger", + "getopts", + "kernel32-sys", + "libc", + "log 0.3.9", + "regex", + "serde", + "serde_derive", + "serde_json", + "strings", + "syntex_errors", + "syntex_syntax", + "term", + "toml", + "unicode-segmentation", + "winapi 0.2.8", ] [[package]] @@ -178,6 +487,21 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.228" @@ -227,6 +551,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "strings" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa481ee1bc42fc3df8195f91f7cb43cf8f2b71b48bac40bf5381cfaf7e481f3c" +dependencies = [ + "log 0.3.9", +] + [[package]] name = "syn" version = "2.0.111" @@ -238,12 +571,120 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syntex_errors" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3133289179676c9f5c5b2845bf5a2e127769f4889fcbada43035ef6bd662605e" +dependencies = [ + "libc", + "serde", + "serde_derive", + "syntex_pos", + "term", + "unicode-xid", +] + +[[package]] +name = "syntex_pos" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ab669fa003d208c681f874bbc76d91cc3d32550d16b5d9d2087cf477316470" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "syntex_syntax" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03815b9f04d95828770d9c974aa39c6e1f6ef3114eb77a3ce09008a0d15dd142" +dependencies = [ + "bitflags 0.9.1", + "extprim", + "log 0.3.9", + "serde", + "serde_derive", + "serde_json", + "syntex_errors", + "syntex_pos", + "unicode-xid", +] + +[[package]] +name = "term" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde", +] + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "uuid" version = "1.19.0" @@ -316,6 +757,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index 0e01406..b4d306f 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -4,7 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] -rusqlite = "0.37.0" +rusqlite = { version = "0.37.0", features = ["uuid"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" +tokio = "1.48.0" uuid = { version = "1.19.0", features = ["v4", "serde"] } + +[dev-dependencies] +rustfmt = "0.10.0" diff --git a/crates/capsule-core/src/config/database.rs b/crates/capsule-core/src/config/database.rs index a904e40..fc99f6b 100644 --- a/crates/capsule-core/src/config/database.rs +++ b/crates/capsule-core/src/config/database.rs @@ -35,7 +35,6 @@ impl From>> for Dat } } - impl From for DatabaseError { fn from(err: serde_json::Error) -> Self { DatabaseError::FsError(err.to_string()) @@ -84,18 +83,26 @@ impl Database { Ok(db) } - pub fn create_table(&self, table: &str, columns: &[&str], constraints: &[&str]) -> Result<(), DatabaseError> { + pub fn create_table( + &self, + table: &str, + columns: &[&str], + constraints: &[&str], + ) -> Result<(), DatabaseError> { self.validate_table_name(table)?; let conn = self.conn.lock()?; - let mut all_columns = vec!["id INTEGER PRIMARY KEY AUTOINCREMENT"]; + let mut all_columns = vec!["id TEXT PRIMARY KEY"]; all_columns.extend_from_slice(columns); all_columns.push("created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"); all_columns.push("updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"); - let mut definitions = all_columns.iter().map(|s| s.to_string()).collect::>(); + let mut definitions = all_columns + .iter() + .map(|s| s.to_string()) + .collect::>(); definitions.extend(constraints.iter().map(|s| s.to_string())); @@ -121,19 +128,23 @@ impl Database { Ok(result) } - pub fn query(&self, query: &str, params: P, mut mapper: F) -> Result, DatabaseError> + pub fn query( + &self, + query: &str, + params: P, + mut mapper: F, + ) -> Result, DatabaseError> where P: rusqlite::Params, F: FnMut(&Row) -> Result, { - let conn = self - .conn - .lock()?; + let conn = self.conn.lock()?; - let mut stmt = conn - .prepare(query)?; + let mut stmt = conn.prepare(query)?; - let rows = stmt.query_map(params, |row| mapper(row).map_err(|_| SqliteError::InvalidQuery))?; + let rows = stmt.query_map(params, |row| { + mapper(row).map_err(|_| SqliteError::InvalidQuery) + })?; let mut results = Vec::new(); for row in rows { @@ -196,8 +207,12 @@ mod tests { #[test] fn test_create_table() { let db = Database::new(None, "state.db-wal").expect("Failed to create database"); - db.create_table("test_table", &["name TEXT NOT NULL", "path TEXT NOT NULL"], &[]) - .expect("Failed to create test table"); + db.create_table( + "test_table", + &["name TEXT NOT NULL", "path TEXT NOT NULL"], + &[], + ) + .expect("Failed to create test table"); let conn = db.conn.lock().unwrap(); @@ -205,7 +220,9 @@ mod tests { .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='test_table'") .expect("Failed to prepare query"); - let test_table_exists: bool = test_table_stmt.exists([]).expect("Failed to check if table exists"); + let test_table_exists: bool = test_table_stmt + .exists([]) + .expect("Failed to check if table exists"); assert!(test_table_exists, "test table was not created"); } @@ -222,11 +239,17 @@ mod tests { let conn = db.conn.lock().unwrap(); - conn.execute("INSERT INTO test_table (name, path) VALUES (?, ?)", ["test", "test"]) - .expect("Failed to insert test"); + conn.execute( + "INSERT INTO test_table (name, path) VALUES (?, ?)", + ["test", "test"], + ) + .expect("Failed to insert test"); assert!( - conn.execute("INSERT INTO test_table (name, path) VALUES (?, ?)", ["test", "test"]) - .is_err(), + conn.execute( + "INSERT INTO test_table (name, path) VALUES (?, ?)", + ["test", "test"] + ) + .is_err(), "Second insert should have failed" ); } @@ -281,7 +304,9 @@ mod tests { } let result = db - .query("SELECT * FROM test_table", [], |row| Ok((row.get::<_, String>(0)?,))) + .query("SELECT * FROM test_table", [], |row| { + Ok((row.get::<_, String>(0)?,)) + }) .expect("Failed to query test"); assert!(result.len() > 0, "Failed to execute test"); @@ -317,7 +342,9 @@ mod tests { } let result = db - .query("SELECT * FROM test_table", [], |row| Ok((row.get::<_, String>(0)?,))) + .query("SELECT * FROM test_table", [], |row| { + Ok((row.get::<_, String>(0)?,)) + }) .expect("Failed to query test"); assert!(result.len() == 3, "Failed to execute test"); @@ -340,7 +367,9 @@ mod tests { .expect("Failed to create test table"); } - let table_exists: bool = db.table_exists("test_table").expect("Failed to check if table exists"); + let table_exists: bool = db + .table_exists("test_table") + .expect("Failed to check if table exists"); assert!(table_exists, "test table was not created"); } @@ -353,7 +382,10 @@ mod tests { assert!(test_simple_name.is_ok(), "Failed to validate table name"); let test_start_by_number = db.validate_table_name("123test_table"); - assert!(test_start_by_number.is_err(), "Failed to validate table name"); + assert!( + test_start_by_number.is_err(), + "Failed to validate table name" + ); let test_start_by_number_and_special_characters = db.validate_table_name("test; dede"); assert!( @@ -361,8 +393,9 @@ mod tests { "Failed to validate table name" ); - let test_long_name = - db.validate_table_name("A_long_naaaaaaaaaaaaaaammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmme"); + let test_long_name = db.validate_table_name( + "A_long_naaaaaaaaaaaaaaammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmme", + ); assert!(test_long_name.is_err(), "Failed to validate table name"); } } diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index 5300a5e..e56a9ca 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; -use uuid::Uuid; +use serde::{Deserialize, Serialize}; use std::fmt; +use uuid::Uuid; use crate::config::database::{Database, DatabaseError}; @@ -30,7 +30,20 @@ pub enum InstanceState { Running, Completed, Failed, - Crashed, + Interrupted, +} + +impl fmt::Display for InstanceState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let state_str = match self { + InstanceState::Created => "created", + InstanceState::Running => "running", + InstanceState::Completed => "completed", + InstanceState::Failed => "failed", + InstanceState::Interrupted => "interrupted", + }; + write!(f, "{}", state_str) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -43,11 +56,28 @@ pub struct InstanceLog { pub state: InstanceState, pub fuel_limit: u64, pub fuel_consumed: u64, - pub gpu_device: Option, pub created_at: i64, pub updated_at: i64, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateInstanceLog { + pub agent_name: String, + pub agent_version: String, + pub task_id: String, + pub task_name: String, + pub state: InstanceState, + pub fuel_limit: u64, + pub fuel_consumed: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateInstanceLog { + pub task_id: String, + pub state: InstanceState, + pub fuel_consumed: u64, +} + #[derive(Clone)] pub struct Log { pub db: Database, @@ -55,58 +85,389 @@ pub struct Log { impl Log { pub fn new(path: Option<&str>, database_name: &str) -> Result { - let db = Database::new(path, database_name)?; let table_exists = db.table_exists("instance_log")?; if !table_exists { - db.create_table("instance_log", &[ - "agent_name TEXT NOT NULL", - "agent_version TEXT NOT NULL", - "task_id TEXT NOT NULL", - "task_name TEXT NOT NULL", - "state TEXT NOT NULL", - "fuel_limit INTEGER NOT NULL", - "fuel_consumed INTEGER NOT NULL", - "gpu_device INTEGER", - ], &[])?; - - db.execute("CREATE INDEX IF NOT EXISTS idx_instance_log_task_id ON instance_log(task_id)", [])?; + db.create_table( + "instance_log", + &[ + "agent_name TEXT NOT NULL", + "agent_version TEXT NOT NULL", + "task_id TEXT NOT NULL", + "task_name TEXT NOT NULL", + "state TEXT NOT NULL", + "fuel_limit INTEGER NOT NULL", + "fuel_consumed INTEGER NOT NULL", + ], + &[], + )?; + + db.execute( + "CREATE INDEX IF NOT EXISTS idx_instance_log_task_id ON instance_log(task_id)", + [], + )?; + + db.execute( + "CREATE INDEX IF NOT EXISTS idx_instance_log_created_at ON instance_log(created_at)", + [], + )?; + } + + Ok(Self { db }) + } + + pub fn commit_log(&self, log: CreateInstanceLog, on_commit: F) -> Result<(), LogError> + where + F: FnOnce(Result<(), LogError>) + Send + 'static, + { + match self.db.execute( + "INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + [ + &Uuid::new_v4().to_string(), + &log.agent_name, + &log.agent_version, + &log.task_id, + &log.task_name, + &log.state.to_string(), + &log.fuel_limit.to_string(), + &log.fuel_consumed.to_string() + ], + ) { + Ok(_) => on_commit(Ok(())), + Err(e) => on_commit(Err(LogError::DatabaseError(e.to_string()))), } - Ok(Self { - db, - }) + Ok(()) + } + + + pub fn update_log(&self, log: UpdateInstanceLog) -> Result<(), LogError> { + self.db.execute( + "UPDATE instance_log SET state = ?, fuel_consumed = ? WHERE task_id = ?", + [ + &log.state.to_string(), + &log.fuel_consumed.to_string(), + &log.task_id.to_string() + ], + )?; + + Ok(()) + } + + pub fn get_logs(&self, task_id: &str) -> Result, LogError> { + let logs = self.db.query( + "SELECT id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at FROM instance_log WHERE task_id = ? ORDER BY created_at DESC", + [task_id], + |row| { + let id_str: String = row.get(0)?; + let state_str: String = row.get(5)?; + Ok(InstanceLog { + id: Uuid::parse_str(&id_str).map_err(|_| DatabaseError::InvalidQuery("Invalid UUID".to_string()))?, + agent_name: row.get(1)?, + agent_version: row.get(2)?, + task_id: row.get(3)?, + task_name: row.get(4)?, + state: match state_str.as_str() { + "created" => InstanceState::Created, + "running" => InstanceState::Running, + "completed" => InstanceState::Completed, + "failed" => InstanceState::Failed, + "interrupted" => InstanceState::Interrupted, + _ => return Err(DatabaseError::InvalidQuery(format!("Invalid state: {}", state_str))), + }, + fuel_limit: row.get::<_, i64>(6)? as u64, + fuel_consumed: row.get::<_, i64>(7)? as u64, + created_at: row.get(8)?, + updated_at: row.get(9)?, + }) + } + )?; + + Ok(logs) + } + + pub fn delete_log(&self, task_id: &str) -> Result<(), LogError> { + self.db.execute( + "DELETE FROM instance_log WHERE task_id = ?", + [task_id], + )?; + + Ok(()) } + } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_new_log() { - let log = Log::new(None, "state.db-wal").unwrap(); + mod creation { + use super::*; - let conn = log.db.conn.lock().unwrap(); + #[test] + fn test_new_log() { + let log = Log::new(None, "state.db-wal").unwrap(); - let mut stmt = conn - .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='instance_log'") - .expect("Failed to prepare query"); + let conn = log.db.conn.lock().unwrap(); - let mut index_stmt = conn - .prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_instance_log_task_id'") - .expect("Failed to prepare query"); + let mut stmt = conn + .prepare( + "SELECT name FROM sqlite_master WHERE type='table' AND name='instance_log'", + ) + .expect("Failed to prepare query"); + + let mut index_stmt = conn + .prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_instance_log_task_id'") + .expect("Failed to prepare query"); + + let exists = stmt.exists([]).expect("Failed to check if table exists"); + + let index_exists: bool = index_stmt + .exists([]) + .expect("Failed to check if index exists"); + + assert!(exists, "Table instance_log does not exist"); + assert!( + index_exists, + "Index idx_instance_log_task_id does not exist" + ); + } - let exists = stmt.exists([]) - .expect("Failed to check if table exists"); + #[test] + fn test_commit_log() { + use std::sync::Arc; + use std::sync::Mutex; - let index_exists: bool = index_stmt - .exists([]) - .expect("Failed to check if index exists"); + let log = Log::new(None, "state.db-wal").unwrap(); - assert!(exists, "Table instance_log does not exist"); - assert!(index_exists, "Index idx_instance_log_task_id does not exist"); + let callback_invoked = Arc::new(Mutex::new(false)); + let callback_invoked_clone = callback_invoked.clone(); + + let _ = log.commit_log( + CreateInstanceLog { + agent_name: "agent_name".to_string(), + agent_version: "agent_version".to_string(), + task_id: "task_id".to_string(), + task_name: "task_name".to_string(), + state: InstanceState::Created, + fuel_limit: 100, + fuel_consumed: 0, + }, + move |result| { + *callback_invoked_clone.lock().unwrap() = true; + assert!(result.is_ok(), "Commit should succeed: {:?}", result); + }, + ); + + assert!( + *callback_invoked.lock().unwrap(), + "Callback should have been invoked" + ); + + let conn = log.db.conn.lock().unwrap(); + + let mut stmt = conn + .prepare("SELECT task_name FROM instance_log WHERE task_id = 'task_id'") + .expect("Failed to prepare query"); + + let exists = stmt.exists([]).expect("Failed to check if instance exists"); + + assert!(exists, "instance does not exist"); + } + + #[test] + fn test_update_log() { + let log = Log::new(None, "state.db-wal").unwrap(); + + { + let conn = log.db.conn.lock().unwrap(); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + &"test_agent".to_string(), + &"1.0.0".to_string(), + &"test_task_123".to_string(), + &"Test Task".to_string(), + &"created".to_string(), + "15000000", + "0", + ]).expect("Failed to insert test data"); + } + + let _ = log.update_log( + UpdateInstanceLog { + task_id: "test_task_123".to_string(), + state: InstanceState::Running, + fuel_consumed: 10, + } + ); + + let conn = log.db.conn.lock().unwrap(); + + let state: String = conn + .query_row("SELECT state FROM instance_log WHERE task_id = 'test_task_123'", [], |row| row.get(0)) + .expect("Failed to query state"); + + let fuel_consumed: i64 = conn + .query_row("SELECT fuel_consumed FROM instance_log WHERE task_id = 'test_task_123'", [], |row| row.get(0)) + .expect("Failed to query fuel_consumed"); + + assert_eq!(state, "running", "State should be updated to running"); + assert_eq!(fuel_consumed, 10, "Fuel consumed should be updated to 10"); + } + + } + + mod queries { + use super::*; + + #[test] + fn test_get_logs() { + let log = Log::new(None, "state.db-wal").unwrap(); + + { + let conn = log.db.conn.lock().unwrap(); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "created", + "10000", + "0", + "1000", + "1000", + ]).expect("Failed to insert first test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "running", + "10000", + "5000", + "2000", + "2000", + ]).expect("Failed to insert second test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "completed", + "10000", + "8500", + "3000", + "3000", + ]).expect("Failed to insert third test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "other_agent", + "2.0.0", + "other_task_456", + "Other Task", + "failed", + "5000", + "2500", + "1500", + "1500", + ]).expect("Failed to insert other task log"); + } + + let logs = log.get_logs("test_task_123").expect("Failed to get logs"); + + assert_eq!(logs.len(), 3, "Expected 3 logs for test_task_123"); + + assert_eq!(logs[0].state.to_string(), "completed", "First log should be completed"); + assert_eq!(logs[0].fuel_consumed, 8500, "First log fuel_consumed should be 8500"); + assert_eq!(logs[0].created_at, 3000, "First log created_at should be 3000"); + + assert_eq!(logs[1].state.to_string(), "running", "Second log should be running"); + assert_eq!(logs[1].fuel_consumed, 5000, "Second log fuel_consumed should be 5000"); + assert_eq!(logs[1].created_at, 2000, "Second log created_at should be 2000"); + + assert_eq!(logs[2].state.to_string(), "created", "Third log should be created"); + assert_eq!(logs[2].fuel_consumed, 0, "Third log fuel_consumed should be 0"); + assert_eq!(logs[2].created_at, 1000, "Third log created_at should be 1000"); + + for log_entry in &logs { + assert_eq!(log_entry.task_id, "test_task_123", "All logs should have task_id test_task_123"); + assert_eq!(log_entry.agent_name, "test_agent", "All logs should have agent_name test_agent"); + assert_eq!(log_entry.task_name, "Test Task", "All logs should have task_name Test Task"); + } + + let empty_logs = log.get_logs("non_existent_task").expect("Failed to get logs for non-existent task"); + assert_eq!(empty_logs.len(), 0, "Expected 0 logs for non-existent task"); + } + + #[test] + fn test_delete_log() { + let log = Log::new(None, "state.db-wal").unwrap(); + + { + let conn = log.db.conn.lock().unwrap(); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_to_delete", + "Task To Delete", + "created", + "10000", + "0", + "1000", + "1000", + ]).expect("Failed to insert first test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_to_delete", + "Task To Delete", + "running", + "10000", + "5000", + "2000", + "2000", + ]).expect("Failed to insert second test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "other_agent", + "2.0.0", + "task_to_keep", + "Task To Keep", + "completed", + "5000", + "2500", + "1500", + "1500", + ]).expect("Failed to insert task to keep"); + } + + let logs_before = log.get_logs("task_to_delete").expect("Failed to get logs before deletion"); + assert_eq!(logs_before.len(), 2, "Expected 2 logs for task_to_delete before deletion"); + + log.delete_log("task_to_delete").expect("Failed to delete logs"); + + let logs_after = log.get_logs("task_to_delete").expect("Failed to get logs after deletion"); + assert_eq!(logs_after.len(), 0, "Expected 0 logs for task_to_delete after deletion"); + + let kept_logs = log.get_logs("task_to_keep").expect("Failed to get logs for task_to_keep"); + assert_eq!(kept_logs.len(), 1, "Expected 1 log for task_to_keep to remain"); + assert_eq!(kept_logs[0].task_id, "task_to_keep", "Kept log should have correct task_id"); + assert_eq!(kept_logs[0].state.to_string(), "completed", "Kept log should have correct state"); + } } } diff --git a/crates/capsule-core/src/config/mod.rs b/crates/capsule-core/src/config/mod.rs index 5c05f94..b6c0d0e 100644 --- a/crates/capsule-core/src/config/mod.rs +++ b/crates/capsule-core/src/config/mod.rs @@ -1,3 +1,2 @@ pub mod database; pub mod log; - From 6189814f0715b192d3a73a9a27b89d67dd069ffe Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 19:23:08 +0100 Subject: [PATCH 08/22] rust fmt clear --- crates/capsule-core/src/config/log.rs | 146 +++++++++++++++++++------- 1 file changed, 107 insertions(+), 39 deletions(-) diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index e56a9ca..8384e42 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -142,14 +142,13 @@ impl Log { Ok(()) } - pub fn update_log(&self, log: UpdateInstanceLog) -> Result<(), LogError> { self.db.execute( "UPDATE instance_log SET state = ?, fuel_consumed = ? WHERE task_id = ?", [ &log.state.to_string(), &log.fuel_consumed.to_string(), - &log.task_id.to_string() + &log.task_id.to_string(), ], )?; @@ -189,14 +188,11 @@ impl Log { } pub fn delete_log(&self, task_id: &str) -> Result<(), LogError> { - self.db.execute( - "DELETE FROM instance_log WHERE task_id = ?", - [task_id], - )?; + self.db + .execute("DELETE FROM instance_log WHERE task_id = ?", [task_id])?; Ok(()) } - } #[cfg(test)] @@ -296,28 +292,33 @@ mod tests { ]).expect("Failed to insert test data"); } - let _ = log.update_log( - UpdateInstanceLog { - task_id: "test_task_123".to_string(), - state: InstanceState::Running, - fuel_consumed: 10, - } - ); + let _ = log.update_log(UpdateInstanceLog { + task_id: "test_task_123".to_string(), + state: InstanceState::Running, + fuel_consumed: 10, + }); let conn = log.db.conn.lock().unwrap(); let state: String = conn - .query_row("SELECT state FROM instance_log WHERE task_id = 'test_task_123'", [], |row| row.get(0)) + .query_row( + "SELECT state FROM instance_log WHERE task_id = 'test_task_123'", + [], + |row| row.get(0), + ) .expect("Failed to query state"); let fuel_consumed: i64 = conn - .query_row("SELECT fuel_consumed FROM instance_log WHERE task_id = 'test_task_123'", [], |row| row.get(0)) + .query_row( + "SELECT fuel_consumed FROM instance_log WHERE task_id = 'test_task_123'", + [], + |row| row.get(0), + ) .expect("Failed to query fuel_consumed"); assert_eq!(state, "running", "State should be updated to running"); assert_eq!(fuel_consumed, 10, "Fuel consumed should be updated to 10"); } - } mod queries { @@ -387,25 +388,66 @@ mod tests { assert_eq!(logs.len(), 3, "Expected 3 logs for test_task_123"); - assert_eq!(logs[0].state.to_string(), "completed", "First log should be completed"); - assert_eq!(logs[0].fuel_consumed, 8500, "First log fuel_consumed should be 8500"); - assert_eq!(logs[0].created_at, 3000, "First log created_at should be 3000"); + assert_eq!( + logs[0].state.to_string(), + "completed", + "First log should be completed" + ); + assert_eq!( + logs[0].fuel_consumed, 8500, + "First log fuel_consumed should be 8500" + ); + assert_eq!( + logs[0].created_at, 3000, + "First log created_at should be 3000" + ); - assert_eq!(logs[1].state.to_string(), "running", "Second log should be running"); - assert_eq!(logs[1].fuel_consumed, 5000, "Second log fuel_consumed should be 5000"); - assert_eq!(logs[1].created_at, 2000, "Second log created_at should be 2000"); + assert_eq!( + logs[1].state.to_string(), + "running", + "Second log should be running" + ); + assert_eq!( + logs[1].fuel_consumed, 5000, + "Second log fuel_consumed should be 5000" + ); + assert_eq!( + logs[1].created_at, 2000, + "Second log created_at should be 2000" + ); - assert_eq!(logs[2].state.to_string(), "created", "Third log should be created"); - assert_eq!(logs[2].fuel_consumed, 0, "Third log fuel_consumed should be 0"); - assert_eq!(logs[2].created_at, 1000, "Third log created_at should be 1000"); + assert_eq!( + logs[2].state.to_string(), + "created", + "Third log should be created" + ); + assert_eq!( + logs[2].fuel_consumed, 0, + "Third log fuel_consumed should be 0" + ); + assert_eq!( + logs[2].created_at, 1000, + "Third log created_at should be 1000" + ); for log_entry in &logs { - assert_eq!(log_entry.task_id, "test_task_123", "All logs should have task_id test_task_123"); - assert_eq!(log_entry.agent_name, "test_agent", "All logs should have agent_name test_agent"); - assert_eq!(log_entry.task_name, "Test Task", "All logs should have task_name Test Task"); + assert_eq!( + log_entry.task_id, "test_task_123", + "All logs should have task_id test_task_123" + ); + assert_eq!( + log_entry.agent_name, "test_agent", + "All logs should have agent_name test_agent" + ); + assert_eq!( + log_entry.task_name, "Test Task", + "All logs should have task_name Test Task" + ); } - let empty_logs = log.get_logs("non_existent_task").expect("Failed to get logs for non-existent task"); + let empty_logs = log + .get_logs("non_existent_task") + .expect("Failed to get logs for non-existent task"); assert_eq!(empty_logs.len(), 0, "Expected 0 logs for non-existent task"); } @@ -456,18 +498,44 @@ mod tests { ]).expect("Failed to insert task to keep"); } - let logs_before = log.get_logs("task_to_delete").expect("Failed to get logs before deletion"); - assert_eq!(logs_before.len(), 2, "Expected 2 logs for task_to_delete before deletion"); + let logs_before = log + .get_logs("task_to_delete") + .expect("Failed to get logs before deletion"); + assert_eq!( + logs_before.len(), + 2, + "Expected 2 logs for task_to_delete before deletion" + ); - log.delete_log("task_to_delete").expect("Failed to delete logs"); + log.delete_log("task_to_delete") + .expect("Failed to delete logs"); - let logs_after = log.get_logs("task_to_delete").expect("Failed to get logs after deletion"); - assert_eq!(logs_after.len(), 0, "Expected 0 logs for task_to_delete after deletion"); + let logs_after = log + .get_logs("task_to_delete") + .expect("Failed to get logs after deletion"); + assert_eq!( + logs_after.len(), + 0, + "Expected 0 logs for task_to_delete after deletion" + ); - let kept_logs = log.get_logs("task_to_keep").expect("Failed to get logs for task_to_keep"); - assert_eq!(kept_logs.len(), 1, "Expected 1 log for task_to_keep to remain"); - assert_eq!(kept_logs[0].task_id, "task_to_keep", "Kept log should have correct task_id"); - assert_eq!(kept_logs[0].state.to_string(), "completed", "Kept log should have correct state"); + let kept_logs = log + .get_logs("task_to_keep") + .expect("Failed to get logs for task_to_keep"); + assert_eq!( + kept_logs.len(), + 1, + "Expected 1 log for task_to_keep to remain" + ); + assert_eq!( + kept_logs[0].task_id, "task_to_keep", + "Kept log should have correct task_id" + ); + assert_eq!( + kept_logs[0].state.to_string(), + "completed", + "Kept log should have correct state" + ); } } } From e1f8fd5728134e7ae39e71eff59b5cca6a845318 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Fri, 5 Dec 2025 23:19:29 +0100 Subject: [PATCH 09/22] Add clear logs --- crates/capsule-core/src/config/log.rs | 348 +++++++++++++++++++----- crates/capsule-core/src/wasm/mod.rs | 1 + crates/capsule-core/src/wasm/runtime.rs | 3 + 3 files changed, 284 insertions(+), 68 deletions(-) create mode 100644 crates/capsule-core/src/wasm/mod.rs create mode 100644 crates/capsule-core/src/wasm/runtime.rs diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index 8384e42..340a4c7 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -23,7 +23,7 @@ impl From for LogError { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum InstanceState { Created, @@ -155,10 +155,10 @@ impl Log { Ok(()) } - pub fn get_logs(&self, task_id: &str) -> Result, LogError> { + pub fn get_logs(&self) -> Result, LogError> { let logs = self.db.query( - "SELECT id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at FROM instance_log WHERE task_id = ? ORDER BY created_at DESC", - [task_id], + "SELECT id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at FROM instance_log ORDER BY created_at DESC", + [], |row| { let id_str: String = row.get(0)?; let state_str: String = row.get(5)?; @@ -187,6 +187,18 @@ impl Log { Ok(logs) } + pub fn clear_logs(&self) -> Result<(), LogError> { + let logs = self.get_logs()?; + + for log in logs { + if log.state != InstanceState::Completed && log.state != InstanceState::Running { + self.delete_log(&log.task_id)?; + } + } + + Ok(()) + } + pub fn delete_log(&self, task_id: &str) -> Result<(), LogError> { self.db .execute("DELETE FROM instance_log WHERE task_id = ?", [task_id])?; @@ -199,7 +211,7 @@ impl Log { mod tests { use super::*; - mod creation { + mod execution { use super::*; #[test] @@ -319,6 +331,108 @@ mod tests { assert_eq!(state, "running", "State should be updated to running"); assert_eq!(fuel_consumed, 10, "Fuel consumed should be updated to 10"); } + + #[test] + fn test_delete_log() { + let log = Log::new(None, "state.db-wal").unwrap(); + + { + let conn = log.db.conn.lock().unwrap(); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_to_delete", + "Task To Delete", + "created", + "10000", + "0", + "1000", + "1000", + ]).expect("Failed to insert first test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_to_delete", + "Task To Delete", + "running", + "10000", + "5000", + "2000", + "2000", + ]).expect("Failed to insert second test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "other_agent", + "2.0.0", + "task_to_keep", + "Task To Keep", + "completed", + "5000", + "2500", + "1500", + "1500", + ]).expect("Failed to insert task to keep"); + } + + let conn = log.db.conn.lock().unwrap(); + + let count_before: i64 = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_to_delete'", + [], + |row| row.get(0), + ) + .expect("Failed to count logs before deletion"); + assert_eq!( + count_before, 2, + "Expected 2 logs for task_to_delete before deletion" + ); + + drop(conn); + + log.delete_log("task_to_delete") + .expect("Failed to delete logs"); + + let conn = log.db.conn.lock().unwrap(); + + let count_after: i64 = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_to_delete'", + [], + |row| row.get(0), + ) + .expect("Failed to count logs after deletion"); + assert_eq!( + count_after, 0, + "Expected 0 logs for task_to_delete after deletion" + ); + + let count_kept: i64 = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_to_keep'", + [], + |row| row.get(0), + ) + .expect("Failed to count kept logs"); + assert_eq!(count_kept, 1, "Expected 1 log for task_to_keep to remain"); + + let kept_state: String = conn + .query_row( + "SELECT state FROM instance_log WHERE task_id = 'task_to_keep'", + [], + |row| row.get(0), + ) + .expect("Failed to query kept log state"); + assert_eq!( + kept_state, "completed", + "Kept log should have correct state" + ); + } } mod queries { @@ -384,9 +498,10 @@ mod tests { ]).expect("Failed to insert other task log"); } - let logs = log.get_logs("test_task_123").expect("Failed to get logs"); + let logs = log.get_logs().expect("Failed to get logs"); - assert_eq!(logs.len(), 3, "Expected 3 logs for test_task_123"); + // get_logs now returns ALL logs, so we expect 4 total (3 for test_task_123 + 1 for other_task_456) + assert_eq!(logs.len(), 4, "Expected 4 total logs"); assert_eq!( logs[0].state.to_string(), @@ -416,43 +531,72 @@ mod tests { "Second log created_at should be 2000" ); + assert_eq!( + logs[2].task_id, "other_task_456", + "Third log should be other_task_456" + ); assert_eq!( logs[2].state.to_string(), + "failed", + "Third log should be failed" + ); + assert_eq!( + logs[2].created_at, 1500, + "Third log created_at should be 1500" + ); + + assert_eq!( + logs[3].task_id, "test_task_123", + "Fourth log should be test_task_123" + ); + assert_eq!( + logs[3].state.to_string(), "created", - "Third log should be created" + "Fourth log should be created" ); assert_eq!( - logs[2].fuel_consumed, 0, - "Third log fuel_consumed should be 0" + logs[3].fuel_consumed, 0, + "Fourth log fuel_consumed should be 0" ); assert_eq!( - logs[2].created_at, 1000, - "Third log created_at should be 1000" + logs[3].created_at, 1000, + "Fourth log created_at should be 1000" ); - for log_entry in &logs { - assert_eq!( - log_entry.task_id, "test_task_123", - "All logs should have task_id test_task_123" - ); + let test_task_logs: Vec<_> = logs + .iter() + .filter(|l| l.task_id == "test_task_123") + .collect(); + assert_eq!(test_task_logs.len(), 3, "Expected 3 logs for test_task_123"); + + for log_entry in test_task_logs { assert_eq!( log_entry.agent_name, "test_agent", - "All logs should have agent_name test_agent" + "test_task_123 logs should have agent_name test_agent" ); assert_eq!( log_entry.task_name, "Test Task", - "All logs should have task_name Test Task" + "test_task_123 logs should have task_name Test Task" ); } - let empty_logs = log - .get_logs("non_existent_task") - .expect("Failed to get logs for non-existent task"); - assert_eq!(empty_logs.len(), 0, "Expected 0 logs for non-existent task"); + let other_task_logs: Vec<_> = logs + .iter() + .filter(|l| l.task_id == "other_task_456") + .collect(); + assert_eq!( + other_task_logs.len(), + 1, + "Expected 1 log for other_task_456" + ); + assert_eq!( + other_task_logs[0].agent_name, "other_agent", + "other_task_456 log should have agent_name other_agent" + ); } #[test] - fn test_delete_log() { + fn test_clear_logs() { let log = Log::new(None, "state.db-wal").unwrap(); { @@ -462,80 +606,148 @@ mod tests { &Uuid::new_v4().to_string(), "test_agent", "1.0.0", - "task_to_delete", - "Task To Delete", + "task_created", + "Task Created", "created", "10000", "0", "1000", "1000", - ]).expect("Failed to insert first test log"); + ]).expect("Failed to insert created log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ &Uuid::new_v4().to_string(), "test_agent", "1.0.0", - "task_to_delete", - "Task To Delete", + "task_running", + "Task Running", "running", "10000", "5000", "2000", "2000", - ]).expect("Failed to insert second test log"); + ]).expect("Failed to insert running log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ &Uuid::new_v4().to_string(), - "other_agent", - "2.0.0", - "task_to_keep", - "Task To Keep", + "test_agent", + "1.0.0", + "task_completed", + "Task Completed", "completed", + "10000", + "8500", + "3000", + "3000", + ]).expect("Failed to insert completed log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_failed", + "Task Failed", + "failed", "5000", "2500", "1500", "1500", - ]).expect("Failed to insert task to keep"); + ]).expect("Failed to insert failed log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "task_interrupted", + "Task Interrupted", + "interrupted", + "7000", + "3000", + "2500", + "2500", + ]).expect("Failed to insert interrupted log"); } - let logs_before = log - .get_logs("task_to_delete") - .expect("Failed to get logs before deletion"); - assert_eq!( - logs_before.len(), - 2, - "Expected 2 logs for task_to_delete before deletion" - ); + let conn = log.db.conn.lock().unwrap(); - log.delete_log("task_to_delete") - .expect("Failed to delete logs"); + let count_before: i64 = conn + .query_row("SELECT COUNT(*) FROM instance_log", [], |row| row.get(0)) + .expect("Failed to count logs before clear"); + assert_eq!(count_before, 5, "Expected 5 logs before clear"); - let logs_after = log - .get_logs("task_to_delete") - .expect("Failed to get logs after deletion"); - assert_eq!( - logs_after.len(), - 0, - "Expected 0 logs for task_to_delete after deletion" - ); + drop(conn); - let kept_logs = log - .get_logs("task_to_keep") - .expect("Failed to get logs for task_to_keep"); - assert_eq!( - kept_logs.len(), - 1, - "Expected 1 log for task_to_keep to remain" - ); - assert_eq!( - kept_logs[0].task_id, "task_to_keep", - "Kept log should have correct task_id" - ); + log.clear_logs().expect("Failed to clear logs"); + + let conn = log.db.conn.lock().unwrap(); + + let count_after: i64 = conn + .query_row("SELECT COUNT(*) FROM instance_log", [], |row| row.get(0)) + .expect("Failed to count logs after clear"); assert_eq!( - kept_logs[0].state.to_string(), - "completed", - "Kept log should have correct state" + count_after, 2, + "Expected 2 logs after clear (running and completed)" ); + + let running_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_running'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .expect("Failed to check running log"); + assert!(running_exists, "Running log should still exist"); + + let completed_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_completed'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .expect("Failed to check completed log"); + assert!(completed_exists, "Completed log should still exist"); + + let created_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_created'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .expect("Failed to check created log"); + assert!(!created_exists, "Created log should be deleted"); + + let failed_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_failed'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .expect("Failed to check failed log"); + assert!(!failed_exists, "Failed log should be deleted"); + + let interrupted_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM instance_log WHERE task_id = 'task_interrupted'", + [], + |row| { + let count: i64 = row.get(0)?; + Ok(count > 0) + }, + ) + .expect("Failed to check interrupted log"); + assert!(!interrupted_exists, "Interrupted log should be deleted"); } } } diff --git a/crates/capsule-core/src/wasm/mod.rs b/crates/capsule-core/src/wasm/mod.rs new file mode 100644 index 0000000..ea62c41 --- /dev/null +++ b/crates/capsule-core/src/wasm/mod.rs @@ -0,0 +1 @@ +pub mod runtime; diff --git a/crates/capsule-core/src/wasm/runtime.rs b/crates/capsule-core/src/wasm/runtime.rs new file mode 100644 index 0000000..0d36145 --- /dev/null +++ b/crates/capsule-core/src/wasm/runtime.rs @@ -0,0 +1,3 @@ +pub struct Runtime {} + +impl Runtime {} From 4ba3a6c63b098c57040fb53b6218044188a4312a Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sat, 6 Dec 2025 16:08:00 +0100 Subject: [PATCH 10/22] Add execution policy --- crates/capsule-core/src/lib.rs | 1 + .../capsule-core/src/wasm/execution_policy.rs | 111 ++++++++++++++++++ crates/capsule-core/src/wasm/mod.rs | 1 + 3 files changed, 113 insertions(+) create mode 100644 crates/capsule-core/src/wasm/execution_policy.rs diff --git a/crates/capsule-core/src/lib.rs b/crates/capsule-core/src/lib.rs index ef68c36..96b08b8 100644 --- a/crates/capsule-core/src/lib.rs +++ b/crates/capsule-core/src/lib.rs @@ -1 +1,2 @@ pub mod config; +pub mod wasm; diff --git a/crates/capsule-core/src/wasm/execution_policy.rs b/crates/capsule-core/src/wasm/execution_policy.rs new file mode 100644 index 0000000..f89d351 --- /dev/null +++ b/crates/capsule-core/src/wasm/execution_policy.rs @@ -0,0 +1,111 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Compute { + Low, + Medium, + High, + Custom(i64), +} + +impl fmt::Display for Compute { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let state_str = match self { + Compute::Low => 100_000_000, + Compute::Medium => 2_000_000_000, + Compute::High => 50_000_000_000, + Compute::Custom(fuel) => *fuel, + }; + write!(f, "{}", state_str) + } +} + +#[derive(Clone)] +pub struct ExecutionPolicy { + name: String, + compute: Compute, + ram: Option, + timeout: Option, + max_retries: i64, + env_vars: Option>, +} + +impl Default for ExecutionPolicy { + fn default() -> Self { + Self { + name: "default".to_string(), + compute: Compute::Low, + ram: None, + timeout: None, + max_retries: 1, + env_vars: None, + } + } +} + +impl ExecutionPolicy { + pub fn new() -> Self { + Self::default() + } + + pub fn name(mut self, name: Option) -> Self { + if let Some(n) = name { + self.name = n; + } + self + } + + pub fn compute(mut self, compute: Option) -> Self { + if let Some(c) = compute { + self.compute = c; + } + self + } + + pub fn ram(mut self, ram: Option) -> Self { + self.ram = ram; + self + } + + pub fn timeout(mut self, timeout: Option) -> Self { + self.timeout = timeout; + self + } + + pub fn max_retries(mut self, max_retries: Option) -> Self { + if let Some(m) = max_retries { + self.max_retries = m; + } + self + } + + pub fn env_vars(mut self, env_vars: Option>) -> Self { + self.env_vars = env_vars; + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_execution_policy() { + let policy = ExecutionPolicy::new() + .name(Some("test".to_string())) + .compute(None) + .ram(Some(128)) + .timeout(Some(60)) + .max_retries(Some(3)) + .env_vars(None); + + assert_eq!(policy.name, "test"); + assert_eq!(policy.compute, Compute::Low); + assert_eq!(policy.ram, Some(128)); + assert_eq!(policy.timeout, Some(60)); + assert_eq!(policy.max_retries, 3); + assert_eq!(policy.env_vars, None); + } +} diff --git a/crates/capsule-core/src/wasm/mod.rs b/crates/capsule-core/src/wasm/mod.rs index ea62c41..661e75d 100644 --- a/crates/capsule-core/src/wasm/mod.rs +++ b/crates/capsule-core/src/wasm/mod.rs @@ -1 +1,2 @@ +pub mod execution_policy; pub mod runtime; From 89b3b3da596b89f7c28f2709a86b7bef22d6e175 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 7 Dec 2025 01:01:29 +0100 Subject: [PATCH 11/22] refacto logs --- crates/capsule-core/src/config/log.rs | 491 +++++++++++++++----------- 1 file changed, 291 insertions(+), 200 deletions(-) diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index 340a4c7..ebd0d46 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -1,5 +1,8 @@ use serde::{Deserialize, Serialize}; use std::fmt; +use std::sync::mpsc; +use std::thread::{Builder, JoinHandle}; +use tokio::sync::oneshot; use uuid::Uuid; use crate::config::database::{Database, DatabaseError}; @@ -7,12 +10,16 @@ use crate::config::database::{Database, DatabaseError}; #[derive(Debug)] pub enum LogError { DatabaseError(String), + WalLoggerDied(String), + LogResponseLost(String), } impl fmt::Display for LogError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LogError::DatabaseError(msg) => write!(f, "Log error > {}", msg), + LogError::WalLoggerDied(msg) => write!(f, "Log error > WAL logger died > {}", msg), + LogError::LogResponseLost(msg) => write!(f, "Log error > Log response lost > {}", msg), } } } @@ -22,6 +29,17 @@ impl From for LogError { LogError::DatabaseError(err.to_string()) } } +impl From> for LogError { + fn from(err: mpsc::SendError) -> Self { + LogError::WalLoggerDied(err.to_string()) + } +} + +impl From for LogError { + fn from(err: oneshot::error::RecvError) -> Self { + LogError::LogResponseLost(err.to_string()) + } +} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -78,15 +96,41 @@ pub struct UpdateInstanceLog { pub fuel_consumed: u64, } -#[derive(Clone)] +enum LogCommand { + Create { + log: CreateInstanceLog, + response: tokio::sync::oneshot::Sender>, + }, + + Update { + log: UpdateInstanceLog, + response: tokio::sync::oneshot::Sender>, + }, +} + +#[derive()] pub struct Log { pub db: Database, + log_tx: mpsc::Sender, + _log_handle: JoinHandle<()>, } impl Log { pub fn new(path: Option<&str>, database_name: &str) -> Result { let db = Database::new(path, database_name)?; + Self::ensure_schema(&db)?; + + let (log_tx, log_handle) = Self::spawn_wal_worker(db.clone()); + + Ok(Self { + db, + log_tx, + _log_handle: log_handle, + }) + } + + fn ensure_schema(db: &Database) -> Result<(), LogError> { let table_exists = db.table_exists("instance_log")?; if !table_exists { @@ -115,14 +159,39 @@ impl Log { )?; } - Ok(Self { db }) + Ok(()) } - pub fn commit_log(&self, log: CreateInstanceLog, on_commit: F) -> Result<(), LogError> - where - F: FnOnce(Result<(), LogError>) + Send + 'static, - { - match self.db.execute( + fn spawn_wal_worker(db: Database) -> (mpsc::Sender, JoinHandle<()>) { + let (tx, rx) = mpsc::channel(); + + let handle = Builder::new() + .name("wal-logger".to_string()) + .spawn(move || { + Self::wal_worker_loop(db, rx); + }) + .expect("Failed to spawn WAL logger thread"); + + (tx, handle) + } + + fn wal_worker_loop(db: Database, rx: mpsc::Receiver) { + while let Ok(cmd) = rx.recv() { + match cmd { + LogCommand::Create { log, response } => { + let result = Self::execute_create(&db, log); + let _ = response.send(result); + } + LogCommand::Update { log, response } => { + let result = Self::execute_update(&db, log); + let _ = response.send(result); + } + } + } + } + + fn execute_create(db: &Database, log: CreateInstanceLog) -> Result<(), LogError> { + db.execute( "INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ &Uuid::new_v4().to_string(), @@ -132,29 +201,42 @@ impl Log { &log.task_name, &log.state.to_string(), &log.fuel_limit.to_string(), - &log.fuel_consumed.to_string() + &log.fuel_consumed.to_string(), ], - ) { - Ok(_) => on_commit(Ok(())), - Err(e) => on_commit(Err(LogError::DatabaseError(e.to_string()))), - } + )?; Ok(()) } - pub fn update_log(&self, log: UpdateInstanceLog) -> Result<(), LogError> { - self.db.execute( + fn execute_update(db: &Database, log: UpdateInstanceLog) -> Result<(), LogError> { + db.execute( "UPDATE instance_log SET state = ?, fuel_consumed = ? WHERE task_id = ?", [ &log.state.to_string(), &log.fuel_consumed.to_string(), - &log.task_id.to_string(), + &log.task_id, ], )?; Ok(()) } + pub async fn commit_log(&self, log: CreateInstanceLog) -> Result<(), LogError> { + let (tx, rx) = oneshot::channel(); + + self.log_tx.send(LogCommand::Create { log, response: tx })?; + + rx.await? + } + + pub async fn update_log(&self, log: UpdateInstanceLog) -> Result<(), LogError> { + let (tx, rx) = oneshot::channel(); + + self.log_tx.send(LogCommand::Update { log, response: tx })?; + + rx.await? + } + pub fn get_logs(&self) -> Result, LogError> { let logs = self.db.query( "SELECT id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at FROM instance_log ORDER BY created_at DESC", @@ -210,8 +292,18 @@ impl Log { #[cfg(test)] mod tests { use super::*; + fn run_async(future: F) -> F::Output + where + F: std::future::Future, + { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Failed to create runtime"); + rt.block_on(future) + } - mod execution { + mod creation { use super::*; #[test] @@ -245,16 +337,10 @@ mod tests { #[test] fn test_commit_log() { - use std::sync::Arc; - use std::sync::Mutex; - let log = Log::new(None, "state.db-wal").unwrap(); - let callback_invoked = Arc::new(Mutex::new(false)); - let callback_invoked_clone = callback_invoked.clone(); - - let _ = log.commit_log( - CreateInstanceLog { + run_async(async { + log.commit_log(CreateInstanceLog { agent_name: "agent_name".to_string(), agent_version: "agent_version".to_string(), task_id: "task_id".to_string(), @@ -262,17 +348,10 @@ mod tests { state: InstanceState::Created, fuel_limit: 100, fuel_consumed: 0, - }, - move |result| { - *callback_invoked_clone.lock().unwrap() = true; - assert!(result.is_ok(), "Commit should succeed: {:?}", result); - }, - ); - - assert!( - *callback_invoked.lock().unwrap(), - "Callback should have been invoked" - ); + }) + .await + .expect("Failed to commit log"); + }); let conn = log.db.conn.lock().unwrap(); @@ -284,6 +363,10 @@ mod tests { assert!(exists, "instance does not exist"); } + } + + mod update { + use super::*; #[test] fn test_update_log() { @@ -304,10 +387,14 @@ mod tests { ]).expect("Failed to insert test data"); } - let _ = log.update_log(UpdateInstanceLog { - task_id: "test_task_123".to_string(), - state: InstanceState::Running, - fuel_consumed: 10, + run_async(async { + log.update_log(UpdateInstanceLog { + task_id: "test_task_123".to_string(), + state: InstanceState::Running, + fuel_consumed: 10, + }) + .await + .expect("Failed to update log"); }); let conn = log.db.conn.lock().unwrap(); @@ -331,6 +418,10 @@ mod tests { assert_eq!(state, "running", "State should be updated to running"); assert_eq!(fuel_consumed, 10, "Fuel consumed should be updated to 10"); } + } + + mod deletion { + use super::*; #[test] fn test_delete_log() { @@ -433,167 +524,6 @@ mod tests { "Kept log should have correct state" ); } - } - - mod queries { - use super::*; - - #[test] - fn test_get_logs() { - let log = Log::new(None, "state.db-wal").unwrap(); - - { - let conn = log.db.conn.lock().unwrap(); - - conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), - "test_agent", - "1.0.0", - "test_task_123", - "Test Task", - "created", - "10000", - "0", - "1000", - "1000", - ]).expect("Failed to insert first test log"); - - conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), - "test_agent", - "1.0.0", - "test_task_123", - "Test Task", - "running", - "10000", - "5000", - "2000", - "2000", - ]).expect("Failed to insert second test log"); - - conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), - "test_agent", - "1.0.0", - "test_task_123", - "Test Task", - "completed", - "10000", - "8500", - "3000", - "3000", - ]).expect("Failed to insert third test log"); - - conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), - "other_agent", - "2.0.0", - "other_task_456", - "Other Task", - "failed", - "5000", - "2500", - "1500", - "1500", - ]).expect("Failed to insert other task log"); - } - - let logs = log.get_logs().expect("Failed to get logs"); - - // get_logs now returns ALL logs, so we expect 4 total (3 for test_task_123 + 1 for other_task_456) - assert_eq!(logs.len(), 4, "Expected 4 total logs"); - - assert_eq!( - logs[0].state.to_string(), - "completed", - "First log should be completed" - ); - assert_eq!( - logs[0].fuel_consumed, 8500, - "First log fuel_consumed should be 8500" - ); - assert_eq!( - logs[0].created_at, 3000, - "First log created_at should be 3000" - ); - - assert_eq!( - logs[1].state.to_string(), - "running", - "Second log should be running" - ); - assert_eq!( - logs[1].fuel_consumed, 5000, - "Second log fuel_consumed should be 5000" - ); - assert_eq!( - logs[1].created_at, 2000, - "Second log created_at should be 2000" - ); - - assert_eq!( - logs[2].task_id, "other_task_456", - "Third log should be other_task_456" - ); - assert_eq!( - logs[2].state.to_string(), - "failed", - "Third log should be failed" - ); - assert_eq!( - logs[2].created_at, 1500, - "Third log created_at should be 1500" - ); - - assert_eq!( - logs[3].task_id, "test_task_123", - "Fourth log should be test_task_123" - ); - assert_eq!( - logs[3].state.to_string(), - "created", - "Fourth log should be created" - ); - assert_eq!( - logs[3].fuel_consumed, 0, - "Fourth log fuel_consumed should be 0" - ); - assert_eq!( - logs[3].created_at, 1000, - "Fourth log created_at should be 1000" - ); - - let test_task_logs: Vec<_> = logs - .iter() - .filter(|l| l.task_id == "test_task_123") - .collect(); - assert_eq!(test_task_logs.len(), 3, "Expected 3 logs for test_task_123"); - - for log_entry in test_task_logs { - assert_eq!( - log_entry.agent_name, "test_agent", - "test_task_123 logs should have agent_name test_agent" - ); - assert_eq!( - log_entry.task_name, "Test Task", - "test_task_123 logs should have task_name Test Task" - ); - } - - let other_task_logs: Vec<_> = logs - .iter() - .filter(|l| l.task_id == "other_task_456") - .collect(); - assert_eq!( - other_task_logs.len(), - 1, - "Expected 1 log for other_task_456" - ); - assert_eq!( - other_task_logs[0].agent_name, "other_agent", - "other_task_456 log should have agent_name other_agent" - ); - } #[test] fn test_clear_logs() { @@ -750,4 +680,165 @@ mod tests { assert!(!interrupted_exists, "Interrupted log should be deleted"); } } + + mod queries { + use super::*; + + #[test] + fn test_get_logs() { + let log = Log::new(None, "state.db-wal").unwrap(); + + { + let conn = log.db.conn.lock().unwrap(); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "created", + "10000", + "0", + "1000", + "1000", + ]).expect("Failed to insert first test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "running", + "10000", + "5000", + "2000", + "2000", + ]).expect("Failed to insert second test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "test_agent", + "1.0.0", + "test_task_123", + "Test Task", + "completed", + "10000", + "8500", + "3000", + "3000", + ]).expect("Failed to insert third test log"); + + conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + &Uuid::new_v4().to_string(), + "other_agent", + "2.0.0", + "other_task_456", + "Other Task", + "failed", + "5000", + "2500", + "1500", + "1500", + ]).expect("Failed to insert other task log"); + } + + let logs = log.get_logs().expect("Failed to get logs"); + + // get_logs now returns ALL logs, so we expect 4 total (3 for test_task_123 + 1 for other_task_456) + assert_eq!(logs.len(), 4, "Expected 4 total logs"); + + assert_eq!( + logs[0].state.to_string(), + "completed", + "First log should be completed" + ); + assert_eq!( + logs[0].fuel_consumed, 8500, + "First log fuel_consumed should be 8500" + ); + assert_eq!( + logs[0].created_at, 3000, + "First log created_at should be 3000" + ); + + assert_eq!( + logs[1].state.to_string(), + "running", + "Second log should be running" + ); + assert_eq!( + logs[1].fuel_consumed, 5000, + "Second log fuel_consumed should be 5000" + ); + assert_eq!( + logs[1].created_at, 2000, + "Second log created_at should be 2000" + ); + + assert_eq!( + logs[2].task_id, "other_task_456", + "Third log should be other_task_456" + ); + assert_eq!( + logs[2].state.to_string(), + "failed", + "Third log should be failed" + ); + assert_eq!( + logs[2].created_at, 1500, + "Third log created_at should be 1500" + ); + + assert_eq!( + logs[3].task_id, "test_task_123", + "Fourth log should be test_task_123" + ); + assert_eq!( + logs[3].state.to_string(), + "created", + "Fourth log should be created" + ); + assert_eq!( + logs[3].fuel_consumed, 0, + "Fourth log fuel_consumed should be 0" + ); + assert_eq!( + logs[3].created_at, 1000, + "Fourth log created_at should be 1000" + ); + + let test_task_logs: Vec<_> = logs + .iter() + .filter(|l| l.task_id == "test_task_123") + .collect(); + assert_eq!(test_task_logs.len(), 3, "Expected 3 logs for test_task_123"); + + for log_entry in test_task_logs { + assert_eq!( + log_entry.agent_name, "test_agent", + "test_task_123 logs should have agent_name test_agent" + ); + assert_eq!( + log_entry.task_name, "Test Task", + "test_task_123 logs should have task_name Test Task" + ); + } + + let other_task_logs: Vec<_> = logs + .iter() + .filter(|l| l.task_id == "other_task_456") + .collect(); + assert_eq!( + other_task_logs.len(), + 1, + "Expected 1 log for other_task_456" + ); + assert_eq!( + other_task_logs[0].agent_name, "other_agent", + "other_task_456 log should have agent_name other_agent" + ); + } + } } From 81a34ff3a7d8db897421490288b6affd6a4e88a3 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 7 Dec 2025 01:02:15 +0100 Subject: [PATCH 12/22] improve wasm execution policy --- .../capsule-core/src/wasm/execution_policy.rs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/capsule-core/src/wasm/execution_policy.rs b/crates/capsule-core/src/wasm/execution_policy.rs index f89d351..3bd150e 100644 --- a/crates/capsule-core/src/wasm/execution_policy.rs +++ b/crates/capsule-core/src/wasm/execution_policy.rs @@ -1,5 +1,4 @@ use serde::{Deserialize, Serialize}; -use std::fmt; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -7,29 +6,32 @@ pub enum Compute { Low, Medium, High, - Custom(i64), + Custom(u64), } -impl fmt::Display for Compute { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let state_str = match self { +impl Compute { + pub fn as_fuel(&self) -> u64 { + match self { Compute::Low => 100_000_000, Compute::Medium => 2_000_000_000, Compute::High => 50_000_000_000, Compute::Custom(fuel) => *fuel, - }; - write!(f, "{}", state_str) + } + } + + pub fn to_u64(&self) -> u64 { + self.as_fuel() } } #[derive(Clone)] pub struct ExecutionPolicy { - name: String, - compute: Compute, - ram: Option, - timeout: Option, - max_retries: i64, - env_vars: Option>, + pub name: String, + pub compute: Compute, + pub ram: Option, + pub timeout: Option, + pub max_retries: u64, + pub env_vars: Option>, } impl Default for ExecutionPolicy { @@ -64,24 +66,24 @@ impl ExecutionPolicy { self } - pub fn ram(mut self, ram: Option) -> Self { + pub fn ram(mut self, ram: Option) -> Self { self.ram = ram; self } - pub fn timeout(mut self, timeout: Option) -> Self { + pub fn timeout(mut self, timeout: Option) -> Self { self.timeout = timeout; self } - pub fn max_retries(mut self, max_retries: Option) -> Self { + pub fn max_retries(mut self, max_retries: Option) -> Self { if let Some(m) = max_retries { self.max_retries = m; } self } - pub fn env_vars(mut self, env_vars: Option>) -> Self { + pub fn env_vars(mut self, env_vars: Option>) -> Self { self.env_vars = env_vars; self } @@ -97,14 +99,14 @@ mod tests { .name(Some("test".to_string())) .compute(None) .ram(Some(128)) - .timeout(Some(60)) + .timeout(Some("60s".to_string())) .max_retries(Some(3)) .env_vars(None); assert_eq!(policy.name, "test"); assert_eq!(policy.compute, Compute::Low); assert_eq!(policy.ram, Some(128)); - assert_eq!(policy.timeout, Some(60)); + assert_eq!(policy.timeout, Some("60s".to_string())); assert_eq!(policy.max_retries, 3); assert_eq!(policy.env_vars, None); } From 715454227ca1d256fd656df4eda64a57f0138793 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 7 Dec 2025 01:18:29 +0100 Subject: [PATCH 13/22] add wasm architecture and instance creation --- crates/capsule-core/Cargo.lock | 3162 +++++++++++++++-- crates/capsule-core/Cargo.toml | 4 + .../capsule-core/src/wasm/commands/create.rs | 111 + crates/capsule-core/src/wasm/commands/mod.rs | 1 + crates/capsule-core/src/wasm/mod.rs | 2 + crates/capsule-core/src/wasm/runtime.rs | 68 +- crates/capsule-core/src/wasm/state.rs | 39 + 7 files changed, 2990 insertions(+), 397 deletions(-) create mode 100644 crates/capsule-core/src/wasm/commands/create.rs create mode 100644 crates/capsule-core/src/wasm/commands/mod.rs create mode 100644 crates/capsule-core/src/wasm/state.rs diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock index 934683a..e4e0fe6 100644 --- a/crates/capsule-core/Cargo.lock +++ b/crates/capsule-core/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "aho-corasick" version = "0.6.10" @@ -11,6 +20,50 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "0.1.8" @@ -26,6 +79,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "0.9.1" @@ -45,487 +104,2691 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] -name = "bumpalo" -version = "3.19.0" +name = "bitmaps" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "capsule-core" -version = "0.1.0" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" dependencies = [ - "rusqlite", - "rustfmt", - "serde", - "serde_json", - "tokio", - "uuid", + "typenum", ] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "bumpalo" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" dependencies = [ - "bitflags 1.3.2", + "allocator-api2", ] [[package]] -name = "diff" -version = "0.1.13" +name = "bytes" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] -name = "env_logger" -version = "0.4.3" +name = "cap-fs-ext" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" dependencies = [ - "log 0.3.9", - "regex", + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", ] [[package]] -name = "extprim" -version = "1.7.1" +name = "cap-net-ext" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b1a357c911c352439b460d7b375b5c85977b9db395b703dfee5a94dfb4d66a2" +checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" dependencies = [ - "num-traits", - "rand", - "rustc_version", - "semver", - "serde", + "cap-primitives", + "cap-std", + "rustix 1.1.2", + "smallvec", ] [[package]] -name = "fallible-iterator" -version = "0.3.0" +name = "cap-primitives" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.2", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] [[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "cap-rand" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] [[package]] -name = "foldhash" -version = "0.1.5" +name = "cap-std" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.2", +] [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "cap-time-ext" +version = "3.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.2", + "winx", +] [[package]] -name = "getopts" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +name = "capsule-core" +version = "0.1.0" dependencies = [ - "unicode-width", + "anyhow", + "rusqlite", + "rustfmt", + "serde", + "serde_json", + "tokio", + "uuid", + "wasmtime", + "wasmtime-wasi", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "cc" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ - "cfg-if", + "find-msvc-tools", + "jobserver", "libc", - "r-efi", - "wasip2", + "shlex", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "foldhash", + "bitflags 1.3.2", ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "cobs" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "hashbrown", + "thiserror 2.0.17", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "js-sys" -version = "0.3.83" +name = "cpp_demangle" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" dependencies = [ - "once_cell", - "wasm-bindgen", + "cfg-if", ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "libc", ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.177" +name = "cranelift-assembler-x64" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "30054f4aef4d614d37f27d5b77e36e165f0b27a71563be348e7c9fcfac41eed8" +dependencies = [ + "cranelift-assembler-x64-meta", +] [[package]] -name = "libsqlite3-sys" -version = "0.35.0" +name = "cranelift-assembler-x64-meta" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +checksum = "0beab56413879d4f515e08bcf118b1cb85f294129bb117057f573d37bfbb925a" dependencies = [ - "pkg-config", - "vcpkg", + "cranelift-srcgen", ] [[package]] -name = "log" -version = "0.3.9" +name = "cranelift-bforest" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +checksum = "6d054747549a69b264d5299c8ca1b0dd45dc6bd0ee43f1edfcc42a8b12952c7a" dependencies = [ - "log 0.4.29", + "cranelift-entity", ] [[package]] -name = "log" -version = "0.4.29" +name = "cranelift-bitset" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "98b92d481b77a7dc9d07c96e24a16f29e0c9c27d042828fdf7e49e54ee9819bf" +dependencies = [ + "serde", + "serde_derive", +] [[package]] -name = "memchr" -version = "2.7.6" +name = "cranelift-codegen" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "6eeccfc043d599b0ef1806942707fc51cdd1c3965c343956dc975a55d82a920f" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.15.5", + "log 0.4.29", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-math", +] [[package]] -name = "num-traits" -version = "0.2.19" +name = "cranelift-codegen-meta" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "1174cdb9d9d43b2bdaa612a07ed82af13db9b95526bc2c286c2aec4689bcc038" dependencies = [ - "autocfg 1.5.0", + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", + "pulley-interpreter", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "cranelift-codegen-shared" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "7d572be73fae802eb115f45e7e67a9ed16acb4ee683b67c4086768786545419a" [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "cranelift-control" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "e1587465cc84c5cc793b44add928771945f3132bbf6b3621ee9473c631a87156" +dependencies = [ + "arbitrary", +] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "cranelift-entity" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "063b83448b1343e79282c3c7cbda7ed5f0816f0b763a4c15f7cecb0a17d87ea6" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", +] [[package]] -name = "proc-macro2" -version = "1.0.103" +name = "cranelift-frontend" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "aa4461c2d2ca48bc72883f5f5c3129d9aefac832df1db824af9db8db3efee109" dependencies = [ - "unicode-ident", + "cranelift-codegen", + "log 0.4.29", + "smallvec", + "target-lexicon", ] [[package]] -name = "quote" -version = "1.0.42" +name = "cranelift-isle" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "acd811b25e18f14810d09c504e06098acc1d9dbfa24879bf0d6b6fb44415fc66" + +[[package]] +name = "cranelift-native" +version = "0.126.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2417046989d8d6367a55bbab2e406a9195d176f4779be4aa484d645887217d37" dependencies = [ - "proc-macro2", + "cranelift-codegen", + "libc", + "target-lexicon", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "cranelift-srcgen" +version = "0.126.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "8d039de901c8d928222b8128e1b9a9ab27b82a7445cb749a871c75d9cb25c57d" [[package]] -name = "rand" -version = "0.6.5" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha", - "rand_core 0.4.2", - "rand_hc", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.9", + "cfg-if", ] [[package]] -name = "rand_chacha" -version = "0.1.1" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "rand_core" -version = "0.3.1" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "rand_core 0.4.2", + "crossbeam-utils", ] [[package]] -name = "rand_core" -version = "0.4.2" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "rand_hc" -version = "0.1.0" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "rand_core 0.3.1", + "generic-array", + "typenum", ] [[package]] -name = "rand_isaac" -version = "0.1.1" +name = "debugid" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "rand_core 0.3.1", + "uuid", ] [[package]] -name = "rand_jitter" -version = "0.1.4" +name = "diff" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "rand_core 0.4.2", + "redox_users", "winapi 0.3.9", ] [[package]] -name = "rand_os" -version = "0.1.3" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b" +dependencies = [ + "log 0.3.9", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "cloudabi", - "fuchsia-cprng", "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.9", + "windows-sys 0.61.2", ] [[package]] -name = "rand_pcg" -version = "0.1.2" +name = "extprim" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +checksum = "2b1a357c911c352439b460d7b375b5c85977b9db395b703dfee5a94dfb4d66a2" dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", + "num-traits", + "rand 0.6.5", + "rustc_version", + "semver 0.9.0", + "serde", ] [[package]] -name = "rand_xorshift" +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.1.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "rand_core 0.3.1", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "rdrand" -version = "0.4.0" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "rand_core 0.3.1", + "futures-core", + "futures-sink", ] [[package]] -name = "regex" -version = "0.2.11" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "aho-corasick", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", "memchr", - "regex-syntax", - "thread_local", - "utf8-ranges", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "regex-syntax" -version = "0.5.6" +name = "fxprof-processed-profile" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" dependencies = [ - "ucd-util", + "bitflags 2.10.0", + "debugid", + "rustc-hash", + "serde", + "serde_derive", + "serde_json", ] [[package]] -name = "rusqlite" -version = "0.37.0" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "bitflags 2.10.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", - "uuid", + "typenum", + "version_check", ] [[package]] -name = "rustc_version" -version = "0.2.3" +name = "getopts" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ - "semver", + "unicode-width", ] [[package]] -name = "rustfmt" -version = "0.10.0" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec940eed814db0fb7ab928c5f5025f97dc55d1c0e345e39dda2ce9f945557500" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "diff", - "env_logger", - "getopts", - "kernel32-sys", + "cfg-if", "libc", - "log 0.3.9", - "regex", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", "serde", - "serde_derive", - "serde_json", - "strings", - "syntex_errors", - "syntex_syntax", - "term", - "toml", - "unicode-segmentation", - "winapi 0.2.8", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log 0.4.29", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log 0.4.29", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.29", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.2", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg 1.5.0", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulley-interpreter" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a09eb45f768f3a0396e85822790d867000c8b5f11551e7268c279e991457b16" +dependencies = [ + "cranelift-bitset", + "log 0.4.29", + "pulley-macros", + "wasmtime-internal-math", +] + +[[package]] +name = "pulley-macros" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29368432b8b7a8a343b75a6914621fad905c95d5c5297449a6546c127224f7a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.8", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.8", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regalloc2" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e249c660440317032a71ddac302f25f1d5dff387667bcc3978d1f77aa31ac34" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log 0.4.29", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "regex" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", + "utf8-ranges", +] + +[[package]] +name = "regex-syntax" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +dependencies = [ + "ucd-util", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags 2.10.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "uuid", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustfmt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec940eed814db0fb7ab928c5f5025f97dc55d1c0e345e39dda2ce9f945557500" +dependencies = [ + "diff", + "env_logger", + "getopts", + "kernel32-sys", + "libc", + "log 0.3.9", + "regex", + "serde", + "serde_derive", + "serde_json", + "strings", + "syntex_errors", + "syntex_syntax", + "term", + "toml 0.4.10", + "unicode-segmentation", + "winapi 0.2.8", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strings" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa481ee1bc42fc3df8195f91f7cb43cf8f2b71b48bac40bf5381cfaf7e481f3c" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syntex_errors" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3133289179676c9f5c5b2845bf5a2e127769f4889fcbada43035ef6bd662605e" +dependencies = [ + "libc", + "serde", + "serde_derive", + "syntex_pos", + "term", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syntex_pos" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ab669fa003d208c681f874bbc76d91cc3d32550d16b5d9d2087cf477316470" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "syntex_syntax" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03815b9f04d95828770d9c974aa39c6e1f6ef3114eb77a3ce09008a0d15dd142" +dependencies = [ + "bitflags 0.9.1", + "extprim", + "log 0.3.9", + "serde", + "serde_derive", + "serde_json", + "syntex_errors", + "syntex_pos", + "unicode-xid 0.1.0", +] + +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags 2.10.0", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "term" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tracing" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-compose" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feeb9a231e63bd5d5dfe07e9f8daa53d5c85e4f7de5ef756d3b4e6a5f501c578" +dependencies = [ + "anyhow", + "heck 0.4.1", + "im-rc", + "indexmap", + "log 0.4.29", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", + "wat", +] + +[[package]] +name = "wasm-encoder" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +dependencies = [ + "leb128fmt", + "wasmparser 0.240.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.243.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" +dependencies = [ + "leb128fmt", + "wasmparser 0.243.0", +] + +[[package]] +name = "wasmparser" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap", + "semver 1.0.27", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.243.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +dependencies = [ + "bitflags 2.10.0", + "indexmap", + "semver 1.0.27", +] + +[[package]] +name = "wasmprinter" +version = "0.240.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84d6e25c198da67d0150ee7c2c62d33d784f0a565d1e670bdf1eeccca8158bc" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.240.0", +] + +[[package]] +name = "wasmtime" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511bc19c2d48f338007dc941cb40c833c4707023fdaf9ec9b97cf1d5a62d26bb" +dependencies = [ + "addr2line", + "anyhow", + "async-trait", + "bitflags 2.10.0", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "futures", + "fxprof-processed-profile", + "gimli", + "hashbrown 0.15.5", + "indexmap", + "ittapi", + "libc", + "log 0.4.29", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix 1.1.2", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "tempfile", + "wasm-compose", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", + "wasmtime-environ", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-math", + "wasmtime-internal-slab", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "wat", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-environ" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b0d53657fea2a8cee8ed1866ad45d2e5bc21be958a626a1dd9b7de589851b3" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap", + "log 0.4.29", + "object", + "postcard", + "rustc-demangle", + "semver 1.0.27", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.240.0", + "wasmparser 0.240.0", + "wasmprinter", + "wasmtime-internal-component-util", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e065628d2a6eccb722de71c6d9b58771f5c3c4f9d35f6cb6d9d92370f4c2b4" +dependencies = [ + "anyhow", + "base64", + "directories-next", + "log 0.4.29", + "postcard", + "rustix 1.1.2", + "serde", + "serde_derive", + "sha2", + "toml 0.9.8", + "windows-sys 0.60.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c933104f57d27dd1e6c7bd9ee5df3242bdd1962d9381bc08fa5d4e60e1f5ebdf" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ef2a95a5dbaa70fc3ef682ea8997e51cdd819b4d157a1100477cf43949d454" + +[[package]] +name = "wasmtime-internal-cranelift" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73122df6a8cf417ce486a94e844d3a60797217ce7ae69653e0ee9e28269e0fa5" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools", + "log 0.4.29", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.17", + "wasmparser 0.240.0", + "wasmtime-environ", + "wasmtime-internal-math", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ead059e58b54a7abbe0bfb9457b3833ebd2ad84326c248a835ff76d64c7c6f" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "libc", + "rustix 1.1.2", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af620a4ac1623298c90d3736644e12d66974951d1e38d0464798de85c984e17" +dependencies = [ + "cc", + "object", + "rustix 1.1.2", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ccd36e25390258ce6720add639ffe5a7d81a5c904350aa08f5bbc60433d22" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-internal-math" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1b856e1bbf0230ab560ba4204e944b141971adc4e6cdf3feb6979c1a7b7953" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmtime-internal-slab" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8908e71a780b97cbd3d8f3a0c446ac8df963069e0f3f38c9eace4f199d4d3e65" + +[[package]] +name = "wasmtime-internal-unwinder" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb9c2f8223a0ef96527f0446b80c7d0d9bb0577c7b918e3104bd6d4cdba1d101" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "log 0.4.29", + "object", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0fb82cdbffd6cafc812c734a22fa753102888b8760ecf6a08cbb50367a458a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1cfd68149cef86afd9a6c9b51e461266dfa66b37b4c6fdf1201ddbf7f906271" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "log 0.4.29", + "object", + "target-lexicon", + "wasmparser 0.240.0", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a628437073400148f1ba2b55beb60eb376dc5ca538745994c83332b037d1f3fa" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "heck 0.5.0", + "indexmap", + "wit-parser", +] + +[[package]] +name = "wasmtime-wasi" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517604b1ce13a56ae3e360217095d7d4db90e84deaa3fba078877c2b80cc5851" +dependencies = [ + "anyhow", + "async-trait", + "bitflags 2.10.0", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "rustix 1.1.2", + "system-interface", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.60.2", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec66fc94ceb9497d62a3d082bd2cce10348975795516553df4cd89f7d5fc14b" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "futures", + "wasmtime", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "243.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.243.0", +] + +[[package]] +name = "wat" +version = "1.243.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226a9a91cd80a50449312fef0c75c23478fcecfcc4092bdebe1dc8e760ef521b" +dependencies = [ + "wast 243.0.0", +] + +[[package]] +name = "wiggle" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9c745158119785cf3098c97151cfcc33104ade6489bfa158b73d3f5979fa24" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "thiserror 2.0.17", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a98d02cd1ba87ca6039f28f4f4c0b53a9ff2684f5f2640f471af9bc608b9d9" +dependencies = [ + "anyhow", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "39.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a111938ed6e662d5f5036bb3cac8d10d5bea77a536885d6d4a4667c9cba97a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] -name = "ryu" -version = "1.0.20" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "semver" -version = "0.9.0" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "semver-parser", + "windows-sys 0.61.2", ] [[package]] -name = "semver-parser" -version = "0.7.0" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "serde" -version = "1.0.228" +name = "winch-codegen" +version = "39.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "b1de5a648102e39c8e817ed25e3820f4b9772f3c9c930984f32737be60e3156b" dependencies = [ - "serde_core", - "serde_derive", + "anyhow", + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "thiserror 2.0.17", + "wasmparser 0.240.0", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "wasmtime-internal-math", ] [[package]] -name = "serde_core" -version = "1.0.228" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "serde_derive", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -533,266 +2796,375 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.145" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "smallvec" -version = "1.15.1" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "strings" -version = "0.1.1" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa481ee1bc42fc3df8195f91f7cb43cf8f2b71b48bac40bf5381cfaf7e481f3c" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "log 0.3.9", + "windows-link", ] [[package]] -name = "syn" -version = "2.0.111" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "windows-link", ] [[package]] -name = "syntex_errors" -version = "0.59.1" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3133289179676c9f5c5b2845bf5a2e127769f4889fcbada43035ef6bd662605e" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "libc", - "serde", - "serde_derive", - "syntex_pos", - "term", - "unicode-xid", + "windows-targets 0.52.6", ] [[package]] -name = "syntex_pos" -version = "0.59.1" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ab669fa003d208c681f874bbc76d91cc3d32550d16b5d9d2087cf477316470" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "serde", - "serde_derive", + "windows-targets 0.53.5", ] [[package]] -name = "syntex_syntax" -version = "0.59.1" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03815b9f04d95828770d9c974aa39c6e1f6ef3114eb77a3ce09008a0d15dd142" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "bitflags 0.9.1", - "extprim", - "log 0.3.9", - "serde", - "serde_derive", - "serde_json", - "syntex_errors", - "syntex_pos", - "unicode-xid", + "windows-link", ] [[package]] -name = "term" -version = "0.4.6" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "kernel32-sys", - "winapi 0.2.8", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "thread_local" -version = "0.3.6" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "lazy_static", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] -name = "tokio" -version = "1.48.0" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" -dependencies = [ - "pin-project-lite", -] +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "toml" -version = "0.4.10" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -dependencies = [ - "serde", -] +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] -name = "ucd-util" -version = "0.1.10" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] -name = "unicode-segmentation" -version = "1.12.0" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "unicode-width" -version = "0.2.2" +name = "windows_i686_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "utf8-ranges" -version = "1.0.5" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] -name = "uuid" -version = "1.19.0" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ - "getrandom", - "js-sys", - "serde_core", - "wasm-bindgen", + "bitflags 2.10.0", + "windows-sys 0.59.0", ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" +name = "wit-parser" +version = "0.240.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9875ea3fa272f57cc1fc50f225a7b94021a7878c484b33792bccad0d93223439" dependencies = [ - "wit-bindgen", + "anyhow", + "id-arena", + "indexmap", + "log 0.4.29", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid 0.2.6", + "wasmparser 0.240.0", ] [[package]] -name = "wasm-bindgen" -version = "0.2.106" +name = "witx" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "anyhow", + "log 0.4.29", + "thiserror 1.0.69", + "wast 35.0.2", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.106" +name = "writeable" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ + "proc-macro2", "quote", - "wasm-bindgen-macro-support", + "syn", + "synstructure", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.106" +name = "zerocopy" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ - "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.106" +name = "zerofrom" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ - "unicode-ident", + "zerofrom-derive", ] [[package]] -name = "winapi" -version = "0.2.8" +name = "zerofrom-derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] -name = "winapi" -version = "0.3.9" +name = "zerotrie" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "displaydoc", + "yoke", + "zerofrom", ] [[package]] -name = "winapi-build" -version = "0.1.1" +name = "zerovec" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "zerovec-derive" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "zstd" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] [[package]] -name = "wit-bindgen" -version = "0.46.0" +name = "zstd-safe" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index b4d306f..699f052 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -4,11 +4,15 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0" rusqlite = { version = "0.37.0", features = ["uuid"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" tokio = "1.48.0" uuid = { version = "1.19.0", features = ["v4", "serde"] } +wasmtime = { version = "39.0.1", features = ["component-model", "async"] } +wasmtime-wasi = "39.0.1" + [dev-dependencies] rustfmt = "0.10.0" diff --git a/crates/capsule-core/src/wasm/commands/create.rs b/crates/capsule-core/src/wasm/commands/create.rs new file mode 100644 index 0000000..3e80ab1 --- /dev/null +++ b/crates/capsule-core/src/wasm/commands/create.rs @@ -0,0 +1,111 @@ +use wasmtime::component::{Component, Linker, ResourceTable}; +use wasmtime::{Store, StoreLimitsBuilder}; +use wasmtime_wasi::WasiCtxBuilder; +use wasmtime_wasi::p2::add_to_linker_async; + +use crate::config::log::{CreateInstanceLog, InstanceState}; +use crate::wasm::execution_policy::ExecutionPolicy; +use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; +use crate::wasm::state::WasmState; + +pub struct CreateInstance { + policy: ExecutionPolicy, + args: Vec, + task_id: String, + task_name: String, + agent_name: String, + agent_version: String, +} + +impl CreateInstance { + pub fn new(policy: ExecutionPolicy, args: Vec) -> Self { + Self { + policy, + args, + task_id: "default_task".to_string(), + task_name: "default_task_name".to_string(), + agent_name: "default_agent".to_string(), + agent_version: "0.0.0".to_string(), + } + } + + pub fn task_id(mut self, task_id: impl Into) -> Self { + self.task_id = task_id.into(); + self + } + + pub fn task_name(mut self, task_name: impl Into) -> Self { + self.task_name = task_name.into(); + self + } + + pub fn agent_name(mut self, agent_name: impl Into) -> Self { + self.agent_name = agent_name.into(); + self + } + + pub fn agent_version(mut self, agent_version: impl Into) -> Self { + self.agent_version = agent_version.into(); + self + } +} + +impl RuntimeCommand for CreateInstance { + type Output = bool; + + async fn execute(self, runtime: &Runtime) -> Result { + runtime + .log + .commit_log(CreateInstanceLog { + agent_name: self.agent_name, + agent_version: self.agent_version, + task_id: self.task_id.clone(), + task_name: self.task_name, + state: InstanceState::Created, + fuel_limit: self.policy.compute.as_fuel(), + fuel_consumed: 0, + }) + .await?; + + let mut linker = Linker::::new(&runtime.engine); + + add_to_linker_async(&mut linker)?; + + let wasi = WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .args(&self.args) + .envs(&self.policy.env_vars.unwrap_or_default()) + .build(); + + let mut limits = StoreLimitsBuilder::new(); + + if let Some(ram_bytes) = self.policy.ram { + limits = limits.memory_size(ram_bytes as usize); + } + + let limits = limits.build(); + + let state = WasmState { + ctx: wasi, + table: ResourceTable::new(), + limits, + }; + + let mut store = Store::new(&runtime.engine, state); + + store.set_fuel(self.policy.compute.as_fuel())?; + + store.limiter(|state| state); + + let component = Component::from_file(&runtime.engine, ".capsule/capsule.wasm")?; + + let instance = linker.instantiate_async(&mut store, &component).await?; + + let run_func = instance.get_typed_func::<(), ()>(&mut store, "wasi:cli/run@0.2.9#run")?; + + run_func.call_async(&mut store, ()).await?; + + Ok(true) + } +} diff --git a/crates/capsule-core/src/wasm/commands/mod.rs b/crates/capsule-core/src/wasm/commands/mod.rs new file mode 100644 index 0000000..c5fb369 --- /dev/null +++ b/crates/capsule-core/src/wasm/commands/mod.rs @@ -0,0 +1 @@ +pub mod create; diff --git a/crates/capsule-core/src/wasm/mod.rs b/crates/capsule-core/src/wasm/mod.rs index 661e75d..cb2c11a 100644 --- a/crates/capsule-core/src/wasm/mod.rs +++ b/crates/capsule-core/src/wasm/mod.rs @@ -1,2 +1,4 @@ +pub mod commands; pub mod execution_policy; pub mod runtime; +pub mod state; diff --git a/crates/capsule-core/src/wasm/runtime.rs b/crates/capsule-core/src/wasm/runtime.rs index 0d36145..786c545 100644 --- a/crates/capsule-core/src/wasm/runtime.rs +++ b/crates/capsule-core/src/wasm/runtime.rs @@ -1,3 +1,67 @@ -pub struct Runtime {} +use std::fmt; -impl Runtime {} +use wasmtime::{Config, Engine}; + +use crate::config::log::{Log, LogError}; + +#[derive(Debug)] +pub enum WasmRuntimeError { + WasmtimeError(wasmtime::Error), + LogError(LogError), +} + +impl fmt::Display for WasmRuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + WasmRuntimeError::WasmtimeError(msg) => { + write!(f, "Runtime error > Wasmtime error > {}", msg) + } + WasmRuntimeError::LogError(msg) => write!(f, "Runtime error > {}", msg), + } + } +} + +impl From for WasmRuntimeError { + fn from(err: wasmtime::Error) -> Self { + WasmRuntimeError::WasmtimeError(err) + } +} + +impl From for WasmRuntimeError { + fn from(err: LogError) -> Self { + WasmRuntimeError::LogError(err) + } +} + +pub trait RuntimeCommand { + type Output; + fn execute(self, runtime: &Runtime) -> impl Future> + Send; +} + +pub struct Runtime { + pub(crate) engine: Engine, + pub(crate) log: Log, +} + +impl Runtime { + pub fn new() -> Result { + let mut config = Config::new(); + let log = Log::new(Some(".capsule"), "state.db-wal")?; + + config.wasm_component_model(true); + config.async_support(true); + config.consume_fuel(true); + + Ok(Self { + engine: Engine::new(&config)?, + log, + }) + } + + pub async fn execute( + &self, + command: C, + ) -> Result { + command.execute(self).await + } +} diff --git a/crates/capsule-core/src/wasm/state.rs b/crates/capsule-core/src/wasm/state.rs new file mode 100644 index 0000000..7868bfd --- /dev/null +++ b/crates/capsule-core/src/wasm/state.rs @@ -0,0 +1,39 @@ +use wasmtime::component::ResourceTable; +use wasmtime::{ResourceLimiter, StoreLimits}; +use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; +use anyhow::Result; + +pub struct WasmState { + pub ctx: WasiCtx, + pub table: ResourceTable, + pub limits: StoreLimits, +} + +impl WasiView for WasmState { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.ctx, + table: &mut self.table, + } + } +} + +impl ResourceLimiter for WasmState { + fn memory_growing( + &mut self, + current: usize, + desired: usize, + maximum: Option, + ) -> Result { + self.limits.memory_growing(current, desired, maximum) + } + + fn table_growing( + &mut self, + current: usize, + desired: usize, + maximum: Option, + ) -> Result { + self.limits.table_growing(current, desired, maximum) + } +} From 5dbe86f8b781387a1a8d8caeaf6e132e0f977d59 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Sun, 7 Dec 2025 14:35:07 +0100 Subject: [PATCH 14/22] add start command for wasm runtime --- crates/capsule-core/Cargo.lock | 14 ++-- crates/capsule-core/Cargo.toml | 4 +- crates/capsule-core/src/config/log.rs | 38 +++++------ .../capsule-core/src/wasm/commands/create.rs | 50 +++++++------- crates/capsule-core/src/wasm/commands/mod.rs | 1 + .../capsule-core/src/wasm/commands/start.rs | 65 +++++++++++++++++++ 6 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 crates/capsule-core/src/wasm/commands/start.rs diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock index e4e0fe6..34780b0 100644 --- a/crates/capsule-core/Cargo.lock +++ b/crates/capsule-core/Cargo.lock @@ -219,12 +219,12 @@ name = "capsule-core" version = "0.1.0" dependencies = [ "anyhow", + "nanoid", "rusqlite", "rustfmt", "serde", "serde_json", "tokio", - "uuid", "wasmtime", "wasmtime-wasi", ] @@ -1205,6 +1205,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1576,7 +1585,6 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", - "uuid", ] [[package]] @@ -2177,9 +2185,7 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ - "getrandom 0.3.4", "js-sys", - "serde_core", "wasm-bindgen", ] diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index 699f052..c7c4a2d 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -5,11 +5,11 @@ edition = "2024" [dependencies] anyhow = "1.0" -rusqlite = { version = "0.37.0", features = ["uuid"] } +nanoid = "0.4.0" +rusqlite = "0.37.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" tokio = "1.48.0" -uuid = { version = "1.19.0", features = ["v4", "serde"] } wasmtime = { version = "39.0.1", features = ["component-model", "async"] } wasmtime-wasi = "39.0.1" diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index ebd0d46..9c1c1dd 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -1,9 +1,10 @@ -use serde::{Deserialize, Serialize}; use std::fmt; use std::sync::mpsc; use std::thread::{Builder, JoinHandle}; + +use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; -use uuid::Uuid; +use nanoid::nanoid; use crate::config::database::{Database, DatabaseError}; @@ -66,7 +67,7 @@ impl fmt::Display for InstanceState { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceLog { - pub id: Uuid, + pub id: String, pub agent_name: String, pub agent_version: String, pub task_id: String, @@ -194,7 +195,7 @@ impl Log { db.execute( "INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), &log.agent_name, &log.agent_version, &log.task_id, @@ -245,7 +246,7 @@ impl Log { let id_str: String = row.get(0)?; let state_str: String = row.get(5)?; Ok(InstanceLog { - id: Uuid::parse_str(&id_str).map_err(|_| DatabaseError::InvalidQuery("Invalid UUID".to_string()))?, + id: id_str, agent_name: row.get(1)?, agent_version: row.get(2)?, task_id: row.get(3)?, @@ -376,7 +377,7 @@ mod tests { let conn = log.db.conn.lock().unwrap(); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), &"test_agent".to_string(), &"1.0.0".to_string(), &"test_task_123".to_string(), @@ -431,7 +432,7 @@ mod tests { let conn = log.db.conn.lock().unwrap(); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_to_delete", @@ -444,7 +445,7 @@ mod tests { ]).expect("Failed to insert first test log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_to_delete", @@ -457,7 +458,7 @@ mod tests { ]).expect("Failed to insert second test log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "other_agent", "2.0.0", "task_to_keep", @@ -533,7 +534,7 @@ mod tests { let conn = log.db.conn.lock().unwrap(); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_created", @@ -546,7 +547,7 @@ mod tests { ]).expect("Failed to insert created log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_running", @@ -559,7 +560,7 @@ mod tests { ]).expect("Failed to insert running log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_completed", @@ -572,7 +573,7 @@ mod tests { ]).expect("Failed to insert completed log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_failed", @@ -585,7 +586,7 @@ mod tests { ]).expect("Failed to insert failed log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "task_interrupted", @@ -692,7 +693,7 @@ mod tests { let conn = log.db.conn.lock().unwrap(); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "test_task_123", @@ -705,7 +706,7 @@ mod tests { ]).expect("Failed to insert first test log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "test_task_123", @@ -718,7 +719,7 @@ mod tests { ]).expect("Failed to insert second test log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "test_agent", "1.0.0", "test_task_123", @@ -731,7 +732,7 @@ mod tests { ]).expect("Failed to insert third test log"); conn.execute("INSERT INTO instance_log (id, agent_name, agent_version, task_id, task_name, state, fuel_limit, fuel_consumed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - &Uuid::new_v4().to_string(), + &nanoid!(10), "other_agent", "2.0.0", "other_task_456", @@ -746,7 +747,6 @@ mod tests { let logs = log.get_logs().expect("Failed to get logs"); - // get_logs now returns ALL logs, so we expect 4 total (3 for test_task_123 + 1 for other_task_456) assert_eq!(logs.len(), 4, "Expected 4 total logs"); assert_eq!( diff --git a/crates/capsule-core/src/wasm/commands/create.rs b/crates/capsule-core/src/wasm/commands/create.rs index 3e80ab1..6757823 100644 --- a/crates/capsule-core/src/wasm/commands/create.rs +++ b/crates/capsule-core/src/wasm/commands/create.rs @@ -1,20 +1,22 @@ -use wasmtime::component::{Component, Linker, ResourceTable}; +use nanoid::nanoid; + +use wasmtime::component::{Component, Linker, ResourceTable, Instance}; use wasmtime::{Store, StoreLimitsBuilder}; use wasmtime_wasi::WasiCtxBuilder; use wasmtime_wasi::p2::add_to_linker_async; -use crate::config::log::{CreateInstanceLog, InstanceState}; +use crate::config::log::{CreateInstanceLog, InstanceState, UpdateInstanceLog}; use crate::wasm::execution_policy::ExecutionPolicy; use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; use crate::wasm::state::WasmState; pub struct CreateInstance { - policy: ExecutionPolicy, - args: Vec, - task_id: String, - task_name: String, - agent_name: String, - agent_version: String, + pub policy: ExecutionPolicy, + pub args: Vec, + pub task_id: String, + pub task_name: String, + pub agent_name: String, + pub agent_version: String, } impl CreateInstance { @@ -22,18 +24,13 @@ impl CreateInstance { Self { policy, args, - task_id: "default_task".to_string(), + task_id: nanoid!(10), task_name: "default_task_name".to_string(), agent_name: "default_agent".to_string(), agent_version: "0.0.0".to_string(), } } - pub fn task_id(mut self, task_id: impl Into) -> Self { - self.task_id = task_id.into(); - self - } - pub fn task_name(mut self, task_name: impl Into) -> Self { self.task_name = task_name.into(); self @@ -51,9 +48,9 @@ impl CreateInstance { } impl RuntimeCommand for CreateInstance { - type Output = bool; + type Output = (Store, Instance); - async fn execute(self, runtime: &Runtime) -> Result { + async fn execute(self, runtime: &Runtime) -> Result<(Store, Instance), WasmRuntimeError> { runtime .log .commit_log(CreateInstanceLog { @@ -100,12 +97,21 @@ impl RuntimeCommand for CreateInstance { let component = Component::from_file(&runtime.engine, ".capsule/capsule.wasm")?; - let instance = linker.instantiate_async(&mut store, &component).await?; - - let run_func = instance.get_typed_func::<(), ()>(&mut store, "wasi:cli/run@0.2.9#run")?; - - run_func.call_async(&mut store, ()).await?; + let instance = match linker.instantiate_async(&mut store, &component).await { + Ok(instance) => instance, + Err(e) => { + runtime + .log + .update_log(UpdateInstanceLog { + task_id: self.task_id, + state: InstanceState::Failed, + fuel_consumed: 0, + }) + .await?; + return Err(WasmRuntimeError::WasmtimeError(e)); + } + }; - Ok(true) + Ok((store, instance)) } } diff --git a/crates/capsule-core/src/wasm/commands/mod.rs b/crates/capsule-core/src/wasm/commands/mod.rs index c5fb369..7e32904 100644 --- a/crates/capsule-core/src/wasm/commands/mod.rs +++ b/crates/capsule-core/src/wasm/commands/mod.rs @@ -1 +1,2 @@ pub mod create; +pub mod start; diff --git a/crates/capsule-core/src/wasm/commands/start.rs b/crates/capsule-core/src/wasm/commands/start.rs new file mode 100644 index 0000000..781bd1d --- /dev/null +++ b/crates/capsule-core/src/wasm/commands/start.rs @@ -0,0 +1,65 @@ +use wasmtime::component::Instance; +use wasmtime::Store; + +use crate::config::log::{InstanceState, UpdateInstanceLog}; +use crate::wasm::execution_policy::ExecutionPolicy; +use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; +use crate::wasm::state::WasmState; + +pub struct StartInstance { + task_id: String, + policy: ExecutionPolicy, + store: Store, + instance: Instance +} + +impl StartInstance { + pub fn new(task_id: String, policy: ExecutionPolicy, store: Store, instance: Instance) -> Self { + Self { + task_id, + policy, + store, + instance + } + } +} + +impl RuntimeCommand for StartInstance { + type Output = Store; + + async fn execute(mut self, runtime: &Runtime) -> Result { + runtime + .log + .update_log(UpdateInstanceLog { + task_id: self.task_id.clone(), + state: InstanceState::Running, + fuel_consumed: self.policy.compute.as_fuel() - self.store.get_fuel().unwrap_or(0), + }) + .await?; + + let run_func = self + .instance + .get_typed_func::<(), ()>(&mut self.store, "wasi:cli/run@0.2.9#run")?; + + let result = run_func.call_async(&mut self.store, ()).await; + + + let output = match result { + Ok(_) => Ok(self.store), + Err(e) => { + runtime + .log + .update_log(UpdateInstanceLog { + task_id: self.task_id, + state: InstanceState::Failed, + fuel_consumed: self.policy.compute.as_fuel() - self.store.get_fuel().unwrap_or(0), + }) + .await?; + + Err(WasmRuntimeError::WasmtimeError(e) + )}, + }; + + output + } +} From c8474ab62b97ec51f73e141b7ee9cb93e10959a5 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 02:19:36 +0100 Subject: [PATCH 15/22] add compiler --- crates/capsule-core/src/lib.rs | 1 + .../capsule-core/src/wasm/commands/create.rs | 5 +- crates/capsule-core/src/wasm/compiler/mod.rs | 1 + .../capsule-core/src/wasm/compiler/python.rs | 82 +++++++++++++++++++ .../capsule-core/src/wasm/execution_policy.rs | 6 +- crates/capsule-core/src/wasm/mod.rs | 1 + crates/capsule-core/src/wasm/runtime.rs | 1 - 7 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 crates/capsule-core/src/wasm/compiler/mod.rs create mode 100644 crates/capsule-core/src/wasm/compiler/python.rs diff --git a/crates/capsule-core/src/lib.rs b/crates/capsule-core/src/lib.rs index 96b08b8..88a149c 100644 --- a/crates/capsule-core/src/lib.rs +++ b/crates/capsule-core/src/lib.rs @@ -1,2 +1,3 @@ pub mod config; pub mod wasm; + diff --git a/crates/capsule-core/src/wasm/commands/create.rs b/crates/capsule-core/src/wasm/commands/create.rs index 6757823..4d1acf5 100644 --- a/crates/capsule-core/src/wasm/commands/create.rs +++ b/crates/capsule-core/src/wasm/commands/create.rs @@ -10,6 +10,7 @@ use crate::wasm::execution_policy::ExecutionPolicy; use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; use crate::wasm::state::WasmState; + pub struct CreateInstance { pub policy: ExecutionPolicy, pub args: Vec, @@ -25,8 +26,8 @@ impl CreateInstance { policy, args, task_id: nanoid!(10), - task_name: "default_task_name".to_string(), - agent_name: "default_agent".to_string(), + task_name: "default".to_string(), + agent_name: "default".to_string(), agent_version: "0.0.0".to_string(), } } diff --git a/crates/capsule-core/src/wasm/compiler/mod.rs b/crates/capsule-core/src/wasm/compiler/mod.rs new file mode 100644 index 0000000..c0a088c --- /dev/null +++ b/crates/capsule-core/src/wasm/compiler/mod.rs @@ -0,0 +1 @@ +pub mod python; diff --git a/crates/capsule-core/src/wasm/compiler/python.rs b/crates/capsule-core/src/wasm/compiler/python.rs new file mode 100644 index 0000000..c3387fd --- /dev/null +++ b/crates/capsule-core/src/wasm/compiler/python.rs @@ -0,0 +1,82 @@ +use std::fs; +use std::fmt; +use std::path::Path; +use std::process::Command; + + +pub enum PythonWasmCompilerError { + CompileFailed(String) +} + +impl fmt::Display for PythonWasmCompilerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PythonWasmCompilerError::CompileFailed(msg) => write!(f, "Compilation failed: {}", msg), + } + } +} + +impl From for PythonWasmCompilerError { + fn from(err: std::io::Error) -> Self { + PythonWasmCompilerError::CompileFailed(err.to_string()) + } +} + +pub struct PythonWasmCompiler { + pub cache_dir: String, + pub output_wasm: String, +} + +impl PythonWasmCompiler { + + pub fn new() -> Result { + + let cache_dir = ".capsule".to_string(); + let output_wasm = format!("{}/capsule.wasm", &cache_dir); + + if !Path::new(&cache_dir).exists() { + fs::create_dir_all(&cache_dir)?; + } + + Ok(Self { + cache_dir, + output_wasm, + }) + } + + pub fn compile_wasm(&self, source: &str) -> Result<(), PythonWasmCompilerError> { + + if !Path::new(&self.cache_dir).exists() { + fs::create_dir_all(&self.cache_dir)?; + } + + if self.needs_rebuild(source, &self.output_wasm) { + let status = Command::new("componentize-py") + .arg("-d") + .arg(".") + .arg("-m") + .arg(source) + .arg("-o") + .arg(&self.output_wasm) + .status()?; + + if !status.success() { + return Err(PythonWasmCompilerError::CompileFailed("Compilation failed".to_string())); + } + } + + Ok(()) + } + + fn needs_rebuild(&self, source: &str, wasm_path: &str) -> bool { + let source_time = fs::metadata(source).and_then(|m| m.modified()).unwrap(); + let wasm_time = fs::metadata(wasm_path).and_then(|m| m.modified()).unwrap(); + + if source_time > wasm_time { + return true; + } + + false + } + +} diff --git a/crates/capsule-core/src/wasm/execution_policy.rs b/crates/capsule-core/src/wasm/execution_policy.rs index 3bd150e..b1687b7 100644 --- a/crates/capsule-core/src/wasm/execution_policy.rs +++ b/crates/capsule-core/src/wasm/execution_policy.rs @@ -24,7 +24,7 @@ impl Compute { } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct ExecutionPolicy { pub name: String, pub compute: Compute, @@ -38,7 +38,7 @@ impl Default for ExecutionPolicy { fn default() -> Self { Self { name: "default".to_string(), - compute: Compute::Low, + compute: Compute::Medium, ram: None, timeout: None, max_retries: 1, @@ -104,7 +104,7 @@ mod tests { .env_vars(None); assert_eq!(policy.name, "test"); - assert_eq!(policy.compute, Compute::Low); + assert_eq!(policy.compute, Compute::Medium); assert_eq!(policy.ram, Some(128)); assert_eq!(policy.timeout, Some("60s".to_string())); assert_eq!(policy.max_retries, 3); diff --git a/crates/capsule-core/src/wasm/mod.rs b/crates/capsule-core/src/wasm/mod.rs index cb2c11a..02218bc 100644 --- a/crates/capsule-core/src/wasm/mod.rs +++ b/crates/capsule-core/src/wasm/mod.rs @@ -2,3 +2,4 @@ pub mod commands; pub mod execution_policy; pub mod runtime; pub mod state; +pub mod compiler; diff --git a/crates/capsule-core/src/wasm/runtime.rs b/crates/capsule-core/src/wasm/runtime.rs index 786c545..98d9344 100644 --- a/crates/capsule-core/src/wasm/runtime.rs +++ b/crates/capsule-core/src/wasm/runtime.rs @@ -4,7 +4,6 @@ use wasmtime::{Config, Engine}; use crate::config::log::{Log, LogError}; -#[derive(Debug)] pub enum WasmRuntimeError { WasmtimeError(wasmtime::Error), LogError(LogError), From b40d0c3182956ff08bdc6aba74a652dee94b98f3 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 11:39:12 +0100 Subject: [PATCH 16/22] wording: rename start to run --- crates/capsule-core/src/wasm/commands/mod.rs | 2 +- crates/capsule-core/src/wasm/commands/{start.rs => run.rs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename crates/capsule-core/src/wasm/commands/{start.rs => run.rs} (95%) diff --git a/crates/capsule-core/src/wasm/commands/mod.rs b/crates/capsule-core/src/wasm/commands/mod.rs index 7e32904..b038f66 100644 --- a/crates/capsule-core/src/wasm/commands/mod.rs +++ b/crates/capsule-core/src/wasm/commands/mod.rs @@ -1,2 +1,2 @@ pub mod create; -pub mod start; +pub mod run; diff --git a/crates/capsule-core/src/wasm/commands/start.rs b/crates/capsule-core/src/wasm/commands/run.rs similarity index 95% rename from crates/capsule-core/src/wasm/commands/start.rs rename to crates/capsule-core/src/wasm/commands/run.rs index 781bd1d..1b2d545 100644 --- a/crates/capsule-core/src/wasm/commands/start.rs +++ b/crates/capsule-core/src/wasm/commands/run.rs @@ -6,14 +6,14 @@ use crate::wasm::execution_policy::ExecutionPolicy; use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; use crate::wasm::state::WasmState; -pub struct StartInstance { +pub struct RunInstance { task_id: String, policy: ExecutionPolicy, store: Store, instance: Instance } -impl StartInstance { +impl RunInstance { pub fn new(task_id: String, policy: ExecutionPolicy, store: Store, instance: Instance) -> Self { Self { task_id, @@ -24,7 +24,7 @@ impl StartInstance { } } -impl RuntimeCommand for StartInstance { +impl RuntimeCommand for RunInstance { type Output = Store; async fn execute(mut self, runtime: &Runtime) -> Result { From 916d159b9b32886ded47aaaf659643accfb6458f Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 21:56:06 +0100 Subject: [PATCH 17/22] add state manage, change run and create command paradigm --- crates/capsule-core/Cargo.lock | 743 ++++++++---------- crates/capsule-core/Cargo.toml | 12 +- crates/capsule-core/src/config/log.rs | 2 +- crates/capsule-core/src/lib.rs | 1 - .../capsule-core/src/wasm/commands/create.rs | 28 +- crates/capsule-core/src/wasm/commands/run.rs | 72 +- .../capsule-core/src/wasm/compiler/python.rs | 13 +- crates/capsule-core/src/wasm/mod.rs | 3 +- crates/capsule-core/src/wasm/runtime.rs | 21 +- crates/capsule-core/src/wasm/state.rs | 82 +- crates/capsule-core/src/wasm/utilities/mod.rs | 1 + .../src/wasm/utilities/task_config.rs | 153 ++++ 12 files changed, 657 insertions(+), 474 deletions(-) create mode 100644 crates/capsule-core/src/wasm/utilities/mod.rs create mode 100644 crates/capsule-core/src/wasm/utilities/task_config.rs diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock index 34780b0..d160849 100644 --- a/crates/capsule-core/Cargo.lock +++ b/crates/capsule-core/Cargo.lock @@ -4,13 +4,25 @@ version = 4 [[package]] name = "addr2line" -version = "0.25.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "0.6.10" @@ -47,6 +59,15 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object 0.32.2", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -81,9 +102,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" -version = "0.22.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -103,15 +124,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -130,6 +142,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.0" @@ -289,38 +307,20 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-assembler-x64" -version = "0.126.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054f4aef4d614d37f27d5b77e36e165f0b27a71563be348e7c9fcfac41eed8" -dependencies = [ - "cranelift-assembler-x64-meta", -] - -[[package]] -name = "cranelift-assembler-x64-meta" -version = "0.126.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beab56413879d4f515e08bcf118b1cb85f294129bb117057f573d37bfbb925a" -dependencies = [ - "cranelift-srcgen", -] - [[package]] name = "cranelift-bforest" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d054747549a69b264d5299c8ca1b0dd45dc6bd0ee43f1edfcc42a8b12952c7a" +checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b92d481b77a7dc9d07c96e24a16f29e0c9c27d042828fdf7e49e54ee9819bf" +checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" dependencies = [ "serde", "serde_derive", @@ -328,12 +328,11 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eeccfc043d599b0ef1806942707fc51cdd1c3965c343956dc975a55d82a920f" +checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" dependencies = [ "bumpalo", - "cranelift-assembler-x64", "cranelift-bforest", "cranelift-bitset", "cranelift-codegen-meta", @@ -342,50 +341,44 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli", - "hashbrown 0.15.5", + "hashbrown 0.14.5", "log 0.4.29", - "pulley-interpreter", "regalloc2", "rustc-hash", "serde", "smallvec", "target-lexicon", - "wasmtime-internal-math", ] [[package]] name = "cranelift-codegen-meta" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1174cdb9d9d43b2bdaa612a07ed82af13db9b95526bc2c286c2aec4689bcc038" +checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" dependencies = [ - "cranelift-assembler-x64-meta", "cranelift-codegen-shared", - "cranelift-srcgen", - "heck 0.5.0", - "pulley-interpreter", ] [[package]] name = "cranelift-codegen-shared" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d572be73fae802eb115f45e7e67a9ed16acb4ee683b67c4086768786545419a" +checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" [[package]] name = "cranelift-control" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1587465cc84c5cc793b44add928771945f3132bbf6b3621ee9473c631a87156" +checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063b83448b1343e79282c3c7cbda7ed5f0816f0b763a4c15f7cecb0a17d87ea6" +checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" dependencies = [ "cranelift-bitset", "serde", @@ -394,9 +387,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4461c2d2ca48bc72883f5f5c3129d9aefac832df1db824af9db8db3efee109" +checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" dependencies = [ "cranelift-codegen", "log 0.4.29", @@ -406,27 +399,21 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd811b25e18f14810d09c504e06098acc1d9dbfa24879bf0d6b6fb44415fc66" +checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" [[package]] name = "cranelift-native" -version = "0.126.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2417046989d8d6367a55bbab2e406a9195d176f4779be4aa484d645887217d37" +checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" dependencies = [ "cranelift-codegen", "libc", "target-lexicon", ] -[[package]] -name = "cranelift-srcgen" -version = "0.126.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d039de901c8d928222b8128e1b9a9ab27b82a7445cb749a871c75d9cb25c57d" - [[package]] name = "crc32fast" version = "1.5.0" @@ -506,6 +493,26 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -606,12 +613,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "fd-lock" version = "4.0.4" @@ -629,12 +630,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "foldhash" version = "0.1.5" @@ -721,28 +716,32 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", ] [[package]] name = "fxprof-processed-profile" -version = "0.8.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" +checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ "bitflags 2.10.0", "debugid", - "rustc-hash", + "fxhash", "serde", - "serde_derive", "serde_json", ] @@ -790,15 +789,24 @@ dependencies = [ [[package]] name = "gimli" -version = "0.32.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator", "indexmap", "stable_deref_trait", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -824,12 +832,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -968,20 +970,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "im-rc" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" -dependencies = [ - "bitmaps", - "rand_core 0.6.4", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", -] - [[package]] name = "indexmap" version = "2.12.1" @@ -1018,9 +1006,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" -version = "0.14.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -1225,9 +1213,18 @@ dependencies = [ [[package]] name = "object" -version = "0.37.3" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", "hashbrown 0.15.5", @@ -1242,20 +1239,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "petgraph" -version = "0.6.5" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" @@ -1315,26 +1308,25 @@ dependencies = [ ] [[package]] -name = "pulley-interpreter" -version = "39.0.1" +name = "psm" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a09eb45f768f3a0396e85822790d867000c8b5f11551e7268c279e991457b16" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" dependencies = [ - "cranelift-bitset", - "log 0.4.29", - "pulley-macros", - "wasmtime-internal-math", + "ar_archive_writer", + "cc", ] [[package]] -name = "pulley-macros" -version = "39.0.1" +name = "pulley-interpreter" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e29368432b8b7a8a343b75a6914621fad905c95d5c5297449a6546c127224f7a" +checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cranelift-bitset", + "log 0.4.29", + "sptr", + "wasmtime-math", ] [[package]] @@ -1488,15 +1480,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rayon" version = "1.11.0" @@ -1539,9 +1522,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.13.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e249c660440317032a71ddac302f25f1d5dff387667bcc3978d1f77aa31ac34" +checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" dependencies = [ "allocator-api2", "bumpalo", @@ -1751,24 +1734,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ - "indexmap", - "itoa", - "ryu", "serde", - "unsafe-libyaml", ] [[package]] @@ -1783,26 +1753,19 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "sized-chunks" -version = "0.6.5" +name = "shellexpand" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" dependencies = [ - "bitmaps", - "typenum", + "dirs", ] [[package]] -name = "slab" -version = "0.4.11" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" @@ -1823,6 +1786,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1923,19 +1892,6 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix 1.1.2", - "windows-sys 0.61.2", -] - [[package]] name = "term" version = "0.4.6" @@ -2039,42 +1995,44 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ - "indexmap", - "serde_core", + "serde", "serde_spanned", "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ - "serde_core", + "serde", ] [[package]] -name = "toml_parser" -version = "1.0.4" +name = "toml_edit" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", "winnow", ] [[package]] -name = "toml_writer" -version = "1.0.4" +name = "toml_write" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tracing" @@ -2107,6 +2065,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.19.0" @@ -2149,12 +2118,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "url" version = "2.5.7" @@ -2261,35 +2224,14 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-compose" -version = "0.240.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeb9a231e63bd5d5dfe07e9f8daa53d5c85e4f7de5ef756d3b4e6a5f501c578" -dependencies = [ - "anyhow", - "heck 0.4.1", - "im-rc", - "indexmap", - "log 0.4.29", - "petgraph", - "serde", - "serde_derive", - "serde_yaml", - "smallvec", - "wasm-encoder 0.240.0", - "wasmparser 0.240.0", - "wat", -] - [[package]] name = "wasm-encoder" -version = "0.240.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" +checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" dependencies = [ - "leb128fmt", - "wasmparser 0.240.0", + "leb128", + "wasmparser 0.221.3", ] [[package]] @@ -2304,9 +2246,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.240.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" dependencies = [ "bitflags 2.10.0", "hashbrown 0.15.5", @@ -2328,20 +2270,20 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.240.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84d6e25c198da67d0150ee7c2c62d33d784f0a565d1e670bdf1eeccca8158bc" +checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.240.0", + "wasmparser 0.221.3", ] [[package]] name = "wasmtime" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511bc19c2d48f338007dc941cb40c833c4707023fdaf9ec9b97cf1d5a62d26bb" +checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" dependencies = [ "addr2line", "anyhow", @@ -2351,122 +2293,105 @@ dependencies = [ "cc", "cfg-if", "encoding_rs", - "futures", "fxprof-processed-profile", "gimli", - "hashbrown 0.15.5", + "hashbrown 0.14.5", "indexmap", "ittapi", "libc", "log 0.4.29", "mach2", "memfd", - "object", + "object 0.36.7", "once_cell", + "paste", "postcard", + "psm", "pulley-interpreter", "rayon", - "rustix 1.1.2", + "rustix 0.38.44", "semver 1.0.27", "serde", "serde_derive", "serde_json", "smallvec", + "sptr", "target-lexicon", - "tempfile", - "wasm-compose", - "wasm-encoder 0.240.0", - "wasmparser 0.240.0", + "trait-variant", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", + "wasmtime-asm-macros", + "wasmtime-cache", + "wasmtime-component-macro", + "wasmtime-component-util", + "wasmtime-cranelift", "wasmtime-environ", - "wasmtime-internal-cache", - "wasmtime-internal-component-macro", - "wasmtime-internal-component-util", - "wasmtime-internal-cranelift", - "wasmtime-internal-fiber", - "wasmtime-internal-jit-debug", - "wasmtime-internal-jit-icache-coherence", - "wasmtime-internal-math", - "wasmtime-internal-slab", - "wasmtime-internal-unwinder", - "wasmtime-internal-versioned-export-macros", - "wasmtime-internal-winch", + "wasmtime-fiber", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-math", + "wasmtime-slab", + "wasmtime-versioned-export-macros", + "wasmtime-winch", "wat", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] -name = "wasmtime-environ" -version = "39.0.1" +name = "wasmtime-asm-macros" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b0d53657fea2a8cee8ed1866ad45d2e5bc21be958a626a1dd9b7de589851b3" +checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" dependencies = [ - "anyhow", - "cpp_demangle", - "cranelift-bitset", - "cranelift-entity", - "gimli", - "indexmap", - "log 0.4.29", - "object", - "postcard", - "rustc-demangle", - "semver 1.0.27", - "serde", - "serde_derive", - "smallvec", - "target-lexicon", - "wasm-encoder 0.240.0", - "wasmparser 0.240.0", - "wasmprinter", - "wasmtime-internal-component-util", + "cfg-if", ] [[package]] -name = "wasmtime-internal-cache" -version = "39.0.1" +name = "wasmtime-cache" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e065628d2a6eccb722de71c6d9b58771f5c3c4f9d35f6cb6d9d92370f4c2b4" +checksum = "8b1161c8f62880deea07358bc40cceddc019f1c81d46007bc390710b2fe24ffc" dependencies = [ "anyhow", "base64", "directories-next", "log 0.4.29", "postcard", - "rustix 1.1.2", + "rustix 0.38.44", "serde", "serde_derive", "sha2", - "toml 0.9.8", - "windows-sys 0.60.2", + "toml 0.8.23", + "windows-sys 0.59.0", "zstd", ] [[package]] -name = "wasmtime-internal-component-macro" -version = "39.0.1" +name = "wasmtime-component-macro" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c933104f57d27dd1e6c7bd9ee5df3242bdd1962d9381bc08fa5d4e60e1f5ebdf" +checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" dependencies = [ "anyhow", "proc-macro2", "quote", "syn", - "wasmtime-internal-component-util", - "wasmtime-internal-wit-bindgen", + "wasmtime-component-util", + "wasmtime-wit-bindgen", "wit-parser", ] [[package]] -name = "wasmtime-internal-component-util" -version = "39.0.1" +name = "wasmtime-component-util" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ef2a95a5dbaa70fc3ef682ea8997e51cdd819b4d157a1100477cf43949d454" +checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" [[package]] -name = "wasmtime-internal-cranelift" -version = "39.0.1" +name = "wasmtime-cranelift" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73122df6a8cf417ce486a94e844d3a60797217ce7ae69653e0ee9e28269e0fa5" +checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" dependencies = [ "anyhow", "cfg-if", @@ -2478,132 +2403,111 @@ dependencies = [ "gimli", "itertools", "log 0.4.29", - "object", - "pulley-interpreter", + "object 0.36.7", "smallvec", "target-lexicon", - "thiserror 2.0.17", - "wasmparser 0.240.0", + "thiserror 1.0.69", + "wasmparser 0.221.3", "wasmtime-environ", - "wasmtime-internal-math", - "wasmtime-internal-unwinder", - "wasmtime-internal-versioned-export-macros", + "wasmtime-versioned-export-macros", ] [[package]] -name = "wasmtime-internal-fiber" -version = "39.0.1" +name = "wasmtime-environ" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ead059e58b54a7abbe0bfb9457b3833ebd2ad84326c248a835ff76d64c7c6f" +checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" dependencies = [ "anyhow", - "cc", - "cfg-if", - "libc", - "rustix 1.1.2", - "wasmtime-internal-versioned-export-macros", - "windows-sys 0.60.2", -] - -[[package]] -name = "wasmtime-internal-jit-debug" -version = "39.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af620a4ac1623298c90d3736644e12d66974951d1e38d0464798de85c984e17" -dependencies = [ - "cc", - "object", - "rustix 1.1.2", - "wasmtime-internal-versioned-export-macros", + "cpp_demangle", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap", + "log 0.4.29", + "object 0.36.7", + "postcard", + "rustc-demangle", + "semver 1.0.27", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", + "wasmprinter", + "wasmtime-component-util", ] [[package]] -name = "wasmtime-internal-jit-icache-coherence" -version = "39.0.1" +name = "wasmtime-fiber" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ccd36e25390258ce6720add639ffe5a7d81a5c904350aa08f5bbc60433d22" +checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" dependencies = [ "anyhow", + "cc", "cfg-if", - "libc", - "windows-sys 0.60.2", + "rustix 0.38.44", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", ] [[package]] -name = "wasmtime-internal-math" -version = "39.0.1" +name = "wasmtime-jit-debug" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1b856e1bbf0230ab560ba4204e944b141971adc4e6cdf3feb6979c1a7b7953" +checksum = "3e7b61488a5ee00c35c8c22de707c36c0aecacf419a3be803a6a2ba5e860f56a" dependencies = [ - "libm", + "object 0.36.7", + "rustix 0.38.44", + "wasmtime-versioned-export-macros", ] [[package]] -name = "wasmtime-internal-slab" -version = "39.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8908e71a780b97cbd3d8f3a0c446ac8df963069e0f3f38c9eace4f199d4d3e65" - -[[package]] -name = "wasmtime-internal-unwinder" -version = "39.0.1" +name = "wasmtime-jit-icache-coherence" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9c2f8223a0ef96527f0446b80c7d0d9bb0577c7b918e3104bd6d4cdba1d101" +checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" dependencies = [ "anyhow", "cfg-if", - "cranelift-codegen", - "log 0.4.29", - "object", + "libc", + "windows-sys 0.59.0", ] [[package]] -name = "wasmtime-internal-versioned-export-macros" -version = "39.0.1" +name = "wasmtime-math" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0fb82cdbffd6cafc812c734a22fa753102888b8760ecf6a08cbb50367a458a" +checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" dependencies = [ - "proc-macro2", - "quote", - "syn", + "libm", ] [[package]] -name = "wasmtime-internal-winch" -version = "39.0.1" +name = "wasmtime-slab" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cfd68149cef86afd9a6c9b51e461266dfa66b37b4c6fdf1201ddbf7f906271" -dependencies = [ - "anyhow", - "cranelift-codegen", - "gimli", - "log 0.4.29", - "object", - "target-lexicon", - "wasmparser 0.240.0", - "wasmtime-environ", - "wasmtime-internal-cranelift", - "winch-codegen", -] +checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" [[package]] -name = "wasmtime-internal-wit-bindgen" -version = "39.0.1" +name = "wasmtime-versioned-export-macros" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a628437073400148f1ba2b55beb60eb376dc5ca538745994c83332b037d1f3fa" +checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" dependencies = [ - "anyhow", - "bitflags 2.10.0", - "heck 0.5.0", - "indexmap", - "wit-parser", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "wasmtime-wasi" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517604b1ce13a56ae3e360217095d7d4db90e84deaa3fba078877c2b80cc5851" +checksum = "8d1be69bfcab1bdac74daa7a1f9695ab992b9c8e21b9b061e7d66434097e0ca4" dependencies = [ "anyhow", "async-trait", @@ -2618,29 +2522,45 @@ dependencies = [ "futures", "io-extras", "io-lifetimes", - "rustix 1.1.2", + "rustix 0.38.44", "system-interface", - "thiserror 2.0.17", + "thiserror 1.0.69", "tokio", "tracing", + "trait-variant", "url", "wasmtime", - "wasmtime-wasi-io", "wiggle", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] -name = "wasmtime-wasi-io" -version = "39.0.1" +name = "wasmtime-winch" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec66fc94ceb9497d62a3d082bd2cce10348975795516553df4cd89f7d5fc14b" +checksum = "fdbabfb8f20502d5e1d81092b9ead3682ae59988487aafcd7567387b7a43cf8f" dependencies = [ "anyhow", - "async-trait", - "bytes", - "futures", - "wasmtime", + "cranelift-codegen", + "gimli", + "object 0.36.7", + "target-lexicon", + "wasmparser 0.221.3", + "wasmtime-cranelift", + "wasmtime-environ", + "winch-codegen", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "wit-parser", ] [[package]] @@ -2676,13 +2596,14 @@ dependencies = [ [[package]] name = "wiggle" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9c745158119785cf3098c97151cfcc33104ade6489bfa158b73d3f5979fa24" +checksum = "4b9af35bc9629c52c261465320a9a07959164928b4241980ba1cf923b9e6751d" dependencies = [ "anyhow", + "async-trait", "bitflags 2.10.0", - "thiserror 2.0.17", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle-macro", @@ -2690,23 +2611,24 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a98d02cd1ba87ca6039f28f4f4c0b53a9ff2684f5f2640f471af9bc608b9d9" +checksum = "2cf267dd05673912c8138f4b54acabe6bd53407d9d1536f0fadb6520dd16e101" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "proc-macro2", "quote", + "shellexpand", "syn", "witx", ] [[package]] name = "wiggle-macro" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a111938ed6e662d5f5036bb3cac8d10d5bea77a536885d6d4a4667c9cba97a2" +checksum = "08c5c473d4198e6c2d377f3809f713ff0c110cab88a0805ae099a82119ee250c" dependencies = [ "proc-macro2", "quote", @@ -2759,22 +2681,20 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "39.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de5a648102e39c8e817ed25e3820f4b9772f3c9c930984f32737be60e3156b" +checksum = "2f849ef2c5f46cb0a20af4b4487aaa239846e52e2c03f13fa3c784684552859c" dependencies = [ "anyhow", - "cranelift-assembler-x64", "cranelift-codegen", "gimli", "regalloc2", "smallvec", "target-lexicon", - "thiserror 2.0.17", - "wasmparser 0.240.0", + "thiserror 1.0.69", + "wasmparser 0.221.3", + "wasmtime-cranelift", "wasmtime-environ", - "wasmtime-internal-cranelift", - "wasmtime-internal-math", ] [[package]] @@ -2997,6 +2917,9 @@ name = "winnow" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] [[package]] name = "winx" @@ -3016,9 +2939,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wit-parser" -version = "0.240.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9875ea3fa272f57cc1fc50f225a7b94021a7878c484b33792bccad0d93223439" +checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" dependencies = [ "anyhow", "id-arena", @@ -3029,7 +2952,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid 0.2.6", - "wasmparser 0.240.0", + "wasmparser 0.221.3", ] [[package]] diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index c7c4a2d..cac0188 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -3,16 +3,22 @@ name = "capsule-core" version = "0.1.0" edition = "2024" +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] -anyhow = "1.0" +anyhow = "1" nanoid = "0.4.0" rusqlite = "0.37.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.145" tokio = "1.48.0" -wasmtime = { version = "39.0.1", features = ["component-model", "async"] } -wasmtime-wasi = "39.0.1" +wasmtime = { version = "29.0.0", features = ["component-model", "async"] } +wasmtime-wasi = "29.0.0" [dev-dependencies] rustfmt = "0.10.0" + + + diff --git a/crates/capsule-core/src/config/log.rs b/crates/capsule-core/src/config/log.rs index 9c1c1dd..288f824 100644 --- a/crates/capsule-core/src/config/log.rs +++ b/crates/capsule-core/src/config/log.rs @@ -2,9 +2,9 @@ use std::fmt; use std::sync::mpsc; use std::thread::{Builder, JoinHandle}; +use nanoid::nanoid; use serde::{Deserialize, Serialize}; use tokio::sync::oneshot; -use nanoid::nanoid; use crate::config::database::{Database, DatabaseError}; diff --git a/crates/capsule-core/src/lib.rs b/crates/capsule-core/src/lib.rs index 88a149c..96b08b8 100644 --- a/crates/capsule-core/src/lib.rs +++ b/crates/capsule-core/src/lib.rs @@ -1,3 +1,2 @@ pub mod config; pub mod wasm; - diff --git a/crates/capsule-core/src/wasm/commands/create.rs b/crates/capsule-core/src/wasm/commands/create.rs index 4d1acf5..e32ae53 100644 --- a/crates/capsule-core/src/wasm/commands/create.rs +++ b/crates/capsule-core/src/wasm/commands/create.rs @@ -1,15 +1,16 @@ +use std::sync::Arc; + use nanoid::nanoid; -use wasmtime::component::{Component, Linker, ResourceTable, Instance}; +use wasmtime::component::{Component, Linker, ResourceTable}; use wasmtime::{Store, StoreLimitsBuilder}; use wasmtime_wasi::WasiCtxBuilder; -use wasmtime_wasi::p2::add_to_linker_async; +use wasmtime_wasi::add_to_linker_async; use crate::config::log::{CreateInstanceLog, InstanceState, UpdateInstanceLog}; use crate::wasm::execution_policy::ExecutionPolicy; use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; -use crate::wasm::state::WasmState; - +use crate::wasm::state::{CapsuleAgent, State, capsule}; pub struct CreateInstance { pub policy: ExecutionPolicy, @@ -49,9 +50,12 @@ impl CreateInstance { } impl RuntimeCommand for CreateInstance { - type Output = (Store, Instance); + type Output = (Store, CapsuleAgent, String); - async fn execute(self, runtime: &Runtime) -> Result<(Store, Instance), WasmRuntimeError> { + async fn execute( + self, + runtime: Arc, + ) -> Result<(Store, CapsuleAgent, String), WasmRuntimeError> { runtime .log .commit_log(CreateInstanceLog { @@ -65,10 +69,12 @@ impl RuntimeCommand for CreateInstance { }) .await?; - let mut linker = Linker::::new(&runtime.engine); + let mut linker = Linker::::new(&runtime.engine); add_to_linker_async(&mut linker)?; + capsule::host::api::add_to_linker(&mut linker, |state: &mut State| state)?; + let wasi = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() @@ -84,10 +90,11 @@ impl RuntimeCommand for CreateInstance { let limits = limits.build(); - let state = WasmState { + let state = State { ctx: wasi, table: ResourceTable::new(), limits, + runtime: Some(Arc::clone(&runtime)), }; let mut store = Store::new(&runtime.engine, state); @@ -98,7 +105,8 @@ impl RuntimeCommand for CreateInstance { let component = Component::from_file(&runtime.engine, ".capsule/capsule.wasm")?; - let instance = match linker.instantiate_async(&mut store, &component).await { + let instance = match CapsuleAgent::instantiate_async(&mut store, &component, &linker).await + { Ok(instance) => instance, Err(e) => { runtime @@ -113,6 +121,6 @@ impl RuntimeCommand for CreateInstance { } }; - Ok((store, instance)) + Ok((store, instance, self.task_id)) } } diff --git a/crates/capsule-core/src/wasm/commands/run.rs b/crates/capsule-core/src/wasm/commands/run.rs index 1b2d545..973ee3b 100644 --- a/crates/capsule-core/src/wasm/commands/run.rs +++ b/crates/capsule-core/src/wasm/commands/run.rs @@ -1,33 +1,42 @@ -use wasmtime::component::Instance; +use std::sync::Arc; + use wasmtime::Store; use crate::config::log::{InstanceState, UpdateInstanceLog}; use crate::wasm::execution_policy::ExecutionPolicy; use crate::wasm::runtime::{Runtime, RuntimeCommand, WasmRuntimeError}; -use crate::wasm::state::WasmState; +use crate::wasm::state::{CapsuleAgent, State}; pub struct RunInstance { task_id: String, policy: ExecutionPolicy, - store: Store, - instance: Instance + store: Store, + instance: CapsuleAgent, + args_json: String, } impl RunInstance { - pub fn new(task_id: String, policy: ExecutionPolicy, store: Store, instance: Instance) -> Self { + pub fn new( + task_id: String, + policy: ExecutionPolicy, + store: Store, + instance: CapsuleAgent, + args_json: String, + ) -> Self { Self { task_id, policy, store, - instance + instance, + args_json, } } } impl RuntimeCommand for RunInstance { - type Output = Store; + type Output = String; - async fn execute(mut self, runtime: &Runtime) -> Result { + async fn execute(mut self, runtime: Arc) -> Result { runtime .log .update_log(UpdateInstanceLog { @@ -37,29 +46,50 @@ impl RuntimeCommand for RunInstance { }) .await?; - let run_func = self + let result = self .instance - .get_typed_func::<(), ()>(&mut self.store, "wasi:cli/run@0.2.9#run")?; - - let result = run_func.call_async(&mut self.store, ()).await; - + .capsule_host_task_runner() + .call_run(&mut self.store, &self.args_json) + .await; - let output = match result { - Ok(_) => Ok(self.store), + match result { + Ok(Ok(value)) => { + runtime + .log + .update_log(UpdateInstanceLog { + task_id: self.task_id, + state: InstanceState::Completed, + fuel_consumed: self.policy.compute.as_fuel() + - self.store.get_fuel().unwrap_or(0), + }) + .await?; + Ok(value) + } + Ok(Err(error_msg)) => { + runtime + .log + .update_log(UpdateInstanceLog { + task_id: self.task_id, + state: InstanceState::Failed, + fuel_consumed: self.policy.compute.as_fuel() + - self.store.get_fuel().unwrap_or(0), + }) + .await?; + Ok(format!(r#"{{"error": "{}"}}"#, error_msg)) + } Err(e) => { runtime .log .update_log(UpdateInstanceLog { task_id: self.task_id, state: InstanceState::Failed, - fuel_consumed: self.policy.compute.as_fuel() - self.store.get_fuel().unwrap_or(0), + fuel_consumed: self.policy.compute.as_fuel() + - self.store.get_fuel().unwrap_or(0), }) .await?; - Err(WasmRuntimeError::WasmtimeError(e) - )}, - }; - - output + Err(WasmRuntimeError::WasmtimeError(e)) + } + } } } diff --git a/crates/capsule-core/src/wasm/compiler/python.rs b/crates/capsule-core/src/wasm/compiler/python.rs index c3387fd..b4bf482 100644 --- a/crates/capsule-core/src/wasm/compiler/python.rs +++ b/crates/capsule-core/src/wasm/compiler/python.rs @@ -1,11 +1,10 @@ -use std::fs; use std::fmt; +use std::fs; use std::path::Path; use std::process::Command; - pub enum PythonWasmCompilerError { - CompileFailed(String) + CompileFailed(String), } impl fmt::Display for PythonWasmCompilerError { @@ -28,9 +27,7 @@ pub struct PythonWasmCompiler { } impl PythonWasmCompiler { - pub fn new() -> Result { - let cache_dir = ".capsule".to_string(); let output_wasm = format!("{}/capsule.wasm", &cache_dir); @@ -45,7 +42,6 @@ impl PythonWasmCompiler { } pub fn compile_wasm(&self, source: &str) -> Result<(), PythonWasmCompilerError> { - if !Path::new(&self.cache_dir).exists() { fs::create_dir_all(&self.cache_dir)?; } @@ -61,7 +57,9 @@ impl PythonWasmCompiler { .status()?; if !status.success() { - return Err(PythonWasmCompilerError::CompileFailed("Compilation failed".to_string())); + return Err(PythonWasmCompilerError::CompileFailed( + "Compilation failed".to_string(), + )); } } @@ -78,5 +76,4 @@ impl PythonWasmCompiler { false } - } diff --git a/crates/capsule-core/src/wasm/mod.rs b/crates/capsule-core/src/wasm/mod.rs index 02218bc..59437bd 100644 --- a/crates/capsule-core/src/wasm/mod.rs +++ b/crates/capsule-core/src/wasm/mod.rs @@ -1,5 +1,6 @@ pub mod commands; +pub mod compiler; pub mod execution_policy; pub mod runtime; pub mod state; -pub mod compiler; +pub mod utilities; diff --git a/crates/capsule-core/src/wasm/runtime.rs b/crates/capsule-core/src/wasm/runtime.rs index 98d9344..49c55ba 100644 --- a/crates/capsule-core/src/wasm/runtime.rs +++ b/crates/capsule-core/src/wasm/runtime.rs @@ -1,12 +1,13 @@ use std::fmt; - -use wasmtime::{Config, Engine}; +use std::sync::Arc; use crate::config::log::{Log, LogError}; +use wasmtime::{Config, Engine}; pub enum WasmRuntimeError { WasmtimeError(wasmtime::Error), LogError(LogError), + ConfigError(String), } impl fmt::Display for WasmRuntimeError { @@ -16,6 +17,7 @@ impl fmt::Display for WasmRuntimeError { write!(f, "Runtime error > Wasmtime error > {}", msg) } WasmRuntimeError::LogError(msg) => write!(f, "Runtime error > {}", msg), + WasmRuntimeError::ConfigError(msg) => write!(f, "Runtime error > Config > {}", msg), } } } @@ -34,7 +36,10 @@ impl From for WasmRuntimeError { pub trait RuntimeCommand { type Output; - fn execute(self, runtime: &Runtime) -> impl Future> + Send; + fn execute( + self, + runtime: Arc, + ) -> impl Future> + Send; } pub struct Runtime { @@ -43,7 +48,7 @@ pub struct Runtime { } impl Runtime { - pub fn new() -> Result { + pub fn new() -> Result, WasmRuntimeError> { let mut config = Config::new(); let log = Log::new(Some(".capsule"), "state.db-wal")?; @@ -51,16 +56,16 @@ impl Runtime { config.async_support(true); config.consume_fuel(true); - Ok(Self { + Ok(Arc::new(Self { engine: Engine::new(&config)?, log, - }) + })) } pub async fn execute( - &self, + self: &Arc, command: C, ) -> Result { - command.execute(self).await + command.execute(Arc::clone(self)).await } } diff --git a/crates/capsule-core/src/wasm/state.rs b/crates/capsule-core/src/wasm/state.rs index 7868bfd..0a2767b 100644 --- a/crates/capsule-core/src/wasm/state.rs +++ b/crates/capsule-core/src/wasm/state.rs @@ -1,24 +1,84 @@ -use wasmtime::component::ResourceTable; -use wasmtime::{ResourceLimiter, StoreLimits}; -use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView}; +use std::sync::Arc; + use anyhow::Result; +use wasmtime::component::{ResourceTable, bindgen}; +use wasmtime::{ResourceLimiter, StoreLimits}; +use wasmtime_wasi::{WasiCtx, WasiView}; + +use crate::wasm::commands::create::CreateInstance; +use crate::wasm::commands::run::RunInstance; +use crate::wasm::utilities::task_config::TaskConfig; +use capsule::host::api::{Host, TaskError}; + +bindgen!({ + path: "../capsule-wit", + world: "capsule-agent", + async: true, +}); -pub struct WasmState { +pub use capsule::host::api as host_api; + +pub struct State { pub ctx: WasiCtx, pub table: ResourceTable, pub limits: StoreLimits, + pub runtime: Option>, +} + +impl WasiView for State { + fn ctx(&mut self) -> &mut WasiCtx { + &mut self.ctx + } + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } } -impl WasiView for WasmState { - fn ctx(&mut self) -> WasiCtxView<'_> { - WasiCtxView { - ctx: &mut self.ctx, - table: &mut self.table, - } +impl Host for State { + async fn schedule_task( + &mut self, + name: String, + args: String, + config: String, + ) -> Result { + println!("📞 [Host] schedule_task: {} (Config: {})", name, config); + + let runtime = match &self.runtime { + Some(r) => Arc::clone(r), + None => { + return Err(TaskError::InternalError( + "No runtime available for recursive task execution".to_string(), + )); + } + }; + + let task_config: TaskConfig = serde_json::from_str(&config).unwrap_or_default(); + let policy = task_config.to_execution_policy(); + + let create_cmd = CreateInstance::new(policy.clone(), vec![]).task_name(&name); + + let (store, instance, task_id) = runtime + .execute(create_cmd) + .await + .map_err(|e| TaskError::InternalError(format!("Failed to create instance: {}", e)))?; + + let args_json = format!( + r#"{{"task_name": "{}", "args": {}, "kwargs": {{}}}}"#, + name, args + ); + + let run_cmd = RunInstance::new(task_id, policy, store, instance, args_json); + + let result = runtime + .execute(run_cmd) + .await + .map_err(|e| TaskError::InternalError(format!("Failed to run instance: {}", e)))?; + + Ok(result) } } -impl ResourceLimiter for WasmState { +impl ResourceLimiter for State { fn memory_growing( &mut self, current: usize, diff --git a/crates/capsule-core/src/wasm/utilities/mod.rs b/crates/capsule-core/src/wasm/utilities/mod.rs new file mode 100644 index 0000000..54f7bcb --- /dev/null +++ b/crates/capsule-core/src/wasm/utilities/mod.rs @@ -0,0 +1 @@ +pub mod task_config; diff --git a/crates/capsule-core/src/wasm/utilities/task_config.rs b/crates/capsule-core/src/wasm/utilities/task_config.rs new file mode 100644 index 0000000..212e7ea --- /dev/null +++ b/crates/capsule-core/src/wasm/utilities/task_config.rs @@ -0,0 +1,153 @@ +use serde::Deserialize; + +use crate::wasm::execution_policy::{Compute, ExecutionPolicy}; + +#[derive(Debug, Deserialize, Default)] +pub struct TaskConfig { + name: Option, + compute: Option, + ram: Option, + timeout: Option, + max_retries: Option, + env_vars: Option>, +} + +impl TaskConfig { + pub fn to_execution_policy(&self) -> ExecutionPolicy { + let compute = self + .compute + .as_ref() + .map(|c| match c.to_uppercase().as_str() { + "LOW" => Compute::Low, + "MEDIUM" => Compute::Medium, + "HIGH" => Compute::High, + _ => Compute::Medium, + }); + + let ram = self.ram.as_ref().and_then(|r| Self::parse_ram_string(r)); + + ExecutionPolicy::new() + .name(self.name.clone()) + .compute(compute) + .ram(ram) + .timeout(self.timeout.clone()) + .max_retries(self.max_retries) + .env_vars(self.env_vars.clone()) + } + + pub fn parse_ram_string(s: &str) -> Option { + let s = s.trim().to_uppercase(); + if s.ends_with("GB") { + s.trim_end_matches("GB") + .trim() + .parse::() + .ok() + .map(|v| v * 1024 * 1024 * 1024) + } else if s.ends_with("MB") { + s.trim_end_matches("MB") + .trim() + .parse::() + .ok() + .map(|v| v * 1024 * 1024) + } else if s.ends_with("KB") { + s.trim_end_matches("KB") + .trim() + .parse::() + .ok() + .map(|v| v * 1024) + } else { + s.parse::().ok() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_ram_string() { + assert_eq!(TaskConfig::parse_ram_string("2GB"), Some(2 * 1024 * 1024 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("1 GB"), Some(1024 * 1024 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("4gb"), Some(4 * 1024 * 1024 * 1024)); + + assert_eq!(TaskConfig::parse_ram_string("512MB"), Some(512 * 1024 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("256 MB"), Some(256 * 1024 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("128mb"), Some(128 * 1024 * 1024)); + + assert_eq!(TaskConfig::parse_ram_string("1024KB"), Some(1024 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("512 KB"), Some(512 * 1024)); + assert_eq!(TaskConfig::parse_ram_string("256kb"), Some(256 * 1024)); + + assert_eq!(TaskConfig::parse_ram_string("1024"), Some(1024)); + assert_eq!(TaskConfig::parse_ram_string("512"), Some(512)); + } + + #[test] + fn test_parse_ram_string_invalid() { + assert_eq!(TaskConfig::parse_ram_string("invalid"), None); + assert_eq!(TaskConfig::parse_ram_string(""), None); + assert_eq!(TaskConfig::parse_ram_string("GB"), None); + } + + #[test] + fn test_to_execution_policy_default() { + let config = TaskConfig::default(); + let policy = config.to_execution_policy(); + + assert_eq!(policy.name, "default"); + assert_eq!(policy.compute, Compute::Medium); + assert_eq!(policy.ram, None); + assert_eq!(policy.timeout, None); + assert_eq!(policy.max_retries, 1); + assert_eq!(policy.env_vars, None); + } + + #[test] + fn test_to_execution_policy_with_values() { + let config = TaskConfig { + name: Some("test_task".to_string()), + compute: Some("HIGH".to_string()), + ram: Some("2GB".to_string()), + timeout: Some("30s".to_string()), + max_retries: Some(3), + env_vars: Some(vec![("KEY".to_string(), "VALUE".to_string())]), + }; + + let policy = config.to_execution_policy(); + + assert_eq!(policy.name, "test_task"); + assert_eq!(policy.compute, Compute::High); + assert_eq!(policy.ram, Some(2 * 1024 * 1024 * 1024)); + assert_eq!(policy.timeout, Some("30s".to_string())); + assert_eq!(policy.max_retries, 3); + assert_eq!(policy.env_vars, Some(vec![("KEY".to_string(), "VALUE".to_string())])); + } + + #[test] + fn test_to_execution_policy_compute_variants() { + let low = TaskConfig { + compute: Some("LOW".to_string()), + ..Default::default() + }; + assert_eq!(low.to_execution_policy().compute, Compute::Low); + + let medium = TaskConfig { + compute: Some("MEDIUM".to_string()), + ..Default::default() + }; + assert_eq!(medium.to_execution_policy().compute, Compute::Medium); + + let high = TaskConfig { + compute: Some("HIGH".to_string()), + ..Default::default() + }; + assert_eq!(high.to_execution_policy().compute, Compute::High); + + let invalid = TaskConfig { + compute: Some("INVALID".to_string()), + ..Default::default() + }; + assert_eq!(invalid.to_execution_policy().compute, Compute::Medium); + } +} From a9acd5d9edb4e363ddb5ce307bd88911b56277af Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 21:59:49 +0100 Subject: [PATCH 18/22] capsule.wit and cli dependencies --- crates/capsule-wit/capsule.wit | 29 + crates/capsule-wit/deps.lock | 29 + crates/capsule-wit/deps.toml | 6 + crates/capsule-wit/deps/cli/command.wit | 7 + crates/capsule-wit/deps/cli/environment.wit | 18 + crates/capsule-wit/deps/cli/exit.wit | 4 + crates/capsule-wit/deps/cli/imports.wit | 20 + crates/capsule-wit/deps/cli/run.wit | 4 + crates/capsule-wit/deps/cli/stdio.wit | 17 + crates/capsule-wit/deps/cli/terminal.wit | 49 ++ .../deps/clocks/monotonic-clock.wit | 45 ++ crates/capsule-wit/deps/clocks/wall-clock.wit | 42 ++ crates/capsule-wit/deps/clocks/world.wit | 6 + .../capsule-wit/deps/filesystem/preopens.wit | 8 + crates/capsule-wit/deps/filesystem/types.wit | 634 ++++++++++++++++++ crates/capsule-wit/deps/filesystem/world.wit | 6 + crates/capsule-wit/deps/io/error.wit | 34 + crates/capsule-wit/deps/io/poll.wit | 41 ++ crates/capsule-wit/deps/io/streams.wit | 262 ++++++++ crates/capsule-wit/deps/io/world.wit | 6 + .../capsule-wit/deps/random/insecure-seed.wit | 25 + crates/capsule-wit/deps/random/insecure.wit | 22 + crates/capsule-wit/deps/random/random.wit | 26 + crates/capsule-wit/deps/random/world.wit | 7 + .../deps/sockets/instance-network.wit | 9 + .../deps/sockets/ip-name-lookup.wit | 51 ++ crates/capsule-wit/deps/sockets/network.wit | 145 ++++ .../deps/sockets/tcp-create-socket.wit | 27 + crates/capsule-wit/deps/sockets/tcp.wit | 353 ++++++++++ .../deps/sockets/udp-create-socket.wit | 27 + crates/capsule-wit/deps/sockets/udp.wit | 266 ++++++++ crates/capsule-wit/deps/sockets/world.wit | 11 + 32 files changed, 2236 insertions(+) create mode 100644 crates/capsule-wit/capsule.wit create mode 100644 crates/capsule-wit/deps.lock create mode 100644 crates/capsule-wit/deps.toml create mode 100644 crates/capsule-wit/deps/cli/command.wit create mode 100644 crates/capsule-wit/deps/cli/environment.wit create mode 100644 crates/capsule-wit/deps/cli/exit.wit create mode 100644 crates/capsule-wit/deps/cli/imports.wit create mode 100644 crates/capsule-wit/deps/cli/run.wit create mode 100644 crates/capsule-wit/deps/cli/stdio.wit create mode 100644 crates/capsule-wit/deps/cli/terminal.wit create mode 100644 crates/capsule-wit/deps/clocks/monotonic-clock.wit create mode 100644 crates/capsule-wit/deps/clocks/wall-clock.wit create mode 100644 crates/capsule-wit/deps/clocks/world.wit create mode 100644 crates/capsule-wit/deps/filesystem/preopens.wit create mode 100644 crates/capsule-wit/deps/filesystem/types.wit create mode 100644 crates/capsule-wit/deps/filesystem/world.wit create mode 100644 crates/capsule-wit/deps/io/error.wit create mode 100644 crates/capsule-wit/deps/io/poll.wit create mode 100644 crates/capsule-wit/deps/io/streams.wit create mode 100644 crates/capsule-wit/deps/io/world.wit create mode 100644 crates/capsule-wit/deps/random/insecure-seed.wit create mode 100644 crates/capsule-wit/deps/random/insecure.wit create mode 100644 crates/capsule-wit/deps/random/random.wit create mode 100644 crates/capsule-wit/deps/random/world.wit create mode 100644 crates/capsule-wit/deps/sockets/instance-network.wit create mode 100644 crates/capsule-wit/deps/sockets/ip-name-lookup.wit create mode 100644 crates/capsule-wit/deps/sockets/network.wit create mode 100644 crates/capsule-wit/deps/sockets/tcp-create-socket.wit create mode 100644 crates/capsule-wit/deps/sockets/tcp.wit create mode 100644 crates/capsule-wit/deps/sockets/udp-create-socket.wit create mode 100644 crates/capsule-wit/deps/sockets/udp.wit create mode 100644 crates/capsule-wit/deps/sockets/world.wit diff --git a/crates/capsule-wit/capsule.wit b/crates/capsule-wit/capsule.wit new file mode 100644 index 0000000..0b88a0a --- /dev/null +++ b/crates/capsule-wit/capsule.wit @@ -0,0 +1,29 @@ + +package capsule:host; + +interface api { + + variant task-error { + invalid-name(string), + invalid-config(string), + invalid-args(string), + internal-error(string), + } + + schedule-task: func( + task-name: string, + args-json: string, + config-json: string + ) -> result; +} + +/// Export interface for running tasks - the Wasm component implements this +interface task-runner { + /// Run a task with the given arguments, returns JSON result + run: func(args-json: string) -> result; +} + +world capsule-agent { + import api; + export task-runner; +} diff --git a/crates/capsule-wit/deps.lock b/crates/capsule-wit/deps.lock new file mode 100644 index 0000000..bb82d99 --- /dev/null +++ b/crates/capsule-wit/deps.lock @@ -0,0 +1,29 @@ +[cli] +url = "https://github.com/WebAssembly/wasi-cli/archive/v0.2.0.tar.gz" +sha256 = "285865a31d777181b075f39e92bcfe59c89cd6bacce660be1b9a627646956258" +sha512 = "da2622210a9e3eea82b99f1a5b8a44ce5443d009cb943f7bca0bf9cf4360829b289913d7ee727c011f0f72994ea7dc8e661ebcc0a6b34b587297d80cd9b3f7e8" + +[clocks] +url = "https://github.com/WebAssembly/wasi-clocks/archive/v0.2.0.tar.gz" +sha256 = "468b4d12892fe926b8eb5d398dbf579d566c93231fa44f415440572c695b7613" +sha512 = "e6b53a07221f1413953c9797c68f08b815fdaebf66419bbc1ea3e8b7dece73731062693634731f311a03957b268cf9cc509c518bd15e513c318aa04a8459b93a" + +[filesystem] +url = "https://github.com/WebAssembly/wasi-filesystem/archive/v0.2.0.tar.gz" +sha256 = "498c465cfd04587db40f970fff2185daa597d074c20b68a8bcbae558f261499b" +sha512 = "ead452f9b7bfb88593a502ec00d76d4228003d51c40fd0408aebc32d35c94673551b00230d730873361567cc209ec218c41fb4e95bad194268592c49e7964347" + +[io] +url = "https://github.com/WebAssembly/wasi-io/archive/v0.2.0.tar.gz" +sha256 = "7210e5653539a15478f894d4da24cc69d61924cbcba21d2804d69314a88e5a4c" +sha512 = "49184a1b0945a889abd52d25271172ed3dc2db6968fcdddb1bab7ee0081f4a3eeee0977ad2291126a37631c0d86eeea75d822fa8af224c422134500bf9f0f2bb" + +[random] +url = "https://github.com/WebAssembly/wasi-random/archive/v0.2.0.tar.gz" +sha256 = "7371d03c037d924caba2587fb2e7c5773a0d3c5fcecbf7971e0e0ba57973c53d" +sha512 = "964c4e8925a53078e4d94ba907b54f89a0b7e154f46823a505391471466c17f53c8692682e5c85771712acd88b348686173fc07c53a3cfe3d301b8cd8ddd0de4" + +[sockets] +url = "https://github.com/WebAssembly/wasi-sockets/archive/v0.2.0.tar.gz" +sha256 = "622bd28bbeb43736375dc02bd003fd3a016ff8ee91e14bab488325c6b38bf966" +sha512 = "5a63c1f36de0c4548e1d2297bdbededb28721cbad94ef7825c469eae29d7451c97e00b4c1d6730ee1ec0c4a5aac922961a2795762d4a0c3bb54e30a391a84bae" diff --git a/crates/capsule-wit/deps.toml b/crates/capsule-wit/deps.toml new file mode 100644 index 0000000..d89e927 --- /dev/null +++ b/crates/capsule-wit/deps.toml @@ -0,0 +1,6 @@ +cli = "https://github.com/WebAssembly/wasi-cli/archive/v0.2.0.tar.gz" +clocks = "https://github.com/WebAssembly/wasi-clocks/archive/v0.2.0.tar.gz" +filesystem = "https://github.com/WebAssembly/wasi-filesystem/archive/v0.2.0.tar.gz" +io = "https://github.com/WebAssembly/wasi-io/archive/v0.2.0.tar.gz" +random = "https://github.com/WebAssembly/wasi-random/archive/v0.2.0.tar.gz" +sockets = "https://github.com/WebAssembly/wasi-sockets/archive/v0.2.0.tar.gz" diff --git a/crates/capsule-wit/deps/cli/command.wit b/crates/capsule-wit/deps/cli/command.wit new file mode 100644 index 0000000..d8005bd --- /dev/null +++ b/crates/capsule-wit/deps/cli/command.wit @@ -0,0 +1,7 @@ +package wasi:cli@0.2.0; + +world command { + include imports; + + export run; +} diff --git a/crates/capsule-wit/deps/cli/environment.wit b/crates/capsule-wit/deps/cli/environment.wit new file mode 100644 index 0000000..7006523 --- /dev/null +++ b/crates/capsule-wit/deps/cli/environment.wit @@ -0,0 +1,18 @@ +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option; +} diff --git a/crates/capsule-wit/deps/cli/exit.wit b/crates/capsule-wit/deps/cli/exit.wit new file mode 100644 index 0000000..d0c2b82 --- /dev/null +++ b/crates/capsule-wit/deps/cli/exit.wit @@ -0,0 +1,4 @@ +interface exit { + /// Exit the current instance and any linked instances. + exit: func(status: result); +} diff --git a/crates/capsule-wit/deps/cli/imports.wit b/crates/capsule-wit/deps/cli/imports.wit new file mode 100644 index 0000000..083b84a --- /dev/null +++ b/crates/capsule-wit/deps/cli/imports.wit @@ -0,0 +1,20 @@ +package wasi:cli@0.2.0; + +world imports { + include wasi:clocks/imports@0.2.0; + include wasi:filesystem/imports@0.2.0; + include wasi:sockets/imports@0.2.0; + include wasi:random/imports@0.2.0; + include wasi:io/imports@0.2.0; + + import environment; + import exit; + import stdin; + import stdout; + import stderr; + import terminal-input; + import terminal-output; + import terminal-stdin; + import terminal-stdout; + import terminal-stderr; +} diff --git a/crates/capsule-wit/deps/cli/run.wit b/crates/capsule-wit/deps/cli/run.wit new file mode 100644 index 0000000..a70ee8c --- /dev/null +++ b/crates/capsule-wit/deps/cli/run.wit @@ -0,0 +1,4 @@ +interface run { + /// Run the program. + run: func() -> result; +} diff --git a/crates/capsule-wit/deps/cli/stdio.wit b/crates/capsule-wit/deps/cli/stdio.wit new file mode 100644 index 0000000..31ef35b --- /dev/null +++ b/crates/capsule-wit/deps/cli/stdio.wit @@ -0,0 +1,17 @@ +interface stdin { + use wasi:io/streams@0.2.0.{input-stream}; + + get-stdin: func() -> input-stream; +} + +interface stdout { + use wasi:io/streams@0.2.0.{output-stream}; + + get-stdout: func() -> output-stream; +} + +interface stderr { + use wasi:io/streams@0.2.0.{output-stream}; + + get-stderr: func() -> output-stream; +} diff --git a/crates/capsule-wit/deps/cli/terminal.wit b/crates/capsule-wit/deps/cli/terminal.wit new file mode 100644 index 0000000..38c724e --- /dev/null +++ b/crates/capsule-wit/deps/cli/terminal.wit @@ -0,0 +1,49 @@ +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. +interface terminal-input { + /// The input side of a terminal. + resource terminal-input; +} + +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. +interface terminal-output { + /// The output side of a terminal. + resource terminal-output; +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +interface terminal-stdin { + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +interface terminal-stdout { + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +interface terminal-stderr { + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stderr: func() -> option; +} diff --git a/crates/capsule-wit/deps/clocks/monotonic-clock.wit b/crates/capsule-wit/deps/clocks/monotonic-clock.wit new file mode 100644 index 0000000..4e4dc3a --- /dev/null +++ b/crates/capsule-wit/deps/clocks/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.2.0; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +interface monotonic-clock { + use wasi:io/poll@0.2.0.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + type instant = u64; + + /// A duration of time, in nanoseconds. + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// occured. + subscribe-instant: func( + when: instant, + ) -> pollable; + + /// Create a `pollable` which will resolve once the given duration has + /// elapsed, starting at the time at which this function was called. + /// occured. + subscribe-duration: func( + when: duration, + ) -> pollable; +} diff --git a/crates/capsule-wit/deps/clocks/wall-clock.wit b/crates/capsule-wit/deps/clocks/wall-clock.wit new file mode 100644 index 0000000..440ca0f --- /dev/null +++ b/crates/capsule-wit/deps/clocks/wall-clock.wit @@ -0,0 +1,42 @@ +package wasi:clocks@0.2.0; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime; +} diff --git a/crates/capsule-wit/deps/clocks/world.wit b/crates/capsule-wit/deps/clocks/world.wit new file mode 100644 index 0000000..c022457 --- /dev/null +++ b/crates/capsule-wit/deps/clocks/world.wit @@ -0,0 +1,6 @@ +package wasi:clocks@0.2.0; + +world imports { + import monotonic-clock; + import wall-clock; +} diff --git a/crates/capsule-wit/deps/filesystem/preopens.wit b/crates/capsule-wit/deps/filesystem/preopens.wit new file mode 100644 index 0000000..da801f6 --- /dev/null +++ b/crates/capsule-wit/deps/filesystem/preopens.wit @@ -0,0 +1,8 @@ +package wasi:filesystem@0.2.0; + +interface preopens { + use types.{descriptor}; + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list>; +} diff --git a/crates/capsule-wit/deps/filesystem/types.wit b/crates/capsule-wit/deps/filesystem/types.wit new file mode 100644 index 0000000..11108fc --- /dev/null +++ b/crates/capsule-wit/deps/filesystem/types.wit @@ -0,0 +1,634 @@ +package wasi:filesystem@0.2.0; +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +interface types { + use wasi:io/streams@0.2.0.{input-stream, output-stream, error}; + use wasi:clocks/wall-clock@0.2.0.{datetime}; + + /// File size or length of a region within a file. + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + type link-count = u64; + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result; + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code>; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code>; + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + ) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code>; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code>; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option; +} diff --git a/crates/capsule-wit/deps/filesystem/world.wit b/crates/capsule-wit/deps/filesystem/world.wit new file mode 100644 index 0000000..663f579 --- /dev/null +++ b/crates/capsule-wit/deps/filesystem/world.wit @@ -0,0 +1,6 @@ +package wasi:filesystem@0.2.0; + +world imports { + import types; + import preopens; +} diff --git a/crates/capsule-wit/deps/io/error.wit b/crates/capsule-wit/deps/io/error.wit new file mode 100644 index 0000000..22e5b64 --- /dev/null +++ b/crates/capsule-wit/deps/io/error.wit @@ -0,0 +1,34 @@ +package wasi:io@0.2.0; + + +interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams/stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// provide functions to further "downcast" this error into more specific + /// error information. For example, `error`s returned in streams derived + /// from filesystem types to be described using the filesystem's own + /// error-code type, using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter + /// `borrow` and returns + /// `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + resource error { + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + to-debug-string: func() -> string; + } +} diff --git a/crates/capsule-wit/deps/io/poll.wit b/crates/capsule-wit/deps/io/poll.wit new file mode 100644 index 0000000..ddc67f8 --- /dev/null +++ b/crates/capsule-wit/deps/io/poll.wit @@ -0,0 +1,41 @@ +package wasi:io@0.2.0; + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// `pollable` represents a single I/O event which may be ready, or not. + resource pollable { + + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + ready: func() -> bool; + + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + block: func(); + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll: func(in: list>) -> list; +} diff --git a/crates/capsule-wit/deps/io/streams.wit b/crates/capsule-wit/deps/io/streams.wit new file mode 100644 index 0000000..6d2f871 --- /dev/null +++ b/crates/capsule-wit/deps/io/streams.wit @@ -0,0 +1,262 @@ +package wasi:io@0.2.0; + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +interface streams { + use error.{error}; + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// When the source of a `read` is binary data, the bytes from the source + /// are returned verbatim. When the source of a `read` is known to the + /// implementation to be text, bytes containing the UTF-8 encoding of the + /// text are returned. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + } + + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result; + + /// Perform a write. This function never blocks. + /// + /// When the destination of a `write` is binary data, the bytes from + /// `contents` are written verbatim. When the destination of a `write` is + /// known to the implementation to be text, the bytes of `contents` are + /// transcoded from UTF-8 into the encoding of the destination and then + /// written. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, stream-error>; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, stream-error>; + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, stream-error>; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + + /// Write zeroes to a stream. + /// + /// This should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivelant to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + blocking-splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + } +} diff --git a/crates/capsule-wit/deps/io/world.wit b/crates/capsule-wit/deps/io/world.wit new file mode 100644 index 0000000..5f0b43f --- /dev/null +++ b/crates/capsule-wit/deps/io/world.wit @@ -0,0 +1,6 @@ +package wasi:io@0.2.0; + +world imports { + import streams; + import poll; +} diff --git a/crates/capsule-wit/deps/random/insecure-seed.wit b/crates/capsule-wit/deps/random/insecure-seed.wit new file mode 100644 index 0000000..47210ac --- /dev/null +++ b/crates/capsule-wit/deps/random/insecure-seed.wit @@ -0,0 +1,25 @@ +package wasi:random@0.2.0; +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple; +} diff --git a/crates/capsule-wit/deps/random/insecure.wit b/crates/capsule-wit/deps/random/insecure.wit new file mode 100644 index 0000000..c58f4ee --- /dev/null +++ b/crates/capsule-wit/deps/random/insecure.wit @@ -0,0 +1,22 @@ +package wasi:random@0.2.0; +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64; +} diff --git a/crates/capsule-wit/deps/random/random.wit b/crates/capsule-wit/deps/random/random.wit new file mode 100644 index 0000000..0c017f0 --- /dev/null +++ b/crates/capsule-wit/deps/random/random.wit @@ -0,0 +1,26 @@ +package wasi:random@0.2.0; +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + get-random-u64: func() -> u64; +} diff --git a/crates/capsule-wit/deps/random/world.wit b/crates/capsule-wit/deps/random/world.wit new file mode 100644 index 0000000..3da3491 --- /dev/null +++ b/crates/capsule-wit/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random@0.2.0; + +world imports { + import random; + import insecure; + import insecure-seed; +} diff --git a/crates/capsule-wit/deps/sockets/instance-network.wit b/crates/capsule-wit/deps/sockets/instance-network.wit new file mode 100644 index 0000000..e455d0f --- /dev/null +++ b/crates/capsule-wit/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +interface instance-network { + use network.{network}; + + /// Get a handle to the default network. + instance-network: func() -> network; + +} diff --git a/crates/capsule-wit/deps/sockets/ip-name-lookup.wit b/crates/capsule-wit/deps/sockets/ip-name-lookup.wit new file mode 100644 index 0000000..8e639ec --- /dev/null +++ b/crates/capsule-wit/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,51 @@ + +interface ip-name-lookup { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network, error-code, ip-address}; + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string) -> result; + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/crates/capsule-wit/deps/sockets/network.wit b/crates/capsule-wit/deps/sockets/network.wit new file mode 100644 index 0000000..9cadf06 --- /dev/null +++ b/crates/capsule-wit/deps/sockets/network.wit @@ -0,0 +1,145 @@ + +interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + /// The TCP connection was forcefully rejected + connection-refused, + + /// The TCP connection was reset. + connection-reset, + + /// A TCP connection was aborted. + connection-aborted, + + + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + datagram-too-large, + + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} diff --git a/crates/capsule-wit/deps/sockets/tcp-create-socket.wit b/crates/capsule-wit/deps/sockets/tcp-create-socket.wit new file mode 100644 index 0000000..c7ddf1f --- /dev/null +++ b/crates/capsule-wit/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,27 @@ + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/crates/capsule-wit/deps/sockets/tcp.wit b/crates/capsule-wit/deps/sockets/tcp.wit new file mode 100644 index 0000000..5902b9e --- /dev/null +++ b/crates/capsule-wit/deps/sockets/tcp.wit @@ -0,0 +1,353 @@ + +interface tcp { + use wasi:io/streams@0.2.0.{input-stream, output-stream}; + use wasi:io/poll@0.2.0.{pollable}; + use wasi:clocks/monotonic-clock@0.2.0.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bind-in-progress` + /// - `bound` (See note below) + /// - `listen-in-progress` + /// - `listening` + /// - `connect-in-progress` + /// - `connected` + /// - `closed` + /// See + /// for a more information. + /// + /// Note: Except where explicitly mentioned, whenever this documentation uses + /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. + /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) + /// + /// In addition to the general error codes documented on the + /// `network::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// Bind can be attempted multiple times on the same socket, even with + /// different arguments on each iteration. But never concurrently and + /// only as long as the previous bind failed. Once a bind succeeds, the + /// binding can't be changed anymore. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT + /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR + /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior + /// and SO_REUSEADDR performs something different entirely. + /// + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the `connection` state. + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// After a failed connection attempt, the socket will be in the `closed` + /// state and the only valid action left is to `drop` the socket. A single + /// socket can not be used to connect more than once. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) + /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A connect operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. + /// Because all WASI sockets are non-blocking this is expected to return + /// EINPROGRESS, which should be translated to `ok()` in WASI. + /// + /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` + /// with a timeout of 0 on the socket descriptor. Followed by a check for + /// the `SO_ERROR` socket option, in case the poll signaled readiness. + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the `listening` state. + /// + /// Unlike POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the `listening` state. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A listen operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the listen operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `listen` as part of either `start-listen` or `finish-listen`. + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + + /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which can be used to poll for, or block on, + /// completion of any of the asynchronous operations of this socket. + /// + /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` + /// return `error(would-block)`, this pollable can be used to wait for + /// their success or failure, after which the method can be retried. + /// + /// The pollable is not limited to the async operation that happens to be + /// in progress at the time of calling `subscribe` (if any). Theoretically, + /// `subscribe` only has to be called once per socket and can then be + /// (re)used for the remainder of the socket's lifetime. + /// + /// See + /// for a more information. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + + /// Initiate a graceful shutdown. + /// + /// - `receive`: The socket is not expecting to receive any data from + /// the peer. The `input-stream` associated with this socket will be + /// closed. Any data still in the receive queue at time of calling + /// this method will be discarded. + /// - `send`: The socket has no more data to send to the peer. The `output-stream` + /// associated with this socket will be closed and a FIN packet will be sent. + /// - `both`: Same effect as `receive` & `send` combined. + /// + /// This function is idempotent. Shutting a down a direction more than once + /// has no effect and returns `ok`. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/crates/capsule-wit/deps/sockets/udp-create-socket.wit b/crates/capsule-wit/deps/sockets/udp-create-socket.wit new file mode 100644 index 0000000..0482d1f --- /dev/null +++ b/crates/capsule-wit/deps/sockets/udp-create-socket.wit @@ -0,0 +1,27 @@ + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/crates/capsule-wit/deps/sockets/udp.wit b/crates/capsule-wit/deps/sockets/udp.wit new file mode 100644 index 0000000..d987a0a --- /dev/null +++ b/crates/capsule-wit/deps/sockets/udp.wit @@ -0,0 +1,266 @@ + +interface udp { + use wasi:io/poll@0.2.0.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + record outgoing-datagram { + /// The payload. + data: list, + + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. + remote-address: option, + } + + + + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, but + /// only the most recently returned pair of streams will be operational. Implementations may trap if + /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + %stream: func(remote-address: option) -> result, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). This function never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/crates/capsule-wit/deps/sockets/world.wit b/crates/capsule-wit/deps/sockets/world.wit new file mode 100644 index 0000000..f8bb92a --- /dev/null +++ b/crates/capsule-wit/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets@0.2.0; + +world imports { + import instance-network; + import network; + import udp; + import udp-create-socket; + import tcp; + import tcp-create-socket; + import ip-name-lookup; +} From d64c06f2bf432acb935f4ec70550b8fa46468dc6 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 22:00:01 +0100 Subject: [PATCH 19/22] capsule.wit and cli dependencies --- crates/capsule-wit/capsule.wit | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/capsule-wit/capsule.wit b/crates/capsule-wit/capsule.wit index 0b88a0a..4243a62 100644 --- a/crates/capsule-wit/capsule.wit +++ b/crates/capsule-wit/capsule.wit @@ -17,9 +17,7 @@ interface api { ) -> result; } -/// Export interface for running tasks - the Wasm component implements this interface task-runner { - /// Run a task with the given arguments, returns JSON result run: func(args-json: string) -> result; } From ecbdf93694d0af0fe792a1e98abb931d7151d632 Mon Sep 17 00:00:00 2001 From: Mavdol Date: Mon, 8 Dec 2025 22:03:36 +0100 Subject: [PATCH 20/22] Delete debug --- crates/capsule-core/src/wasm/state.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/capsule-core/src/wasm/state.rs b/crates/capsule-core/src/wasm/state.rs index 0a2767b..cacf659 100644 --- a/crates/capsule-core/src/wasm/state.rs +++ b/crates/capsule-core/src/wasm/state.rs @@ -41,8 +41,6 @@ impl Host for State { args: String, config: String, ) -> Result { - println!("📞 [Host] schedule_task: {} (Config: {})", name, config); - let runtime = match &self.runtime { Some(r) => Arc::clone(r), None => { From 16478c43d7b55e0e1b3b7d0e8974f31f62b3957c Mon Sep 17 00:00:00 2001 From: Mavdol Date: Tue, 9 Dec 2025 14:36:54 +0100 Subject: [PATCH 21/22] add better cach system --- .gitignore | 17 +- crates/capsule-core/Cargo.toml | 3 +- .../capsule-core/src/wasm/commands/create.rs | 45 ++++- crates/capsule-core/src/wasm/compiler/mod.rs | 2 + .../capsule-core/src/wasm/compiler/python.rs | 174 +++++++++++++++--- crates/capsule-core/src/wasm/runtime.rs | 47 ++++- 6 files changed, 244 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index ad67955..a5277f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,12 @@ # Generated by Cargo -# will have compiled files and executables debug target -# These are backup files generated by rustfmt -**/*.rs.bk +.capsule +__pycache__ +.DS_Store -# MSVC Windows builds of rustc generate these, which store debugging information +**/*.rs.bk *.pdb - -# Generated by cargo mutants -# Contains mutation testing data **/mutants.out*/ -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml index cac0188..f87b9a5 100644 --- a/crates/capsule-core/Cargo.toml +++ b/crates/capsule-core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["rlib"] [dependencies] anyhow = "1" @@ -16,7 +16,6 @@ tokio = "1.48.0" wasmtime = { version = "29.0.0", features = ["component-model", "async"] } wasmtime-wasi = "29.0.0" - [dev-dependencies] rustfmt = "0.10.0" diff --git a/crates/capsule-core/src/wasm/commands/create.rs b/crates/capsule-core/src/wasm/commands/create.rs index e32ae53..2f54e68 100644 --- a/crates/capsule-core/src/wasm/commands/create.rs +++ b/crates/capsule-core/src/wasm/commands/create.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::sync::Arc; use nanoid::nanoid; @@ -19,6 +20,7 @@ pub struct CreateInstance { pub task_name: String, pub agent_name: String, pub agent_version: String, + pub wasm_path: PathBuf, } impl CreateInstance { @@ -30,6 +32,7 @@ impl CreateInstance { task_name: "default".to_string(), agent_name: "default".to_string(), agent_version: "0.0.0".to_string(), + wasm_path: PathBuf::from(".capsule/capsule.wasm"), } } @@ -47,6 +50,11 @@ impl CreateInstance { self.agent_version = agent_version.into(); self } + + pub fn wasm_path(mut self, wasm_path: PathBuf) -> Self { + self.wasm_path = wasm_path; + self + } } impl RuntimeCommand for CreateInstance { @@ -103,7 +111,42 @@ impl RuntimeCommand for CreateInstance { store.limiter(|state| state); - let component = Component::from_file(&runtime.engine, ".capsule/capsule.wasm")?; + let component = match runtime.get_component().await { + Some(c) => c, + None => { + let cwasm_path = self.wasm_path.with_extension("cwasm"); + + let use_cached = if cwasm_path.exists() { + let wasm_time = std::fs::metadata(&self.wasm_path) + .and_then(|m| m.modified()) + .ok(); + let cwasm_time = std::fs::metadata(&cwasm_path) + .and_then(|m| m.modified()) + .ok(); + + match (wasm_time, cwasm_time) { + (Some(w), Some(c)) => c > w, + _ => false, + } + } else { + false + }; + + let c = if use_cached { + unsafe { Component::deserialize_file(&runtime.engine, &cwasm_path)? } + } else { + let c = Component::from_file(&runtime.engine, &self.wasm_path)?; + + if let Ok(bytes) = c.serialize() { + let _ = std::fs::write(&cwasm_path, bytes); + } + c + }; + + runtime.set_component(c.clone()).await; + c + } + }; let instance = match CapsuleAgent::instantiate_async(&mut store, &component, &linker).await { diff --git a/crates/capsule-core/src/wasm/compiler/mod.rs b/crates/capsule-core/src/wasm/compiler/mod.rs index c0a088c..9f8f154 100644 --- a/crates/capsule-core/src/wasm/compiler/mod.rs +++ b/crates/capsule-core/src/wasm/compiler/mod.rs @@ -1 +1,3 @@ +pub const CAPSULE_WIT: &str = include_str!("../../../../capsule-wit/capsule.wit"); + pub mod python; diff --git a/crates/capsule-core/src/wasm/compiler/python.rs b/crates/capsule-core/src/wasm/compiler/python.rs index b4bf482..08c17e6 100644 --- a/crates/capsule-core/src/wasm/compiler/python.rs +++ b/crates/capsule-core/src/wasm/compiler/python.rs @@ -1,16 +1,20 @@ use std::fmt; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; +use super::CAPSULE_WIT; + pub enum PythonWasmCompilerError { CompileFailed(String), + FsError(String), } impl fmt::Display for PythonWasmCompilerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PythonWasmCompilerError::CompileFailed(msg) => write!(f, "Compilation failed: {}", msg), + PythonWasmCompilerError::CompileFailed(msg) => write!(f, "Compilation failed > {}", msg), + PythonWasmCompilerError::FsError(msg) => write!(f, "File system error > {}", msg), } } } @@ -21,37 +25,98 @@ impl From for PythonWasmCompilerError { } } +impl From for PythonWasmCompilerError { + fn from(err: std::time::SystemTimeError) -> Self { + PythonWasmCompilerError::FsError(err.to_string()) + } +} + pub struct PythonWasmCompiler { - pub cache_dir: String, - pub output_wasm: String, + pub source_path: PathBuf, + pub cache_dir: PathBuf, + pub output_wasm: PathBuf, } impl PythonWasmCompiler { - pub fn new() -> Result { - let cache_dir = ".capsule".to_string(); - let output_wasm = format!("{}/capsule.wasm", &cache_dir); + pub fn new(source_path: &Path) -> Result { + let source_path = source_path.canonicalize() + .map_err(|e| PythonWasmCompilerError::FsError(format!("Cannot resolve source path: {}", e)))?; + + let source_dir = source_path.parent() + .ok_or(PythonWasmCompilerError::FsError("Cannot determine source directory".to_string()))?; - if !Path::new(&cache_dir).exists() { + let cache_dir = source_dir.join(".capsule"); + let output_wasm = cache_dir.join("capsule.wasm"); + + if !cache_dir.exists() { fs::create_dir_all(&cache_dir)?; } Ok(Self { + source_path, cache_dir, output_wasm, }) } - pub fn compile_wasm(&self, source: &str) -> Result<(), PythonWasmCompilerError> { - if !Path::new(&self.cache_dir).exists() { - fs::create_dir_all(&self.cache_dir)?; - } + pub fn compile_wasm(&self) -> Result { + if self.needs_rebuild(&self.source_path, &self.output_wasm)? { + let module_name = self.source_path + .file_stem() + .ok_or(PythonWasmCompilerError::FsError("Invalid source file name".to_string()))? + .to_str() + .ok_or(PythonWasmCompilerError::FsError("Invalid UTF-8 in file name".to_string()))?; + + let python_path = self.source_path + .parent() + .ok_or(PythonWasmCompilerError::FsError("Cannot determine parent directory".to_string()))?; + + let wit_path = self.get_wit_path()?; + + let sdk_path = self.get_sdk_path()?; + + if !sdk_path.exists() { + return Err(PythonWasmCompilerError::FsError( + format!("SDK directory not found: {}", sdk_path.display()) + )); + } + + if !sdk_path.exists() { + return Err(PythonWasmCompilerError::FsError( + format!("SDK directory not found: {}", sdk_path.display()) + )); + } + + let bootloader_path = self.cache_dir.join("_capsule_boot.py"); + let bootloader_content = format!( + r#"# Auto-generated bootloader for Capsule +# This file imports the user's module and exports TaskRunner for componentize-py + +# Import the user's module - this registers all @task decorated functions +import {module_name} + +# Re-export the TaskRunner and exports from the SDK +# This is what componentize-py needs to find +from capsule.app import TaskRunner, exports +"#, + module_name = module_name + ); + + fs::write(&bootloader_path, bootloader_content)?; - if self.needs_rebuild(source, &self.output_wasm) { let status = Command::new("componentize-py") .arg("-d") - .arg(".") - .arg("-m") - .arg(source) + .arg(&wit_path) + .arg("-w") + .arg("capsule-agent") + .arg("componentize") + .arg("_capsule_boot") + .arg("-p") + .arg(&self.cache_dir) + .arg("-p") + .arg(python_path) + .arg("-p") + .arg(&sdk_path) .arg("-o") .arg(&self.output_wasm) .status()?; @@ -63,17 +128,82 @@ impl PythonWasmCompiler { } } - Ok(()) + Ok(self.output_wasm.clone()) } - fn needs_rebuild(&self, source: &str, wasm_path: &str) -> bool { - let source_time = fs::metadata(source).and_then(|m| m.modified()).unwrap(); - let wasm_time = fs::metadata(wasm_path).and_then(|m| m.modified()).unwrap(); + fn needs_rebuild(&self, source: &Path, wasm_path: &Path) -> Result { + if !wasm_path.exists() { + return Ok(true); + } + + let wasm_time = fs::metadata(wasm_path).and_then(|m| m.modified())?; + let source_time = fs::metadata(source).and_then(|m| m.modified())?; if source_time > wasm_time { - return true; + return Ok(true); + } + + if let Some(source_dir) = source.parent() { + if let Ok(entries) = fs::read_dir(source_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "py") && path != source { + if let Ok(metadata) = fs::metadata(&path) { + if let Ok(modified) = metadata.modified() { + if modified > wasm_time { + return Ok(true); + } + } + } + } + } + } + } + + Ok(false) + } + + fn get_wit_path(&self) -> Result { + if let Ok(path) = std::env::var("CAPSULE_WIT_PATH") { + let wit_path = PathBuf::from(path); + if wit_path.exists() { + return Ok(wit_path); + } + } + + let wit_dir = self.cache_dir.join("wit"); + let wit_file = wit_dir.join("capsule.wit"); + + if !wit_file.exists() { + fs::create_dir_all(&wit_dir)?; + fs::write(&wit_file, CAPSULE_WIT)?; + } + + Ok(wit_dir) + } + + fn get_sdk_path(&self) -> Result { + if let Ok(path) = std::env::var("CAPSULE_SDK_PATH") { + return Ok(PathBuf::from(path)); + } + + if let Ok(exe_path) = std::env::current_exe() { + if let Some(project_root) = exe_path + .parent() + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + { + let sdk_path = project_root.join("crates/capsule-sdk/python/src"); + if sdk_path.exists() { + return Ok(sdk_path); + } + } } - false + Err(PythonWasmCompilerError::FsError( + "Cannot find SDK. Set CAPSULE_SDK_PATH environment variable.".to_string() + )) } } diff --git a/crates/capsule-core/src/wasm/runtime.rs b/crates/capsule-core/src/wasm/runtime.rs index 49c55ba..5a5d7b4 100644 --- a/crates/capsule-core/src/wasm/runtime.rs +++ b/crates/capsule-core/src/wasm/runtime.rs @@ -1,7 +1,10 @@ use std::fmt; +use std::path::PathBuf; use std::sync::Arc; +use tokio::sync::RwLock; use crate::config::log::{Log, LogError}; +use wasmtime::component::Component; use wasmtime::{Config, Engine}; pub enum WasmRuntimeError { @@ -42,23 +45,47 @@ pub trait RuntimeCommand { ) -> impl Future> + Send; } +pub struct RuntimeConfig { + pub cache_dir: PathBuf, +} + +impl Default for RuntimeConfig { + fn default() -> Self { + Self { + cache_dir: PathBuf::from(".capsule"), + } + } +} + pub struct Runtime { pub(crate) engine: Engine, pub(crate) log: Log, + + #[allow(dead_code)] + pub(crate) cache_dir: PathBuf, + + component: RwLock>, } impl Runtime { pub fn new() -> Result, WasmRuntimeError> { - let mut config = Config::new(); - let log = Log::new(Some(".capsule"), "state.db-wal")?; + Self::with_config(RuntimeConfig::default()) + } - config.wasm_component_model(true); - config.async_support(true); - config.consume_fuel(true); + pub fn with_config(config: RuntimeConfig) -> Result, WasmRuntimeError> { + let mut engine_config = Config::new(); + let db_path = config.cache_dir.join("state.db"); + let log = Log::new(Some(db_path.parent().unwrap().to_str().unwrap()), db_path.file_name().unwrap().to_str().unwrap())?; + + engine_config.wasm_component_model(true); + engine_config.async_support(true); + engine_config.consume_fuel(true); Ok(Arc::new(Self { - engine: Engine::new(&config)?, + engine: Engine::new(&engine_config)?, log, + cache_dir: config.cache_dir, + component: RwLock::new(None), })) } @@ -68,4 +95,12 @@ impl Runtime { ) -> Result { command.execute(Arc::clone(self)).await } + + pub async fn get_component(&self) -> Option { + self.component.read().await.clone() + } + + pub async fn set_component(&self, component: Component) { + *self.component.write().await = Some(component); + } } From 7661748378bdb72ccdb147179ce65884eeab1a0d Mon Sep 17 00:00:00 2001 From: Mavdol Date: Tue, 9 Dec 2025 14:40:27 +0100 Subject: [PATCH 22/22] update roadmap --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 97f2dcf..dc7b841 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,7 +10,7 @@ This document tracks the development status of Capsule. We follow a "Release Ear **Goal:** Validating the core Host/Guest technology and Python interoperability. -- [ ] **Core:** Rust Host capable of loading Wasm Components. +- [x] **Core:** Rust Host capable of loading Wasm Components. - [ ] **SDK (Python):** Basic `@task` decorator with JSON serialization. - [ ] **CLI:** `capsule run main.py` with JIT compilation. - [ ] **Limits:** Basic Fuel metering for CPU protection.