refactor(settings): stage 6.3b promote sandbox runtime types into fabro-sandbox

Moves the sandbox runtime types from `fabro-types/src/settings/sandbox.rs`
into a new `fabro-sandbox/src/config.rs` module:

- `SandboxSettings`, `LocalSandboxSettings`, `DaytonaSettings`,
  `DaytonaSnapshotSettings`, `DaytonaNetwork`, `DockerfileSource`,
  `WorktreeMode` (with the custom serde `DaytonaNetwork`
  serialize/deserialize impls intact).
- `bridge_sandbox` and `bridge_worktree_mode` (v2
  `RunSandboxLayer` → `SandboxSettings` converters) also move from
  `fabro-types/src/settings/v2/to_runtime.rs` into the new config
  module.

`fabro-sandbox/src/daytona/mod.rs` and `sandbox_spec.rs` update to
import from the crate-local `config` module instead of
`fabro_types::settings::sandbox`. The daytona module still re-exports
`DaytonaSettings as DaytonaConfig` etc., so no breaking changes for
callers of `fabro_sandbox::daytona::*`.

Consumer updates:

- `fabro-workflow/src/operations/start.rs` and `pipeline/types.rs`
  now import `WorktreeMode`, `SandboxSettings` (as `sandbox_config`
  alias), `bridge_sandbox`, and `bridge_worktree_mode` from
  `fabro_sandbox::config`.
- `fabro-server/src/run_manifest.rs` imports `bridge_sandbox` from
  `fabro_sandbox::config`.

`to_runtime.rs` in fabro-types shrinks to just the three remaining
helpers tied to the legacy `run.rs` module types
(`bridge_merge_strategy`, `bridge_pull_request`, `bridge_run_artifacts`).
Those move out in the next 6.3b pass when the `run.rs` module itself
moves.

Five of the seven legacy runtime type modules are now gone; two
remain (run, server). 3,758 workspace tests pass. `cargo fmt
--check --all` and `cargo clippy --workspace -- -D warnings` are
clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-09 18:17:06 -04:00
parent 5905befa8d
commit 0883cf77ee
9 changed files with 120 additions and 107 deletions

View file

@ -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<DockerfileSource>,
}
#[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<DaytonaSettings>,
pub env: Option<HashMap<String, String>>,
}
/// 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)
}

View file

@ -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,
};

View file

@ -1,3 +1,4 @@
pub mod config;
pub mod sandbox;
pub mod sandbox_spec;

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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)
}

View file

@ -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;

View file

@ -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;