diff --git a/lib/crates/fabro-checkpoint/src/metadata.rs b/lib/crates/fabro-checkpoint/src/metadata.rs index d1a5ed93f..35a715081 100644 --- a/lib/crates/fabro-checkpoint/src/metadata.rs +++ b/lib/crates/fabro-checkpoint/src/metadata.rs @@ -178,7 +178,7 @@ mod tests { use super::*; use chrono::{TimeZone, Utc}; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{Graph, fixtures}; /// Create a temporary git repo with an initial commit. @@ -207,7 +207,7 @@ mod tests { fn test_run_record(run_id: fabro_types::RunId) -> RunRecord { RunRecord { run_id, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: None, working_directory: PathBuf::from("/tmp"), diff --git a/lib/crates/fabro-cli/src/command_context.rs b/lib/crates/fabro-cli/src/command_context.rs index 4f4108147..f93bb9d8c 100644 --- a/lib/crates/fabro-cli/src/command_context.rs +++ b/lib/crates/fabro-cli/src/command_context.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::{Context as _, Result, bail}; -use fabro_types::settings::{CliSettings, SettingsFile}; +use fabro_types::settings::{CliSettings, SettingsLayer}; use tokio::sync::OnceCell; use crate::args::{ServerConnectionArgs, ServerTargetArgs}; @@ -24,7 +24,7 @@ pub(crate) enum ServerMode { pub(crate) struct CommandContext { cwd: PathBuf, base_config_path: PathBuf, - machine_settings: SettingsFile, + machine_settings: SettingsLayer, cli_settings: CliSettings, server_mode: ServerMode, server: OnceCell>, @@ -78,7 +78,7 @@ impl CommandContext { &self.base_config_path } - pub(crate) fn machine_settings(&self) -> &SettingsFile { + pub(crate) fn machine_settings(&self) -> &SettingsLayer { &self.machine_settings } diff --git a/lib/crates/fabro-cli/src/commands/config/mod.rs b/lib/crates/fabro-cli/src/commands/config/mod.rs index 753897407..6938759ee 100644 --- a/lib/crates/fabro-cli/src/commands/config/mod.rs +++ b/lib/crates/fabro-cli/src/commands/config/mod.rs @@ -9,7 +9,7 @@ use fabro_config::effective_settings; use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSettingsMode}; use fabro_config::load_settings_project; use fabro_config::project; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; fn config_layers( ctx: &CommandContext, @@ -18,14 +18,14 @@ fn config_layers( let cwd = ctx.cwd(); let (workflow_layer, project_layer) = match workflow { Some(path) => workflow_and_project_layers(path, cwd)?, - None => (SettingsFile::default(), load_settings_project(cwd)?), + None => (SettingsLayer::default(), load_settings_project(cwd)?), }; let user_layer = user_config::settings_layer_with_config_and_storage_dir( Some(ctx.base_config_path()), None, )?; Ok(EffectiveSettingsLayers::new( - SettingsFile::default(), + SettingsLayer::default(), workflow_layer, project_layer, user_layer, @@ -35,7 +35,7 @@ fn config_layers( fn workflow_and_project_layers( path: &Path, cwd: &Path, -) -> anyhow::Result<(SettingsFile, SettingsFile)> { +) -> anyhow::Result<(SettingsLayer, SettingsLayer)> { let resolution = project::resolve_workflow_path(path, cwd)?; if resolution.workflow_config.is_none() && !resolution.resolved_workflow_path.is_file() { anyhow::bail!( @@ -57,7 +57,7 @@ fn workflow_and_project_layers( Ok((workflow_layer, project_layer)) } -async fn merged_config(args: &SettingsArgs) -> anyhow::Result { +async fn merged_config(args: &SettingsArgs) -> anyhow::Result { let base_ctx = CommandContext::base()?; let layers = config_layers(&base_ctx, args.workflow.as_deref())?; if args.local { diff --git a/lib/crates/fabro-cli/src/commands/graph.rs b/lib/crates/fabro-cli/src/commands/graph.rs index b03606845..caae38781 100644 --- a/lib/crates/fabro-cli/src/commands/graph.rs +++ b/lib/crates/fabro-cli/src/commands/graph.rs @@ -2,7 +2,7 @@ use std::io::Write; use anyhow::bail; use fabro_api::types; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_util::terminal::Styles; use tracing::debug; @@ -25,7 +25,7 @@ pub(crate) async fn run( let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), cwd: ctx.cwd().to_path_buf(), - args_layer: SettingsFile::default(), + args_layer: SettingsLayer::default(), args: None, run_id: None, })?; diff --git a/lib/crates/fabro-cli/src/commands/install.rs b/lib/crates/fabro-cli/src/commands/install.rs index fa4893b56..493cb243d 100644 --- a/lib/crates/fabro-cli/src/commands/install.rs +++ b/lib/crates/fabro-cli/src/commands/install.rs @@ -1024,9 +1024,9 @@ mod tests { #[test] fn config_toml_roundtrips() { - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; let toml_str = format_config_toml("brynary"); - let cfg: SettingsFile = fabro_types::settings::parse_settings_file(&toml_str) + let cfg: SettingsLayer = fabro_config::parse_settings_layer(&toml_str) .expect("generated config should parse as v2"); let allowed = cfg .server @@ -1040,9 +1040,9 @@ mod tests { #[test] fn config_toml_has_auth_strategies() { - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; let toml_str = format_config_toml("alice"); - let cfg: SettingsFile = fabro_types::settings::parse_settings_file(&toml_str).unwrap(); + let cfg: SettingsLayer = fabro_config::parse_settings_layer(&toml_str).unwrap(); let auth_api = cfg .server .as_ref() @@ -1065,10 +1065,10 @@ mod tests { #[test] fn config_toml_has_tls_paths() { - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::settings::server::ServerListenLayer; let toml_str = format_config_toml("bob"); - let cfg: SettingsFile = fabro_types::settings::parse_settings_file(&toml_str).unwrap(); + let cfg: SettingsLayer = fabro_config::parse_settings_layer(&toml_str).unwrap(); let listen = cfg .server .as_ref() diff --git a/lib/crates/fabro-cli/src/commands/run/create.rs b/lib/crates/fabro-cli/src/commands/run/create.rs index a74950eae..614385c89 100644 --- a/lib/crates/fabro-cli/src/commands/run/create.rs +++ b/lib/crates/fabro-cli/src/commands/run/create.rs @@ -4,7 +4,7 @@ use crate::args::RunArgs; use crate::command_context::CommandContext; use fabro_config::Storage; use fabro_types::RunId; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_util::terminal::Styles; use super::output::{api_diagnostics_to_local, print_preflight_workflow_summary}; @@ -23,7 +23,7 @@ pub(crate) struct CreatedRun { pub(crate) async fn create_run( ctx: &CommandContext, args: &RunArgs, - _cli_defaults: SettingsFile, + _cli_defaults: SettingsLayer, styles: &Styles, quiet: bool, ) -> anyhow::Result { diff --git a/lib/crates/fabro-cli/src/commands/run/overrides.rs b/lib/crates/fabro-cli/src/commands/run/overrides.rs index 407ed4a09..36daa69b2 100644 --- a/lib/crates/fabro-cli/src/commands/run/overrides.rs +++ b/lib/crates/fabro-cli/src/commands/run/overrides.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use anyhow::{Result, anyhow}; use fabro_sandbox::SandboxProvider; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::cli::{CliLayer, CliOutputLayer, OutputVerbosity}; use fabro_types::settings::interp::InterpString; use fabro_types::settings::run::{ @@ -116,7 +116,7 @@ fn current_dir_or_dot() -> PathBuf { std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) } -pub(crate) fn run_args_layer(args: &RunArgs) -> Result { +pub(crate) fn run_args_layer(args: &RunArgs) -> Result { let model = model_from_args(args.model.as_deref(), args.provider.as_deref()); let sandbox = sandbox_layer( args.sandbox.map(Into::into), @@ -140,14 +140,14 @@ pub(crate) fn run_args_layer(args: &RunArgs) -> Result { ..RunLayer::default() }; - Ok(SettingsFile { + Ok(SettingsLayer { run: Some(run), cli: cli_layer_for_verbose(args.verbose), - ..SettingsFile::default() + ..SettingsLayer::default() }) } -pub(crate) fn preflight_args_layer(args: &PreflightArgs) -> Result { +pub(crate) fn preflight_args_layer(args: &PreflightArgs) -> Result { let model = model_from_args(args.model.as_deref(), args.provider.as_deref()); let sandbox = args.sandbox.map(|s| RunSandboxLayer { provider: Some(SandboxProvider::from(s).to_string()), @@ -164,10 +164,10 @@ pub(crate) fn preflight_args_layer(args: &PreflightArgs) -> Result ..RunLayer::default() }; - Ok(SettingsFile { + Ok(SettingsLayer { run: Some(run), cli: cli_layer_for_verbose(args.verbose), - ..SettingsFile::default() + ..SettingsLayer::default() }) } diff --git a/lib/crates/fabro-cli/src/commands/run/runner.rs b/lib/crates/fabro-cli/src/commands/run/runner.rs index 830561486..df9cd7915 100644 --- a/lib/crates/fabro-cli/src/commands/run/runner.rs +++ b/lib/crates/fabro-cli/src/commands/run/runner.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use fabro_interview::{ControlInterviewer, WorkerControlEnvelope, WorkerControlMessage}; use fabro_store::{EventEnvelope, EventPayload, RunProjection}; use fabro_types::settings::InterpString; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::{EventBody, RunBlobId, RunEvent, RunId, StatusReason}; use fabro_workflow::artifact_snapshot::CapturedArtifactInfo; use fabro_workflow::artifact_upload::{ArtifactSink, StageArtifactUploader}; @@ -420,7 +420,7 @@ fn update_worker_title_from_event(event: &RunEvent) { } fn maybe_build_github_app_credentials( - settings: &SettingsFile, + settings: &SettingsLayer, ) -> Result> { let resolved_run = fabro_config::resolve_run_from_file(settings).ok(); let resolved_server = fabro_config::resolve_server_from_file(settings).ok(); diff --git a/lib/crates/fabro-cli/src/commands/store/dump.rs b/lib/crates/fabro-cli/src/commands/store/dump.rs index ad5064dac..9ef00d1cd 100644 --- a/lib/crates/fabro-cli/src/commands/store/dump.rs +++ b/lib/crates/fabro-cli/src/commands/store/dump.rs @@ -297,7 +297,7 @@ mod tests { use chrono::{DateTime, Utc}; use fabro_store::{Database, EventEnvelope, EventPayload}; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{ AggregateStats, AttrValue, BilledTokenCounts, Checkpoint, Conclusion, Graph, NodeStatusRecord, Retro, RunId, RunRecord, RunStatus, RunStatusRecord, SandboxRecord, @@ -335,7 +335,7 @@ mod tests { ); RunRecord { run_id, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph, workflow_slug: Some("night-sky".to_string()), working_directory: PathBuf::from("/tmp/night-sky"), diff --git a/lib/crates/fabro-cli/src/commands/validate.rs b/lib/crates/fabro-cli/src/commands/validate.rs index eaa6ef3de..06353d1f8 100644 --- a/lib/crates/fabro-cli/src/commands/validate.rs +++ b/lib/crates/fabro-cli/src/commands/validate.rs @@ -1,5 +1,5 @@ use anyhow::bail; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_util::terminal::Styles; use crate::args::{GlobalArgs, ValidateArgs}; @@ -17,7 +17,7 @@ pub(crate) async fn run( let built = build_run_manifest(ManifestBuildInput { workflow: args.workflow.clone(), cwd: ctx.cwd().to_path_buf(), - args_layer: SettingsFile::default(), + args_layer: SettingsLayer::default(), args: None, run_id: None, })?; diff --git a/lib/crates/fabro-cli/src/manifest_builder.rs b/lib/crates/fabro-cli/src/manifest_builder.rs index b004fa7f3..04eed6fcb 100644 --- a/lib/crates/fabro-cli/src/manifest_builder.rs +++ b/lib/crates/fabro-cli/src/manifest_builder.rs @@ -12,7 +12,7 @@ use fabro_graphviz::graph::AttrValue; use fabro_graphviz::parser; use fabro_sandbox::daytona::detect_repo_info; use fabro_types::RunId; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{DaytonaDockerfileLayer, ResolvedGoalSource, ResolvedRunGoal}; use fabro_workflow::git::{GitSyncStatus, head_sha, sync_status}; @@ -22,7 +22,7 @@ use crate::args::{PreflightArgs, RunArgs}; pub(crate) struct ManifestBuildInput { pub workflow: PathBuf, pub cwd: PathBuf, - pub args_layer: SettingsFile, + pub args_layer: SettingsLayer, pub args: Option, pub run_id: Option, } @@ -384,8 +384,8 @@ fn collect_bundled_file( } fn resolve_manifest_goal( - args_layer: &SettingsFile, - settings: &SettingsFile, + args_layer: &SettingsLayer, + settings: &SettingsLayer, root_source: &str, root_dot_path: &Path, cwd: &Path, @@ -615,7 +615,7 @@ mod tests { let built = build_run_manifest(ManifestBuildInput { workflow: PathBuf::from("fabro/workflows/demo/workflow.toml"), cwd: project.to_path_buf(), - args_layer: SettingsFile::default(), + args_layer: SettingsLayer::default(), args: None, run_id: None, }) @@ -702,7 +702,7 @@ file = "prompts/goal.md" let built = build_run_manifest(ManifestBuildInput { workflow: PathBuf::from("fabro/workflows/demo/workflow.toml"), cwd: project.to_path_buf(), - args_layer: SettingsFile::default(), + args_layer: SettingsLayer::default(), args: None, run_id: None, }) @@ -766,7 +766,7 @@ file = "prompts/goal.md" let built = build_run_manifest(ManifestBuildInput { workflow: PathBuf::from("fabro/workflows/demo/workflow.toml"), cwd: project.to_path_buf(), - args_layer: SettingsFile::default(), + args_layer: SettingsLayer::default(), args: None, run_id: None, }) diff --git a/lib/crates/fabro-cli/src/server_client.rs b/lib/crates/fabro-cli/src/server_client.rs index 15e78b863..b6d0c9b44 100644 --- a/lib/crates/fabro-cli/src/server_client.rs +++ b/lib/crates/fabro-cli/src/server_client.rs @@ -8,7 +8,7 @@ use bytes::Bytes; use fabro_api::types; use fabro_server::bind::Bind; use fabro_store::{EventEnvelope, RunSummary, StageId}; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::{RunBlobId, RunEvent, RunId}; use fabro_workflow::artifact_snapshot::CapturedArtifactInfo; use futures::StreamExt; @@ -100,7 +100,7 @@ pub(crate) async fn connect_server_target_direct(target: &str) -> Result Result { let target = user_config::resolve_server_target(args, settings)?; @@ -278,7 +278,7 @@ impl ServerStoreClient { &self.base_url } - pub(crate) async fn retrieve_server_settings(&self) -> Result { + pub(crate) async fn retrieve_server_settings(&self) -> Result { let response = self .client .retrieve_server_settings() @@ -286,7 +286,7 @@ impl ServerStoreClient { .await .map_err(map_api_error)?; let raw = serde_json::Value::Object(response.into_inner().into()); - serde_json::from_value::(raw) + serde_json::from_value::(raw) .context("server returned a settings payload that does not match the v2 schema") } diff --git a/lib/crates/fabro-cli/src/user_config.rs b/lib/crates/fabro-cli/src/user_config.rs index 23324c577..96beffe94 100644 --- a/lib/crates/fabro-cli/src/user_config.rs +++ b/lib/crates/fabro-cli/src/user_config.rs @@ -4,7 +4,7 @@ pub(crate) use fabro_config::user::*; use anyhow::{Result, bail}; use fabro_types::settings::cli::CliTargetSettings; -use fabro_types::settings::{CliSettings, SettingsFile}; +use fabro_types::settings::{CliSettings, SettingsLayer}; use fabro_util::version::FABRO_VERSION; use serde::{Deserialize, Serialize}; use tracing::debug; @@ -19,34 +19,34 @@ pub(crate) struct ClientTlsSettings { use crate::args::ServerTargetArgs; -pub(crate) fn load_settings() -> anyhow::Result { +pub(crate) fn load_settings() -> anyhow::Result { load_settings_with_config_and_storage_dir(None, None) } pub(crate) fn settings_layer_with_config_and_storage_dir( config_path: Option<&Path>, storage_dir: Option<&Path>, -) -> anyhow::Result { +) -> anyhow::Result { let layer = load_settings_config(config_path)?; Ok(apply_storage_dir_override(layer, storage_dir)) } pub(crate) fn settings_layer_with_storage_dir( storage_dir: Option<&Path>, -) -> anyhow::Result { +) -> anyhow::Result { settings_layer_with_config_and_storage_dir(None, storage_dir) } pub(crate) fn load_settings_with_storage_dir( storage_dir: Option<&Path>, -) -> anyhow::Result { +) -> anyhow::Result { settings_layer_with_storage_dir(storage_dir) } pub(crate) fn load_settings_with_config_and_storage_dir( config_path: Option<&Path>, storage_dir: Option<&Path>, -) -> anyhow::Result { +) -> anyhow::Result { settings_layer_with_config_and_storage_dir(config_path, storage_dir) } @@ -61,14 +61,14 @@ fn render_resolve_errors(errors: Vec) -> anyhow::Err ) } -pub(crate) fn resolve_cli_settings(file: &SettingsFile) -> anyhow::Result { +pub(crate) fn resolve_cli_settings(file: &SettingsLayer) -> anyhow::Result { fabro_config::resolve_cli_from_file(file).map_err(render_resolve_errors) } pub(crate) fn apply_storage_dir_override( - mut layer: SettingsFile, + mut layer: SettingsLayer, storage_dir: Option<&Path>, -) -> SettingsFile { +) -> SettingsLayer { use fabro_types::settings::interp::InterpString; use fabro_types::settings::server::{ServerLayer, ServerStorageLayer}; if let Some(dir) = storage_dir { @@ -109,7 +109,7 @@ fn cli_target_from_settings(settings: &CliSettings) -> Option<(String, Option Result> { +fn configured_server_target(settings: &SettingsLayer) -> Result> { let cli_settings = resolve_cli_settings(settings)?; let Some((value, tls)) = cli_target_from_settings(&cli_settings) else { return Ok(None); @@ -121,7 +121,7 @@ pub(crate) fn default_server_target() -> ServerTarget { ServerTarget::UnixSocket(default_socket_path()) } -pub(crate) fn storage_dir(settings: &SettingsFile) -> anyhow::Result { +pub(crate) fn storage_dir(settings: &SettingsLayer) -> anyhow::Result { let resolved = fabro_config::resolve_server_from_file(settings).map_err(|errors| { anyhow::anyhow!( "failed to resolve server settings:\n{}", @@ -163,7 +163,7 @@ fn parse_server_target(value: &str, tls: Option) -> Result Result> { let cli_settings = resolve_cli_settings(settings)?; args.as_deref() @@ -178,7 +178,7 @@ fn explicit_server_target( pub(crate) fn resolve_server_target( args: &ServerTargetArgs, - settings: &SettingsFile, + settings: &SettingsLayer, ) -> Result { explicit_server_target(args, settings)? .or(configured_server_target(settings)?) @@ -187,7 +187,7 @@ pub(crate) fn resolve_server_target( pub(crate) fn exec_server_target( args: &ServerTargetArgs, - settings: &SettingsFile, + settings: &SettingsLayer, ) -> Result> { let target = explicit_server_target(args, settings)?; debug!(?target, "Resolved exec server target"); @@ -233,7 +233,7 @@ pub(crate) fn build_server_client( mod tests { use super::*; use crate::args::ServerTargetArgs; - use fabro_types::settings::parse_settings_file; + use fabro_config::parse_settings_layer; fn server_target_args(value: Option<&str>) -> ServerTargetArgs { ServerTargetArgs { @@ -241,13 +241,13 @@ mod tests { } } - fn parse_v2(source: &str) -> SettingsFile { - parse_settings_file(source).expect("fixture should parse") + fn parse_v2(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("fixture should parse") } #[test] fn exec_has_no_server_target_by_default() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); assert_eq!( exec_server_target(&server_target_args(None), &settings).unwrap(), None @@ -256,7 +256,7 @@ mod tests { #[test] fn exec_uses_cli_server_target() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); assert_eq!( exec_server_target( &server_target_args(Some("https://cli.example.com")), @@ -272,7 +272,7 @@ mod tests { #[test] fn exec_supports_explicit_unix_socket_target() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); assert_eq!( exec_server_target(&server_target_args(Some("/tmp/fabro.sock")), &settings).unwrap(), Some(ServerTarget::UnixSocket(PathBuf::from("/tmp/fabro.sock"))) @@ -342,7 +342,7 @@ url = "https://config.example.com" #[test] fn resolve_server_target_defaults_to_default_unix_socket_target() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); assert_eq!( resolve_server_target(&server_target_args(None), &settings).unwrap(), ServerTarget::UnixSocket(dirs::home_dir().unwrap().join(".fabro/fabro.sock")) @@ -409,7 +409,7 @@ ca = "ca.pem" #[test] fn invalid_server_target_is_rejected() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let error = exec_server_target(&server_target_args(Some("fabro.internal")), &settings).unwrap_err(); assert_eq!( diff --git a/lib/crates/fabro-cli/tests/it/cmd/config.rs b/lib/crates/fabro-cli/tests/it/cmd/config.rs index b26c32f2e..8f641c41d 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/config.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/config.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; +use fabro_config::parse_settings_layer; use fabro_test::{fabro_snapshot, test_context}; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; use httpmock::MockServer; use predicates::prelude::*; @@ -30,34 +31,34 @@ fn old_config_show_command_is_rejected() { // Helpers // --------------------------------------------------------------------------- -fn parse_settings(stdout: &[u8]) -> SettingsFile { - serde_yaml::from_slice(stdout).expect("stdout should be valid YAML SettingsFile") +fn parse_settings(stdout: &[u8]) -> SettingsLayer { + serde_yaml::from_slice(stdout).expect("stdout should be valid YAML SettingsLayer") } -fn resolve_cli(settings: &SettingsFile) -> fabro_types::settings::CliSettings { +fn resolve_cli(settings: &SettingsLayer) -> fabro_types::settings::CliSettings { fabro_config::resolve_cli_from_file(settings).expect("cli settings should resolve") } -fn resolve_project(settings: &SettingsFile) -> fabro_types::settings::ProjectSettings { +fn resolve_project(settings: &SettingsLayer) -> fabro_types::settings::ProjectSettings { fabro_config::resolve_project_from_file(settings).expect("project settings should resolve") } -fn resolve_run(settings: &SettingsFile) -> fabro_types::settings::RunSettings { +fn resolve_run(settings: &SettingsLayer) -> fabro_types::settings::RunSettings { fabro_config::resolve_run_from_file(settings).expect("run settings should resolve") } -fn resolve_server(settings: &SettingsFile) -> fabro_types::settings::ServerSettings { +fn resolve_server(settings: &SettingsLayer) -> fabro_types::settings::ServerSettings { fabro_config::resolve_server_from_file(settings).expect("server settings should resolve") } -fn run_goal_inline(settings: &SettingsFile) -> Option { +fn run_goal_inline(settings: &SettingsLayer) -> Option { match resolve_run(settings).goal { Some(fabro_types::settings::run::RunGoal::Inline(value)) => Some(value.as_source()), _ => None, } } -fn run_model_name(settings: &SettingsFile) -> Option { +fn run_model_name(settings: &SettingsLayer) -> Option { resolve_run(settings) .model .name @@ -65,7 +66,7 @@ fn run_model_name(settings: &SettingsFile) -> Option { .map(|value| value.as_source()) } -fn run_model_provider(settings: &SettingsFile) -> Option { +fn run_model_provider(settings: &SettingsLayer) -> Option { resolve_run(settings) .model .provider @@ -73,7 +74,7 @@ fn run_model_provider(settings: &SettingsFile) -> Option { .map(|value| value.as_source()) } -fn run_inputs(settings: &SettingsFile) -> &std::collections::HashMap { +fn run_inputs(settings: &SettingsLayer) -> &std::collections::HashMap { settings .run .as_ref() @@ -81,7 +82,7 @@ fn run_inputs(settings: &SettingsFile) -> &std::collections::HashMap &fabro_types::settings::run::RunSandboxLayer { +fn run_sandbox(settings: &SettingsLayer) -> &fabro_types::settings::run::RunSandboxLayer { settings .run .as_ref() @@ -89,7 +90,7 @@ fn run_sandbox(settings: &SettingsFile) -> &fabro_types::settings::run::RunSandb .expect("run.sandbox") } -fn run_checkpoint(settings: &SettingsFile) -> &fabro_types::settings::run::RunCheckpointLayer { +fn run_checkpoint(settings: &SettingsLayer) -> &fabro_types::settings::run::RunCheckpointLayer { settings .run .as_ref() @@ -97,7 +98,7 @@ fn run_checkpoint(settings: &SettingsFile) -> &fabro_types::settings::run::RunCh .expect("run.checkpoint") } -fn run_hooks(settings: &SettingsFile) -> &[fabro_types::settings::run::HookEntry] { +fn run_hooks(settings: &SettingsLayer) -> &[fabro_types::settings::run::HookEntry] { settings .run .as_ref() @@ -106,7 +107,7 @@ fn run_hooks(settings: &SettingsFile) -> &[fabro_types::settings::run::HookEntry } fn run_agent_mcps( - settings: &SettingsFile, + settings: &SettingsLayer, ) -> &std::collections::HashMap { settings .run @@ -116,20 +117,20 @@ fn run_agent_mcps( .expect("run.agent.mcps") } -fn auto_approve_enabled(settings: &SettingsFile) -> bool { +fn auto_approve_enabled(settings: &SettingsLayer) -> bool { resolve_run(settings).execution.approval == fabro_types::settings::run::ApprovalMode::Auto } -fn run_prepare_commands(settings: &SettingsFile) -> Vec { +fn run_prepare_commands(settings: &SettingsLayer) -> Vec { resolve_run(settings).prepare.commands } -fn server_storage_root(settings: &SettingsFile) -> String { +fn server_storage_root(settings: &SettingsLayer) -> String { resolve_server(settings).storage.root.as_source() } -fn server_settings_fixture() -> SettingsFile { - parse_settings_file( +fn server_settings_fixture() -> SettingsLayer { + parse_settings_layer( r#" _version = 1 @@ -148,7 +149,7 @@ shared = "server" .expect("server settings fixture should parse") } -fn server_settings_body(settings: &SettingsFile) -> String { +fn server_settings_body(settings: &SettingsLayer) -> String { serde_json::to_string(settings).expect("settings payload should serialize") } diff --git a/lib/crates/fabro-cli/tests/it/cmd/create.rs b/lib/crates/fabro-cli/tests/it/cmd/create.rs index 09565db30..aaf70bbfa 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/create.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/create.rs @@ -9,7 +9,7 @@ use crate::support::{fabro_json_snapshot, unique_run_id}; use super::support::{fixture, output_stdout, resolve_run, run_count_for_test_case, run_state}; fn resolved_run( - settings: &fabro_types::settings::SettingsFile, + settings: &fabro_types::settings::SettingsLayer, ) -> fabro_types::settings::RunSettings { fabro_config::resolve_run_from_file(settings).expect("run settings should resolve") } diff --git a/lib/crates/fabro-config/src/effective_settings.rs b/lib/crates/fabro-config/src/effective_settings.rs index a8d4a2a86..ede35c585 100644 --- a/lib/crates/fabro-config/src/effective_settings.rs +++ b/lib/crates/fabro-config/src/effective_settings.rs @@ -1,4 +1,4 @@ -//! Effective settings resolution: combine layers into one resolved [`SettingsFile`]. +//! Effective settings resolution: combine layers into one resolved [`SettingsLayer`]. //! //! Shared layered domains (`project`, `workflow`, `run`, `features`) merge //! across all three config files (settings.toml, fabro.toml, workflow.toml). @@ -7,7 +7,7 @@ //! stanzas in `fabro.toml` and `workflow.toml` remain schema-valid but inert. use anyhow::{Result, anyhow}; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{RunExecutionLayer, RunLayer}; use fabro_types::settings::server::ServerLayer; @@ -22,19 +22,19 @@ pub enum EffectiveSettingsMode { #[derive(Clone, Debug, Default)] pub struct EffectiveSettingsLayers { - pub args: SettingsFile, - pub workflow: SettingsFile, - pub project: SettingsFile, - pub user: SettingsFile, + pub args: SettingsLayer, + pub workflow: SettingsLayer, + pub project: SettingsLayer, + pub user: SettingsLayer, } impl EffectiveSettingsLayers { #[must_use] pub fn new( - args: SettingsFile, - workflow: SettingsFile, - project: SettingsFile, - user: SettingsFile, + args: SettingsLayer, + workflow: SettingsLayer, + project: SettingsLayer, + user: SettingsLayer, ) -> Self { Self { args, @@ -45,12 +45,12 @@ impl EffectiveSettingsLayers { } } -/// Resolve layered configuration down to a single effective [`SettingsFile`]. +/// Resolve layered configuration down to a single effective [`SettingsLayer`]. pub fn resolve_settings( layers: EffectiveSettingsLayers, - server_settings: Option<&SettingsFile>, + server_settings: Option<&SettingsLayer>, mode: EffectiveSettingsMode, -) -> Result { +) -> Result { let EffectiveSettingsLayers { args, mut workflow, @@ -103,7 +103,7 @@ pub fn resolve_settings( } } -fn strip_owner_domains(file: &mut SettingsFile) { +fn strip_owner_domains(file: &mut SettingsLayer) { file.cli = None; file.server = None; } @@ -111,7 +111,7 @@ fn strip_owner_domains(file: &mut SettingsFile) { /// Copy of the server settings with startup-time dry-run fallback cleared. /// Run manifests carry their own dry-run intent; a daemon's startup-time /// fallback mode must not silently force every submitted run into simulation. -fn server_defaults_file(settings: &SettingsFile) -> SettingsFile { +fn server_defaults_file(settings: &SettingsLayer) -> SettingsLayer { let mut out = settings.clone(); if let Some(run) = out.run.as_mut() { if let Some(execution) = run.execution.as_mut() { @@ -121,14 +121,14 @@ fn server_defaults_file(settings: &SettingsFile) -> SettingsFile { out } -/// Apply server-side defaults to a client-layered [`SettingsFile`]. +/// Apply server-side defaults to a client-layered [`SettingsLayer`]. /// /// Server-owned domains (`server`, `features`, and parts of `run`) flow from /// the server's local `~/.fabro/settings.toml` when the corresponding client /// value is absent. Run-shaped defaults (model, prepare, sandbox, checkpoint, /// hooks, agent mcps, etc.) also flow from server to client so the persisted /// run record matches the server's local configuration. -fn apply_server_defaults(mut settings: SettingsFile, server: &SettingsFile) -> SettingsFile { +fn apply_server_defaults(mut settings: SettingsLayer, server: &SettingsLayer) -> SettingsLayer { // Server-owned domains: server-side always wins when client left blank. // Use the v2 merge matrix with the server layer in lower precedence so // that client-supplied values still dominate when present. @@ -141,7 +141,10 @@ fn apply_server_defaults(mut settings: SettingsFile, server: &SettingsFile) -> S /// In LocalDaemon mode, a subset of server-owned fields unconditionally /// override any client-side values. Client-controlled run-level fields are /// left alone. -fn apply_local_daemon_overrides(mut settings: SettingsFile, server: &SettingsFile) -> SettingsFile { +fn apply_local_daemon_overrides( + mut settings: SettingsLayer, + server: &SettingsLayer, +) -> SettingsLayer { if let Some(server_layer) = server.server.clone() { let client = settings.server.get_or_insert_with(ServerLayer::default); if let Some(storage) = server_layer.storage { @@ -177,22 +180,23 @@ fn apply_local_daemon_overrides(mut settings: SettingsFile, server: &SettingsFil #[cfg(test)] mod tests { + use crate::parse::parse_settings_layer; use fabro_types::settings::InterpString; + use fabro_types::settings::SettingsLayer; use fabro_types::settings::server::{ServerLayer, ServerSchedulerLayer, ServerStorageLayer}; - use fabro_types::settings::{SettingsFile, parse_settings_file}; use super::{EffectiveSettingsLayers, EffectiveSettingsMode, resolve_settings}; - fn layer(source: &str) -> SettingsFile { - parse_settings_file(source).expect("v2 fixture should parse") + fn layer(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("v2 fixture should parse") } #[test] fn local_only_merges_project_and_user_layers() { let settings = resolve_settings( EffectiveSettingsLayers::new( - SettingsFile::default(), - SettingsFile::default(), + SettingsLayer::default(), + SettingsLayer::default(), layer( r#" _version = 1 @@ -259,7 +263,7 @@ shared = "user" fn local_only_merges_workflow_project_user() { let settings = resolve_settings( EffectiveSettingsLayers::new( - SettingsFile::default(), + SettingsLayer::default(), layer( r#" _version = 1 @@ -327,7 +331,7 @@ provider = "openai" #[test] fn cli_and_server_domains_from_fabro_toml_are_inert_under_remote_mode() { - let mut server_settings = fabro_types::settings::SettingsFile::default(); + let mut server_settings = fabro_types::settings::SettingsLayer::default(); server_settings.server = Some(ServerLayer { storage: Some(ServerStorageLayer { root: Some(InterpString::parse("/srv/fabro")), @@ -352,10 +356,10 @@ root = "/tmp/should-be-inert" let settings = resolve_settings( EffectiveSettingsLayers::new( - SettingsFile::default(), - SettingsFile::default(), + SettingsLayer::default(), + SettingsLayer::default(), project_with_server, - SettingsFile::default(), + SettingsLayer::default(), ), Some(&server_settings), EffectiveSettingsMode::RemoteServer, @@ -386,7 +390,7 @@ root = "/tmp/should-be-inert" #[test] fn local_daemon_mode_only_applies_server_owned_overrides() { - let mut server_settings = fabro_types::settings::SettingsFile::default(); + let mut server_settings = fabro_types::settings::SettingsLayer::default(); server_settings.server = Some(ServerLayer { storage: Some(ServerStorageLayer { root: Some(InterpString::parse("/srv/fabro")), diff --git a/lib/crates/fabro-config/src/lib.rs b/lib/crates/fabro-config/src/lib.rs index e40bdfe2e..3ec7574c8 100644 --- a/lib/crates/fabro-config/src/lib.rs +++ b/lib/crates/fabro-config/src/lib.rs @@ -5,6 +5,7 @@ pub mod home; pub mod legacy_env; pub mod load; pub mod merge; +pub mod parse; pub mod project; pub mod resolve; pub mod run; @@ -16,6 +17,7 @@ pub use home::Home; pub use load::{ load_settings_for_workflow, load_settings_path, load_settings_project, load_settings_user, }; +pub use parse::{ParseError, parse_settings_layer}; pub use resolve::{ ResolveError, resolve, resolve_cli, resolve_cli_from_file, resolve_features, resolve_features_from_file, resolve_project, resolve_project_from_file, resolve_run, @@ -26,12 +28,12 @@ pub use storage::{RunScratch, ServerState, Storage}; use std::path::Path; -use fabro_types::settings::{Settings, SettingsFile}; +use fabro_types::settings::{Settings, SettingsLayer}; use serde::de::DeserializeOwned; pub fn load_and_resolve( layers: effective_settings::EffectiveSettingsLayers, - server_settings: Option<&SettingsFile>, + server_settings: Option<&SettingsLayer>, mode: effective_settings::EffectiveSettingsMode, ) -> anyhow::Result { let layer = effective_settings::resolve_settings(layers, server_settings, mode)?; diff --git a/lib/crates/fabro-config/src/load.rs b/lib/crates/fabro-config/src/load.rs index 5667ae7a4..89865da21 100644 --- a/lib/crates/fabro-config/src/load.rs +++ b/lib/crates/fabro-config/src/load.rs @@ -2,16 +2,17 @@ use std::path::{Path, PathBuf}; use anyhow::Context; use fabro_types::settings::run::RunGoalLayer; -use fabro_types::settings::{InterpString, SettingsFile, parse_settings_file}; +use fabro_types::settings::{InterpString, SettingsLayer}; use crate::merge::combine_files; +use crate::parse::parse_settings_layer; use crate::project; use crate::user; -pub fn load_settings_path(path: &Path) -> anyhow::Result { +pub fn load_settings_path(path: &Path) -> anyhow::Result { let content = std::fs::read_to_string(path) .with_context(|| format!("Failed to read {}", path.display()))?; - let mut layer = parse_settings_file(&content) + let mut layer = parse_settings_layer(&content) .map_err(|err| anyhow::anyhow!("{err}")) .context("Failed to parse settings file")?; let base_dir = path.parent().unwrap_or_else(|| Path::new(".")); @@ -19,7 +20,7 @@ pub fn load_settings_path(path: &Path) -> anyhow::Result { Ok(layer) } -pub fn load_settings_for_workflow(path: &Path, cwd: &Path) -> anyhow::Result { +pub fn load_settings_for_workflow(path: &Path, cwd: &Path) -> anyhow::Result { let resolution = project::resolve_workflow_path(path, cwd)?; if resolution.workflow_config.is_none() && !resolution.resolved_workflow_path.is_file() { anyhow::bail!( @@ -41,17 +42,17 @@ pub fn load_settings_for_workflow(path: &Path, cwd: &Path) -> anyhow::Result anyhow::Result { +pub fn load_settings_project(start: &Path) -> anyhow::Result { Ok(project::discover_project_config(start)? .map(|(_, config)| config) .unwrap_or_default()) } -pub fn load_settings_user() -> anyhow::Result { +pub fn load_settings_user() -> anyhow::Result { user::load_settings_config(None) } -pub(crate) fn resolve_goal_file_paths(file: &mut SettingsFile, base_dir: &Path) { +pub(crate) fn resolve_goal_file_paths(file: &mut SettingsLayer, base_dir: &Path) { let Some(run) = file.run.as_mut() else { return; }; diff --git a/lib/crates/fabro-config/src/merge.rs b/lib/crates/fabro-config/src/merge.rs index fa662c94d..2afb632f3 100644 --- a/lib/crates/fabro-config/src/merge.rs +++ b/lib/crates/fabro-config/src/merge.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use fabro_types::settings::cli::{ CliExecAgentLayer, CliExecLayer, CliExecModelLayer, CliLayer, CliTargetLayer, }; +use fabro_types::settings::layer::SettingsLayer; use fabro_types::settings::project::ProjectLayer; use fabro_types::settings::run::{ DaytonaSandboxLayer, GitAuthorLayer, HookEntry, InterviewsLayer, ModelRefOrSplice, @@ -23,14 +24,13 @@ use fabro_types::settings::server::{ ServerArtifactsLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerLayer, ServerListenLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, }; -use fabro_types::settings::tree::SettingsFile; use fabro_types::settings::workflow::WorkflowLayer; /// Combine two settings files: `higher` takes precedence over `lower` wherever /// the merge matrix does not dictate otherwise. #[must_use] -pub fn combine_files(lower: SettingsFile, higher: SettingsFile) -> SettingsFile { - SettingsFile { +pub fn combine_files(lower: SettingsLayer, higher: SettingsLayer) -> SettingsLayer { + SettingsLayer { version: higher.version.or(lower.version), project: merge_option(lower.project, higher.project, combine_project), workflow: merge_option(lower.workflow, higher.workflow, combine_workflow), @@ -479,12 +479,13 @@ fn combine_server_integrations( #[cfg(test)] mod tests { - use fabro_types::settings::{InterpString, parse_settings_file}; + use crate::parse::parse_settings_layer; + use fabro_types::settings::InterpString; use super::*; - fn parse(input: &str) -> SettingsFile { - parse_settings_file(input).expect("fixture should parse") + fn parse(input: &str) -> SettingsLayer { + parse_settings_layer(input).expect("fixture should parse") } #[test] diff --git a/lib/crates/fabro-config/src/parse.rs b/lib/crates/fabro-config/src/parse.rs new file mode 100644 index 000000000..ea975708b --- /dev/null +++ b/lib/crates/fabro-config/src/parse.rs @@ -0,0 +1,169 @@ +use std::fmt; + +use fabro_types::settings::SettingsLayer; + +const CURRENT_VERSION: u32 = 1; + +const ALLOWED_TOP_LEVEL_KEYS: &[&str] = &[ + "_version", "project", "workflow", "run", "cli", "server", "features", +]; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParseError { + Toml(String), + Version(VersionError), + UnknownTopLevelKey { key: String, hint: Option }, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Toml(msg) => write!(f, "settings file is not valid TOML: {msg}"), + Self::Version(err) => fmt::Display::fmt(err, f), + Self::UnknownTopLevelKey { key, hint } => { + if let Some(hint) = hint { + write!(f, "unknown top-level settings key `{key}`: {hint}") + } else { + write!( + f, + "unknown top-level settings key `{key}`: expected one of `_version`, `project`, `workflow`, `run`, `cli`, `server`, `features`" + ) + } + } + } + } +} + +impl std::error::Error for ParseError {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VersionError { + LegacyVersionKey, + UnsupportedHigherVersion { found: u32 }, +} + +impl fmt::Display for VersionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::LegacyVersionKey => f.write_str( + "settings files must use `_version` instead of `version`. Rename the key and try again.", + ), + Self::UnsupportedHigherVersion { found } => write!( + f, + "settings schema version {found} is newer than this build supports (current: {CURRENT_VERSION}). Upgrade Fabro to read this file." + ), + } + } +} + +impl std::error::Error for VersionError {} + +pub fn parse_settings_layer(input: &str) -> Result { + let raw: toml::Value = toml::from_str(input).map_err(|e| ParseError::Toml(e.to_string()))?; + validate_version(&raw).map_err(ParseError::Version)?; + + if let Some(table) = raw.as_table() { + for key in table.keys() { + if !ALLOWED_TOP_LEVEL_KEYS.contains(&key.as_str()) { + return Err(ParseError::UnknownTopLevelKey { + key: key.clone(), + hint: rename_hint(key), + }); + } + } + } + + raw.try_into::() + .map_err(|e| ParseError::Toml(e.to_string())) +} + +fn validate_version(raw: &toml::Value) -> Result<(), VersionError> { + if let Some(table) = raw.as_table() { + if table.contains_key("version") { + return Err(VersionError::LegacyVersionKey); + } + if let Some(value) = table.get("_version").and_then(toml::Value::as_integer) { + let found = u32::try_from(value).unwrap_or(u32::MAX); + if found > CURRENT_VERSION { + return Err(VersionError::UnsupportedHigherVersion { found }); + } + } + } + Ok(()) +} + +fn rename_hint(key: &str) -> Option { + let target = match key { + "version" => "rename to `_version`", + "goal" | "goal_file" | "work_dir" | "directory" => "move to `[run]`", + "graph" => "move to `[workflow]`", + "labels" => "move to `[run.metadata]`", + "llm" => "rename to `[run.model]`", + "vars" => "rename to `[run.inputs]`", + "setup" => "rename to `[run.prepare]`", + "sandbox" => "move under `[run.sandbox]`", + "checkpoint" => "move under `[run.checkpoint]`", + "pull_request" => "move under `[run.pull_request]`", + "artifacts" => "move under `[run.artifacts]`", + "hooks" => "move under `[[run.hooks]]`", + "mcp_servers" => "move under `[run.agent.mcps.]` or `[cli.exec.agent.mcps.]`", + "exec" => "rename to `[cli.exec]`", + "api" => "rename to `[server.api]`", + "web" => "rename to `[server.web]`", + "artifact_storage" => "rename to `[server.artifacts]`", + "storage_dir" | "data_dir" => "rename to `[server.storage] root`", + "max_concurrent_runs" => "rename to `[server.scheduler]` field", + "fabro" => "rename to `[project]`; `fabro.root` becomes `project.directory`", + "git" => "split into `[run.git]` (local git behavior) and `[server.integrations.github]`", + "github" => "rename to `[server.integrations.github]`", + "slack" => "move under `[server.integrations.slack]`", + "log" => "rename to `[server.logging]` or `[cli.logging]` depending on owner", + "prevent_idle_sleep" => "rename to `[cli.exec] prevent_idle_sleep`", + "verbose" => "rename to `[cli.output] verbosity`", + "upgrade_check" => "rename to `[cli.updates] check`", + "dry_run" => "rename to `[run.execution] mode = \"dry_run\"`", + "auto_approve" => "rename to `[run.execution] approval = \"auto\"`", + "no_retro" => "rename to `[run.execution] retros = false`", + _ => return None, + }; + Some(target.to_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_empty_file() { + let file = parse_settings_layer("").unwrap(); + assert_eq!(file, SettingsLayer::default()); + } + + #[test] + fn parses_minimal_valid_file() { + let file = parse_settings_layer("_version = 1\n").unwrap(); + assert_eq!(file.version, Some(1)); + } + + #[test] + fn rejects_legacy_version_key_with_rename_hint() { + let err = parse_settings_layer("version = 1").unwrap_err(); + assert!(matches!( + err, + ParseError::Version(VersionError::LegacyVersionKey) + )); + assert!(err.to_string().contains("_version")); + } + + #[test] + fn rejects_unknown_top_level_key() { + let err = parse_settings_layer("unknown_key = 1").unwrap_err(); + assert!(matches!(err, ParseError::UnknownTopLevelKey { .. })); + } + + #[test] + fn higher_version_rejected_with_upgrade_hint() { + let err = parse_settings_layer("_version = 99").unwrap_err(); + assert!(err.to_string().contains("Upgrade")); + } +} diff --git a/lib/crates/fabro-config/src/project.rs b/lib/crates/fabro-config/src/project.rs index fcdcab7e9..16963cbfc 100644 --- a/lib/crates/fabro-config/src/project.rs +++ b/lib/crates/fabro-config/src/project.rs @@ -11,23 +11,24 @@ use anyhow::{Context, bail}; use serde::Serialize; use crate::load::load_settings_path; +use crate::parse::parse_settings_layer; use crate::run; use crate::{resolve_project_from_file, resolve_run_from_file, resolve_workflow_from_file}; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; const CONFIG_FILENAME: &str = "fabro.toml"; #[derive(Clone, Debug)] pub struct WorkflowPathResolution { pub resolved_workflow_path: PathBuf, pub dot_path: PathBuf, - pub workflow_config: Option, + pub workflow_config: Option, pub workflow_toml_path: Option, pub workflow_slug: Option, } /// Parse a project config from a TOML string. -pub fn parse_project_config(content: &str) -> anyhow::Result { - parse_settings_file(content) +pub fn parse_project_config(content: &str) -> anyhow::Result { + parse_settings_layer(content) .map_err(|err| anyhow::anyhow!("{err}")) .context("Failed to parse project config") } @@ -36,7 +37,7 @@ pub fn parse_project_config(content: &str) -> anyhow::Result { /// /// Goes through [`load_settings_path`] so that relative `run.goal.file` /// paths are anchored at the directory of `path` at load time. -pub fn load_project_config(path: &Path) -> anyhow::Result { +pub fn load_project_config(path: &Path) -> anyhow::Result { let config = load_settings_path(path).context("Failed to parse project config")?; let root = resolve_project_from_file(&config) .map_err(|errors| anyhow::anyhow!("Failed to resolve project settings: {errors:?}"))? @@ -47,7 +48,7 @@ pub fn load_project_config(path: &Path) -> anyhow::Result { /// Walk ancestor directories from `start` looking for `fabro.toml`. /// Returns the config file path and parsed config, or `None` if not found. -pub fn discover_project_config(start: &Path) -> anyhow::Result> { +pub fn discover_project_config(start: &Path) -> anyhow::Result> { for ancestor in start.ancestors() { let candidate = ancestor.join(CONFIG_FILENAME); if candidate.is_file() { @@ -118,7 +119,7 @@ pub fn resolve_workflow_path( } } -pub fn resolve_working_directory(settings: &SettingsFile, caller_cwd: &Path) -> PathBuf { +pub fn resolve_working_directory(settings: &SettingsLayer, caller_cwd: &Path) -> PathBuf { let Some(work_dir) = resolve_run_from_file(settings) .ok() .and_then(|settings| settings.working_dir) @@ -338,7 +339,7 @@ fn find_closest_match(input: &str, candidates: &[String]) -> Option { } /// Resolve a workflow argument to a DOT path and optional run config. -pub fn resolve_workflow(arg: &Path) -> anyhow::Result<(PathBuf, Option)> { +pub fn resolve_workflow(arg: &Path) -> anyhow::Result<(PathBuf, Option)> { let start = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let resolution = resolve_workflow_path(arg, &start)?; Ok((resolution.dot_path, resolution.workflow_config)) @@ -362,7 +363,7 @@ pub fn is_retro_enabled() -> bool { /// Resolve the fabro root directory from a config file path and its config. /// The returned path is the directory containing `fabro.toml` joined with the /// `project.directory` value (default: `fabro/`). -pub fn resolve_fabro_root(config_path: &Path, config: &SettingsFile) -> PathBuf { +pub fn resolve_fabro_root(config_path: &Path, config: &SettingsLayer) -> PathBuf { let project_dir = config_path .parent() .expect("config_path should have a parent directory"); diff --git a/lib/crates/fabro-config/src/resolve/mod.rs b/lib/crates/fabro-config/src/resolve/mod.rs index e4f2ce0e3..fa3587cf4 100644 --- a/lib/crates/fabro-config/src/resolve/mod.rs +++ b/lib/crates/fabro-config/src/resolve/mod.rs @@ -8,7 +8,7 @@ mod workflow; use fabro_types::settings::{ CliSettings, FeaturesSettings, InterpString, ProjectSettings, RunSettings, ServerSettings, - Settings, SettingsFile, WorkflowSettings, + Settings, SettingsLayer, WorkflowSettings, }; pub use cli::resolve_cli; @@ -19,7 +19,7 @@ pub use run::resolve_run; pub use server::resolve_server; pub use workflow::resolve_workflow; -pub fn resolve(file: &SettingsFile) -> Result> { +pub fn resolve(file: &SettingsLayer) -> Result> { let mut errors = Vec::new(); let project_layer = file.project.clone().unwrap_or_default(); let workflow_layer = file.workflow.clone().unwrap_or_default(); @@ -44,32 +44,32 @@ pub fn resolve(file: &SettingsFile) -> Result> { } } -pub fn resolve_cli_from_file(file: &SettingsFile) -> Result> { +pub fn resolve_cli_from_file(file: &SettingsLayer) -> Result> { resolve(file).map(|settings| settings.cli) } -pub fn resolve_server_from_file(file: &SettingsFile) -> Result> { +pub fn resolve_server_from_file(file: &SettingsLayer) -> Result> { resolve(file).map(|settings| settings.server) } pub fn resolve_project_from_file( - file: &SettingsFile, + file: &SettingsLayer, ) -> Result> { resolve(file).map(|settings| settings.project) } pub fn resolve_features_from_file( - file: &SettingsFile, + file: &SettingsLayer, ) -> Result> { resolve(file).map(|settings| settings.features) } -pub fn resolve_run_from_file(file: &SettingsFile) -> Result> { +pub fn resolve_run_from_file(file: &SettingsLayer) -> Result> { resolve(file).map(|settings| settings.run) } pub fn resolve_workflow_from_file( - file: &SettingsFile, + file: &SettingsLayer, ) -> Result> { resolve(file).map(|settings| settings.workflow) } diff --git a/lib/crates/fabro-config/src/run.rs b/lib/crates/fabro-config/src/run.rs index 5ec9a3e18..20458c462 100644 --- a/lib/crates/fabro-config/src/run.rs +++ b/lib/crates/fabro-config/src/run.rs @@ -1,6 +1,6 @@ //! Workflow / run config loading helpers. //! -//! Thin wrappers around `parse_settings_file` / `load_settings_path` plus +//! Thin wrappers around `parse_settings_layer` / `load_settings_path` plus //! path resolution for the `[workflow] graph` override. Runtime types //! that used to be re-exported from here live under //! `fabro_types::settings::run` now. @@ -10,12 +10,13 @@ use std::path::{Path, PathBuf}; use anyhow::Context; use crate::load::{load_settings_path, resolve_goal_file_path}; +use crate::parse::parse_settings_layer; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{ResolvedGoalSource, ResolvedRunGoal, RunGoalLayer}; -use fabro_types::settings::{SettingsFile, parse_settings_file}; /// Load and parse a run config from a TOML file. -pub fn parse_run_config(contents: &str) -> anyhow::Result { - parse_settings_file(contents) +pub fn parse_run_config(contents: &str) -> anyhow::Result { + parse_settings_layer(contents) .map_err(|err| anyhow::anyhow!("{err}")) .context("Failed to parse run config TOML") } @@ -24,7 +25,7 @@ pub fn parse_run_config(contents: &str) -> anyhow::Result { /// /// Goes through [`load_settings_path`] so that relative `run.goal.file` /// paths are anchored at the directory of `path` at load time. -pub fn load_run_config(path: &Path) -> anyhow::Result { +pub fn load_run_config(path: &Path) -> anyhow::Result { load_settings_path(path) .with_context(|| format!("Failed to parse workflow config at {}", path.display())) } @@ -73,7 +74,7 @@ impl std::error::Error for ResolveRunGoalError { } pub fn resolve_run_goal( - settings: &SettingsFile, + settings: &SettingsLayer, base_dir: &Path, ) -> Result, ResolveRunGoalError> { let Some(goal) = settings.run.as_ref().and_then(|run| run.goal.as_ref()) else { diff --git a/lib/crates/fabro-config/src/user.rs b/lib/crates/fabro-config/src/user.rs index b48d90fea..415878877 100644 --- a/lib/crates/fabro-config/src/user.rs +++ b/lib/crates/fabro-config/src/user.rs @@ -10,7 +10,7 @@ use std::sync::{Mutex, OnceLock}; use crate::home::Home; use crate::load::load_settings_path; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; pub const SETTINGS_CONFIG_FILENAME: &str = "settings.toml"; pub const LEGACY_USER_CONFIG_FILENAME: &str = "cli.toml"; @@ -69,7 +69,7 @@ fn should_warn_about_legacy_user_config(path: &Path) -> bool { /// returning defaults if the default file doesn't exist. An explicit path that /// doesn't exist is an error. #[allow(clippy::print_stderr)] -pub fn load_settings_config(path: Option<&Path>) -> anyhow::Result { +pub fn load_settings_config(path: Option<&Path>) -> anyhow::Result { if let Some(explicit) = path .map(Path::to_path_buf) .or_else(|| std::env::var_os(FABRO_CONFIG_ENV).map(PathBuf::from)) @@ -99,11 +99,11 @@ pub fn load_settings_config(path: Option<&Path>) -> anyhow::Result if default.is_file() { load_v2_layer_from_path(&default) } else { - Ok(SettingsFile::default()) + Ok(SettingsLayer::default()) } } -fn load_v2_layer_from_path(path: &Path) -> anyhow::Result { +fn load_v2_layer_from_path(path: &Path) -> anyhow::Result { load_settings_path(path) } diff --git a/lib/crates/fabro-config/tests/resolve_cli.rs b/lib/crates/fabro-config/tests/resolve_cli.rs index 6fd7b11c0..1d801a933 100644 --- a/lib/crates/fabro-config/tests/resolve_cli.rs +++ b/lib/crates/fabro-config/tests/resolve_cli.rs @@ -1,11 +1,12 @@ +use fabro_config::parse_settings_layer; use fabro_config::resolve_cli_from_file; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::cli::{CliTargetSettings, OutputFormat, OutputVerbosity}; use fabro_types::settings::run::AgentPermissions; -use fabro_types::settings::{SettingsFile, parse_settings_file}; #[test] fn resolves_cli_defaults_from_empty_settings() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let cli = resolve_cli_from_file(&settings).expect("empty settings should resolve"); @@ -19,7 +20,7 @@ fn resolves_cli_defaults_from_empty_settings() { #[test] fn resolves_cli_target_exec_and_output_settings() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 diff --git a/lib/crates/fabro-config/tests/resolve_features.rs b/lib/crates/fabro-config/tests/resolve_features.rs index 4d7a5f596..07dc2b261 100644 --- a/lib/crates/fabro-config/tests/resolve_features.rs +++ b/lib/crates/fabro-config/tests/resolve_features.rs @@ -1,9 +1,10 @@ +use fabro_config::parse_settings_layer; use fabro_config::resolve_features_from_file; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; #[test] fn resolves_features_defaults_from_empty_settings() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let features = resolve_features_from_file(&settings).expect("empty settings should resolve"); @@ -12,7 +13,7 @@ fn resolves_features_defaults_from_empty_settings() { #[test] fn resolves_session_sandboxes_flag() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 diff --git a/lib/crates/fabro-config/tests/resolve_project.rs b/lib/crates/fabro-config/tests/resolve_project.rs index 975777173..69b69ebaf 100644 --- a/lib/crates/fabro-config/tests/resolve_project.rs +++ b/lib/crates/fabro-config/tests/resolve_project.rs @@ -1,9 +1,10 @@ +use fabro_config::parse_settings_layer; use fabro_config::resolve_project_from_file; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; #[test] fn resolves_project_defaults_from_empty_settings() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let project = resolve_project_from_file(&settings).expect("empty settings should resolve"); @@ -15,7 +16,7 @@ fn resolves_project_defaults_from_empty_settings() { #[test] fn resolves_project_directory_and_metadata() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 diff --git a/lib/crates/fabro-config/tests/resolve_root.rs b/lib/crates/fabro-config/tests/resolve_root.rs index 139b835a7..8eaccf442 100644 --- a/lib/crates/fabro-config/tests/resolve_root.rs +++ b/lib/crates/fabro-config/tests/resolve_root.rs @@ -1,16 +1,17 @@ use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSettingsMode}; +use fabro_config::parse_settings_layer; use fabro_types::run::RunRecord; use fabro_types::run_event::run::RunCreatedProps; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; -fn parse(source: &str) -> SettingsFile { - parse_settings_file(source).expect("fixture should parse") +fn parse(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("fixture should parse") } #[test] fn resolves_root_settings_defaults() { let settings = - fabro_config::resolve(&SettingsFile::default()).expect("empty settings should resolve"); + fabro_config::resolve(&SettingsLayer::default()).expect("empty settings should resolve"); assert_eq!(settings.project.directory, "fabro/"); assert_eq!(settings.workflow.graph, "workflow.fabro"); @@ -57,7 +58,7 @@ provider = "not-a-provider" fn load_and_resolve_merges_layers_before_resolution() { let settings = fabro_config::load_and_resolve( EffectiveSettingsLayers::new( - SettingsFile::default(), + SettingsLayer::default(), parse( r#" _version = 1 diff --git a/lib/crates/fabro-config/tests/resolve_run.rs b/lib/crates/fabro-config/tests/resolve_run.rs index ed0aa7029..a329dd923 100644 --- a/lib/crates/fabro-config/tests/resolve_run.rs +++ b/lib/crates/fabro-config/tests/resolve_run.rs @@ -1,13 +1,14 @@ +use fabro_config::parse_settings_layer; use fabro_types::settings::run::{ApprovalMode, RunGoal, RunMode, WorktreeMode}; -use fabro_types::settings::{InterpString, SettingsFile, parse_settings_file}; +use fabro_types::settings::{InterpString, SettingsLayer}; -fn parse(source: &str) -> SettingsFile { - parse_settings_file(source).expect("fixture should parse") +fn parse(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("fixture should parse") } #[test] fn resolves_run_defaults_from_empty_settings() { - let settings = fabro_config::resolve_run_from_file(&SettingsFile::default()) + let settings = fabro_config::resolve_run_from_file(&SettingsLayer::default()) .expect("empty settings should resolve"); assert_eq!(settings.execution.mode, RunMode::Normal); diff --git a/lib/crates/fabro-config/tests/resolve_server.rs b/lib/crates/fabro-config/tests/resolve_server.rs index 6ef2a563a..e4c2a28c4 100644 --- a/lib/crates/fabro-config/tests/resolve_server.rs +++ b/lib/crates/fabro-config/tests/resolve_server.rs @@ -1,14 +1,15 @@ +use fabro_config::parse_settings_layer; use fabro_types::settings::server::{ObjectStoreSettings, ServerListenSettings}; -use fabro_types::settings::{InterpString, SettingsFile, parse_settings_file}; +use fabro_types::settings::{InterpString, SettingsLayer}; use fabro_util::Home; -fn parse(source: &str) -> SettingsFile { - parse_settings_file(source).expect("fixture should parse") +fn parse(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("fixture should parse") } #[test] fn resolves_server_defaults_from_empty_settings() { - let settings = fabro_config::resolve_server_from_file(&SettingsFile::default()) + let settings = fabro_config::resolve_server_from_file(&SettingsLayer::default()) .expect("empty settings should resolve"); assert_eq!( diff --git a/lib/crates/fabro-config/tests/resolve_workflow.rs b/lib/crates/fabro-config/tests/resolve_workflow.rs index bf84b6bb1..272fe2f60 100644 --- a/lib/crates/fabro-config/tests/resolve_workflow.rs +++ b/lib/crates/fabro-config/tests/resolve_workflow.rs @@ -1,9 +1,10 @@ +use fabro_config::parse_settings_layer; use fabro_config::resolve_workflow_from_file; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; #[test] fn resolves_workflow_defaults_from_empty_settings() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let workflow = resolve_workflow_from_file(&settings).expect("empty settings should resolve"); @@ -15,7 +16,7 @@ fn resolves_workflow_defaults_from_empty_settings() { #[test] fn resolves_workflow_graph_and_metadata() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs index 11116e8c0..6b54aeae0 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -1319,7 +1319,7 @@ mod runs { } pub(super) fn settings() -> serde_json::Value { - // v2 SettingsFile shape — matches what /api/v1/runs/:id/settings + // v2 SettingsLayer shape — matches what /api/v1/runs/:id/settings // returns in production, so the demo renders identically. serde_json::json!({ "_version": 1, @@ -1464,7 +1464,7 @@ mod insights { mod settings { pub(super) fn server_settings() -> serde_json::Value { - // v2 SettingsFile shape — matches what /api/v1/settings returns in + // v2 SettingsLayer shape — matches what /api/v1/settings returns in // production, so the demo renders identically. serde_json::json!({ "_version": 1, diff --git a/lib/crates/fabro-server/src/jwt_auth.rs b/lib/crates/fabro-server/src/jwt_auth.rs index fdc91f2c2..c9f2c3ee0 100644 --- a/lib/crates/fabro-server/src/jwt_auth.rs +++ b/lib/crates/fabro-server/src/jwt_auth.rs @@ -452,8 +452,8 @@ mod tests { use axum::http::{Request, StatusCode}; use axum::response::IntoResponse; use axum::routing::get; + use fabro_config::parse_settings_layer; use fabro_config::resolve_server_from_file; - use fabro_types::settings::parse_settings_file; use tower::ServiceExt; use crate::web_auth::SessionCookie; @@ -461,7 +461,7 @@ mod tests { // --- Fail-closed resolver tests (R52/R53) ----------------------------------- fn settings(source: &str) -> ResolvedServerSettings { - let file = parse_settings_file(source).expect("fixture should parse"); + let file = parse_settings_layer(source).expect("fixture should parse"); resolve_server_from_file(&file).expect("fixture should resolve") } diff --git a/lib/crates/fabro-server/src/run_manifest.rs b/lib/crates/fabro-server/src/run_manifest.rs index 25cd73260..a01ac3245 100644 --- a/lib/crates/fabro-server/src/run_manifest.rs +++ b/lib/crates/fabro-server/src/run_manifest.rs @@ -7,6 +7,7 @@ use fabro_api::types; use fabro_config::effective_settings; use fabro_config::effective_settings::{EffectiveSettingsLayers, EffectiveSettingsMode}; use fabro_config::merge::combine_files; +use fabro_config::parse_settings_layer; use fabro_config::project::resolve_working_directory; use fabro_config::run::parse_run_config; use fabro_graphviz::graph::{Graph, is_llm_handler_type}; @@ -20,6 +21,7 @@ use fabro_sandbox::daytona::DaytonaConfig; use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec}; use fabro_types::RunId; use fabro_types::settings::ServerSettings; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::cli::{CliLayer, CliOutputLayer, OutputVerbosity}; use fabro_types::settings::interp::InterpString; use fabro_types::settings::run::{ @@ -27,7 +29,6 @@ use fabro_types::settings::run::{ RunExecutionLayer, RunGoalLayer, RunLayer, RunMode, RunModelLayer, RunSandboxLayer, RunSettings, }; -use fabro_types::settings::{SettingsFile, parse_settings_file}; use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus}; use fabro_validate::Severity; use fabro_workflow::error::FabroError; @@ -44,7 +45,7 @@ pub(crate) struct PreparedManifest { pub git: Option, pub root_source: String, pub run_id: Option, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub target_path: PathBuf, pub workflow_bundle: WorkflowBundle, pub workflow_input: BundledWorkflow, @@ -52,7 +53,7 @@ pub(crate) struct PreparedManifest { } pub(crate) fn prepare_manifest_with_mode( - server_settings: &SettingsFile, + server_settings: &SettingsLayer, manifest: &types::RunManifest, local_daemon_mode: bool, ) -> Result { @@ -75,14 +76,14 @@ pub(crate) fn prepare_manifest_with_mode( .configs .iter() .filter(|config| config.type_ == types::ManifestConfigType::Project) - .try_fold(SettingsFile::default(), |layer, config| { + .try_fold(SettingsLayer::default(), |layer, config| { Ok::<_, anyhow::Error>(combine_files(layer, parse_manifest_config(config)?)) })?; let user_layer = manifest .configs .iter() .filter(|config| config.type_ == types::ManifestConfigType::User) - .try_fold(SettingsFile::default(), |layer, config| { + .try_fold(SettingsLayer::default(), |layer, config| { Ok::<_, anyhow::Error>(combine_files(layer, parse_manifest_config(config)?)) })?; let mut settings = effective_settings::resolve_settings( @@ -198,12 +199,12 @@ fn workflow_bundle_from_manifest( fn root_workflow_config_layer( manifest: &types::RunManifest, workflow: &BundledWorkflow, -) -> Result { +) -> Result { let Some(root) = manifest.workflows.get(&manifest.target.path) else { bail!("manifest target path is missing from workflows map"); }; let Some(config) = root.config.as_ref() else { - return Ok(SettingsFile::default()); + return Ok(SettingsLayer::default()); }; let mut layer = parse_run_config(&config.source)?; @@ -211,16 +212,16 @@ fn root_workflow_config_layer( Ok(layer) } -fn parse_manifest_config(config: &types::ManifestConfig) -> Result { +fn parse_manifest_config(config: &types::ManifestConfig) -> Result { let Some(source) = config.source.as_deref() else { - return Ok(SettingsFile::default()); + return Ok(SettingsLayer::default()); }; - parse_settings_file(source).map_err(|err| anyhow!("Failed to parse settings file: {err}")) + parse_settings_layer(source).map_err(|err| anyhow!("Failed to parse settings file: {err}")) } -fn manifest_args_layer(args: Option<&types::ManifestArgs>) -> SettingsFile { +fn manifest_args_layer(args: Option<&types::ManifestArgs>) -> SettingsLayer { let Some(args) = args else { - return SettingsFile::default(); + return SettingsLayer::default(); }; let model = (args.model.is_some() || args.provider.is_some()).then(|| RunModelLayer { @@ -273,10 +274,10 @@ fn manifest_args_layer(args: Option<&types::ManifestArgs>) -> SettingsFile { }) }); - SettingsFile { + SettingsLayer { run, cli, - ..SettingsFile::default() + ..SettingsLayer::default() } } @@ -289,7 +290,7 @@ fn parse_labels(labels: &[String]) -> HashMap { } fn resolve_manifest_dockerfile( - layer: &mut SettingsFile, + layer: &mut SettingsLayer, config_path: &Path, files: &HashMap, ) -> Result<()> { @@ -865,8 +866,8 @@ mod tests { } } - fn server_settings_fixture(source: &str) -> SettingsFile { - fabro_types::settings::parse_settings_file(source).expect("v2 fixture should parse") + fn server_settings_fixture(source: &str) -> SettingsLayer { + fabro_config::parse_settings_layer(source).expect("v2 fixture should parse") } #[test] diff --git a/lib/crates/fabro-server/src/serve.rs b/lib/crates/fabro-server/src/serve.rs index 441764343..154ef5db6 100644 --- a/lib/crates/fabro-server/src/serve.rs +++ b/lib/crates/fabro-server/src/serve.rs @@ -20,7 +20,7 @@ use clap::Args; use fabro_types::settings::{ InterpString, ObjectStoreSettings, ServerListenSettings, - ServerSettings as ResolvedServerSettings, SettingsFile, + ServerSettings as ResolvedServerSettings, SettingsLayer, }; use crate::bind::{self, Bind, BindRequest}; @@ -84,7 +84,7 @@ pub struct ServeArgs { pub config: Option, } -fn load_settings(path: Option<&Path>) -> anyhow::Result { +fn load_settings(path: Option<&Path>) -> anyhow::Result { load_settings_config(path) } @@ -93,10 +93,10 @@ fn resolved_config_path(path: Option<&Path>) -> PathBuf { } fn apply_serve_overrides( - base: &SettingsFile, + base: &SettingsLayer, args: &ServeArgs, dry_run_mode: bool, -) -> SettingsFile { +) -> SettingsLayer { use fabro_types::settings::cli::CliLayer; use fabro_types::settings::interp::InterpString; use fabro_types::settings::run::{ @@ -135,11 +135,11 @@ fn apply_serve_overrides( } fn apply_runtime_settings( - base: &SettingsFile, + base: &SettingsLayer, args: &ServeArgs, dry_run_mode: bool, data_dir: &Path, -) -> SettingsFile { +) -> SettingsLayer { use fabro_types::settings::interp::InterpString; use fabro_types::settings::server::{ServerLayer, ServerStorageLayer}; let mut settings = apply_serve_overrides(base, args, dry_run_mode); @@ -174,7 +174,7 @@ fn build_object_store(store_path: &Path) -> anyhow::Result> build_object_store_with_preference(store_path, use_in_memory_store()) } -fn resolve_server_settings(file: &SettingsFile) -> anyhow::Result { +fn resolve_server_settings(file: &SettingsLayer) -> anyhow::Result { resolve_server_from_file(file).map_err(|errors| { anyhow::anyhow!( "failed to resolve server settings:\n{}", @@ -695,15 +695,16 @@ mod tests { build_object_store_with_preference, server_bind_title, server_title, }; use crate::bind::Bind; - use fabro_types::settings::{SettingsFile, parse_settings_file}; + use fabro_config::parse_settings_layer; + use fabro_types::settings::SettingsLayer; - fn parse_settings(source: &str) -> SettingsFile { - parse_settings_file(source).expect("v2 fixture should parse") + fn parse_settings(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("v2 fixture should parse") } #[test] fn apply_runtime_settings_preserves_storage_dir() { - let base = SettingsFile::default(); + let base = SettingsLayer::default(); let args = ServeArgs { bind: None, model: None, @@ -764,7 +765,7 @@ enabled = false #[test] fn apply_runtime_settings_disables_web_from_cli_flag() { - let base = SettingsFile::default(); + let base = SettingsLayer::default(); let args = ServeArgs { bind: None, model: None, diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index 9adae4f30..ff2afb235 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -35,7 +35,9 @@ use fabro_store::{ ArtifactStore, Database, EventEnvelope, EventPayload, PendingInterviewRecord, StageId, }; use fabro_types::settings::run::RunMode; -use fabro_types::settings::{InterpString, ServerSettings as ResolvedServerSettings, SettingsFile}; +use fabro_types::settings::{ + InterpString, ServerSettings as ResolvedServerSettings, SettingsLayer, +}; use fabro_types::{ ActorRef, EventBody, InterviewQuestionRecord, InterviewQuestionType, RunBlobId, RunClientProvenance, RunControlAction, RunEvent, RunId, RunProvenance, RunServerProvenance, @@ -524,7 +526,7 @@ pub struct AppState { global_event_tx: broadcast::Sender, pub(crate) secret_store: AsyncRwLock, - pub(crate) settings: Arc>, + pub(crate) settings: Arc>, pub(crate) server_settings: RwLock>, pub(crate) config_path: PathBuf, pub(crate) local_daemon_mode: bool, @@ -690,7 +692,7 @@ impl AppState { self.shutting_down.load(Ordering::Relaxed) } - pub(crate) fn replace_settings(&self, settings: SettingsFile) -> anyhow::Result<()> { + pub(crate) fn replace_settings(&self, settings: SettingsLayer) -> anyhow::Result<()> { let resolved = Arc::new(resolve_server_from_file(&settings).map_err(|errors| { anyhow::anyhow!( "failed to resolve server settings:\n{}", @@ -1467,7 +1469,7 @@ fn build_prune_plan( }) } -fn system_sandbox_provider(settings: &SettingsFile) -> String { +fn system_sandbox_provider(settings: &SettingsLayer) -> String { fabro_config::resolve_run_from_file(settings).map_or_else( |_| SandboxProvider::default().to_string(), |settings| settings.sandbox.provider, @@ -1482,7 +1484,7 @@ fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String { .join("; ") } -fn resolved_storage_dir(settings: &SettingsFile) -> Result { +fn resolved_storage_dir(settings: &SettingsLayer) -> Result { let resolved = resolve_server_from_file(settings).map_err(|errors| render_resolve_errors(&errors))?; resolved @@ -1498,7 +1500,7 @@ fn resolved_storage_dir(settings: &SettingsFile) -> Result { }) } -fn resolved_github_app_id(settings: &SettingsFile) -> Result, String> { +fn resolved_github_app_id(settings: &SettingsLayer) -> Result, String> { let resolved = resolve_server_from_file(settings).map_err(|errors| render_resolve_errors(&errors))?; Ok(resolved @@ -2021,7 +2023,7 @@ async fn get_run_billing( /// Create an `AppState` with default settings. pub fn create_app_state() -> Arc { - create_app_state_with_options(SettingsFile::default(), 5) + create_app_state_with_options(SettingsLayer::default(), 5) } #[doc(hidden)] @@ -2029,14 +2031,14 @@ pub fn create_app_state_with_registry_factory( registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, ) -> Arc { create_app_state_with_settings_and_registry_factory( - SettingsFile::default(), + SettingsLayer::default(), registry_factory_override, ) } #[doc(hidden)] pub fn create_app_state_with_settings_and_registry_factory( - settings: SettingsFile, + settings: SettingsLayer, registry_factory_override: impl Fn(Arc) -> HandlerRegistry + Send + Sync + 'static, ) -> Arc { let (store, artifact_store) = test_store_bundle(); @@ -2055,7 +2057,7 @@ pub fn create_app_state_with_settings_and_registry_factory( /// Create an `AppState` with the given settings and concurrency limit. pub fn create_app_state_with_options( - settings: SettingsFile, + settings: SettingsLayer, max_concurrent_runs: usize, ) -> Arc { let (store, artifact_store) = test_store_bundle(); @@ -2079,7 +2081,7 @@ fn test_store_bundle() -> (Arc, ArtifactStore) { } pub fn create_app_state_with_store( - settings: Arc>, + settings: Arc>, max_concurrent_runs: usize, store: Arc, artifact_store: ArtifactStore, @@ -2098,7 +2100,7 @@ pub fn create_app_state_with_store( } pub(crate) fn build_app_state_with_path( - settings: Arc>, + settings: Arc>, registry_factory_override: Option>, max_concurrent_runs: usize, store: Arc, @@ -6057,9 +6059,9 @@ mod tests { start -> exit }"#; - fn dry_run_settings() -> SettingsFile { + fn dry_run_settings() -> SettingsLayer { use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; - SettingsFile { + SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -6067,7 +6069,7 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } } @@ -6350,7 +6352,7 @@ mod tests { #[tokio::test] async fn auth_login_github_redirects_to_github() { - let settings: SettingsFile = fabro_types::settings::parse_settings_file( + let settings: SettingsLayer = fabro_config::parse_settings_layer( r#" _version = 1 @@ -7490,7 +7492,7 @@ slug = "fabro" #[tokio::test] async fn start_run_persists_full_settings_snapshot() { - let settings: SettingsFile = fabro_types::settings::parse_settings_file( + let settings: SettingsLayer = fabro_config::parse_settings_layer( r#" _version = 1 @@ -7960,7 +7962,7 @@ level = "debug" #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn cancel_during_startup_persists_cancelled_reason() { - let settings: SettingsFile = fabro_types::settings::parse_settings_file( + let settings: SettingsLayer = fabro_config::parse_settings_layer( r#" _version = 1 @@ -8074,7 +8076,7 @@ timeout = "30s" #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn concurrency_limit_respected() { - let state = create_app_state_with_options(SettingsFile::default(), 1); + let state = create_app_state_with_options(SettingsLayer::default(), 1); let app = test_app_with_scheduler(Arc::clone(&state)); // Create and start two runs with max_concurrent_runs=1 diff --git a/lib/crates/fabro-server/src/settings_view.rs b/lib/crates/fabro-server/src/settings_view.rs index 3f5c6dba3..9cc655179 100644 --- a/lib/crates/fabro-server/src/settings_view.rs +++ b/lib/crates/fabro-server/src/settings_view.rs @@ -1,7 +1,7 @@ -//! Outward-facing view of [`SettingsFile`] for API responses. +//! Outward-facing view of [`SettingsLayer`] for API responses. //! //! `/api/v1/settings` and `/api/v1/runs/:id/settings` return the server's v2 -//! [`SettingsFile`] directly as JSON so authenticated clients (the `fabro +//! [`SettingsLayer`] directly as JSON so authenticated clients (the `fabro //! settings` CLI, the web UI) can see the effective configuration. Before //! serialization, this module drops the handful of fields that would leak //! operational secrets or host-specific filesystem layout. @@ -34,13 +34,13 @@ //! Any future field that carries a raw secret in-band (without env //! interpolation) must be added to the drop list below. -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; /// Build a redacted clone of `settings` safe to serialize outward. /// /// See the module docs for the drop-list rationale. #[must_use] -pub(crate) fn redact_for_api(settings: &SettingsFile) -> SettingsFile { +pub(crate) fn redact_for_api(settings: &SettingsLayer) -> SettingsLayer { let mut out = settings.clone(); if let Some(server) = out.server.as_mut() { @@ -73,10 +73,10 @@ pub(crate) fn redact_for_api(settings: &SettingsFile) -> SettingsFile { #[cfg(test)] mod tests { use super::*; - use fabro_types::settings::parse_settings_file; + use fabro_config::parse_settings_layer; - fn parse(source: &str) -> SettingsFile { - parse_settings_file(source).expect("fixture should parse") + fn parse(source: &str) -> SettingsLayer { + parse_settings_layer(source).expect("fixture should parse") } #[test] diff --git a/lib/crates/fabro-server/src/web_auth.rs b/lib/crates/fabro-server/src/web_auth.rs index d0211f16a..0104957c2 100644 --- a/lib/crates/fabro-server/src/web_auth.rs +++ b/lib/crates/fabro-server/src/web_auth.rs @@ -6,7 +6,7 @@ use axum::http::{HeaderMap, HeaderValue, StatusCode, header}; use axum::response::{IntoResponse, Redirect, Response}; use axum::{Json, Router, routing::get, routing::post}; use cookie::{Cookie, CookieJar, Expiration, Key, SameSite, time::Duration}; -use fabro_types::settings::{InterpString, SettingsFile}; +use fabro_types::settings::{InterpString, SettingsLayer}; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{debug, error, info, warn}; @@ -146,7 +146,7 @@ fn json_response(status: StatusCode, body: serde_json::Value) -> Response { (status, Json(body)).into_response() } -fn features_json(settings: &SettingsFile) -> serde_json::Value { +fn features_json(settings: &SettingsLayer) -> serde_json::Value { let session_sandboxes = fabro_config::resolve_features_from_file(settings) .map(|settings| settings.session_sandboxes) .unwrap_or(false); @@ -779,10 +779,10 @@ mod tests { assert_eq!(web["enabled"].as_bool(), Some(true)); // Re-parse the emitted document to prove it round-trips into a - // valid v2 `SettingsFile`. + // valid v2 `SettingsLayer`. let emitted = doc.to_string(); - let file = fabro_types::settings::parse_settings_file(&emitted) - .expect("merged output should parse as a v2 SettingsFile"); + let file = fabro_config::parse_settings_layer(&emitted) + .expect("merged output should parse as a v2 SettingsLayer"); let server = file.server.as_ref().expect("[server] should be present"); let integrations = server .integrations @@ -864,8 +864,8 @@ name = "claude-sonnet" ); // Finally, the whole thing must still parse as a valid v2 - // SettingsFile. - fabro_types::settings::parse_settings_file(&emitted) + // SettingsLayer. + fabro_config::parse_settings_layer(&emitted) .expect("merged output should still parse as v2 after the edit"); } } diff --git a/lib/crates/fabro-server/tests/it/api/routing.rs b/lib/crates/fabro-server/tests/it/api/routing.rs index 3cdb79388..bee3a41ab 100644 --- a/lib/crates/fabro-server/tests/it/api/routing.rs +++ b/lib/crates/fabro-server/tests/it/api/routing.rs @@ -1,11 +1,12 @@ use axum::body::{Body, to_bytes}; use axum::http::{Method, Request, StatusCode}; +use fabro_config::parse_settings_layer; use fabro_server::jwt_auth::AuthMode; use fabro_server::server::{ RouterOptions, build_router, build_router_with_options, create_app_state, create_app_state_with_options, }; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; use tower::ServiceExt; use crate::helpers::body_json; @@ -120,7 +121,7 @@ async fn web_enabled_serves_web_only_routes() { #[tokio::test] async fn web_disabled_returns_404_for_web_routes_and_keeps_machine_api() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 @@ -177,7 +178,7 @@ enabled = false #[tokio::test] async fn web_disabled_ignores_demo_header_dispatch() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 diff --git a/lib/crates/fabro-server/tests/it/api/settings.rs b/lib/crates/fabro-server/tests/it/api/settings.rs index 61243ec11..834da2167 100644 --- a/lib/crates/fabro-server/tests/it/api/settings.rs +++ b/lib/crates/fabro-server/tests/it/api/settings.rs @@ -1,15 +1,16 @@ use axum::body::Body; use axum::http::{Request, StatusCode}; +use fabro_config::parse_settings_layer; use fabro_server::jwt_auth::AuthMode; use fabro_server::server::{build_router, create_app_state_with_options}; -use fabro_types::settings::{SettingsFile, parse_settings_file}; +use fabro_types::settings::SettingsLayer; use tower::ServiceExt; use crate::helpers::body_json; #[tokio::test] async fn retrieve_server_settings_returns_runtime_settings() { - let settings: SettingsFile = parse_settings_file( + let settings: SettingsLayer = parse_settings_layer( r#" _version = 1 @@ -41,7 +42,7 @@ server_only = "1" assert_eq!(response.status(), StatusCode::OK); let body = body_json(response.into_body()).await; - // `/api/v1/settings` emits the v2 SettingsFile shape directly now. + // `/api/v1/settings` emits the v2 SettingsLayer shape directly now. // Stage 6.6 will replace this with an explicit allow-list DTO. assert_eq!(body["server"]["storage"]["root"], "/srv/fabro"); assert_eq!(body["server"]["scheduler"]["max_concurrent_runs"], 9); diff --git a/lib/crates/fabro-server/tests/it/api/system.rs b/lib/crates/fabro-server/tests/it/api/system.rs index 52191a688..e30603858 100644 --- a/lib/crates/fabro-server/tests/it/api/system.rs +++ b/lib/crates/fabro-server/tests/it/api/system.rs @@ -4,7 +4,7 @@ use axum::body::Body; use axum::http::{Request, StatusCode}; use fabro_config::Storage; use fabro_types::RunId; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::interp::InterpString; use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; use fabro_types::settings::server::{ServerLayer, ServerStorageLayer}; @@ -19,7 +19,7 @@ use crate::helpers::{ test_app_with_scheduler, test_settings, wait_for_run_status, }; -fn temp_storage_settings() -> (tempfile::TempDir, SettingsFile, PathBuf) { +fn temp_storage_settings() -> (tempfile::TempDir, SettingsLayer, PathBuf) { let temp = tempdir().expect("tempdir should create"); let mut settings = test_settings(); let storage_dir = temp.path().join("storage"); diff --git a/lib/crates/fabro-server/tests/it/helpers.rs b/lib/crates/fabro-server/tests/it/helpers.rs index 4487235a0..61cf0fb51 100644 --- a/lib/crates/fabro-server/tests/it/helpers.rs +++ b/lib/crates/fabro-server/tests/it/helpers.rs @@ -8,7 +8,7 @@ use fabro_server::server::{ AppState, build_router, create_app_state, create_app_state_with_settings_and_registry_factory, spawn_scheduler, }; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{ LocalSandboxLayer, RunExecutionLayer, RunLayer, RunMode, RunSandboxLayer, WorktreeMode, }; @@ -30,7 +30,7 @@ pub(crate) fn test_app_state() -> Arc { } pub(crate) fn test_app_state_with_options( - settings: SettingsFile, + settings: SettingsLayer, max_concurrent_runs: usize, ) -> Arc { let _ = max_concurrent_runs; @@ -39,8 +39,8 @@ pub(crate) fn test_app_state_with_options( }) } -pub(crate) fn test_settings() -> SettingsFile { - SettingsFile { +pub(crate) fn test_settings() -> SettingsLayer { + SettingsLayer { run: Some(RunLayer { sandbox: Some(RunSandboxLayer { local: Some(LocalSandboxLayer { @@ -50,11 +50,11 @@ pub(crate) fn test_settings() -> SettingsFile { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } } -pub(crate) fn dry_run_settings() -> SettingsFile { +pub(crate) fn dry_run_settings() -> SettingsLayer { let mut settings = test_settings(); let run = settings.run.get_or_insert_with(RunLayer::default); let execution = run.execution.get_or_insert_with(RunExecutionLayer::default); diff --git a/lib/crates/fabro-server/tests/it/openapi_conformance.rs b/lib/crates/fabro-server/tests/it/openapi_conformance.rs index da24d4b0b..b0a9b7f57 100644 --- a/lib/crates/fabro-server/tests/it/openapi_conformance.rs +++ b/lib/crates/fabro-server/tests/it/openapi_conformance.rs @@ -99,6 +99,6 @@ async fn all_spec_routes_are_routable() { // was deleted in Stage 6.3b alongside the legacy flat `fabro_types::Settings` // struct that it instantiated. The v2 `/api/v1/settings` and // `/api/v1/runs/:id/settings` endpoints now return the freely-shaped -// `SettingsFile` tree which the OpenAPI spec declares as +// `SettingsLayer` tree which the OpenAPI spec declares as // `type: object, additionalProperties: true`, so there is nothing to diff // at the property-key level. diff --git a/lib/crates/fabro-store/src/run_state.rs b/lib/crates/fabro-store/src/run_state.rs index df4f0340e..dc95925ef 100644 --- a/lib/crates/fabro-store/src/run_state.rs +++ b/lib/crates/fabro-store/src/run_state.rs @@ -599,7 +599,7 @@ mod tests { use super::{NodeState, RunProjection}; use crate::{EventEnvelope, EventPayload, StageId}; use fabro_types::run_event::{InterviewCompletedProps, InterviewOption, InterviewStartedProps}; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{ Checkpoint, EventBody, InterviewQuestionType, RunBlobId, RunControlAction, RunEvent, fixtures, @@ -814,7 +814,7 @@ mod tests { "run_id": fixtures::RUN_1, "event": "run.created", "properties": { - "settings": SettingsFile::default(), + "settings": SettingsLayer::default(), "graph": { "name": "test", "nodes": {}, diff --git a/lib/crates/fabro-store/src/slate/mod.rs b/lib/crates/fabro-store/src/slate/mod.rs index 813f3b05d..5bd56ca17 100644 --- a/lib/crates/fabro-store/src/slate/mod.rs +++ b/lib/crates/fabro-store/src/slate/mod.rs @@ -233,7 +233,7 @@ mod tests { use super::*; use chrono::{DateTime, Utc}; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{AttrValue, Graph, RunControlAction, RunRecord, RunStatus, StatusReason}; use futures::TryStreamExt; use object_store::memory::InMemory; @@ -279,7 +279,7 @@ mod tests { ); RunRecord { run_id: test_run_id(label), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph, workflow_slug: Some("night-sky".to_string()), working_directory: PathBuf::from(format!("/tmp/{label}")), diff --git a/lib/crates/fabro-types/src/run.rs b/lib/crates/fabro-types/src/run.rs index 5ab442a6f..cfa79d7e7 100644 --- a/lib/crates/fabro-types/src/run.rs +++ b/lib/crates/fabro-types/src/run.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::graph::Graph; use crate::run_blob_id::RunBlobId; use crate::run_id::RunId; -use crate::settings::SettingsFile; +use crate::settings::SettingsLayer; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -52,7 +52,7 @@ pub struct RunProvenance { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RunRecord { pub run_id: RunId, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub graph: Graph, #[serde(default, skip_serializing_if = "Option::is_none")] pub workflow_slug: Option, diff --git a/lib/crates/fabro-types/src/run_event/mod.rs b/lib/crates/fabro-types/src/run_event/mod.rs index 18f6389ab..da07635c8 100644 --- a/lib/crates/fabro-types/src/run_event/mod.rs +++ b/lib/crates/fabro-types/src/run_event/mod.rs @@ -777,7 +777,7 @@ mod tests { use serde_json::json; - use crate::settings::SettingsFile; + use crate::settings::SettingsLayer; use crate::{Edge, Graph, Node, RunBlobId, fixtures}; use super::*; @@ -829,7 +829,7 @@ mod tests { #[test] fn run_event_deserializes_adjacent_layout() { - let settings = SettingsFile::default(); + let settings = SettingsLayer::default(); let graph = Graph { name: "test".to_string(), nodes: HashMap::from([( @@ -874,7 +874,7 @@ mod tests { "run_id": fixtures::RUN_1, "event": "run.created", "properties": { - "settings": SettingsFile::default(), + "settings": SettingsLayer::default(), "graph": Graph::new("test"), "labels": {}, "run_dir": "/tmp/run", diff --git a/lib/crates/fabro-types/src/run_event/run.rs b/lib/crates/fabro-types/src/run_event/run.rs index 75fda5db0..394b590b0 100644 --- a/lib/crates/fabro-types/src/run_event/run.rs +++ b/lib/crates/fabro-types/src/run_event/run.rs @@ -2,14 +2,14 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use crate::settings::SettingsFile; +use crate::settings::SettingsLayer; use crate::{Graph, RunBlobId, RunControlAction, RunProvenance, StatusReason}; use super::{BilledTokenCounts, RunNoticeLevel}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RunCreatedProps { - pub settings: SettingsFile, + pub settings: SettingsLayer, pub graph: Graph, #[serde(default, skip_serializing_if = "Option::is_none")] pub workflow_source: Option, diff --git a/lib/crates/fabro-types/src/settings/layer.rs b/lib/crates/fabro-types/src/settings/layer.rs new file mode 100644 index 000000000..f7525d34a --- /dev/null +++ b/lib/crates/fabro-types/src/settings/layer.rs @@ -0,0 +1,33 @@ +//! The top-level sparse settings layer. +//! +//! This struct models a single settings file (`~/.fabro/settings.toml`, +//! `fabro.toml`, or `workflow.toml`) after deserialization. Fields unset in +//! the source stay `None`/empty and are layered later by `fabro-config`. + +use serde::{Deserialize, Serialize}; + +use super::cli::CliLayer; +use super::features::FeaturesLayer; +use super::project::ProjectLayer; +use super::run::RunLayer; +use super::server::ServerLayer; +use super::workflow::WorkflowLayer; + +/// A sparse settings layer before merge/resolve. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct SettingsLayer { + #[serde(default, rename = "_version", skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub project: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workflow: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub run: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cli: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub features: Option, +} diff --git a/lib/crates/fabro-types/src/settings/mod.rs b/lib/crates/fabro-types/src/settings/mod.rs index a0ad9d935..30f41374e 100644 --- a/lib/crates/fabro-types/src/settings/mod.rs +++ b/lib/crates/fabro-types/src/settings/mod.rs @@ -13,6 +13,7 @@ pub mod cli; pub mod duration; pub mod features; pub mod interp; +pub mod layer; pub mod model_ref; pub mod project; pub mod resolved; @@ -20,8 +21,6 @@ pub mod run; pub mod server; pub mod size; pub mod splice_array; -pub mod tree; -pub mod version; pub mod workflow; pub use cli::{ @@ -32,6 +31,7 @@ pub use cli::{ pub use duration::{Duration, ParseDurationError}; pub use features::{FeaturesLayer, FeaturesSettings}; pub use interp::{InterpString, Provenance, ResolveEnvError, Resolved}; +pub use layer::SettingsLayer; pub use model_ref::{ AmbiguousModelRef, ModelRef, ModelRegistry, ParseModelRefError, ResolvedModelRef, }; @@ -56,6 +56,4 @@ pub use server::{ }; pub use size::{ParseSizeError, Size}; pub use splice_array::{SPLICE_MARKER, SpliceArray, SpliceArrayError}; -pub use tree::{ParseError, SettingsFile, parse_settings_file}; -pub use version::{CURRENT_VERSION, SchemaVersion, VersionError, validate_version}; pub use workflow::{WorkflowLayer, WorkflowSettings}; diff --git a/lib/crates/fabro-types/src/settings/tree.rs b/lib/crates/fabro-types/src/settings/tree.rs deleted file mode 100644 index 498151d00..000000000 --- a/lib/crates/fabro-types/src/settings/tree.rs +++ /dev/null @@ -1,422 +0,0 @@ -//! The top-level v2 sparse parse tree. -//! -//! This struct models a single settings file (`~/.fabro/settings.toml`, -//! `fabro.toml`, or `workflow.toml`) after parsing. Fields unset in the source -//! stay `None`/empty. Strict unknown-key handling catches any top-level key -//! that is not one of the reserved domains, with targeted rename hints for -//! legacy flat shapes. - -use std::fmt; - -use serde::{Deserialize, Serialize}; - -use super::cli::CliLayer; -use super::features::FeaturesLayer; -use super::project::ProjectLayer; -use super::run::RunLayer; -use super::server::ServerLayer; -use super::workflow::WorkflowLayer; - -/// A parsed settings file before layering. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct SettingsFile { - #[serde(default, rename = "_version", skip_serializing_if = "Option::is_none")] - pub version: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub project: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workflow: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub run: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cli: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub server: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub features: Option, -} - -/// A top-level key in a v2 settings file. Anything not in this list is rejected -/// at parse time with a targeted rename hint when possible. -const ALLOWED_TOP_LEVEL_KEYS: &[&str] = &[ - "_version", "project", "workflow", "run", "cli", "server", "features", -]; - -/// An error returned when a settings file fails parse-level validation. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ParseError { - /// A low-level TOML parse error. - Toml(String), - /// Schema version pre-validation failed. - Version(super::version::VersionError), - /// A top-level key is not part of the v2 schema. Rename hints are - /// populated for known-legacy keys. - UnknownTopLevelKey { key: String, hint: Option }, -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Toml(msg) => write!(f, "settings file is not valid TOML: {msg}"), - Self::Version(err) => fmt::Display::fmt(err, f), - Self::UnknownTopLevelKey { key, hint } => { - if let Some(hint) = hint { - write!(f, "unknown top-level settings key `{key}`: {hint}") - } else { - write!( - f, - "unknown top-level settings key `{key}`: expected one of `_version`, `project`, `workflow`, `run`, `cli`, `server`, `features`" - ) - } - } - } - } -} - -impl std::error::Error for ParseError {} - -/// Parse a v2 settings file from TOML text. -/// -/// This runs `_version` pre-validation, top-level unknown-key validation -/// with rename hints, and then decodes the sparse namespaced tree. Deeper -/// unknown-key validation for nested tables is enforced by the individual -/// layer types via `#[serde(deny_unknown_fields)]`. -pub fn parse_settings_file(input: &str) -> Result { - let raw: toml::Value = toml::from_str(input).map_err(|e| ParseError::Toml(e.to_string()))?; - super::version::validate_version(&raw).map_err(ParseError::Version)?; - - if let Some(table) = raw.as_table() { - for key in table.keys() { - if !ALLOWED_TOP_LEVEL_KEYS.contains(&key.as_str()) { - return Err(ParseError::UnknownTopLevelKey { - key: key.clone(), - hint: rename_hint(key), - }); - } - } - } - - let file: SettingsFile = raw - .try_into::() - .map_err(|e| ParseError::Toml(e.to_string()))?; - Ok(file) -} - -/// Targeted rename hint for known legacy top-level keys. -fn rename_hint(key: &str) -> Option { - let target = match key { - "version" => "rename to `_version`", - "goal" | "goal_file" | "work_dir" | "directory" => "move to `[run]`", - "graph" => "move to `[workflow]`", - "labels" => "move to `[run.metadata]`", - "llm" => "rename to `[run.model]`", - "vars" => "rename to `[run.inputs]`", - "setup" => "rename to `[run.prepare]`", - "sandbox" => "move under `[run.sandbox]`", - "checkpoint" => "move under `[run.checkpoint]`", - "pull_request" => "move under `[run.pull_request]`", - "artifacts" => "move under `[run.artifacts]`", - "hooks" => "move under `[[run.hooks]]`", - "mcp_servers" => "move under `[run.agent.mcps.]` or `[cli.exec.agent.mcps.]`", - "exec" => "rename to `[cli.exec]`", - "api" => "rename to `[server.api]`", - "web" => "rename to `[server.web]`", - "artifact_storage" => "rename to `[server.artifacts]`", - "storage_dir" | "data_dir" => "rename to `[server.storage] root`", - "max_concurrent_runs" => "rename to `[server.scheduler]` field", - "fabro" => "rename to `[project]`; `fabro.root` becomes `project.directory`", - "git" => "split into `[run.git]` (local git behavior) and `[server.integrations.github]`", - "github" => "rename to `[server.integrations.github]`", - "slack" => "move under `[server.integrations.slack]`", - "log" => "rename to `[server.logging]` or `[cli.logging]` depending on owner", - "prevent_idle_sleep" => "rename to `[cli.exec] prevent_idle_sleep`", - "verbose" => "rename to `[cli.output] verbosity`", - "upgrade_check" => "rename to `[cli.updates] check`", - "dry_run" => "rename to `[run.execution] mode = \"dry_run\"`", - "auto_approve" => "rename to `[run.execution] approval = \"auto\"`", - "no_retro" => "rename to `[run.execution] retros = false`", - _ => return None, - }; - Some(target.to_owned()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_empty_file() { - let file = parse_settings_file("").unwrap(); - assert_eq!(file, SettingsFile::default()); - } - - #[test] - fn parses_minimal_valid_file() { - let input = r#" -_version = 1 - -[project] -name = "Fabro" -"#; - let file = parse_settings_file(input).unwrap(); - assert_eq!(file.version, Some(1)); - assert!(file.project.is_some()); - } - - #[test] - fn rejects_legacy_version_key_with_rename_hint() { - let err = parse_settings_file("version = 1").unwrap_err(); - assert!(matches!(err, ParseError::Version(_))); - assert!(err.to_string().contains("_version")); - } - - #[test] - fn rejects_unknown_top_level_key() { - let err = parse_settings_file("unknown_key = 1").unwrap_err(); - let ParseError::UnknownTopLevelKey { key, .. } = err else { - panic!("expected UnknownTopLevelKey, got: {err:?}"); - }; - assert_eq!(key, "unknown_key"); - } - - #[test] - fn legacy_llm_section_gets_run_model_rename_hint() { - let err = parse_settings_file("[llm]\nprovider = \"openai\"").unwrap_err(); - assert!( - err.to_string().contains("run.model"), - "expected rename hint for [llm]: {err}" - ); - } - - #[test] - fn legacy_vars_section_gets_run_inputs_rename_hint() { - let err = parse_settings_file("[vars]\nk = \"v\"").unwrap_err(); - assert!( - err.to_string().contains("run.inputs"), - "expected rename hint for [vars]: {err}" - ); - } - - #[test] - fn legacy_exec_section_gets_cli_exec_rename_hint() { - let err = parse_settings_file("[exec]\nmodel = \"claude-opus\"").unwrap_err(); - assert!( - err.to_string().contains("cli.exec"), - "expected rename hint for [exec]: {err}" - ); - } - - #[test] - fn legacy_fabro_section_gets_project_rename_hint() { - let err = parse_settings_file("[fabro]\nroot = \"fabro/\"").unwrap_err(); - assert!( - err.to_string().contains("project"), - "expected rename hint for [fabro]: {err}" - ); - } - - #[test] - fn higher_version_rejected_with_upgrade_hint() { - let err = parse_settings_file("_version = 99").unwrap_err(); - assert!(err.to_string().contains("Upgrade")); - } - - #[test] - fn representative_full_tree_parses() { - let input = r##" -_version = 1 - -[project] -name = "Fabro" -description = "AI workflow orchestration" -directory = "fabro/" - -[project.metadata] -owner = "platform" - -[workflow] -name = "Implement Feature" -description = "Turns a request into a code change" - -[run] -goal = "Implement OAuth refresh tokens" -working_dir = "/workspace" - -[run.inputs] -repo = "fabro" -branch = "main" - -[run.metadata] -team = "auth" - -[run.model] -provider = "anthropic" -name = "sonnet" -fallbacks = ["openai", "gpt-5.4", "gemini/gemini-flash"] - -[run.git.author] -name = "fabro-bot" -email = "bot@fabro.sh" - -[[run.prepare.steps]] -script = "bun install" - -[[run.prepare.steps]] -command = ["bun", "run", "typecheck"] - -[run.execution] -mode = "normal" -approval = "prompt" -retros = true - -[run.checkpoint] -exclude_globs = ["target/", "node_modules/"] - -[run.sandbox] -provider = "daytona" -preserve = false - -[run.sandbox.env] -AWS_REGION = "us-west-2" - -[run.sandbox.daytona] -auto_stop_interval = 60 - -[run.sandbox.daytona.snapshot] -name = "fabro-dev" -cpu = 4 -memory = "8GB" -disk = "20GB" - -[run.agent] -permissions = "read-write" - -[run.agent.mcps.fs] -type = "stdio" -command = ["npx", "-y", "@modelcontextprotocol/server-filesystem"] - -[run.notifications.ops] -enabled = true -provider = "slack" -events = ["run.failed", "run.completed"] - -[run.notifications.ops.slack] -channel = "#ops" - -[run.interviews] -provider = "slack" - -[run.interviews.slack] -channel = "#approvals" - -[[run.hooks]] -id = "pre-commit" -name = "Run linter before each commit" -event = "pre_tool_use" -script = "bun run lint" - -[run.pull_request] -enabled = true -draft = true -auto_merge = false -merge_strategy = "squash" - -[run.artifacts] -include = ["target/debug/fabro"] - -[cli.target] -type = "http" -url = "https://fabro.example.com/api/v1" - -[cli.auth] -strategy = "mtls" - -[cli.exec] -prevent_idle_sleep = true - -[cli.exec.model] -provider = "anthropic" -name = "claude-opus" - -[cli.exec.agent] -permissions = "read-write" - -[cli.output] -format = "text" -verbosity = "normal" - -[cli.updates] -check = true - -[cli.logging] -level = "info" - -[server.listen] -type = "tcp" -address = "127.0.0.1:32276" - -[server.api] -url = "https://fabro.example.com/api/v1" - -[server.web] -enabled = true -url = "https://fabro.example.com" - -[server.storage] -root = "/var/lib/fabro" - -[server.artifacts] -provider = "s3" -prefix = "artifacts" - -[server.artifacts.s3] -bucket = "fabro-artifacts" -region = "us-west-2" - -[server.slatedb] -provider = "s3" -prefix = "runs" -flush_interval = "1s" - -[server.slatedb.s3] -bucket = "fabro-slatedb" -region = "us-west-2" - -[server.scheduler] -max_concurrent_runs = 10 - -[server.logging] -level = "info" - -[features] -session_sandboxes = true -"##; - - let file = parse_settings_file(input).expect("full fixture should parse"); - let project = file.project.expect("project present"); - assert_eq!(project.name.as_deref(), Some("Fabro")); - assert_eq!(project.directory.as_deref(), Some("fabro/")); - - let run = file.run.expect("run present"); - let model = run.model.expect("run.model present"); - assert_eq!(model.fallbacks.len(), 3); - - let sandbox = run.sandbox.expect("run.sandbox present"); - assert_eq!(sandbox.env.len(), 1); - let daytona = sandbox.daytona.expect("daytona leaf present"); - let snap = daytona.snapshot.expect("daytona snapshot present"); - assert_eq!(snap.memory.map(|s| s.as_bytes()), Some(8_000_000_000)); - - let hooks = run.hooks; - assert_eq!(hooks.len(), 1); - assert_eq!(hooks[0].id.as_deref(), Some("pre-commit")); - - let cli = file.cli.expect("cli present"); - assert!(cli.target.is_some()); - assert!(cli.exec.is_some()); - - let server = file.server.expect("server present"); - let slate = server.slatedb.expect("slatedb present"); - assert!(slate.flush_interval.is_some()); - } -} diff --git a/lib/crates/fabro-types/src/settings/version.rs b/lib/crates/fabro-types/src/settings/version.rs deleted file mode 100644 index bb3dc8e15..000000000 --- a/lib/crates/fabro-types/src/settings/version.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Schema version handling. -//! -//! The settings schema version lives under the reserved top-level key -//! `_version`. Missing defaults to `1`. The legacy top-level `version` key is -//! a targeted rename hint. Unsupported higher versions hard-fail with an -//! upgrade hint before deeper validation continues. - -use std::fmt; - -/// The highest schema version this parser can consume. -pub const CURRENT_VERSION: u32 = 1; - -/// An error returned when `_version` pre-validation fails. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VersionError { - /// The file contains a legacy top-level `version` key. Offer a rename hint. - LegacyVersionKey, - /// The file declares `_version` higher than [`CURRENT_VERSION`]. - UnsupportedHigherVersion { found: u32 }, -} - -impl fmt::Display for VersionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::LegacyVersionKey => f.write_str( - "settings files must use `_version` instead of `version`. Rename the key and try again.", - ), - Self::UnsupportedHigherVersion { found } => write!( - f, - "settings schema version {found} is newer than this build supports (current: {CURRENT_VERSION}). Upgrade Fabro to read this file." - ), - } - } -} - -impl std::error::Error for VersionError {} - -/// The parsed schema version for a settings file. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SchemaVersion(pub u32); - -impl Default for SchemaVersion { - fn default() -> Self { - Self(CURRENT_VERSION) - } -} - -/// Validate and extract the schema version from a parsed TOML value before -/// deeper validation continues. -/// -/// This function peeks at the top-level table and enforces three rules: -/// -/// 1. `version = ...` (no underscore) is an explicit rename hint error. -/// 2. `_version` higher than [`CURRENT_VERSION`] is an upgrade hint error. -/// 3. Missing `_version` defaults to [`CURRENT_VERSION`]. -pub fn validate_version(raw: &toml::Value) -> Result { - if let Some(table) = raw.as_table() { - if table.contains_key("version") { - return Err(VersionError::LegacyVersionKey); - } - if let Some(value) = table.get("_version") { - if let Some(n) = value.as_integer() { - let found = u32::try_from(n).unwrap_or(u32::MAX); - if found > CURRENT_VERSION { - return Err(VersionError::UnsupportedHigherVersion { found }); - } - return Ok(SchemaVersion(found)); - } - } - } - Ok(SchemaVersion::default()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn parse(input: &str) -> toml::Value { - toml::from_str(input).expect("toml parse") - } - - #[test] - fn missing_version_defaults_to_current() { - let raw = parse(""); - let v = validate_version(&raw).unwrap(); - assert_eq!(v, SchemaVersion(CURRENT_VERSION)); - } - - #[test] - fn explicit_version_one_is_accepted() { - let raw = parse("_version = 1"); - let v = validate_version(&raw).unwrap(); - assert_eq!(v, SchemaVersion(1)); - } - - #[test] - fn legacy_version_key_errors_with_rename_hint() { - let raw = parse("version = 1"); - let err = validate_version(&raw).unwrap_err(); - assert_eq!(err, VersionError::LegacyVersionKey); - assert!(err.to_string().contains("_version")); - } - - #[test] - fn unsupported_higher_version_errors_with_upgrade_hint() { - let raw = parse("_version = 99"); - let err = validate_version(&raw).unwrap_err(); - assert_eq!(err, VersionError::UnsupportedHigherVersion { found: 99 }); - assert!(err.to_string().contains("Upgrade")); - } -} diff --git a/lib/crates/fabro-workflow/src/event.rs b/lib/crates/fabro-workflow/src/event.rs index 7e56abb92..a7caf4869 100644 --- a/lib/crates/fabro-workflow/src/event.rs +++ b/lib/crates/fabro-workflow/src/event.rs @@ -3421,7 +3421,7 @@ mod tests { #[test] fn run_created_populates_user_actor_from_provenance() { - use ::fabro_types::settings::SettingsFile; + use ::fabro_types::settings::SettingsLayer; use ::fabro_types::{Graph, RunAuthMethod, RunSubjectProvenance, fixtures}; let provenance = RunProvenance { @@ -3437,7 +3437,7 @@ mod tests { &fixtures::RUN_1, &Event::RunCreated { run_id: fixtures::RUN_1, - settings: serde_json::to_value(SettingsFile::default()).unwrap(), + settings: serde_json::to_value(SettingsLayer::default()).unwrap(), graph: serde_json::to_value(Graph::new("test")).unwrap(), workflow_source: None, workflow_config: None, diff --git a/lib/crates/fabro-workflow/src/git.rs b/lib/crates/fabro-workflow/src/git.rs index 02f746c1c..b971c4da0 100644 --- a/lib/crates/fabro-workflow/src/git.rs +++ b/lib/crates/fabro-workflow/src/git.rs @@ -2,7 +2,7 @@ use std::path::Path; use std::process::Command; use fabro_checkpoint::git::Store; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use crate::error::{FabroError, Result}; use tokio::task::{JoinError, spawn_blocking}; @@ -15,7 +15,7 @@ pub use fabro_checkpoint::metadata::MetadataStore; /// Branch prefix for workflow run branches (e.g. `fabro/run/{run_id}`). pub const RUN_BRANCH_PREFIX: &str = "fabro/run/"; -pub fn git_author_from_settings(settings: &SettingsFile) -> GitAuthor { +pub fn git_author_from_settings(settings: &SettingsLayer) -> GitAuthor { fabro_config::resolve_run_from_file(settings) .ok() .and_then(|settings| settings.git.author) diff --git a/lib/crates/fabro-workflow/src/handler/manager_loop.rs b/lib/crates/fabro-workflow/src/handler/manager_loop.rs index 43cb95b74..e89a800e5 100644 --- a/lib/crates/fabro-workflow/src/handler/manager_loop.rs +++ b/lib/crates/fabro-workflow/src/handler/manager_loop.rs @@ -18,7 +18,7 @@ use crate::run_options::RunOptions; use async_trait::async_trait; use fabro_graphviz::graph::{AttrValue, Graph, Node}; use fabro_store::{ArtifactStore, Database}; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use object_store::memory::InMemory; use tokio::time::{sleep, timeout}; @@ -74,7 +74,7 @@ fn parse_child_graph( source: dot.to_string(), base_dir: None, }, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: cwd.clone(), custom_transforms: Vec::new(), })?; @@ -116,7 +116,7 @@ fn parse_child_graph( }; let validated = validate(ValidateInput { workflow, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd, custom_transforms: Vec::new(), })?; @@ -202,7 +202,7 @@ impl Handler for SubWorkflowHandler { let child_cancel = Arc::clone(&cancel_token); let child_run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: child_logs, cancel_token: Some(cancel_token), // Child workflows are part of the parent run's event stream. diff --git a/lib/crates/fabro-workflow/src/operations/create.rs b/lib/crates/fabro-workflow/src/operations/create.rs index bcdcd0eec..a03aae3ef 100644 --- a/lib/crates/fabro-workflow/src/operations/create.rs +++ b/lib/crates/fabro-workflow/src/operations/create.rs @@ -4,7 +4,7 @@ use fabro_model::Catalog; use fabro_sandbox::SandboxProvider; use fabro_store::Database; use fabro_types::settings::run::RunMode; -use fabro_types::settings::{Settings, SettingsFile}; +use fabro_types::settings::{Settings, SettingsLayer}; use fabro_types::{RunId, RunProvenance}; use std::collections::BTreeMap; use std::collections::HashMap; @@ -29,7 +29,7 @@ use crate::event::{Event, append_event, to_run_event_at}; #[derive(Clone, Debug)] pub struct CreateRunInput { pub workflow: WorkflowInput, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub cwd: PathBuf, pub workflow_slug: Option, pub workflow_path: Option, @@ -51,7 +51,7 @@ pub struct CreatedRun { } struct PersistCreateOptions { - settings: SettingsFile, + settings: SettingsLayer, run_id: Option, run_dir: Option, workflow_slug: Option, @@ -273,7 +273,7 @@ fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String { .join("; ") } -fn resolve_settings_tree(settings: &SettingsFile) -> Result { +fn resolve_settings_tree(settings: &SettingsLayer) -> Result { fabro_config::resolve(settings) .map_err(|errors| FabroError::Precondition(render_resolve_errors(&errors))) } @@ -285,7 +285,7 @@ fn combined_labels(settings: &Settings) -> HashMap { labels } -fn validate_sandbox_provider(settings: &SettingsFile) -> Result<(), FabroError> { +fn validate_sandbox_provider(settings: &SettingsLayer) -> Result<(), FabroError> { let resolved = fabro_config::resolve_run_from_file(settings) .map_err(|errors| FabroError::Precondition(render_resolve_errors(&errors)))?; resolved @@ -327,7 +327,7 @@ pub(super) fn preprocess_and_validate( current_dir: Option, file_resolver: Option>, custom_transforms: Vec>, - settings: Option<&SettingsFile>, + settings: Option<&SettingsLayer>, goal_override: Option<&str>, ) -> Result { let source = match run_inputs_as_strings(settings) { @@ -353,7 +353,7 @@ pub(super) fn preprocess_and_validate( Ok(pipeline::validate(transformed, &[])) } -fn run_inputs_as_strings(settings: Option<&SettingsFile>) -> Option> { +fn run_inputs_as_strings(settings: Option<&SettingsLayer>) -> Option> { settings .and_then(|settings| settings.run.as_ref()) .and_then(|run| run.inputs.as_ref()) @@ -458,7 +458,7 @@ mod tests { )) } - fn validate_dot(dot_source: &str, settings: SettingsFile) -> Validated { + fn validate_dot(dot_source: &str, settings: SettingsLayer) -> Validated { validate(ValidateInput { workflow: WorkflowInput::DotSource { source: dot_source.to_string(), @@ -480,7 +480,7 @@ mod tests { #[test] fn validate_minimal() { - let validated = validate_dot(MINIMAL_DOT, SettingsFile::default()); + let validated = validate_dot(MINIMAL_DOT, SettingsLayer::default()); validated.raise_on_errors().unwrap(); assert_eq!(validated.graph().name, "Test"); @@ -497,7 +497,7 @@ mod tests { exit [shape=Msquare] start -> work -> exit }"#; - let validated = validate_dot(dot, SettingsFile::default()); + let validated = validate_dot(dot, SettingsLayer::default()); validated.raise_on_errors().unwrap(); let prompt = validated.graph().nodes["work"] @@ -535,7 +535,7 @@ mod tests { exit [shape=Msquare] start -> work -> exit }"#; - let validated = validate_dot(dot, SettingsFile::default()); + let validated = validate_dot(dot, SettingsLayer::default()); validated.raise_on_errors().unwrap(); assert_eq!( @@ -557,13 +557,13 @@ mod tests { use fabro_types::settings::run::{RunGoalLayer, RunLayer}; let mut inputs = std::collections::HashMap::new(); inputs.insert("who".to_string(), toml::Value::String("agent".to_string())); - SettingsFile { + SettingsLayer { run: Some(RunLayer { goal: Some(RunGoalLayer::Inline(InterpString::parse("override"))), inputs: Some(inputs), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } }); validated.raise_on_errors().unwrap(); @@ -584,7 +584,7 @@ mod tests { source: "not a graph".to_string(), base_dir: None, }, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: PathBuf::from("."), custom_transforms: Vec::new(), }); @@ -597,7 +597,7 @@ mod tests { graph [goal="Test"] work [label="Work"] }"#; - let validated = validate_dot(dot, SettingsFile::default()); + let validated = validate_dot(dot, SettingsLayer::default()); assert!(validated.has_errors()); assert!(validated.raise_on_errors().is_err()); @@ -624,7 +624,7 @@ mod tests { source: MINIMAL_DOT.to_string(), base_dir: None, }, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: PathBuf::from("."), custom_transforms: vec![Box::new(TagTransform)], }) @@ -657,7 +657,7 @@ mod tests { let validated = validate(ValidateInput { workflow: WorkflowInput::Path(dot_path), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: dir.path().to_path_buf(), custom_transforms: Vec::new(), }) @@ -693,7 +693,7 @@ mod tests { (PathBuf::from("prompts/lint.md"), "Lint $goal".to_string()), ]), }), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: PathBuf::from("."), custom_transforms: Vec::new(), }) @@ -724,7 +724,7 @@ mod tests { source: dot.to_string(), base_dir: None, }, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), cwd: dir.path().to_path_buf(), workflow_slug: None, workflow_path: None, @@ -766,7 +766,7 @@ mod tests { }; let mut metadata = HashMap::new(); metadata.insert("env".to_string(), "test".to_string()); - SettingsFile { + SettingsLayer { run: Some(RunLayer { goal: Some(RunGoalLayer::Inline(InterpString::parse("override goal"))), metadata, @@ -784,7 +784,7 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } }, cwd: dir.path().to_path_buf(), @@ -872,7 +872,7 @@ mod tests { }, settings: { use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; - SettingsFile { + SettingsLayer { run: Some(RunLayer { working_dir: Some(InterpString::parse("workspace")), execution: Some(RunExecutionLayer { @@ -881,7 +881,7 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } }, cwd: dir.path().to_path_buf(), @@ -946,9 +946,9 @@ mod tests { ); } - fn dry_run_only_settings() -> SettingsFile { + fn dry_run_only_settings() -> SettingsLayer { use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; - SettingsFile { + SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -956,14 +956,14 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } } - fn dry_run_with_storage(storage_dir: &Path) -> SettingsFile { + fn dry_run_with_storage(storage_dir: &Path) -> SettingsLayer { use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; use fabro_types::settings::server::{ServerLayer, ServerStorageLayer}; - SettingsFile { + SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -977,7 +977,7 @@ mod tests { }), ..ServerLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() } } diff --git a/lib/crates/fabro-workflow/src/operations/rebuild_meta.rs b/lib/crates/fabro-workflow/src/operations/rebuild_meta.rs index 93f920310..f87cc9ce7 100644 --- a/lib/crates/fabro-workflow/src/operations/rebuild_meta.rs +++ b/lib/crates/fabro-workflow/src/operations/rebuild_meta.rs @@ -335,7 +335,7 @@ mod tests { use chrono::{TimeZone, Utc}; use fabro_graphviz::graph::Graph; use fabro_store::{Database, StageId}; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{RunId, RunRecord, SandboxRecord, StartRecord, fixtures}; use object_store::memory::InMemory; use std::collections::HashMap; @@ -371,7 +371,7 @@ mod tests { fn sample_run_record(run_id: RunId, host_repo_path: Option<&str>) -> RunRecord { RunRecord { run_id, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: None, working_directory: PathBuf::from("/tmp/project"), diff --git a/lib/crates/fabro-workflow/src/operations/source.rs b/lib/crates/fabro-workflow/src/operations/source.rs index aa126c75e..d6728e871 100644 --- a/lib/crates/fabro-workflow/src/operations/source.rs +++ b/lib/crates/fabro-workflow/src/operations/source.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Context; use fabro_config::project as project_config; use fabro_config::run::resolve_run_goal; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use crate::file_resolver::{FileResolver, FilesystemFileResolver}; use crate::workflow_bundle::BundledWorkflow; @@ -22,14 +22,14 @@ pub enum WorkflowInput { #[derive(Clone, Debug)] pub(crate) struct ResolveWorkflowInput { pub workflow: WorkflowInput, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub cwd: PathBuf, } #[derive(Clone)] pub(crate) struct ResolvedWorkflow { pub raw_source: String, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub workflow_slug: Option, pub workflow_toml_path: Option, pub dot_path: Option, @@ -135,7 +135,7 @@ pub(crate) fn resolve_workflow(request: ResolveWorkflowInput) -> anyhow::Result< /// Relative paths that survived config load (e.g. env-interpolated ones) /// are anchored at `working_directory`. fn resolve_goal_override( - settings: &SettingsFile, + settings: &SettingsLayer, working_directory: &Path, ) -> anyhow::Result> { resolve_run_goal(settings, working_directory) @@ -158,12 +158,12 @@ mod tests { source: "digraph Test { start -> exit }".to_string(), base_dir: None, }, - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { working_dir: Some(InterpString::parse("workspace")), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, cwd: dir.path().to_path_buf(), }) diff --git a/lib/crates/fabro-workflow/src/operations/start.rs b/lib/crates/fabro-workflow/src/operations/start.rs index ea9fa6357..dca5c24ba 100644 --- a/lib/crates/fabro-workflow/src/operations/start.rs +++ b/lib/crates/fabro-workflow/src/operations/start.rs @@ -982,7 +982,7 @@ mod tests { use chrono::Utc; use fabro_store::Database; use fabro_types::fixtures; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; use object_store::memory::InMemory; @@ -1021,7 +1021,7 @@ mod tests { source: dot.to_string(), base_dir: None, }, - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -1029,7 +1029,7 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, cwd: run_dir .parent() @@ -1207,7 +1207,7 @@ mod tests { .unwrap() .clone(), ), - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -1215,7 +1215,7 @@ mod tests { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, cwd: temp.path().to_path_buf(), workflow_slug: Some("bundle-child".to_string()), diff --git a/lib/crates/fabro-workflow/src/operations/validate.rs b/lib/crates/fabro-workflow/src/operations/validate.rs index 24feb395f..a7e8e8641 100644 --- a/lib/crates/fabro-workflow/src/operations/validate.rs +++ b/lib/crates/fabro-workflow/src/operations/validate.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use crate::error::FabroError; use crate::pipeline::Validated; @@ -11,7 +11,7 @@ use super::source::{ResolveWorkflowInput, WorkflowInput, resolve_workflow}; pub struct ValidateInput { pub workflow: WorkflowInput, - pub settings: SettingsFile, + pub settings: SettingsLayer, pub cwd: PathBuf, pub custom_transforms: Vec>, } diff --git a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs index c03d18480..bf7229285 100644 --- a/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs +++ b/lib/crates/fabro-workflow/src/pipeline/execute/tests.rs @@ -13,7 +13,7 @@ use fabro_hooks::HookSettings; use fabro_interview::AutoApproveInterviewer; use fabro_sandbox::SandboxSpec; use fabro_store::Database; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::{RunId, fixtures}; use object_store::memory::InMemory; @@ -91,7 +91,7 @@ fn test_run_options(run_dir: &Path, run_id: &str) -> RunOptions { run_dir: run_dir.to_path_buf(), cancel_token: None, run_id: test_run_id(run_id), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), git: None, host_repo_path: None, labels: HashMap::new(), @@ -133,7 +133,7 @@ fn persisted_workflow(graph: Graph, source: String, run_dir: &Path, run_id: RunI run_dir.to_path_buf(), RunRecord { run_id, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph, workflow_slug: Some("test".to_string()), working_directory: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")), diff --git a/lib/crates/fabro-workflow/src/pipeline/finalize.rs b/lib/crates/fabro-workflow/src/pipeline/finalize.rs index 33425e5c6..f640e36c6 100644 --- a/lib/crates/fabro-workflow/src/pipeline/finalize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/finalize.rs @@ -306,7 +306,7 @@ mod tests { use fabro_graphviz::graph::Graph; use fabro_store::Database; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{RunId, fixtures}; use object_store::memory::InMemory; @@ -321,7 +321,7 @@ mod tests { fn test_run_options(run_dir: &std::path::Path) -> RunOptions { RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.to_path_buf(), cancel_token: None, run_id: test_run_id(), diff --git a/lib/crates/fabro-workflow/src/pipeline/initialize.rs b/lib/crates/fabro-workflow/src/pipeline/initialize.rs index 7eba23f4a..4cd866d6f 100644 --- a/lib/crates/fabro-workflow/src/pipeline/initialize.rs +++ b/lib/crates/fabro-workflow/src/pipeline/initialize.rs @@ -688,7 +688,7 @@ mod tests { use fabro_interview::AutoApproveInterviewer; use fabro_sandbox::SandboxSpec; use fabro_store::Database; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{RunId, fixtures}; use object_store::memory::InMemory; @@ -736,7 +736,7 @@ mod tests { fn test_settings(run_dir: &std::path::Path) -> RunOptions { RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.to_path_buf(), cancel_token: None, run_id: test_run_id(), @@ -758,7 +758,7 @@ mod tests { run_dir.to_path_buf(), RunRecord { run_id: test_run_id(), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph, workflow_slug: Some("test".to_string()), working_directory: std::env::current_dir().unwrap(), diff --git a/lib/crates/fabro-workflow/src/pipeline/persist.rs b/lib/crates/fabro-workflow/src/pipeline/persist.rs index 68537edb3..3d0f70fb7 100644 --- a/lib/crates/fabro-workflow/src/pipeline/persist.rs +++ b/lib/crates/fabro-workflow/src/pipeline/persist.rs @@ -55,7 +55,7 @@ mod tests { use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; use fabro_store::{Database, RunDatabase}; use fabro_types::fixtures; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::settings::cli::{CliLayer, CliOutputLayer, OutputVerbosity}; use fabro_types::settings::run::{RunExecutionLayer, RunLayer, RunMode}; use object_store::memory::InMemory; @@ -121,7 +121,7 @@ mod tests { fn sample_record(graph: Graph) -> RunRecord { RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { execution: Some(RunExecutionLayer { mode: Some(RunMode::DryRun), @@ -136,7 +136,7 @@ mod tests { }), ..CliLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, graph, workflow_slug: Some("ship".to_string()), diff --git a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs index 8bb601aac..5ced3483d 100644 --- a/lib/crates/fabro-workflow/src/pipeline/pull_request.rs +++ b/lib/crates/fabro-workflow/src/pipeline/pull_request.rs @@ -595,7 +595,7 @@ mod tests { AggregateStats, FrictionKind, FrictionPoint, OpenItem, OpenItemKind, StageRetro, }; use fabro_store::Database; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{BilledTokenCounts, RunRecord, fixtures}; use futures::stream; use object_store::memory::InMemory; @@ -1083,7 +1083,7 @@ mod tests { let run_record = RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: Some("test".to_string()), working_directory: PathBuf::from("/tmp/project"), @@ -1156,7 +1156,7 @@ mod tests { let run_record = RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: Some("test".to_string()), working_directory: PathBuf::from("/tmp/project"), @@ -1382,7 +1382,7 @@ mod tests { let run_store = store.create_run(&fixtures::RUN_1).await.unwrap(); let run_record = RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: None, working_directory: tmp.path().to_path_buf(), diff --git a/lib/crates/fabro-workflow/src/pipeline/retro.rs b/lib/crates/fabro-workflow/src/pipeline/retro.rs index 82db57ebd..bd320c410 100644 --- a/lib/crates/fabro-workflow/src/pipeline/retro.rs +++ b/lib/crates/fabro-workflow/src/pipeline/retro.rs @@ -184,7 +184,7 @@ mod tests { use fabro_graphviz::graph::Graph; use fabro_store::Database; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{RunId, fixtures}; use object_store::memory::InMemory; @@ -234,7 +234,7 @@ mod tests { let run_store = inner; let run_record = RunRecord { run_id: test_run_id(), - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: None, working_directory: run_dir.to_path_buf(), @@ -305,7 +305,7 @@ mod tests { fn test_run_options(run_dir: &std::path::Path) -> RunOptions { RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.to_path_buf(), cancel_token: None, run_id: test_run_id(), diff --git a/lib/crates/fabro-workflow/src/run_lookup.rs b/lib/crates/fabro-workflow/src/run_lookup.rs index e6d393ced..e9293787c 100644 --- a/lib/crates/fabro-workflow/src/run_lookup.rs +++ b/lib/crates/fabro-workflow/src/run_lookup.rs @@ -396,7 +396,7 @@ mod tests { use fabro_graphviz::graph::Graph; use fabro_store::Database; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{RunStatus, fixtures}; use object_store::memory::InMemory; @@ -416,7 +416,7 @@ mod tests { fn sample_run_record() -> RunRecord { RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: Some("test".to_string()), working_directory: PathBuf::from("/tmp/project"), diff --git a/lib/crates/fabro-workflow/src/run_materialization.rs b/lib/crates/fabro-workflow/src/run_materialization.rs index 765a18776..1f607dae2 100644 --- a/lib/crates/fabro-workflow/src/run_materialization.rs +++ b/lib/crates/fabro-workflow/src/run_materialization.rs @@ -1,9 +1,13 @@ use fabro_graphviz::graph::Graph; use fabro_model::{Catalog, Provider}; use fabro_types::settings::run::{RunGoalLayer, RunLayer, RunModelLayer}; -use fabro_types::settings::{InterpString, SettingsFile}; +use fabro_types::settings::{InterpString, SettingsLayer}; -pub fn materialize_run(mut layer: SettingsFile, graph: &Graph, catalog: &Catalog) -> SettingsFile { +pub fn materialize_run( + mut layer: SettingsLayer, + graph: &Graph, + catalog: &Catalog, +) -> SettingsLayer { let configured_model = layer .run .as_ref() diff --git a/lib/crates/fabro-workflow/src/run_options.rs b/lib/crates/fabro-workflow/src/run_options.rs index 6f3dbe044..ce2cc26a4 100644 --- a/lib/crates/fabro-workflow/src/run_options.rs +++ b/lib/crates/fabro-workflow/src/run_options.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use fabro_types::RunId; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::RunMode; use crate::git::{GitAuthor, git_author_from_settings}; @@ -20,7 +20,7 @@ pub struct GitCheckpointOptions { /// Options for a workflow run. #[derive(Clone)] pub struct RunOptions { - pub settings: SettingsFile, + pub settings: SettingsLayer, pub run_dir: PathBuf, pub cancel_token: Option>, /// Unique identifier for this workflow run. diff --git a/lib/crates/fabro-workflow/src/runtime_store.rs b/lib/crates/fabro-workflow/src/runtime_store.rs index c83dbdb6f..3dc558ce4 100644 --- a/lib/crates/fabro-workflow/src/runtime_store.rs +++ b/lib/crates/fabro-workflow/src/runtime_store.rs @@ -113,7 +113,7 @@ mod tests { use fabro_store::Database; use fabro_types::fixtures; use fabro_types::run_event::RunSubmittedProps; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_types::{EventBody, RunEvent}; use object_store::memory::InMemory; @@ -133,7 +133,7 @@ mod tests { fn test_run_record() -> RunRecord { RunRecord { run_id: fixtures::RUN_1, - settings: SettingsFile::default(), + settings: SettingsLayer::default(), graph: Graph::new("test"), workflow_slug: Some("test".to_string()), working_directory: PathBuf::from("/tmp/test"), diff --git a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs index b206f23cb..c2ef704f0 100644 --- a/lib/crates/fabro-workflow/tests/it/daytona_integration.rs +++ b/lib/crates/fabro-workflow/tests/it/daytona_integration.rs @@ -22,7 +22,7 @@ use fabro_graphviz::graph::{AttrValue, Edge, Graph, Node}; use fabro_llm::provider::Provider; use fabro_sandbox::daytona::{DaytonaConfig, DaytonaSandbox, DaytonaSnapshotConfig}; use fabro_store::{ArtifactStore, Database}; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{RunArtifactsLayer, RunLayer}; use fabro_types::{RunId, StageId}; use fabro_workflow::artifact::sync_artifacts_to_env; @@ -498,7 +498,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -676,7 +676,7 @@ async fn daytona_git_checkpoint_remote_emits_events() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("git-cp-test"), @@ -847,7 +847,7 @@ async fn daytona_parallel_git_branching_e2e() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), Arc::clone(&env)); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_tmp.path().to_path_buf(), cancel_token: None, run_id, @@ -1194,7 +1194,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() { let meta_branch = MetadataStore::branch_name(&run_id.to_string()); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id, @@ -1328,14 +1328,14 @@ async fn daytona_asset_collection() { graph.edges.push(Edge::new("create_assets", "exit")); let run_options = RunOptions { - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { artifacts: Some(RunArtifactsLayer { include: vec!["test-results/**".to_string()], }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, run_dir: dir.path().to_path_buf(), cancel_token: None, @@ -1591,7 +1591,7 @@ async fn daytona_git_push_run_branch_to_origin() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id, diff --git a/lib/crates/fabro-workflow/tests/it/integration.rs b/lib/crates/fabro-workflow/tests/it/integration.rs index b3c440fc4..52fcc8b2d 100644 --- a/lib/crates/fabro-workflow/tests/it/integration.rs +++ b/lib/crates/fabro-workflow/tests/it/integration.rs @@ -26,7 +26,7 @@ use fabro_interview::{ }; use fabro_llm::provider::Provider; use fabro_store::{ArtifactStore, Database}; -use fabro_types::settings::SettingsFile; +use fabro_types::settings::SettingsLayer; use fabro_types::settings::run::{RunArtifactsLayer, RunLayer}; use fabro_types::{RunEvent, RunId, StageId}; use fabro_validate::{Severity, validate, validate_or_raise}; @@ -338,7 +338,7 @@ async fn end_to_end_linear_pipeline() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -467,7 +467,7 @@ async fn end_to_end_branching_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -586,7 +586,7 @@ async fn end_to_end_human_gate_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -681,7 +681,7 @@ async fn human_gate_interrupted_input_fails_closed_without_fail_route() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -791,7 +791,7 @@ async fn human_gate_interrupted_input_routes_via_outcome_fail_condition() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -903,7 +903,7 @@ async fn goal_gate_routes_to_retry_target_on_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1023,7 +1023,7 @@ async fn goal_gate_routes_to_retry_target_when_present() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1334,7 +1334,7 @@ async fn retry_on_failure_then_succeed() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1408,7 +1408,7 @@ async fn pipeline_with_many_nodes() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1753,7 +1753,7 @@ async fn smoke_test_with_mock_codergen_backend() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1854,7 +1854,7 @@ async fn end_to_end_parallel_fan_out_fan_in() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -1966,7 +1966,7 @@ async fn resume_from_checkpoint_completes_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2064,7 +2064,7 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2106,7 +2106,7 @@ async fn graph_goal_in_context() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2144,7 +2144,7 @@ async fn event_streaming_lifecycle() { let events = collect_events(&emitter); let engine = WorkflowRunner::new(make_linear_registry(), Arc::new(emitter), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2223,7 +2223,7 @@ async fn context_flow_between_stages() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2278,7 +2278,7 @@ async fn tool_handler_e2e() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2352,7 +2352,7 @@ async fn auto_approve_interviewer_e2e() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2391,7 +2391,7 @@ async fn codergen_without_backend_simulated() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2495,7 +2495,7 @@ async fn branching_loop_back_on_failure() { ); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2580,7 +2580,7 @@ async fn human_gate_loops_back() { registry.register("human", Box::new(HumanHandler::new(interviewer))); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2640,7 +2640,7 @@ async fn scenario_ship_a_feature() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2724,7 +2724,7 @@ async fn scenario_parallel_expert_review() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2810,7 +2810,7 @@ async fn scenario_node_retries_on_retry_status() { ); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2874,7 +2874,7 @@ async fn scenario_loop_restart_resets_context() { ); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -2941,7 +2941,7 @@ async fn scenario_bug_triage_router() { registry.register("conditional", Box::new(ConditionalHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3002,7 +3002,7 @@ async fn scenario_crash_recovery() { registry.register("exit", Box::new(ExitHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3110,7 +3110,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3191,7 +3191,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() { registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3331,7 +3331,7 @@ async fn conditional_branching_success_fail_paths() { registry.register("always_fail", Box::new(AlwaysFailHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3386,7 +3386,7 @@ async fn edge_selection_condition_match_wins_over_weight() { registry.register("exit", Box::new(ExitHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3435,7 +3435,7 @@ async fn edge_selection_weight_breaks_ties() { registry.register("exit", Box::new(ExitHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3476,7 +3476,7 @@ async fn edge_selection_lexical_tiebreak() { registry.register("exit", Box::new(ExitHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3536,7 +3536,7 @@ async fn context_updates_visible_across_nodes() { registry.register("context_setter", Box::new(ContextSetterHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3582,7 +3582,7 @@ async fn stylesheet_applies_model_override() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3637,7 +3637,7 @@ async fn custom_handler_registration_and_execution() { registry.register("my_custom", Box::new(CustomHandler)); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3710,7 +3710,7 @@ async fn integration_smoke_plan_implement_review_done() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3801,7 +3801,7 @@ async fn manager_loop_runs_child_engine_e2e() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -3934,7 +3934,7 @@ async fn manager_loop_context_flows_e2e() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4009,7 +4009,7 @@ async fn manager_loop_child_dotfile_e2e() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4113,7 +4113,7 @@ async fn import_e2e_through_engine() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4266,7 +4266,7 @@ async fn fidelity_default_is_compact() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4322,7 +4322,7 @@ async fn fidelity_graph_default_applied() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4374,7 +4374,7 @@ async fn fidelity_node_overrides_graph_default() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4432,7 +4432,7 @@ async fn fidelity_edge_overrides_node_and_graph() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4480,7 +4480,7 @@ async fn fidelity_full_produces_empty_preamble() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4538,7 +4538,7 @@ async fn fidelity_truncate_preamble_minimal() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4609,7 +4609,7 @@ async fn fidelity_summary_low_mode() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4675,7 +4675,7 @@ async fn fidelity_summary_medium_mode() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4741,7 +4741,7 @@ async fn fidelity_summary_high_mode() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4800,7 +4800,7 @@ async fn fidelity_full_sets_thread_id_in_context() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4870,7 +4870,7 @@ async fn fidelity_full_nodes_share_thread_id() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -4950,7 +4950,7 @@ async fn fidelity_resume_degrades_full_to_summary_high() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5046,7 +5046,7 @@ async fn fidelity_resume_degrade_only_affects_first_hop() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5129,7 +5129,7 @@ async fn fidelity_resume_no_degrade_when_not_full() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5170,7 +5170,7 @@ async fn fidelity_stored_in_checkpoint_context() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5262,7 +5262,7 @@ async fn fidelity_precedence_multi_node_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5329,7 +5329,7 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5403,7 +5403,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { ); let engine_low = WorkflowRunner::new(registry_low, Arc::new(Emitter::default()), local_env()); let run_options_low = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir_low.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5469,7 +5469,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { ); let engine_med = WorkflowRunner::new(registry_med, Arc::new(Emitter::default()), local_env()); let run_options_med = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir_med.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5539,7 +5539,7 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5592,7 +5592,7 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5648,7 +5648,7 @@ async fn fidelity_edge_thread_id_override_in_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5705,7 +5705,7 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5772,7 +5772,7 @@ async fn fidelity_from_parsed_dot_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5819,7 +5819,7 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5890,7 +5890,7 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -5976,7 +5976,7 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6019,7 +6019,7 @@ mod real_llm { use async_trait::async_trait; use fabro_graphviz::graph::Node; - use fabro_types::settings::SettingsFile; + use fabro_types::settings::SettingsLayer; use fabro_workflow::context::Context; use fabro_workflow::error::FabroError; use fabro_workflow::handler::agent::{AgentHandler, CodergenBackend, CodergenResult}; @@ -6213,7 +6213,7 @@ mod real_llm { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6321,7 +6321,7 @@ mod real_llm { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6453,7 +6453,7 @@ mod real_llm { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6553,7 +6553,7 @@ mod real_llm { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6646,7 +6646,7 @@ async fn human_gate_freeform_only_routes_text() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6775,7 +6775,7 @@ async fn human_gate_freeform_with_fixed_choice_match() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -6889,7 +6889,7 @@ async fn human_gate_freeform_fallback_on_unmatched_text() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -7016,7 +7016,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -7123,7 +7123,7 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -7423,7 +7423,7 @@ fn engine_with_hooks_and_events( fn make_run_options(dir: &std::path::Path) -> RunOptions { RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.to_path_buf(), cancel_token: None, run_id: test_run_id("hook-test-run"), @@ -8090,8 +8090,8 @@ async fn hook_config_merge_run_overrides_by_name() { } // The legacy `Settings`-based TOML parsing tests were deleted in Stage -// 6.3b. Hook TOML parsing now flows through the v2 `SettingsFile` path, -// with coverage in `fabro-types::settings::tree::tests` and the +// 6.3b. Hook TOML parsing now flows through the v2 `SettingsLayer` path, +// with coverage in `fabro-types::settings::layer::tests` and the // fabro-cli integration tests under `cmd::config`. // --- Blocking vs non-blocking behavior --- @@ -8241,7 +8241,7 @@ async fn hook_sandbox_false_runs_on_host() { // Prompt and Agent hook TOML parsing: the legacy `Settings`-based // variant of this test was deleted in Stage 6.3b; v2 coverage lives in -// `fabro-types::settings::tree::tests`. +// `fabro-types::settings::layer::tests`. // --- Events emitted correctly alongside hooks --- @@ -8358,7 +8358,7 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -8558,7 +8558,7 @@ async fn large_context_values_are_offloaded_to_artifact_store() { let events = collect_events(&emitter); let engine = WorkflowRunner::new(registry, Arc::new(emitter), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -8761,7 +8761,7 @@ async fn artifact_pointers_rewritten_for_remote_sandbox() { let remote_env = Arc::new(RemoteMockEnv::new("/sandbox")); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), remote_env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -8848,7 +8848,7 @@ async fn downstream_local_execution_materializes_blob_refs_to_runtime_files() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -8935,7 +8935,7 @@ async fn downstream_remote_execution_materializes_blob_refs_to_sandbox_files() { let remote_env = Arc::new(RemoteMockEnv::new("/sandbox")); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), remote_env.clone()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -9065,7 +9065,7 @@ async fn node_dir_uses_visit_count_on_revisit() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -9934,7 +9934,7 @@ async fn full_pipeline_with_cli_backend_node() { let dir = tempfile::tempdir().unwrap(); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), env); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -10052,7 +10052,7 @@ async fn stylesheet_backend_property_routes_to_cli() { let dir = tempfile::tempdir().unwrap(); let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), env); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), @@ -10242,7 +10242,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), env); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-docker"), @@ -10408,7 +10408,7 @@ async fn git_checkpoint_host_writes_shadow_branch() { let meta_branch = MetadataStore::branch_name(&run_id.to_string()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.path().to_path_buf(), cancel_token: None, run_id, @@ -10605,7 +10605,7 @@ async fn parallel_git_branching_host_e2e() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), env); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.path().to_path_buf(), cancel_token: None, run_id, @@ -10854,7 +10854,7 @@ async fn git_checkpoint_host_skips_empty_diff_patch() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), env); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: run_dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("empty-diff"), @@ -11221,7 +11221,7 @@ async fn e2e_circuit_breaker_deterministic_self_loop() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-circuit-breaker"), @@ -11267,7 +11267,7 @@ async fn e2e_circuit_breaker_custom_limit() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-custom-limit"), @@ -11306,7 +11306,7 @@ async fn e2e_circuit_breaker_ignores_transient_failures() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-transient-no-breaker"), @@ -11352,7 +11352,7 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-varying-reasons"), @@ -11391,7 +11391,7 @@ async fn e2e_circuit_breaker_loop_restart() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-breaker"), @@ -11452,7 +11452,7 @@ async fn e2e_failure_signature_persisted_in_context() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-sig-context"), @@ -11515,7 +11515,7 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-sig-hint"), @@ -11570,7 +11570,7 @@ async fn e2e_signature_maps_persist_in_checkpoint() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-sig-persist"), @@ -11696,7 +11696,7 @@ async fn e2e_circuit_breaker_emits_events_before_abort() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-events"), @@ -11760,7 +11760,7 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-below-limit"), @@ -11855,7 +11855,7 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-impl-verify-cycle"), @@ -11951,7 +11951,7 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-blocked-det"), @@ -11990,7 +11990,7 @@ async fn e2e_loop_restart_blocked_for_structural_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-blocked-struct"), @@ -12029,7 +12029,7 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-blocked-budget"), @@ -12068,7 +12068,7 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-blocked-canceled"), @@ -12104,7 +12104,7 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-blocked-comploop"), @@ -12144,7 +12144,7 @@ async fn e2e_loop_restart_allowed_for_transient_infra() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("e2e-restart-allowed-transient"), @@ -12251,7 +12251,7 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() { let engine = WorkflowRunner::new(registry, Arc::new(emitter), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("stall-e2e"), @@ -12306,7 +12306,7 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("stall-alive-e2e"), @@ -12351,7 +12351,7 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("stall-disabled-e2e"), @@ -12415,7 +12415,7 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() { let engine = WorkflowRunner::new(registry, Arc::new(Emitter::default()), local_env()); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("stall-override-e2e"), @@ -12545,14 +12545,14 @@ async fn asset_collection_local_sandbox_success() { graph.edges.push(Edge::new("create_assets", "exit")); let run_options = RunOptions { - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { artifacts: Some(RunArtifactsLayer { include: vec!["test-results/**".to_string()], }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, run_dir: run_dir.path().to_path_buf(), cancel_token: None, @@ -12677,14 +12677,14 @@ async fn asset_collection_local_sandbox_on_failure() { graph.edges.push(Edge::new("create_assets", "exit")); let run_options = RunOptions { - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { artifacts: Some(RunArtifactsLayer { include: vec!["test-results/**".to_string()], }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, run_dir: run_dir.path().to_path_buf(), cancel_token: None, @@ -12781,14 +12781,14 @@ async fn asset_collection_docker_sandbox() { graph.edges.push(Edge::new("create_assets", "exit")); let run_options = RunOptions { - settings: SettingsFile { + settings: SettingsLayer { run: Some(RunLayer { artifacts: Some(RunArtifactsLayer { include: vec!["test-results/**".to_string()], }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }, run_dir: run_dir.path().to_path_buf(), cancel_token: None, @@ -12857,7 +12857,7 @@ async fn wait_timer_e2e() { local_env(), ); let run_options = RunOptions { - settings: SettingsFile::default(), + settings: SettingsLayer::default(), run_dir: dir.path().to_path_buf(), cancel_token: None, run_id: test_run_id("test-run"), diff --git a/lib/crates/fabro-workflow/tests/materialize_run.rs b/lib/crates/fabro-workflow/tests/materialize_run.rs index f86f2152b..614dca157 100644 --- a/lib/crates/fabro-workflow/tests/materialize_run.rs +++ b/lib/crates/fabro-workflow/tests/materialize_run.rs @@ -1,7 +1,7 @@ use fabro_graphviz::graph::Graph; use fabro_model::Catalog; use fabro_types::settings::run::{RunGoalLayer, RunLayer, RunModelLayer, RunPullRequestLayer}; -use fabro_types::settings::{InterpString, SettingsFile}; +use fabro_types::settings::{InterpString, SettingsLayer}; use fabro_workflow::run_materialization::materialize_run; fn graph(source: &str) -> Graph { @@ -17,7 +17,7 @@ fn materialize_run_applies_graph_and_catalog_defaults() { start -> exit }"#; - let settings = SettingsFile { + let settings = SettingsLayer { run: Some(RunLayer { model: Some(RunModelLayer { name: Some(InterpString::parse("sonnet")), @@ -29,7 +29,7 @@ fn materialize_run_applies_graph_and_catalog_defaults() { }), ..RunLayer::default() }), - ..SettingsFile::default() + ..SettingsLayer::default() }; let materialized = materialize_run(settings, &graph(source), &Catalog::builtin());