diff --git a/shell/Cargo.lock b/shell/Cargo.lock index 7ef55c9ce..05041a586 100644 --- a/shell/Cargo.lock +++ b/shell/Cargo.lock @@ -643,16 +643,18 @@ dependencies = [ ] [[package]] -name = "iii-observability" -version = "0.19.1-next.1" +name = "iii-helpers" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7169861d75f52022881edf09657763c84299e935e60abe19faf98308dc4122e6" +checksum = "09daa7c14a9e4c1f7077c4a181918d207e3f05cdfea8b2d7781bbeb80caf4c5d" dependencies = [ "futures-util", "opentelemetry", "opentelemetry-http", "opentelemetry_sdk", "reqwest", + "schemars", + "serde", "serde_json", "sysinfo", "tokio", @@ -663,14 +665,14 @@ dependencies = [ [[package]] name = "iii-sdk" -version = "0.19.1-next.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619bbf68e82f91fa54d23986f00958a5bd573a08751c89b6528d2e35916a8642" +checksum = "5957568413b9a5178c11bf91b20909e93e568b187e259e941ba6009a7ec3f5c1" dependencies = [ "async-trait", "futures-util", "hostname", - "iii-observability", + "iii-helpers", "reqwest", "schemars", "serde", @@ -1368,13 +1370,13 @@ dependencies = [ [[package]] name = "shell" -version = "0.5.1" +version = "0.5.2" dependencies = [ "anyhow", "async-trait", "base64", "clap", - "iii-observability", + "iii-helpers", "iii-sdk", "libc", "once_cell", diff --git a/shell/Cargo.toml b/shell/Cargo.toml index af53bd57c..d4029611f 100644 --- a/shell/Cargo.toml +++ b/shell/Cargo.toml @@ -11,8 +11,8 @@ name = "shell" path = "src/main.rs" [dependencies] -iii-sdk = "=0.19.1-next.1" -iii-observability = "=0.19.1-next.1" +iii-sdk = "=0.20.0" +iii-helpers = "=0.20.0" schemars = { version = "0.8", features = ["uuid1"] } libc = "0.2" tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "signal", "process", "time", "io-util", "fs"] } diff --git a/shell/src/configuration.rs b/shell/src/configuration.rs index 0471188e6..958e886c6 100644 --- a/shell/src/configuration.rs +++ b/shell/src/configuration.rs @@ -5,7 +5,9 @@ use std::sync::Arc; use std::time::Duration; -use iii_sdk::{IIIError, RegisterFunction, RegisterTriggerInput, TriggerRequest, III}; +use iii_sdk::errors::Error; +use iii_sdk::protocol::{RegisterTriggerInput, TriggerRequest}; +use iii_sdk::{IIIClient, RegisterFunction}; use serde_json::{json, Value}; use tokio::sync::{Mutex, RwLock}; @@ -32,7 +34,7 @@ pub struct ShellRuntime { #[derive(Clone)] pub struct AppState { pub runtime: Arc>, - pub iii: III, + pub iii: IIIClient, /// Serializes hot-reloads: held across the authoritative fetch + build + swap /// so an older event's slow build can never clobber a newer applied config. pub reload_lock: Arc>, @@ -102,7 +104,7 @@ pub fn prepare_config(cfg: &ShellConfig) -> Result, String> { } /// Build the live runtime: validate the config, then build the host fs backend. -pub fn build_runtime(cfg: &ShellConfig, iii: &III) -> Result { +pub fn build_runtime(cfg: &ShellConfig, iii: &IIIClient) -> Result { let config = prepare_config(cfg)?; if config.fs.host_root.is_none() { tracing::warn!( @@ -137,7 +139,7 @@ pub fn build_runtime(cfg: &ShellConfig, iii: &III) -> Result) -> Result<(), String> { +pub async fn register_config(iii: &IIIClient, seed: Option<&ShellConfig>) -> Result<(), String> { let mut payload = json!({ "id": CONFIG_ID, "name": "Shell", @@ -170,7 +172,7 @@ pub async fn register_config(iii: &III, seed: Option<&ShellConfig>) -> Result<() /// Seed the built-in default only when nothing is stored yet — never overwrite /// an operator's persisted value. -async fn should_seed_default_value(iii: &III) -> Result { +async fn should_seed_default_value(iii: &IIIClient) -> Result { match try_get_config_value(iii).await? { None => Ok(true), Some(value) if value.is_null() => Ok(true), @@ -179,7 +181,7 @@ async fn should_seed_default_value(iii: &III) -> Result { } /// Read the live `shell` configuration (env-expanded by the configuration worker). -pub async fn fetch_config(iii: &III) -> Result { +pub async fn fetch_config(iii: &IIIClient) -> Result { let value = get_config_value(iii).await?; if value.is_null() { // Null means register_config did not seed (its seed_default failed @@ -199,18 +201,18 @@ pub async fn fetch_config(iii: &III) -> Result { ShellConfig::from_json(&value) } -async fn get_config_value(iii: &III) -> Result { +async fn get_config_value(iii: &IIIClient) -> Result { try_get_config_value(iii) .await? .ok_or_else(|| format!("configuration `{CONFIG_ID}` not found")) } -async fn try_get_config_value(iii: &III) -> Result, String> { +async fn try_get_config_value(iii: &IIIClient) -> Result, String> { match trigger_with_retry(iii, "configuration::get", json!({ "id": CONFIG_ID })).await { Ok(resp) => Ok(resp.get("value").cloned()), - // `trigger_with_retry` flattens the structured `IIIError` to its + // `trigger_with_retry` flattens the structured `Error` to its // Display string, so we substring-match the recovered message rather - // than branch on `IIIError::Remote { code }`. The engine's missing-entry + // than branch on `Error::Remote { code }`. The engine's missing-entry // codes vary in case (`function_not_found`, `STATEMENT_NOT_FOUND`, // `NOT_FOUND`), so uppercase before matching to catch them all. A // false negative is non-fatal — it just propagates the raw retry error @@ -243,15 +245,15 @@ async fn apply_config(state: &AppState, cfg: ShellConfig) -> Result<(), String> } /// Register the internal config-change handler and bind a `configuration` trigger. -pub fn register_config_trigger(iii: &III, state: AppState) -> Result<(), IIIError> { +pub fn register_config_trigger(iii: &IIIClient, state: AppState) -> Result<(), Error> { let st = state.clone(); iii.register_function( CONFIG_FN_ID, RegisterFunction::new_async(move |_payload: Value| { let st = st.clone(); async move { - on_config_change(&st).await.map_err(IIIError::from)?; - Ok::(json!({ "ok": true })) + on_config_change(&st).await.map_err(Error::from)?; + Ok::(json!({ "ok": true })) } }) .description("Internal: reload the security policy + fs backend on configuration change."), @@ -363,7 +365,11 @@ where } } -async fn trigger_with_retry(iii: &III, function_id: &str, payload: Value) -> Result { +async fn trigger_with_retry( + iii: &IIIClient, + function_id: &str, + payload: Value, +) -> Result { let mut last_err = String::new(); for attempt in 1..=CONFIG_RETRIES { match iii diff --git a/shell/src/exec/error.rs b/shell/src/exec/error.rs index 3505547de..c82d18a59 100644 --- a/shell/src/exec/error.rs +++ b/shell/src/exec/error.rs @@ -24,8 +24,8 @@ impl ExecError { /// (OOM only); `expect` so future shape changes fail loudly rather /// than producing malformed JSON. /// - /// The handler-return path lifts `ExecError` to `IIIError::Remote` directly - /// (see `From for IIIError` below), so it no longer stringifies. + /// The handler-return path lifts `ExecError` to `Error::Remote` directly + /// (see `From for Error` below), so it no longer stringifies. /// `to_json` is kept as the canonical `{code,message}` serialization /// (round-trip coverage in tests) and for any caller that needs the wire /// shape as a `String`. @@ -35,13 +35,13 @@ impl ExecError { } /// Carry the S-code to the wire as the top-level `code`. The engine SDK maps -/// `IIIError::Remote { code, message, .. }` to the wire `ErrorBody` verbatim, -/// so an agent can branch on `error.code` (e.g. "S211"). Any other `IIIError` +/// `Error::Remote { code, message, .. }` to the wire `ErrorBody` verbatim, +/// so an agent can branch on `error.code` (e.g. "S211"). Any other `Error` /// variant collapses to `code: "invocation_failed"` with the real code buried /// in the message — which is exactly what we are escaping here. -impl From for iii_sdk::IIIError { +impl From for iii_sdk::errors::Error { fn from(err: ExecError) -> Self { - iii_sdk::IIIError::Remote { + iii_sdk::errors::Error::Remote { code: err.code.to_string(), message: err.message, stacktrace: None, @@ -67,15 +67,15 @@ mod tests { assert_ne!(ExecError::new("S210", "x"), ExecError::new("S211", "x"),); } - /// The wire contract: `ExecError` lifts to `IIIError::Remote { code, .. }` + /// The wire contract: `ExecError` lifts to `Error::Remote { code, .. }` /// so the S-code reaches the wire `code` verbatim. Any other variant (e.g. /// Handler) would collapse to `code: "invocation_failed"` — pin against that /// regression so an agent can keep branching on `error.code`. #[test] fn converts_to_iii_remote_carrying_the_s_code() { - let err: iii_sdk::IIIError = ExecError::new("S216", "host exec: boom").into(); + let err: iii_sdk::errors::Error = ExecError::new("S216", "host exec: boom").into(); match err { - iii_sdk::IIIError::Remote { + iii_sdk::errors::Error::Remote { code, message, stacktrace, @@ -84,7 +84,7 @@ mod tests { assert_eq!(message, "host exec: boom"); assert!(stacktrace.is_none()); } - other => panic!("expected IIIError::Remote, got {other:?}"), + other => panic!("expected Error::Remote, got {other:?}"), } } } diff --git a/shell/src/exec/sandbox.rs b/shell/src/exec/sandbox.rs index 780b5c74d..022a35b81 100644 --- a/shell/src/exec/sandbox.rs +++ b/shell/src/exec/sandbox.rs @@ -140,16 +140,16 @@ fn is_engine_timeout(err: &ExecError) -> bool { #[cfg(test)] mod tests { use super::*; - use iii_sdk::IIIError; + use iii_sdk::errors::Error; use serde_json::Value; use std::sync::Mutex; /// Stub forwarder using `Mutex>` to handle the - /// non-Clone `IIIError` shape. Same pattern as + /// non-Clone `Error` shape. Same pattern as /// `tests/sandbox_dispatch.rs::StubFwd`. struct StubFwd { captured: Mutex>, - next: Mutex>>, + next: Mutex>>, } impl StubFwd { @@ -162,13 +162,13 @@ mod tests { fn handler_err(json_msg: &'static str) -> Arc { Arc::new(Self { captured: Mutex::new(None), - next: Mutex::new(Some(Err(IIIError::Handler(json_msg.to_string())))), + next: Mutex::new(Some(Err(Error::Handler(json_msg.to_string())))), }) } fn remote_err(code: &str, message: &str) -> Arc { Arc::new(Self { captured: Mutex::new(None), - next: Mutex::new(Some(Err(IIIError::Remote { + next: Mutex::new(Some(Err(Error::Remote { code: code.into(), message: message.into(), stacktrace: None, @@ -179,7 +179,7 @@ mod tests { #[async_trait] impl TriggerFwd for StubFwd { - async fn trigger(&self, fid: &str, payload: Value) -> Result { + async fn trigger(&self, fid: &str, payload: Value) -> Result { *self.captured.lock().unwrap() = Some((fid.into(), payload)); self.next .lock() diff --git a/shell/src/exec_dispatch.rs b/shell/src/exec_dispatch.rs index dc1df98f1..b7a2a23f1 100644 --- a/shell/src/exec_dispatch.rs +++ b/shell/src/exec_dispatch.rs @@ -15,7 +15,7 @@ use crate::triggers::IiiTriggerFwd; pub fn pick_exec_backend( target: Target, cfg: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, ) -> Arc { match target { Target::Host => Arc::new(HostExecBackend::new(cfg)), @@ -23,7 +23,7 @@ pub fn pick_exec_backend( } } -fn sandbox_for(id: Uuid, iii: iii_sdk::III, enabled: bool) -> Arc { +fn sandbox_for(id: Uuid, iii: iii_sdk::IIIClient, enabled: bool) -> Arc { Arc::new(SandboxExecBackend::new( Arc::new(IiiTriggerFwd::new(iii)), enabled, @@ -32,6 +32,6 @@ fn sandbox_for(id: Uuid, iii: iii_sdk::III, enabled: bool) -> Arc wire-code conversion is covered by -// `exec::error::tests` (the `From for IIIError` Remote lift). +// `exec::error::tests` (the `From for Error` Remote lift). diff --git a/shell/src/fs/error.rs b/shell/src/fs/error.rs index 408a15292..c851da98b 100644 --- a/shell/src/fs/error.rs +++ b/shell/src/fs/error.rs @@ -38,8 +38,8 @@ impl FsError { /// effectively infallible (OOM only); `expect` so future changes that /// break the invariant fail loudly instead of producing malformed JSON. /// - /// The handler-return path lifts `FsError` to `IIIError::Remote` directly - /// (see `From for IIIError` below), so it no longer stringifies. + /// The handler-return path lifts `FsError` to `Error::Remote` directly + /// (see `From for Error` below), so it no longer stringifies. /// `to_json` is kept as the canonical `{code,message}` serialization /// (round-trip coverage in tests) and for any caller that needs the wire /// shape as a `String`. @@ -49,13 +49,13 @@ impl FsError { } /// Carry the S2xx code to the wire as the top-level `code`. The engine SDK -/// maps `IIIError::Remote { code, message, .. }` to the wire `ErrorBody` +/// maps `Error::Remote { code, message, .. }` to the wire `ErrorBody` /// verbatim, so an agent can branch on `error.code` (e.g. "S211"). Any other -/// `IIIError` variant collapses to `code: "invocation_failed"` with the real +/// `Error` variant collapses to `code: "invocation_failed"` with the real /// code buried in the message — which is exactly what we are escaping here. -impl From for iii_sdk::IIIError { +impl From for iii_sdk::errors::Error { fn from(err: FsError) -> Self { - iii_sdk::IIIError::Remote { + iii_sdk::errors::Error::Remote { code: err.code.to_string(), message: err.message, stacktrace: None, @@ -118,15 +118,15 @@ mod tests { assert!(j.contains("\"message\":\"nope\"")); } - /// The wire contract: `FsError` lifts to `IIIError::Remote { code, .. }` so + /// The wire contract: `FsError` lifts to `Error::Remote { code, .. }` so /// the S-code reaches the wire `code` verbatim. Any other variant (e.g. /// Handler) would collapse to `code: "invocation_failed"` — pin against that /// regression so an agent can keep branching on `error.code`. #[test] fn converts_to_iii_remote_carrying_the_s_code() { - let err: iii_sdk::IIIError = FsError::new("S215", "denied").into(); + let err: iii_sdk::errors::Error = FsError::new("S215", "denied").into(); match err { - iii_sdk::IIIError::Remote { + iii_sdk::errors::Error::Remote { code, message, stacktrace, @@ -135,7 +135,7 @@ mod tests { assert_eq!(message, "denied"); assert!(stacktrace.is_none()); } - other => panic!("expected IIIError::Remote, got {other:?}"), + other => panic!("expected Error::Remote, got {other:?}"), } } } diff --git a/shell/src/fs/host.rs b/shell/src/fs/host.rs index c5db64751..64dfb1a41 100644 --- a/shell/src/fs/host.rs +++ b/shell/src/fs/host.rs @@ -4,7 +4,8 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use async_trait::async_trait; -use iii_sdk::{Channel, IIIError}; +use iii_sdk::channel::Channel; +use iii_sdk::errors::Error; use crate::fs::error::FsError; @@ -35,12 +36,12 @@ impl Drop for TempGuard { #[async_trait] pub trait ChannelMaker: Send + Sync + std::fmt::Debug { - async fn create_channel(&self, buffer: usize) -> Result; + async fn create_channel(&self, buffer: usize) -> Result; fn engine_address(&self) -> String; } pub struct IiiChannelMaker { - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, } impl std::fmt::Debug for IiiChannelMaker { @@ -52,14 +53,14 @@ impl std::fmt::Debug for IiiChannelMaker { } impl IiiChannelMaker { - pub fn new(iii: iii_sdk::III) -> Self { + pub fn new(iii: iii_sdk::IIIClient) -> Self { Self { iii } } } #[async_trait] impl ChannelMaker for IiiChannelMaker { - async fn create_channel(&self, buffer: usize) -> Result { + async fn create_channel(&self, buffer: usize) -> Result { iii_sdk::helpers::create_channel(&self.iii, Some(buffer)).await } fn engine_address(&self) -> String { @@ -1619,8 +1620,11 @@ mod tests { struct StubChan; #[async_trait::async_trait] impl super::ChannelMaker for StubChan { - async fn create_channel(&self, _: usize) -> Result { - Err(iii_sdk::IIIError::Handler("stub".into())) + async fn create_channel( + &self, + _: usize, + ) -> Result { + Err(iii_sdk::errors::Error::Handler("stub".into())) } fn engine_address(&self) -> String { "ws://stub:0".into() @@ -1646,7 +1650,7 @@ mod tests { iii_sdk::channels::StreamChannelRef { channel_id: "c".into(), access_key: "k".into(), - direction: iii_sdk::channels::ChannelDirection::Read, + direction: iii_sdk::helpers::ChannelDirection::Read, } } diff --git a/shell/src/fs/mod.rs b/shell/src/fs/mod.rs index ec5fac901..919162635 100644 --- a/shell/src/fs/mod.rs +++ b/shell/src/fs/mod.rs @@ -44,8 +44,8 @@ impl From for iii_sdk::channels::StreamChannelRef { channel_id: c.channel_id, access_key: c.access_key, direction: match c.direction { - ContentDirection::Read => iii_sdk::channels::ChannelDirection::Read, - ContentDirection::Write => iii_sdk::channels::ChannelDirection::Write, + ContentDirection::Read => iii_sdk::helpers::ChannelDirection::Read, + ContentDirection::Write => iii_sdk::helpers::ChannelDirection::Write, }, } } @@ -57,8 +57,8 @@ impl From for ContentRef { channel_id: c.channel_id, access_key: c.access_key, direction: match c.direction { - iii_sdk::channels::ChannelDirection::Read => ContentDirection::Read, - iii_sdk::channels::ChannelDirection::Write => ContentDirection::Write, + iii_sdk::helpers::ChannelDirection::Read => ContentDirection::Read, + iii_sdk::helpers::ChannelDirection::Write => ContentDirection::Write, }, } } diff --git a/shell/src/fs/sandbox.rs b/shell/src/fs/sandbox.rs index 2d39b45e9..2fb2c8b36 100644 --- a/shell/src/fs/sandbox.rs +++ b/shell/src/fs/sandbox.rs @@ -166,7 +166,7 @@ impl FsBackend for SandboxFsBackend { #[cfg(test)] mod tests { use super::*; - use iii_sdk::IIIError; + use iii_sdk::errors::Error; use std::sync::Mutex; struct StubFwd { @@ -176,11 +176,11 @@ mod tests { #[async_trait] impl TriggerFwd for StubFwd { - async fn trigger(&self, fid: &str, payload: Value) -> Result { + async fn trigger(&self, fid: &str, payload: Value) -> Result { *self.captured.lock().unwrap() = Some((fid.into(), payload)); match &self.respond_with { Ok(v) => Ok(v.clone()), - Err(json) => Err(IIIError::Handler(json.to_string())), + Err(json) => Err(Error::Handler(json.to_string())), } } } @@ -248,7 +248,7 @@ mod tests { let content = iii_sdk::channels::StreamChannelRef { channel_id: "c-1".into(), access_key: "k-1".into(), - direction: iii_sdk::channels::ChannelDirection::Read, + direction: iii_sdk::helpers::ChannelDirection::Read, }; let resp = b .write(WriteArgs { @@ -334,8 +334,8 @@ mod tests { struct RemoteFwd; #[async_trait] impl TriggerFwd for RemoteFwd { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { - Err(IIIError::Remote { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + Err(Error::Remote { code: "S214".into(), message: "directory not empty".into(), stacktrace: None, @@ -361,8 +361,8 @@ mod tests { struct WrappedFwd; #[async_trait] impl TriggerFwd for WrappedFwd { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { - Err(IIIError::Remote { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + Err(Error::Remote { code: "invocation_failed".into(), message: "handler error: S211: not found".into(), stacktrace: None, diff --git a/shell/src/functions/exec.rs b/shell/src/functions/exec.rs index d1fc66d28..d8b3aa843 100644 --- a/shell/src/functions/exec.rs +++ b/shell/src/functions/exec.rs @@ -8,9 +8,9 @@ use crate::functions::types::{ExecRequest, ExecResponse}; pub async fn handle( cfg: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, req: ExecRequest, -) -> Result { +) -> Result { // Field-level type errors (wrong-type `command`, non-string `args[i]`, // bad `target.kind`) come from the per-field deserializers in // `functions::types`; they surface here as the trigger `Err` carrying @@ -21,10 +21,10 @@ pub async fn handle( // The typed-schema migration must NOT collapse "absent args" into // "args: []" or callers lose the shell-words path. // argv-parse and allowlist/denylist rejections are plain Strings with no - // S-code; via `From for IIIError` they become the engine's + // S-code; via `From for Error` they become the engine's // `invocation_failed` envelope, message naming the violation. Only the // backend `ExecError` below carries an S-code, surfaced as the wire `code` - // through `From for IIIError` (Remote) so an agent can branch + // through `From for Error` (Remote) so an agent can branch // on `error.code`. let argv = parse_argv(&req.command, req.args.as_ref()).map_err(|e| format!("argv: {}", e))?; @@ -35,7 +35,7 @@ pub async fn handle( // rejects here, carrying the S-code to the wire via From. The // sandbox backend additionally rejects any populated override (host-only). let mut overrides = build_overrides(req.cwd.as_deref(), req.env.as_ref(), &cfg) - .map_err(iii_sdk::IIIError::from)?; + .map_err(iii_sdk::errors::Error::from)?; // stdin needs no gating (opaque input bytes); it is host-only, enforced by // the sandbox backend's is_empty() rejection of any populated override. overrides.stdin = req.stdin; @@ -47,7 +47,7 @@ pub async fn handle( let out = backend .run(&argv, timeout, &overrides) .await - .map_err(iii_sdk::IIIError::from)?; + .map_err(iii_sdk::errors::Error::from)?; Ok(ExecResponse::from(out)) } diff --git a/shell/src/functions/exec_bg.rs b/shell/src/functions/exec_bg.rs index 22ea1048b..b928a90da 100644 --- a/shell/src/functions/exec_bg.rs +++ b/shell/src/functions/exec_bg.rs @@ -26,7 +26,7 @@ const SANDBOX_RPC_SLACK_MS: u64 = 30_000; pub async fn handle( cfg: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, req: ExecBgRequest, ) -> Result { // Field-level type errors (wrong-type `command`, non-string `args[i]`, @@ -770,7 +770,7 @@ mod sandbox_path_tests { use crate::jobs::{self, JobStatus}; use crate::triggers::TriggerFwd; use async_trait::async_trait; - use iii_sdk::IIIError; + use iii_sdk::errors::Error; use serde_json::{json, Value}; use std::sync::{Arc, Mutex}; use uuid::Uuid; @@ -779,7 +779,7 @@ mod sandbox_path_tests { #[async_trait] impl TriggerFwd for ImmediateOk { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { Ok(self.0.lock().unwrap().take().unwrap()) } } @@ -867,8 +867,8 @@ mod sandbox_path_tests { struct AlwaysErr; #[async_trait] impl TriggerFwd for AlwaysErr { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { - Err(IIIError::Remote { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + Err(Error::Remote { code: "S300".into(), message: "VM boot failed".into(), stacktrace: None, @@ -908,7 +908,7 @@ mod sandbox_path_tests { } #[async_trait] impl TriggerFwd for GatedFwd { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { // Wait until the test releases us via the oneshot channel. let rx = self.release.lock().await.take().unwrap(); rx.await.unwrap(); @@ -970,7 +970,7 @@ mod sandbox_path_tests { jobs::JOBS.map.lock().await.remove(&resp.job_id); } - /// Test seam: the production `handle` accepts `iii_sdk::III` and + /// Test seam: the production `handle` accepts `iii_sdk::IIIClient` and /// constructs the backend internally. Tests inject the TriggerFwd /// directly to avoid spinning up an engine. The implementation /// factors out a `spawn_sandbox_job` helper that this shim calls. diff --git a/shell/src/functions/fs_chmod.rs b/shell/src/functions/fs_chmod.rs index e60bf9cdf..7527aeb48 100644 --- a/shell/src/functions/fs_chmod.rs +++ b/shell/src/functions/fs_chmod.rs @@ -8,13 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: ChmodRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad chmod payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.chmod(args).await.map_err(iii_sdk::IIIError::from) + backend + .chmod(args) + .await + .map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_dispatch.rs b/shell/src/functions/fs_dispatch.rs index 5e31e3c47..d8b066fcb 100644 --- a/shell/src/functions/fs_dispatch.rs +++ b/shell/src/functions/fs_dispatch.rs @@ -10,7 +10,7 @@ use crate::fs::{FsBackend, Target}; pub fn pick_backend( target: Target, host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, ) -> Arc { match target { @@ -19,7 +19,7 @@ pub fn pick_backend( } } -fn sandbox_for(id: Uuid, iii: iii_sdk::III, enabled: bool) -> Arc { +fn sandbox_for(id: Uuid, iii: iii_sdk::IIIClient, enabled: bool) -> Arc { Arc::new(SandboxFsBackend::new( Arc::new(IiiTriggerFwd::new(iii)), enabled, diff --git a/shell/src/functions/fs_grep.rs b/shell/src/functions/fs_grep.rs index 74dbd42a9..2b03c142c 100644 --- a/shell/src/functions/fs_grep.rs +++ b/shell/src/functions/fs_grep.rs @@ -8,13 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: GrepRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad grep payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.grep(args).await.map_err(iii_sdk::IIIError::from) + backend + .grep(args) + .await + .map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_ls.rs b/shell/src/functions/fs_ls.rs index 13f18cf15..0043ea36d 100644 --- a/shell/src/functions/fs_ls.rs +++ b/shell/src/functions/fs_ls.rs @@ -8,16 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { // Both the payload-deser error (S210) and the backend error carry their - // S-code to the wire `code` via `From for IIIError` (Remote), so + // S-code to the wire `code` via `From for Error` (Remote), so // an agent can branch on `error.code` instead of parsing the message. let req: LsRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad ls payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.ls(args).await.map_err(iii_sdk::IIIError::from) + backend.ls(args).await.map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_mkdir.rs b/shell/src/functions/fs_mkdir.rs index 341ef86fa..67dbbcfee 100644 --- a/shell/src/functions/fs_mkdir.rs +++ b/shell/src/functions/fs_mkdir.rs @@ -8,13 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: MkdirRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad mkdir payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.mkdir(args).await.map_err(iii_sdk::IIIError::from) + backend + .mkdir(args) + .await + .map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_mv.rs b/shell/src/functions/fs_mv.rs index 939452607..6024838f2 100644 --- a/shell/src/functions/fs_mv.rs +++ b/shell/src/functions/fs_mv.rs @@ -8,13 +8,13 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: MvRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad mv payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.mv(args).await.map_err(iii_sdk::IIIError::from) + backend.mv(args).await.map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_read.rs b/shell/src/functions/fs_read.rs index 59507ade9..8b53026b0 100644 --- a/shell/src/functions/fs_read.rs +++ b/shell/src/functions/fs_read.rs @@ -8,14 +8,17 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: ReadRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad read payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - let resp = backend.read(args).await.map_err(iii_sdk::IIIError::from)?; + let resp = backend + .read(args) + .await + .map_err(iii_sdk::errors::Error::from)?; Ok(resp.into()) } diff --git a/shell/src/functions/fs_rm.rs b/shell/src/functions/fs_rm.rs index 38cff0f41..53f331a98 100644 --- a/shell/src/functions/fs_rm.rs +++ b/shell/src/functions/fs_rm.rs @@ -8,13 +8,13 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: RmRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad rm payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.rm(args).await.map_err(iii_sdk::IIIError::from) + backend.rm(args).await.map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_sed.rs b/shell/src/functions/fs_sed.rs index 5181b4dd9..5b5449be9 100644 --- a/shell/src/functions/fs_sed.rs +++ b/shell/src/functions/fs_sed.rs @@ -8,13 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: SedRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad sed payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.sed(args).await.map_err(iii_sdk::IIIError::from) + backend + .sed(args) + .await + .map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_stat.rs b/shell/src/functions/fs_stat.rs index 6dd7b3b75..6564324d1 100644 --- a/shell/src/functions/fs_stat.rs +++ b/shell/src/functions/fs_stat.rs @@ -8,13 +8,16 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: StatRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad stat payload: {e}")))?; let (target, args) = req.split(); let backend = pick_backend(target, host, iii, sandbox_enabled); - backend.stat(args).await.map_err(iii_sdk::IIIError::from) + backend + .stat(args) + .await + .map_err(iii_sdk::errors::Error::from) } diff --git a/shell/src/functions/fs_write.rs b/shell/src/functions/fs_write.rs index 653dc024f..0268b9a17 100644 --- a/shell/src/functions/fs_write.rs +++ b/shell/src/functions/fs_write.rs @@ -8,13 +8,13 @@ use crate::functions::fs_dispatch::pick_backend; pub async fn handle( host: Arc, - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, sandbox_enabled: bool, payload: Value, -) -> Result { +) -> Result { let req: WriteRequest = serde_json::from_value(payload) .map_err(|e| FsError::new("S210", format!("bad write payload: {e}")))?; - let (target, mut specs, batch) = req.into_specs().map_err(iii_sdk::IIIError::from)?; + let (target, mut specs, batch) = req.into_specs().map_err(iii_sdk::errors::Error::from)?; let backend = pick_backend(target, host, iii, sandbox_enabled); // Single-file form (`path`+`content`): return the backend response verbatim @@ -26,7 +26,7 @@ pub async fn handle( return backend .write(specs.pop().unwrap()) .await - .map_err(iii_sdk::IIIError::from); + .map_err(iii_sdk::errors::Error::from); } // Batch: write each file in order, aggregating per-file results. A failure @@ -34,7 +34,10 @@ pub async fn handle( let mut files = Vec::with_capacity(specs.len()); let mut total: u64 = 0; for spec in specs { - let r = backend.write(spec).await.map_err(iii_sdk::IIIError::from)?; + let r = backend + .write(spec) + .await + .map_err(iii_sdk::errors::Error::from)?; total += r.bytes_written; files.push(WriteFileResult { path: r.path, diff --git a/shell/src/functions/kill.rs b/shell/src/functions/kill.rs index 7d8cb38ca..af289a991 100644 --- a/shell/src/functions/kill.rs +++ b/shell/src/functions/kill.rs @@ -4,7 +4,7 @@ use crate::jobs::{self, JobStatus}; pub async fn handle(req: KillRequest) -> Result { // Return the TYPED ExecError (not its JSON string): main.rs's - // `.map_err(IIIError::from)` lifts it to `IIIError::Remote`, so the S-code + // `.map_err(Error::from)` lifts it to `Error::Remote`, so the S-code // lands as the top-level wire `code` and an agent's single shell:: error // handler works here too. job-not-found maps to S211; operational kill // failures below use S216 (the exec/fs "other io" code). @@ -110,7 +110,7 @@ mod missing_job_tests { } /// Pin the wire contract: the handler's `Err` lifts to - /// `IIIError::Remote { code: "S211", .. }`, which the engine SDK maps to + /// `Error::Remote { code: "S211", .. }`, which the engine SDK maps to /// the wire `code` verbatim — NOT the `invocation_failed`/Handler collapse. #[tokio::test] async fn killing_missing_job_lifts_to_remote_s211() { @@ -119,12 +119,12 @@ mod missing_job_tests { }) .await .expect_err("missing job must error"); - match iii_sdk::IIIError::from(err) { - iii_sdk::IIIError::Remote { code, message, .. } => { + match iii_sdk::errors::Error::from(err) { + iii_sdk::errors::Error::Remote { code, message, .. } => { assert_eq!(code, "S211"); assert!(message.contains("no such job")); } - other => panic!("expected IIIError::Remote, got {other:?}"), + other => panic!("expected Error::Remote, got {other:?}"), } } } diff --git a/shell/src/functions/status.rs b/shell/src/functions/status.rs index 8b8ff4b47..569bb26e5 100644 --- a/shell/src/functions/status.rs +++ b/shell/src/functions/status.rs @@ -4,7 +4,7 @@ use crate::jobs; pub async fn handle(req: StatusRequest) -> Result { // Return the TYPED ExecError (not its JSON string): main.rs's - // `.map_err(IIIError::from)` lifts it to `IIIError::Remote`, so the S-code + // `.map_err(Error::from)` lifts it to `Error::Remote`, so the S-code // (S211 for job-not-found) lands as the top-level wire `code` — an agent // runs one error handler across every shell:: call instead of branching on // a plain-string contract for status/kill alone. @@ -36,7 +36,7 @@ mod tests { } /// Pin the wire contract: the handler's `Err` lifts to - /// `IIIError::Remote { code: "S211", .. }`, which the engine SDK maps to + /// `Error::Remote { code: "S211", .. }`, which the engine SDK maps to /// the wire `code` verbatim — NOT the `invocation_failed`/Handler collapse. #[tokio::test] async fn status_missing_job_lifts_to_remote_s211() { @@ -45,12 +45,12 @@ mod tests { }) .await .expect_err("missing job must error"); - match iii_sdk::IIIError::from(err) { - iii_sdk::IIIError::Remote { code, message, .. } => { + match iii_sdk::errors::Error::from(err) { + iii_sdk::errors::Error::Remote { code, message, .. } => { assert_eq!(code, "S211"); assert!(message.contains("no such job")); } - other => panic!("expected IIIError::Remote, got {other:?}"), + other => panic!("expected Error::Remote, got {other:?}"), } } } diff --git a/shell/src/main.rs b/shell/src/main.rs index 0a4121f47..82e41847e 100644 --- a/shell/src/main.rs +++ b/shell/src/main.rs @@ -1,7 +1,8 @@ use anyhow::{Context, Result}; use clap::Parser; -use iii_observability::OtelConfig; -use iii_sdk::{register_worker, IIIError, InitOptions, RegisterFunction}; +use iii_helpers::observability::OtelConfig; +use iii_sdk::errors::Error; +use iii_sdk::{register_worker, InitOptions, RegisterFunction}; use serde_json::Value; mod config; @@ -50,7 +51,7 @@ async fn main() -> Result<()> { .init(); let cli = Cli::parse(); - tracing::info!(url = %cli.url, seed_config = %cli.config, "connecting to III engine"); + tracing::info!(url = %cli.url, seed_config = %cli.config, "connecting to IIIClient engine"); let iii = register_worker( &cli.url, @@ -139,7 +140,7 @@ async fn main() -> Result<()> { let st = st.clone(); telemetry::record_call("shell::exec", async move { let cfg = { st.runtime.read().await.config.clone() }; - // handle already returns Result<_, IIIError> (S-codes lifted + // handle already returns Result<_, Error> (S-codes lifted // to Remote inside); no map_err needed. let res = functions::exec::handle(cfg, st.iii.clone(), req).await; // Truncation is only visible on the typed Ok response (the @@ -184,7 +185,7 @@ async fn main() -> Result<()> { let cfg = { st.runtime.read().await.config.clone() }; functions::exec_bg::handle(cfg, st.iii.clone(), req) .await - .map_err(IIIError::from) + .map_err(Error::from) }) }) .description( @@ -210,7 +211,7 @@ async fn main() -> Result<()> { "shell::kill", RegisterFunction::new_async(|req: KillRequest| { telemetry::record_call("shell::kill", async move { - functions::kill::handle(req).await.map_err(IIIError::from) + functions::kill::handle(req).await.map_err(Error::from) }) }) .description( @@ -224,7 +225,7 @@ async fn main() -> Result<()> { "shell::status", RegisterFunction::new_async(|req: StatusRequest| { telemetry::record_call("shell::status", async move { - functions::status::handle(req).await.map_err(IIIError::from) + functions::status::handle(req).await.map_err(Error::from) }) }) .description( @@ -245,7 +246,7 @@ async fn main() -> Result<()> { let st = st.clone(); telemetry::record_call("shell::list", async move { let cfg = { st.runtime.read().await.config.clone() }; - functions::list::handle(cfg).await.map_err(IIIError::from) + functions::list::handle(cfg).await.map_err(Error::from) }) }) .request_format(schema_value::()) @@ -266,7 +267,7 @@ async fn main() -> Result<()> { let st = st.clone(); telemetry::record_call("shell::config-status", async move { let status = { st.reload_status.read().await.clone() }; - Ok::(serde_json::to_value(status)?) + Ok::(serde_json::to_value(status)?) }) }) .request_format(schema_value::()) @@ -343,7 +344,7 @@ fn spawn_job_reaper(state: AppState) { /// Register the 10 shell::fs::* functions. Each keeps a `Value` handler (so the /// inline S210 mapping survives), reads the live host backend + sandbox toggle /// from AppState, and publishes its typed schema via request/response_format. -fn register_fs(iii: &iii_sdk::III, state: &AppState) { +fn register_fs(iii: &iii_sdk::IIIClient, state: &AppState) { macro_rules! fs_fn { ($id:literal, $module:ident, $req:ty, $resp:ty, $desc:expr) => {{ let st = state.clone(); @@ -359,7 +360,7 @@ fn register_fs(iii: &iii_sdk::III, state: &AppState) { let rt = st.runtime.read().await; (rt.host_backend.clone(), rt.config.sandbox.enabled) }; - // handle already returns Result<_, IIIError> (FsError + // handle already returns Result<_, Error> (FsError // S-codes lifted to Remote inside); no map_err needed. functions::$module::handle(host, st.iii.clone(), sb_enabled, req).await }) diff --git a/shell/src/scode.rs b/shell/src/scode.rs index 5f8d4d22e..72bc3d702 100644 --- a/shell/src/scode.rs +++ b/shell/src/scode.rs @@ -2,10 +2,10 @@ //! //! Both `fs::sandbox` and `exec::sandbox` forward ops to the engine //! daemon and must translate the engine's error envelope (an -//! `IIIError`, or a stringified `{code,message}` payload) back into a +//! `Error`, or a stringified `{code,message}` payload) back into a //! worker error type. The translation logic — scanning for an S-code, //! canonicalizing it against the known table, and lifting an -//! `IIIError` into a worker error — was previously duplicated in both +//! `Error` into a worker error — was previously duplicated in both //! modules. The two copies of the canonical-code table had already //! diverged: each path recognized a different subset of S-codes, so //! the same engine code could canonicalize to its specific value on @@ -17,7 +17,7 @@ //! `ExecError::new`; both share the SAME canonical table — the union //! of every code either path legitimately handles. -use iii_sdk::IIIError; +use iii_sdk::errors::Error; use serde_json::Value; /// The generic fallback code for any engine error whose S-code we do @@ -99,7 +99,7 @@ pub fn scan_s_code(s: &str) -> Option<&str> { None } -/// Recover a canonical S-code from an `IIIError` and lift it into the +/// Recover a canonical S-code from an `Error` and lift it into the /// caller's error type via `make`. /// /// Engine `Remote` errors carry the code structurally; the engine's @@ -110,15 +110,15 @@ pub fn scan_s_code(s: &str) -> Option<&str> { /// Generic over the error constructor so both `FsError::new` and /// `ExecError::new` reuse the identical recovery logic; the only /// difference between the fs and exec paths is which `make` is passed. -pub fn map_iii_err(err: &IIIError, make: impl Fn(&'static str, String) -> E) -> E { +pub fn map_iii_err(err: &Error, make: impl Fn(&'static str, String) -> E) -> E { match err { - IIIError::Remote { code, message, .. } if code.starts_with('S') => { + Error::Remote { code, message, .. } if code.starts_with('S') => { return make( map_static_code(code), format!("forwarded from engine: {message}"), ); } - IIIError::Remote { message, .. } => { + Error::Remote { message, .. } => { if let Some(c) = scan_s_code(message) { return make( map_static_code(c), @@ -126,7 +126,7 @@ pub fn map_iii_err(err: &IIIError, make: impl Fn(&'static str, String) -> E) ); } } - IIIError::Handler(s) => { + Error::Handler(s) => { if let Ok(parsed) = serde_json::from_str::(s) { if let Some(c) = parsed.get("code").and_then(|v| v.as_str()) { let msg = parsed.get("message").and_then(|v| v.as_str()).unwrap_or(""); @@ -210,7 +210,7 @@ mod tests { let exec_make = |code: &'static str, msg: String| ("exec", code, msg); for (input, expected) in canonical_cases() { - let remote = IIIError::Remote { + let remote = Error::Remote { code: input.to_string(), message: "boom".into(), stacktrace: None, @@ -258,7 +258,7 @@ mod tests { #[test] fn map_iii_err_recovers_from_wrapped_message() { - let err = IIIError::Remote { + let err = Error::Remote { code: "invocation_failed".into(), message: "handler error: S211: not found".into(), stacktrace: None, @@ -270,21 +270,21 @@ mod tests { #[test] fn map_iii_err_recovers_from_handler_json() { - let err = IIIError::Handler(r#"{"code":"S214","message":"directory not empty"}"#.into()); + let err = Error::Handler(r#"{"code":"S214","message":"directory not empty"}"#.into()); let (code, _) = map_iii_err(&err, |c, m| (c, m)); assert_eq!(code, "S214"); } #[test] fn map_iii_err_recovers_from_handler_raw_scan() { - let err = IIIError::Handler("something broke S217 bad regex".into()); + let err = Error::Handler("something broke S217 bad regex".into()); let (code, _) = map_iii_err(&err, |c, m| (c, m)); assert_eq!(code, "S217"); } #[test] fn map_iii_err_unknown_falls_back_to_s216() { - let err = IIIError::Handler("just a plain old string".into()); + let err = Error::Handler("just a plain old string".into()); let (code, _) = map_iii_err(&err, |c, m| (c, m)); assert_eq!(code, "S216"); } diff --git a/shell/src/telemetry.rs b/shell/src/telemetry.rs index a99ff9201..d654f9130 100644 --- a/shell/src/telemetry.rs +++ b/shell/src/telemetry.rs @@ -20,9 +20,11 @@ use std::future::Future; use std::time::Instant; -use iii_observability::opentelemetry::metrics::{Counter, Histogram, Meter, ObservableGauge}; -use iii_observability::opentelemetry::{global, KeyValue}; -use iii_sdk::IIIError; +use iii_helpers::observability::opentelemetry::metrics::{ + Counter, Histogram, Meter, ObservableGauge, +}; +use iii_helpers::observability::opentelemetry::{global, KeyValue}; +use iii_sdk::errors::Error; use once_cell::sync::Lazy; use tracing::Instrument; @@ -96,7 +98,7 @@ pub const OUTCOME_OK: &str = "ok"; pub const OUTCOME_ERROR: &str = "error"; /// Fine-grained code label used when a call fails without a coded remote error -/// (e.g. an argv-parse or allowlist rejection surfaced as `IIIError::Handler`). +/// (e.g. an argv-parse or allowlist rejection surfaced as `Error::Handler`). pub const CODE_INVOCATION_FAILED: &str = "invocation_failed"; /// Pure classification of a handler result into the `(outcome, code)` pair used @@ -106,10 +108,10 @@ pub const CODE_INVOCATION_FAILED: &str = "invocation_failed"; /// - `Ok(_)` -> (`ok`, `ok`) /// - `Err(Remote { code, .. })` -> (`error`, the S-code verbatim) /// - `Err(any other variant)` -> (`error`, `invocation_failed`) -pub fn classify(result: &Result) -> (&'static str, String) { +pub fn classify(result: &Result) -> (&'static str, String) { match result { Ok(_) => (OUTCOME_OK, OUTCOME_OK.to_string()), - Err(IIIError::Remote { code, .. }) => (OUTCOME_ERROR, code.clone()), + Err(Error::Remote { code, .. }) => (OUTCOME_ERROR, code.clone()), Err(_) => (OUTCOME_ERROR, CODE_INVOCATION_FAILED.to_string()), } } @@ -122,9 +124,9 @@ pub fn classify(result: &Result) -> (&'static str, String) { /// `Result` to derive labels. The future is awaited inside an `info_span!` /// carrying `function_id` so logs/traces emitted by the handler correlate with /// the same call. -pub async fn record_call(function_id: &'static str, fut: F) -> Result +pub async fn record_call(function_id: &'static str, fut: F) -> Result where - F: Future>, + F: Future>, { // `.instrument()` (not `span.enter()`) attaches the span to the future // across await points without holding a `!Send` `Entered` guard, keeping @@ -173,7 +175,7 @@ mod tests { #[test] fn classify_ok_is_ok_ok() { - let result: Result = Ok(7); + let result: Result = Ok(7); let (outcome, code) = classify(&result); assert_eq!(outcome, OUTCOME_OK); assert_eq!(code, OUTCOME_OK); @@ -181,7 +183,7 @@ mod tests { #[test] fn classify_remote_derives_outcome_error_and_the_scode() { - let result: Result = Err(IIIError::Remote { + let result: Result = Err(Error::Remote { code: "S215".to_string(), message: "jail/denylist".to_string(), stacktrace: None, @@ -194,14 +196,14 @@ mod tests { #[test] fn classify_non_coded_error_falls_back_to_invocation_failed() { // An argv-parse / allowlist rejection surfaces as Handler (no S-code). - let result: Result = - Err(IIIError::Handler("argv: command not allowed".to_string())); + let result: Result = + Err(Error::Handler("argv: command not allowed".to_string())); let (outcome, code) = classify(&result); assert_eq!(outcome, OUTCOME_ERROR); assert_eq!(code, CODE_INVOCATION_FAILED); // A timeout (distinct non-Remote variant) classifies the same way. - let timed_out: Result = Err(IIIError::Timeout); + let timed_out: Result = Err(Error::Timeout); let (outcome, code) = classify(&timed_out); assert_eq!(outcome, OUTCOME_ERROR); assert_eq!(code, CODE_INVOCATION_FAILED); diff --git a/shell/src/triggers.rs b/shell/src/triggers.rs index 16cd6caa5..f778b14d1 100644 --- a/shell/src/triggers.rs +++ b/shell/src/triggers.rs @@ -1,30 +1,31 @@ //! Shared `iii.trigger` indirection. Both `fs::sandbox::SandboxFsBackend` //! and `exec::sandbox::SandboxExecBackend` (landing in T6) consume //! `Arc` so unit tests can inject a stub. The production -//! wiring is `IiiTriggerFwd` wrapping a real `iii_sdk::III`. +//! wiring is `IiiTriggerFwd` wrapping a real `iii_sdk::IIIClient`. use async_trait::async_trait; -use iii_sdk::{IIIError, TriggerRequest}; +use iii_sdk::errors::Error; +use iii_sdk::protocol::TriggerRequest; use serde_json::Value; #[async_trait] pub trait TriggerFwd: Send + Sync { - async fn trigger(&self, function_id: &str, payload: Value) -> Result; + async fn trigger(&self, function_id: &str, payload: Value) -> Result; } pub struct IiiTriggerFwd { - iii: iii_sdk::III, + iii: iii_sdk::IIIClient, } impl IiiTriggerFwd { - pub fn new(iii: iii_sdk::III) -> Self { + pub fn new(iii: iii_sdk::IIIClient) -> Self { Self { iii } } } #[async_trait] impl TriggerFwd for IiiTriggerFwd { - async fn trigger(&self, function_id: &str, payload: Value) -> Result { + async fn trigger(&self, function_id: &str, payload: Value) -> Result { self.iii .trigger(TriggerRequest { function_id: function_id.to_string(), @@ -48,7 +49,7 @@ mod tests { #[async_trait] impl TriggerFwd for CountingFwd { - async fn trigger(&self, _fid: &str, _payload: Value) -> Result { + async fn trigger(&self, _fid: &str, _payload: Value) -> Result { *self.count.lock().unwrap() += 1; Ok(json!({"ok": true})) } diff --git a/shell/tests/function_handlers.rs b/shell/tests/function_handlers.rs index 3d8b84019..fb312b2a8 100644 --- a/shell/tests/function_handlers.rs +++ b/shell/tests/function_handlers.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use iii_sdk::III; +use iii_sdk::IIIClient; use serde_json::{json, Value}; use shell::config::ShellConfig; @@ -32,8 +32,8 @@ fn cfg_with_allow(allow: &[&str]) -> Arc { Arc::new(c) } -fn fresh_iii() -> III { - III::new("ws://stub-not-connected:0") +fn fresh_iii() -> IIIClient { + IIIClient::new("ws://stub-not-connected:0") } fn tmpdir(prefix: &str) -> std::path::PathBuf { @@ -115,11 +115,11 @@ async fn exec_handler_rejects_unlisted_command() { .await .unwrap_err(); // Allowlist rejections are intentionally plain-string (no S-code): they - // flow through `From` to `IIIError::Handler`, which the engine maps + // flow through `From` to `Error::Handler`, which the engine maps // to `code: "invocation_failed"` with the violation in the message — NOT a // Remote S-code. Assert that contract so the split stays explicit. assert!( - matches!(err, iii_sdk::IIIError::Handler(_)), + matches!(err, iii_sdk::errors::Error::Handler(_)), "allowlist rejection must be the plain-string Handler path, got {err:?}" ); assert!(err.to_string().contains("allowlist"), "got: {err}"); @@ -243,13 +243,13 @@ async fn status_handler_rejects_unknown_job_id() { .await .unwrap_err(); // The handler now returns the TYPED ExecError carrying the S-code, and it - // lifts to `IIIError::Remote { code, .. }` so the engine maps the S-code to + // lifts to `Error::Remote { code, .. }` so the engine maps the S-code to // the wire `code` verbatim (not the old `invocation_failed`/Handler collapse). assert_eq!(err.code, "S211"); assert!(err.message.contains("no such job")); - match iii_sdk::IIIError::from(err) { - iii_sdk::IIIError::Remote { code, .. } => assert_eq!(code, "S211"), - other => panic!("expected IIIError::Remote, got {other:?}"), + match iii_sdk::errors::Error::from(err) { + iii_sdk::errors::Error::Remote { code, .. } => assert_eq!(code, "S211"), + other => panic!("expected Error::Remote, got {other:?}"), } } @@ -266,13 +266,13 @@ async fn kill_handler_rejects_unknown_job_id() { )) .await .unwrap_err(); - // Typed ExecError carrying the S-code; lifts to `IIIError::Remote` so the + // Typed ExecError carrying the S-code; lifts to `Error::Remote` so the // S-code reaches the wire `code` verbatim. assert_eq!(err.code, "S211"); assert!(err.message.contains("no such job")); - match iii_sdk::IIIError::from(err) { - iii_sdk::IIIError::Remote { code, .. } => assert_eq!(code, "S211"), - other => panic!("expected IIIError::Remote, got {other:?}"), + match iii_sdk::errors::Error::from(err) { + iii_sdk::errors::Error::Remote { code, .. } => assert_eq!(code, "S211"), + other => panic!("expected Error::Remote, got {other:?}"), } } @@ -346,8 +346,11 @@ fn fs_host_backend() -> Arc { struct StubChan; #[async_trait::async_trait] impl shell::fs::host::ChannelMaker for StubChan { - async fn create_channel(&self, _: usize) -> Result { - Err(iii_sdk::IIIError::Handler("stub channel".into())) + async fn create_channel( + &self, + _: usize, + ) -> Result { + Err(iii_sdk::errors::Error::Handler("stub channel".into())) } fn engine_address(&self) -> String { "ws://stub:0".into() @@ -553,21 +556,21 @@ async fn fs_dispatch_split_target_rejects_unknown_kind() { // The S210 payload-deser code is now the top-level wire `code` (Remote), // not buried in a stringified-JSON message. match err { - iii_sdk::IIIError::Remote { code, .. } => assert_eq!(code, "S210"), - other => panic!("expected IIIError::Remote {{ code: S210 }}, got {other:?}"), + iii_sdk::errors::Error::Remote { code, .. } => assert_eq!(code, "S210"), + other => panic!("expected Error::Remote {{ code: S210 }}, got {other:?}"), } } #[tokio::test] async fn fs_handler_rejects_bad_payload_shape() { // path must be a string. Hits the S210 mapping in fs_ls::handle, which now - // lifts to `IIIError::Remote { code: "S210", .. }`. + // lifts to `Error::Remote { code: "S210", .. }`. let err = functions::fs_ls::handle(fs_host_backend(), fresh_iii(), true, json!({"path": 42})) .await .unwrap_err(); match err { - iii_sdk::IIIError::Remote { code, .. } => assert_eq!(code, "S210"), - other => panic!("expected IIIError::Remote {{ code: S210 }}, got {other:?}"), + iii_sdk::errors::Error::Remote { code, .. } => assert_eq!(code, "S210"), + other => panic!("expected Error::Remote {{ code: S210 }}, got {other:?}"), } } diff --git a/shell/tests/host_fs_branches.rs b/shell/tests/host_fs_branches.rs index 7477a83ea..63cde6c3f 100644 --- a/shell/tests/host_fs_branches.rs +++ b/shell/tests/host_fs_branches.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use async_trait::async_trait; -use iii_sdk::{Channel, IIIError}; +use iii_sdk::channel::Channel; +use iii_sdk::errors::Error; use shell::fs::host::{ChannelMaker, HostFsBackend, HostFsConfig}; use shell::fs::{ChmodArgs, FsBackend, GrepArgs, MkdirArgs, MvArgs, RmArgs, SedArgs, StatArgs}; @@ -13,8 +14,8 @@ struct StubChan; #[async_trait] impl ChannelMaker for StubChan { - async fn create_channel(&self, _: usize) -> Result { - Err(IIIError::Handler( + async fn create_channel(&self, _: usize) -> Result { + Err(Error::Handler( "stub channel maker — non-streaming tests only".into(), )) } diff --git a/shell/tests/sandbox_dispatch.rs b/shell/tests/sandbox_dispatch.rs index fcc6b87bd..e784a4b89 100644 --- a/shell/tests/sandbox_dispatch.rs +++ b/shell/tests/sandbox_dispatch.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; -use iii_sdk::IIIError; +use iii_sdk::errors::Error; use serde_json::{json, Value}; use uuid::Uuid; @@ -44,12 +44,12 @@ impl StubFwd { #[async_trait] impl TriggerFwd for StubFwd { - async fn trigger(&self, fid: &str, payload: Value) -> Result { + async fn trigger(&self, fid: &str, payload: Value) -> Result { self.captured.lock().unwrap().push((fid.into(), payload)); match self.next.lock().unwrap().take() { Some(Response::Ok(v)) => Ok(v), - Some(Response::HandlerErr(s)) => Err(IIIError::Handler(s)), - Some(Response::RemoteErr { code, message }) => Err(IIIError::Remote { + Some(Response::HandlerErr(s)) => Err(Error::Handler(s)), + Some(Response::RemoteErr { code, message }) => Err(Error::Remote { code, message, stacktrace: None, diff --git a/shell/tests/sandbox_exec_dispatch.rs b/shell/tests/sandbox_exec_dispatch.rs index a57b3838e..4c5b45cc3 100644 --- a/shell/tests/sandbox_exec_dispatch.rs +++ b/shell/tests/sandbox_exec_dispatch.rs @@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; -use iii_sdk::IIIError; +use iii_sdk::errors::Error; use serde_json::{json, Value}; use uuid::Uuid; @@ -17,11 +17,11 @@ use shell::triggers::TriggerFwd; struct StubFwd { captured: Mutex>, - next: Mutex>>, + next: Mutex>>, } impl StubFwd { - fn new(resp: Result) -> Arc { + fn new(resp: Result) -> Arc { Arc::new(Self { captured: Mutex::new(Vec::new()), next: Mutex::new(Some(resp)), @@ -34,7 +34,7 @@ impl StubFwd { #[async_trait] impl TriggerFwd for StubFwd { - async fn trigger(&self, fid: &str, payload: Value) -> Result { + async fn trigger(&self, fid: &str, payload: Value) -> Result { self.captured.lock().unwrap().push((fid.into(), payload)); self.next .lock()