diff --git a/lib/crates/fabro-types/src/settings/sandbox.rs b/lib/crates/fabro-sandbox/src/config.rs similarity index 54% rename from lib/crates/fabro-types/src/settings/sandbox.rs rename to lib/crates/fabro-sandbox/src/config.rs index f352f8352..479b9a2e5 100644 --- a/lib/crates/fabro-types/src/settings/sandbox.rs +++ b/lib/crates/fabro-sandbox/src/config.rs @@ -1,5 +1,20 @@ +//! Sandbox configuration runtime types. +//! +//! These types are the runtime shape that the sandbox providers consume. +//! The v2 parse tree lives in `fabro_types::settings::v2::run::RunSandboxLayer`. +//! Conversion from the v2 shape lives in [`bridge_sandbox`]. +//! +//! The `DaytonaSettings`/`DaytonaSnapshotSettings` names are kept for +//! backward compatibility with the old import path; [`crate::daytona`] +//! continues to re-export them under `DaytonaConfig`/`DaytonaSnapshotConfig` +//! aliases. + use std::collections::HashMap; +use fabro_types::settings::v2::InterpString; +use fabro_types::settings::v2::run::{ + DaytonaDockerfileLayer, DaytonaNetworkLayer, RunSandboxLayer, WorktreeMode as V2WorktreeMode, +}; use serde::de::{self, MapAccess, Visitor}; use serde::{Deserialize, Serialize}; @@ -13,7 +28,7 @@ pub struct DaytonaSettings { pub skip_clone: bool, } -#[derive(Clone, Debug, PartialEq, crate::Combine)] +#[derive(Clone, Debug, PartialEq)] pub enum DaytonaNetwork { Block, AllowAll, @@ -98,7 +113,7 @@ impl<'de> Deserialize<'de> for DaytonaNetwork { } } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, crate::Combine)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum DockerfileSource { Inline(String), @@ -114,7 +129,7 @@ pub struct DaytonaSnapshotSettings { pub dockerfile: Option, } -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize, crate::Combine)] +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum WorktreeMode { Always, @@ -139,3 +154,81 @@ pub struct SandboxSettings { pub daytona: Option, pub env: Option>, } + +/// Convert a v2 [`RunSandboxLayer`] into the runtime [`SandboxSettings`] shape. +#[must_use] +pub fn bridge_sandbox(sb: &RunSandboxLayer) -> SandboxSettings { + SandboxSettings { + provider: sb.provider.clone(), + preserve: sb.preserve, + devcontainer: sb.devcontainer, + local: sb.local.as_ref().map(|local| LocalSandboxSettings { + worktree_mode: local + .worktree_mode + .map(bridge_worktree_mode) + .unwrap_or_default(), + }), + daytona: sb.daytona.as_ref().map(|d| DaytonaSettings { + auto_stop_interval: d.auto_stop_interval, + labels: if d.labels.is_empty() { + None + } else { + Some(d.labels.clone()) + }, + snapshot: d.snapshot.as_ref().and_then(|s| { + s.name.as_ref().map(|name| DaytonaSnapshotSettings { + name: name.clone(), + cpu: s.cpu, + memory: s.memory.map(|sz| size_to_gb_i32(sz.as_bytes())), + disk: s.disk.map(|sz| size_to_gb_i32(sz.as_bytes())), + dockerfile: s.dockerfile.as_ref().map(|d| match d { + DaytonaDockerfileLayer::Inline(text) => { + DockerfileSource::Inline(text.clone()) + } + DaytonaDockerfileLayer::Path { path } => { + DockerfileSource::Path { path: path.clone() } + } + }), + }) + }), + network: d.network.as_ref().map(|n| match n { + DaytonaNetworkLayer::Block => DaytonaNetwork::Block, + DaytonaNetworkLayer::AllowAll => DaytonaNetwork::AllowAll, + DaytonaNetworkLayer::AllowList { allow_list } => { + DaytonaNetwork::AllowList(allow_list.clone()) + } + }), + skip_clone: d.skip_clone.unwrap_or(false), + }), + env: if sb.env.is_empty() { + None + } else { + Some( + sb.env + .iter() + .map(|(k, v)| (k.clone(), interp_to_string(v))) + .collect(), + ) + }, + } +} + +/// Convert a v2 [`V2WorktreeMode`] into the runtime [`WorktreeMode`]. +#[must_use] +pub fn bridge_worktree_mode(m: V2WorktreeMode) -> WorktreeMode { + match m { + V2WorktreeMode::Always => WorktreeMode::Always, + V2WorktreeMode::Clean => WorktreeMode::Clean, + V2WorktreeMode::Dirty => WorktreeMode::Dirty, + V2WorktreeMode::Never => WorktreeMode::Never, + } +} + +fn interp_to_string(value: &InterpString) -> String { + value.as_source() +} + +fn size_to_gb_i32(bytes: u64) -> i32 { + let gb = bytes / 1_000_000_000; + i32::try_from(gb).unwrap_or(i32::MAX) +} diff --git a/lib/crates/fabro-sandbox/src/daytona/mod.rs b/lib/crates/fabro-sandbox/src/daytona/mod.rs index 1d58f8da3..c580d6bdb 100644 --- a/lib/crates/fabro-sandbox/src/daytona/mod.rs +++ b/lib/crates/fabro-sandbox/src/daytona/mod.rs @@ -22,7 +22,7 @@ use tokio_util::sync::CancellationToken; const WORKING_DIRECTORY: &str = "/home/daytona/workspace"; const DEFAULT_SNAPSHOT: &str = "daytona-medium"; -pub use fabro_types::settings::sandbox::{ +pub use crate::config::{ DaytonaNetwork, DaytonaSettings as DaytonaConfig, DaytonaSnapshotSettings as DaytonaSnapshotConfig, DockerfileSource, }; diff --git a/lib/crates/fabro-sandbox/src/lib.rs b/lib/crates/fabro-sandbox/src/lib.rs index fec1dd390..cfb14befd 100644 --- a/lib/crates/fabro-sandbox/src/lib.rs +++ b/lib/crates/fabro-sandbox/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod sandbox; pub mod sandbox_spec; diff --git a/lib/crates/fabro-sandbox/src/sandbox_spec.rs b/lib/crates/fabro-sandbox/src/sandbox_spec.rs index c05f7aa56..61f9c5aeb 100644 --- a/lib/crates/fabro-sandbox/src/sandbox_spec.rs +++ b/lib/crates/fabro-sandbox/src/sandbox_spec.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; use std::sync::Arc; -use fabro_types::{RunId, settings::WorktreeMode}; +use crate::config::WorktreeMode; +use fabro_types::RunId; #[cfg(any(feature = "docker", feature = "daytona"))] use anyhow::anyhow; diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs index 132a48ab7..fb7808a5c 100644 --- a/lib/crates/fabro-server/src/run_manifest.rs +++ b/lib/crates/fabro-server/src/run_manifest.rs @@ -13,6 +13,7 @@ use fabro_graphviz::graph::{Graph, is_llm_handler_type}; use fabro_graphviz::render::apply_direction; use fabro_llm::Provider; use fabro_model::Catalog; +use fabro_sandbox::config::bridge_sandbox; use fabro_sandbox::daytona::DaytonaConfig; use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec}; use fabro_types::RunId; @@ -23,7 +24,6 @@ use fabro_types::settings::v2::run::{ ApprovalMode, DaytonaDockerfileLayer, RunExecutionLayer, RunLayer, RunMode, RunModelLayer, RunSandboxLayer, }; -use fabro_types::settings::v2::to_runtime::bridge_sandbox; use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus}; use fabro_validate::Severity; use fabro_workflow::error::FabroError; diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index 383d65552..ec488758b 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -20,7 +20,6 @@ //! accessors, at which point this module goes away. pub mod run; -pub mod sandbox; pub mod server; pub mod v2; @@ -28,10 +27,6 @@ pub use run::{ ArtifactsSettings, CheckpointSettings, GitHubSettings, LlmSettings, MergeStrategy, PullRequestSettings, SetupSettings, }; -pub use sandbox::{ - DaytonaNetwork, DaytonaSettings, DaytonaSnapshotSettings, DockerfileSource, - LocalSandboxSettings, SandboxSettings, WorktreeMode, -}; pub use server::{ ApiAuthStrategy, ApiSettings, ArtifactStorageBackend, ArtifactStorageSettings, AuthProvider, AuthSettings, FeaturesSettings, GitAuthorSettings, GitProvider, GitSettings, LogSettings, diff --git a/lib/crates/fabro-types/src/settings/v2/to_runtime.rs b/lib/crates/fabro-types/src/settings/v2/to_runtime.rs index f27b251b5..192a77862 100644 --- a/lib/crates/fabro-types/src/settings/v2/to_runtime.rs +++ b/lib/crates/fabro-types/src/settings/v2/to_runtime.rs @@ -1,92 +1,24 @@ //! v2 → runtime-type conversion helpers. //! -//! The runtime types in `fabro_types::settings::{run,sandbox}` are the -//! shapes that downstream crates (fabro-workflow, fabro-sandbox) still -//! consume at runtime. Each helper here reads the v2 parse tree and -//! builds the equivalent runtime value. +//! Everything but the pull-request / artifacts / merge-strategy conversion +//! has moved out of this module into the consumer crate that owns the +//! target runtime type: //! -//! Hook bridging has moved to `fabro_hooks::config::bridge_hook`; MCP -//! bridging has moved to `fabro_mcp::config::{bridge_mcps, bridge_mcp_entry}`. -//! Consumer crates will pull the rest of these helpers into their own -//! crates in follow-up 6.3b passes. +//! - Hook bridging: [`fabro_hooks::config::bridge_hook`] +//! - MCP bridging: [`fabro_mcp::config::bridge_mcp_entry`] / +//! [`fabro_mcp::config::bridge_mcps`] +//! - Sandbox bridging: [`fabro_sandbox::config::bridge_sandbox`] / +//! [`fabro_sandbox::config::bridge_worktree_mode`] +//! +//! Pull-request / artifacts / merge-strategy still live here because their +//! target runtime types (`PullRequestSettings`, `ArtifactsSettings`, +//! `MergeStrategy`) are still in `fabro-types::settings::run`. When that +//! module moves into `fabro-workflow` the remaining helpers will follow. -use super::interp::InterpString; -use super::run::{ - MergeStrategy as V2MergeStrategy, RunArtifactsLayer, RunPullRequestLayer, RunSandboxLayer, - WorktreeMode as V2WorktreeMode, -}; +use super::run::{MergeStrategy as V2MergeStrategy, RunArtifactsLayer, RunPullRequestLayer}; use crate::settings::run::{ ArtifactsSettings, MergeStrategy as OldMergeStrategy, PullRequestSettings, }; -use crate::settings::sandbox::{ - DaytonaNetwork, DaytonaSettings, DaytonaSnapshotSettings, DockerfileSource, - LocalSandboxSettings, SandboxSettings, WorktreeMode as OldWorktreeMode, -}; - -pub fn bridge_sandbox(sb: &RunSandboxLayer) -> SandboxSettings { - SandboxSettings { - provider: sb.provider.clone(), - preserve: sb.preserve, - devcontainer: sb.devcontainer, - local: sb.local.as_ref().map(|local| LocalSandboxSettings { - worktree_mode: local - .worktree_mode - .map(bridge_worktree_mode) - .unwrap_or_default(), - }), - daytona: sb.daytona.as_ref().map(|d| DaytonaSettings { - auto_stop_interval: d.auto_stop_interval, - labels: if d.labels.is_empty() { - None - } else { - Some(d.labels.clone()) - }, - snapshot: d.snapshot.as_ref().and_then(|s| { - s.name.as_ref().map(|name| DaytonaSnapshotSettings { - name: name.clone(), - cpu: s.cpu, - memory: s.memory.map(|sz| size_to_gb_i32(sz.as_bytes())), - disk: s.disk.map(|sz| size_to_gb_i32(sz.as_bytes())), - dockerfile: s.dockerfile.as_ref().map(|d| match d { - super::run::DaytonaDockerfileLayer::Inline(text) => { - DockerfileSource::Inline(text.clone()) - } - super::run::DaytonaDockerfileLayer::Path { path } => { - DockerfileSource::Path { path: path.clone() } - } - }), - }) - }), - network: d.network.as_ref().map(|n| match n { - super::run::DaytonaNetworkLayer::Block => DaytonaNetwork::Block, - super::run::DaytonaNetworkLayer::AllowAll => DaytonaNetwork::AllowAll, - super::run::DaytonaNetworkLayer::AllowList { allow_list } => { - DaytonaNetwork::AllowList(allow_list.clone()) - } - }), - skip_clone: d.skip_clone.unwrap_or(false), - }), - env: if sb.env.is_empty() { - None - } else { - Some( - sb.env - .iter() - .map(|(k, v)| (k.clone(), interp_to_string(v))) - .collect(), - ) - }, - } -} - -pub fn bridge_worktree_mode(m: V2WorktreeMode) -> OldWorktreeMode { - match m { - V2WorktreeMode::Always => OldWorktreeMode::Always, - V2WorktreeMode::Clean => OldWorktreeMode::Clean, - V2WorktreeMode::Dirty => OldWorktreeMode::Dirty, - V2WorktreeMode::Never => OldWorktreeMode::Never, - } -} pub fn bridge_merge_strategy(m: V2MergeStrategy) -> OldMergeStrategy { match m { @@ -113,12 +45,3 @@ pub fn bridge_run_artifacts(artifacts: &RunArtifactsLayer) -> ArtifactsSettings include: artifacts.include.clone(), } } - -fn interp_to_string(value: &InterpString) -> String { - value.as_source() -} - -fn size_to_gb_i32(bytes: u64) -> i32 { - let gb = bytes / 1_000_000_000; - i32::try_from(gb).unwrap_or(i32::MAX) -} diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index b962b4d1e..55a2c140e 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -9,13 +9,13 @@ use fabro_hooks::config::bridge_hook; use fabro_interview::{AutoApproveInterviewer, Interviewer}; use fabro_mcp::config::bridge_mcp_entry; use fabro_model::{Catalog, FallbackTarget, Provider}; +use fabro_sandbox::config::{ + self as sandbox_config, WorktreeMode, bridge_sandbox, bridge_worktree_mode, +}; use fabro_sandbox::{SandboxProvider, SandboxSpec}; use fabro_types::RunId; -use fabro_types::settings::sandbox::{self as sandbox_config, WorktreeMode}; use fabro_types::settings::v2::run::ModelRefOrSplice; -use fabro_types::settings::v2::to_runtime::{ - bridge_pull_request, bridge_sandbox, bridge_worktree_mode, -}; +use fabro_types::settings::v2::to_runtime::bridge_pull_request; use fabro_types::settings::v2::{InterpString, SettingsFile}; use crate::artifact_upload::ArtifactSink; diff --git a/lib/crates/fabro-workflow/src/pipeline/types.rs b/lib/crates/fabro-workflow/src/pipeline/types.rs index d070fd0db..883093f28 100644 --- a/lib/crates/fabro-workflow/src/pipeline/types.rs +++ b/lib/crates/fabro-workflow/src/pipeline/types.rs @@ -10,8 +10,8 @@ use fabro_llm::Provider; use fabro_mcp::config::McpServerSettings; use fabro_model::FallbackTarget; use fabro_sandbox::SandboxSpec; +use fabro_sandbox::config::WorktreeMode; use fabro_types::RunId; -use fabro_types::settings::sandbox::WorktreeMode; use fabro_validate::Diagnostic; use crate::artifact_upload::ArtifactSink;