mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
refactor(settings): rename settings layer and move parsing
This commit is contained in:
parent
990812db3f
commit
c5c81d2985
73 changed files with 720 additions and 1025 deletions
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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<Arc<ServerStoreClient>>,
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
async fn merged_config(args: &SettingsArgs) -> anyhow::Result<SettingsLayer> {
|
||||
let base_ctx = CommandContext::base()?;
|
||||
let layers = config_layers(&base_ctx, args.workflow.as_deref())?;
|
||||
if args.local {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<CreatedRun> {
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
pub(crate) fn run_args_layer(args: &RunArgs) -> Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
|||
..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<SettingsFile> {
|
||||
pub(crate) fn preflight_args_layer(args: &PreflightArgs) -> Result<SettingsLayer> {
|
||||
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<SettingsFile>
|
|||
..RunLayer::default()
|
||||
};
|
||||
|
||||
Ok(SettingsFile {
|
||||
Ok(SettingsLayer {
|
||||
run: Some(run),
|
||||
cli: cli_layer_for_verbose(args.verbose),
|
||||
..SettingsFile::default()
|
||||
..SettingsLayer::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Option<fabro_github::GitHubAppCredentials>> {
|
||||
let resolved_run = fabro_config::resolve_run_from_file(settings).ok();
|
||||
let resolved_server = fabro_config::resolve_server_from_file(settings).ok();
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -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<types::ManifestArgs>,
|
||||
pub run_id: Option<RunId>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<ServerS
|
|||
|
||||
pub(crate) async fn connect_server_with_settings(
|
||||
args: &ServerTargetArgs,
|
||||
settings: &SettingsFile,
|
||||
settings: &SettingsLayer,
|
||||
base_config_path: &Path,
|
||||
) -> Result<ServerStoreClient> {
|
||||
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<SettingsFile> {
|
||||
pub(crate) async fn retrieve_server_settings(&self) -> Result<SettingsLayer> {
|
||||
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::<SettingsFile>(raw)
|
||||
serde_json::from_value::<SettingsLayer>(raw)
|
||||
.context("server returned a settings payload that does not match the v2 schema")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
pub(crate) fn load_settings() -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
||||
) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
||||
) -> anyhow::Result<SettingsLayer> {
|
||||
settings_layer_with_config_and_storage_dir(None, storage_dir)
|
||||
}
|
||||
|
||||
pub(crate) fn load_settings_with_storage_dir(
|
||||
storage_dir: Option<&Path>,
|
||||
) -> anyhow::Result<SettingsFile> {
|
||||
) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
||||
) -> anyhow::Result<SettingsLayer> {
|
||||
settings_layer_with_config_and_storage_dir(config_path, storage_dir)
|
||||
}
|
||||
|
||||
|
|
@ -61,14 +61,14 @@ fn render_resolve_errors(errors: Vec<fabro_config::ResolveError>) -> anyhow::Err
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_cli_settings(file: &SettingsFile) -> anyhow::Result<CliSettings> {
|
||||
pub(crate) fn resolve_cli_settings(file: &SettingsLayer) -> anyhow::Result<CliSettings> {
|
||||
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<Cl
|
|||
}
|
||||
}
|
||||
|
||||
fn configured_server_target(settings: &SettingsFile) -> Result<Option<ServerTarget>> {
|
||||
fn configured_server_target(settings: &SettingsLayer) -> Result<Option<ServerTarget>> {
|
||||
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<PathBuf> {
|
||||
pub(crate) fn storage_dir(settings: &SettingsLayer) -> anyhow::Result<PathBuf> {
|
||||
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<ClientTlsSettings>) -> Result<Se
|
|||
|
||||
fn explicit_server_target(
|
||||
args: &ServerTargetArgs,
|
||||
settings: &SettingsFile,
|
||||
settings: &SettingsLayer,
|
||||
) -> Result<Option<ServerTarget>> {
|
||||
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<ServerTarget> {
|
||||
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<Option<ServerTarget>> {
|
||||
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!(
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
fn run_goal_inline(settings: &SettingsLayer) -> Option<String> {
|
||||
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<String> {
|
||||
fn run_model_name(settings: &SettingsLayer) -> Option<String> {
|
||||
resolve_run(settings)
|
||||
.model
|
||||
.name
|
||||
|
|
@ -65,7 +66,7 @@ fn run_model_name(settings: &SettingsFile) -> Option<String> {
|
|||
.map(|value| value.as_source())
|
||||
}
|
||||
|
||||
fn run_model_provider(settings: &SettingsFile) -> Option<String> {
|
||||
fn run_model_provider(settings: &SettingsLayer) -> Option<String> {
|
||||
resolve_run(settings)
|
||||
.model
|
||||
.provider
|
||||
|
|
@ -73,7 +74,7 @@ fn run_model_provider(settings: &SettingsFile) -> Option<String> {
|
|||
.map(|value| value.as_source())
|
||||
}
|
||||
|
||||
fn run_inputs(settings: &SettingsFile) -> &std::collections::HashMap<String, toml::Value> {
|
||||
fn run_inputs(settings: &SettingsLayer) -> &std::collections::HashMap<String, toml::Value> {
|
||||
settings
|
||||
.run
|
||||
.as_ref()
|
||||
|
|
@ -81,7 +82,7 @@ fn run_inputs(settings: &SettingsFile) -> &std::collections::HashMap<String, tom
|
|||
.expect("run.inputs")
|
||||
}
|
||||
|
||||
fn run_sandbox(settings: &SettingsFile) -> &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<String, fabro_types::settings::run::McpEntryLayer> {
|
||||
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<String> {
|
||||
fn run_prepare_commands(settings: &SettingsLayer) -> Vec<String> {
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
) -> Result<SettingsLayer> {
|
||||
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")),
|
||||
|
|
|
|||
|
|
@ -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<Settings> {
|
||||
let layer = effective_settings::resolve_settings(layers, server_settings, mode)?;
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
pub fn load_settings_path(path: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
|||
Ok(layer)
|
||||
}
|
||||
|
||||
pub fn load_settings_for_workflow(path: &Path, cwd: &Path) -> anyhow::Result<SettingsFile> {
|
||||
pub fn load_settings_for_workflow(path: &Path, cwd: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
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<Set
|
|||
Ok(combine_files(project_config, workflow_config))
|
||||
}
|
||||
|
||||
pub fn load_settings_project(start: &Path) -> anyhow::Result<SettingsFile> {
|
||||
pub fn load_settings_project(start: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
Ok(project::discover_project_config(start)?
|
||||
.map(|(_, config)| config)
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn load_settings_user() -> anyhow::Result<SettingsFile> {
|
||||
pub fn load_settings_user() -> anyhow::Result<SettingsLayer> {
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
169
lib/crates/fabro-config/src/parse.rs
Normal file
169
lib/crates/fabro-config/src/parse.rs
Normal file
|
|
@ -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<String> },
|
||||
}
|
||||
|
||||
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<SettingsLayer, ParseError> {
|
||||
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::<SettingsLayer>()
|
||||
.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<String> {
|
||||
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.<name>]` or `[cli.exec.agent.mcps.<name>]`",
|
||||
"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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SettingsFile>,
|
||||
pub workflow_config: Option<SettingsLayer>,
|
||||
pub workflow_toml_path: Option<PathBuf>,
|
||||
pub workflow_slug: Option<String>,
|
||||
}
|
||||
|
||||
/// Parse a project config from a TOML string.
|
||||
pub fn parse_project_config(content: &str) -> anyhow::Result<SettingsFile> {
|
||||
parse_settings_file(content)
|
||||
pub fn parse_project_config(content: &str) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
|||
///
|
||||
/// 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<SettingsFile> {
|
||||
pub fn load_project_config(path: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
|||
|
||||
/// 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<Option<(PathBuf, SettingsFile)>> {
|
||||
pub fn discover_project_config(start: &Path) -> anyhow::Result<Option<(PathBuf, SettingsLayer)>> {
|
||||
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<String> {
|
|||
}
|
||||
|
||||
/// Resolve a workflow argument to a DOT path and optional run config.
|
||||
pub fn resolve_workflow(arg: &Path) -> anyhow::Result<(PathBuf, Option<SettingsFile>)> {
|
||||
pub fn resolve_workflow(arg: &Path) -> anyhow::Result<(PathBuf, Option<SettingsLayer>)> {
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -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<Settings, Vec<ResolveError>> {
|
||||
pub fn resolve(file: &SettingsLayer) -> Result<Settings, Vec<ResolveError>> {
|
||||
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<Settings, Vec<ResolveError>> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve_cli_from_file(file: &SettingsFile) -> Result<CliSettings, Vec<ResolveError>> {
|
||||
pub fn resolve_cli_from_file(file: &SettingsLayer) -> Result<CliSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.cli)
|
||||
}
|
||||
|
||||
pub fn resolve_server_from_file(file: &SettingsFile) -> Result<ServerSettings, Vec<ResolveError>> {
|
||||
pub fn resolve_server_from_file(file: &SettingsLayer) -> Result<ServerSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.server)
|
||||
}
|
||||
|
||||
pub fn resolve_project_from_file(
|
||||
file: &SettingsFile,
|
||||
file: &SettingsLayer,
|
||||
) -> Result<ProjectSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.project)
|
||||
}
|
||||
|
||||
pub fn resolve_features_from_file(
|
||||
file: &SettingsFile,
|
||||
file: &SettingsLayer,
|
||||
) -> Result<FeaturesSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.features)
|
||||
}
|
||||
|
||||
pub fn resolve_run_from_file(file: &SettingsFile) -> Result<RunSettings, Vec<ResolveError>> {
|
||||
pub fn resolve_run_from_file(file: &SettingsLayer) -> Result<RunSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.run)
|
||||
}
|
||||
|
||||
pub fn resolve_workflow_from_file(
|
||||
file: &SettingsFile,
|
||||
file: &SettingsLayer,
|
||||
) -> Result<WorkflowSettings, Vec<ResolveError>> {
|
||||
resolve(file).map(|settings| settings.workflow)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
parse_settings_file(contents)
|
||||
pub fn parse_run_config(contents: &str) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
|||
///
|
||||
/// 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<SettingsFile> {
|
||||
pub fn load_run_config(path: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
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<Option<ResolvedRunGoal>, ResolveRunGoalError> {
|
||||
let Some(goal) = settings.run.as_ref().and_then(|run| run.goal.as_ref()) else {
|
||||
|
|
|
|||
|
|
@ -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<SettingsFile> {
|
||||
pub fn load_settings_config(path: Option<&Path>) -> anyhow::Result<SettingsLayer> {
|
||||
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<SettingsFile>
|
|||
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<SettingsFile> {
|
||||
fn load_v2_layer_from_path(path: &Path) -> anyhow::Result<SettingsLayer> {
|
||||
load_settings_path(path)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<types::ManifestGit>,
|
||||
pub root_source: String,
|
||||
pub run_id: Option<RunId>,
|
||||
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<PreparedManifest> {
|
||||
|
|
@ -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<SettingsFile> {
|
||||
) -> Result<SettingsLayer> {
|
||||
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<SettingsFile> {
|
||||
fn parse_manifest_config(config: &types::ManifestConfig) -> Result<SettingsLayer> {
|
||||
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<String, String> {
|
|||
}
|
||||
|
||||
fn resolve_manifest_dockerfile(
|
||||
layer: &mut SettingsFile,
|
||||
layer: &mut SettingsLayer,
|
||||
config_path: &Path,
|
||||
files: &HashMap<PathBuf, String>,
|
||||
) -> 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]
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>,
|
||||
}
|
||||
|
||||
fn load_settings(path: Option<&Path>) -> anyhow::Result<SettingsFile> {
|
||||
fn load_settings(path: Option<&Path>) -> anyhow::Result<SettingsLayer> {
|
||||
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<Arc<dyn ObjectStore>>
|
|||
build_object_store_with_preference(store_path, use_in_memory_store())
|
||||
}
|
||||
|
||||
fn resolve_server_settings(file: &SettingsFile) -> anyhow::Result<ResolvedServerSettings> {
|
||||
fn resolve_server_settings(file: &SettingsLayer) -> anyhow::Result<ResolvedServerSettings> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<EventEnvelope>,
|
||||
|
||||
pub(crate) secret_store: AsyncRwLock<SecretStore>,
|
||||
pub(crate) settings: Arc<RwLock<SettingsFile>>,
|
||||
pub(crate) settings: Arc<RwLock<SettingsLayer>>,
|
||||
pub(crate) server_settings: RwLock<Arc<ResolvedServerSettings>>,
|
||||
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<PathBuf, String> {
|
||||
fn resolved_storage_dir(settings: &SettingsLayer) -> Result<PathBuf, String> {
|
||||
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<PathBuf, String> {
|
|||
})
|
||||
}
|
||||
|
||||
fn resolved_github_app_id(settings: &SettingsFile) -> Result<Option<String>, String> {
|
||||
fn resolved_github_app_id(settings: &SettingsLayer) -> Result<Option<String>, 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<AppState> {
|
||||
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<dyn Interviewer>) -> HandlerRegistry + Send + Sync + 'static,
|
||||
) -> Arc<AppState> {
|
||||
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<dyn Interviewer>) -> HandlerRegistry + Send + Sync + 'static,
|
||||
) -> Arc<AppState> {
|
||||
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<AppState> {
|
||||
let (store, artifact_store) = test_store_bundle();
|
||||
|
|
@ -2079,7 +2081,7 @@ fn test_store_bundle() -> (Arc<Database>, ArtifactStore) {
|
|||
}
|
||||
|
||||
pub fn create_app_state_with_store(
|
||||
settings: Arc<RwLock<SettingsFile>>,
|
||||
settings: Arc<RwLock<SettingsLayer>>,
|
||||
max_concurrent_runs: usize,
|
||||
store: Arc<Database>,
|
||||
artifact_store: ArtifactStore,
|
||||
|
|
@ -2098,7 +2100,7 @@ pub fn create_app_state_with_store(
|
|||
}
|
||||
|
||||
pub(crate) fn build_app_state_with_path(
|
||||
settings: Arc<RwLock<SettingsFile>>,
|
||||
settings: Arc<RwLock<SettingsLayer>>,
|
||||
registry_factory_override: Option<Box<RegistryFactoryOverride>>,
|
||||
max_concurrent_runs: usize,
|
||||
store: Arc<Database>,
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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<AppState> {
|
|||
}
|
||||
|
||||
pub(crate) fn test_app_state_with_options(
|
||||
settings: SettingsFile,
|
||||
settings: SettingsLayer,
|
||||
max_concurrent_runs: usize,
|
||||
) -> Arc<AppState> {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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": {},
|
||||
|
|
|
|||
|
|
@ -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}")),
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
|
|
|||
33
lib/crates/fabro-types/src/settings/layer.rs
Normal file
33
lib/crates/fabro-types/src/settings/layer.rs
Normal file
|
|
@ -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<u32>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub project: Option<ProjectLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub workflow: Option<WorkflowLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub run: Option<RunLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cli: Option<CliLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server: Option<ServerLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub features: Option<FeaturesLayer>,
|
||||
}
|
||||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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<u32>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub project: Option<ProjectLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub workflow: Option<WorkflowLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub run: Option<RunLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cli: Option<CliLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub server: Option<ServerLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub features: Option<FeaturesLayer>,
|
||||
}
|
||||
|
||||
/// 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<String> },
|
||||
}
|
||||
|
||||
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<SettingsFile, ParseError> {
|
||||
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::<SettingsFile>()
|
||||
.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<String> {
|
||||
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.<name>]` or `[cli.exec.agent.mcps.<name>]`",
|
||||
"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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SchemaVersion, VersionError> {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub workflow_path: Option<PathBuf>,
|
||||
|
|
@ -51,7 +51,7 @@ pub struct CreatedRun {
|
|||
}
|
||||
|
||||
struct PersistCreateOptions {
|
||||
settings: SettingsFile,
|
||||
settings: SettingsLayer,
|
||||
run_id: Option<RunId>,
|
||||
run_dir: Option<PathBuf>,
|
||||
workflow_slug: Option<String>,
|
||||
|
|
@ -273,7 +273,7 @@ fn render_resolve_errors(errors: &[fabro_config::ResolveError]) -> String {
|
|||
.join("; ")
|
||||
}
|
||||
|
||||
fn resolve_settings_tree(settings: &SettingsFile) -> Result<Settings, FabroError> {
|
||||
fn resolve_settings_tree(settings: &SettingsLayer) -> Result<Settings, FabroError> {
|
||||
fabro_config::resolve(settings)
|
||||
.map_err(|errors| FabroError::Precondition(render_resolve_errors(&errors)))
|
||||
}
|
||||
|
|
@ -285,7 +285,7 @@ fn combined_labels(settings: &Settings) -> HashMap<String, String> {
|
|||
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<PathBuf>,
|
||||
file_resolver: Option<Arc<dyn FileResolver>>,
|
||||
custom_transforms: Vec<Box<dyn Transform>>,
|
||||
settings: Option<&SettingsFile>,
|
||||
settings: Option<&SettingsLayer>,
|
||||
goal_override: Option<&str>,
|
||||
) -> Result<Validated, FabroError> {
|
||||
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<HashMap<String, String>> {
|
||||
fn run_inputs_as_strings(settings: Option<&SettingsLayer>) -> Option<HashMap<String, String>> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
pub workflow_toml_path: Option<PathBuf>,
|
||||
pub dot_path: Option<PathBuf>,
|
||||
|
|
@ -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<Option<String>> {
|
||||
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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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<Box<dyn Transform>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(".")),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Arc<AtomicBool>>,
|
||||
/// Unique identifier for this workflow run.
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue