simplify: drop duplicate settings plumbing from cli/server refactor

- Remove CommandContext::cli_settings and cascade through 11 functions
  whose only use of `cli: &CliNamespace` was constructing it; dispatchers
  now forward only cli_layer.
- Drop `ServerSettings as CurrentServerSettings` /
  `ServerNamespace as ResolvedServerSettings` rename aliases; use the
  canonical type names in fabro-server.
- Inline `local_server::server_settings` and `user_config::{resolve_user_settings,
  resolve_cli_settings}` wrappers; callers use `ServerSettings::from_layer`
  / `UserSettings::from_layer` directly (anyhow converts via `?`).
- Trim narrative module doc in fabro-config/src/lib.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-22 21:02:37 -04:00
parent bb0d05be2b
commit 93b6577cd3
No known key found for this signature in database
55 changed files with 104 additions and 185 deletions

View file

@ -4,8 +4,8 @@ use std::sync::Arc;
use anyhow::{Context as _, Result, bail};
use fabro_config::UserSettings;
use fabro_config::merge::combine_files;
use fabro_types::settings::SettingsLayer;
use fabro_types::settings::cli::CliLayer;
use fabro_types::settings::{CliNamespace, SettingsLayer};
use fabro_util::printer::Printer;
use tokio::sync::OnceCell;
@ -35,24 +35,18 @@ pub(crate) struct CommandContext {
base_config_path: PathBuf,
machine_settings: SettingsLayer,
user_settings: UserSettings,
cli_settings: CliNamespace,
server_mode: ServerMode,
server: OnceCell<Arc<Client>>,
}
impl CommandContext {
pub(crate) fn base(
printer: Printer,
cli_settings: CliNamespace,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(printer, ServerMode::None, cli_settings, cli_layer)
pub(crate) fn base(printer: Printer, cli_layer: &CliLayer) -> Result<Self> {
Self::new(printer, ServerMode::None, cli_layer)
}
pub(crate) fn for_target(
args: &ServerTargetArgs,
printer: Printer,
cli_settings: CliNamespace,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(
@ -60,7 +54,6 @@ impl CommandContext {
ServerMode::ByTarget {
target_override: args.server.clone(),
},
cli_settings,
cli_layer,
)
}
@ -68,7 +61,6 @@ impl CommandContext {
pub(crate) fn for_connection(
args: &ServerConnectionArgs,
printer: Printer,
cli_settings: CliNamespace,
cli_layer: &CliLayer,
) -> Result<Self> {
Self::new(
@ -77,17 +69,11 @@ impl CommandContext {
target_override: args.target.server.clone(),
storage_dir_override: args.storage_dir.clone_path(),
},
cli_settings,
cli_layer,
)
}
fn new(
printer: Printer,
server_mode: ServerMode,
cli_settings: CliNamespace,
cli_layer: &CliLayer,
) -> Result<Self> {
fn new(printer: Printer, server_mode: ServerMode, cli_layer: &CliLayer) -> Result<Self> {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
let base_config_path = user_config::active_settings_path(None);
let disk_settings = match &server_mode {
@ -101,7 +87,7 @@ impl CommandContext {
cli: Some(cli_layer.clone()),
..SettingsLayer::default()
});
let user_settings = user_config::resolve_user_settings(&machine_settings)?;
let user_settings = fabro_config::UserSettings::from_layer(&machine_settings)?;
Ok(Self {
printer,
@ -109,7 +95,6 @@ impl CommandContext {
base_config_path,
machine_settings,
user_settings,
cli_settings,
server_mode,
server: OnceCell::new(),
})
@ -135,10 +120,6 @@ impl CommandContext {
&self.user_settings
}
pub(crate) fn cli_settings(&self) -> &CliNamespace {
&self.cli_settings
}
pub(crate) async fn server(&self) -> Result<Arc<Client>> {
let server_mode = self.server_mode.clone();
let base_config_path = self.base_config_path.clone();

View file

@ -26,7 +26,6 @@ pub(super) async fn cp_command(
run_id_selector,
args.node.as_deref(),
args.retry,
cli,
cli_layer,
printer,
)

View file

@ -19,7 +19,6 @@ pub(super) async fn list_command(
&args.run_id,
args.node.as_deref(),
args.retry,
cli,
cli_layer,
printer,
)

View file

@ -26,11 +26,10 @@ pub(super) async fn resolve_artifacts(
run_selector: &str,
node: Option<&str>,
retry: Option<u32>,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<(RunId, Client, Vec<ArtifactEntry>)> {
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(run_selector).await?.run_id;
let mut entries = Vec::new();

View file

@ -4,7 +4,6 @@ use anyhow::{Context as _, Result, bail};
use chrono::{DateTime, Utc};
use fabro_client::{AuthEntry, AuthStore, StoredSubject};
use fabro_http::header::CONTENT_TYPE;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::browser;
use fabro_util::printer::Printer;
@ -36,7 +35,6 @@ struct CliTokenSubject {
pub(super) async fn login_command(
args: AuthLoginArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
@ -45,7 +43,7 @@ pub(super) async fn login_command(
#[cfg(not(unix))]
{
let _ = (args, cli, cli_layer, printer);
let _ = (args, cli_layer, printer);
bail!(
"CLI OAuth login is not supported on Windows in this release. Use WSL, or use a dev-token server."
);
@ -53,7 +51,7 @@ pub(super) async fn login_command(
#[cfg(unix)]
{
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::base(printer, cli_layer)?;
let target = user_config::resolve_server_target(&args.server, ctx.machine_settings())?;
let web_url = browser_origin(&target)?;
let pkce = fabro_oauth::generate_pkce();

View file

@ -1,7 +1,6 @@
use anyhow::{Result, bail};
use fabro_client::{AuthEntry, AuthStore};
use fabro_http::header::AUTHORIZATION;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
@ -12,14 +11,13 @@ use crate::user_config::ServerTarget;
pub(super) async fn logout_command(
args: AuthLogoutArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
) -> Result<()> {
require_no_json_override(process_local_json)?;
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::base(printer, cli_layer)?;
let store = AuthStore::default();
if args.all {
let entries = store.list()?;

View file

@ -3,7 +3,6 @@ mod logout;
mod status;
use anyhow::Result;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
@ -11,20 +10,19 @@ use crate::args::{AuthCommand, AuthNamespace};
pub(crate) async fn dispatch(
ns: AuthNamespace,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
) -> Result<()> {
match ns.command {
AuthCommand::Login(args) => {
login::login_command(args, cli, cli_layer, process_local_json, printer).await
login::login_command(args, cli_layer, process_local_json, printer).await
}
AuthCommand::Logout(args) => {
logout::logout_command(args, cli, cli_layer, process_local_json, printer).await
logout::logout_command(args, cli_layer, process_local_json, printer).await
}
AuthCommand::Status(args) => {
status::status_command(&args, cli, cli_layer, process_local_json, printer)
status::status_command(&args, cli_layer, process_local_json, printer)
}
}
}

View file

@ -1,7 +1,6 @@
use anyhow::Result;
use chrono::{DateTime, Utc};
use fabro_client::{AuthEntry, AuthStore};
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::dev_token::{read_dev_token_file, validate_dev_token_format};
use fabro_util::printer::Printer;
@ -43,12 +42,11 @@ struct StatusOutput {
pub(super) fn status_command(
args: &AuthStatusArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::base(printer, cli_layer)?;
let store = AuthStore::default();
let now = Utc::now();
let rows = if args.server.as_deref().is_some() {

View file

@ -26,11 +26,10 @@ struct RenderedConfig {
async fn rendered_config(
args: &SettingsArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> anyhow::Result<serde_json::Value> {
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let user = fabro_config::UserSettings::resolve()?;
let server = ctx
.server()
@ -46,7 +45,7 @@ pub(crate) async fn execute(
cli_layer: &CliLayer,
printer: Printer,
) -> anyhow::Result<()> {
let config = Box::pin(rendered_config(args, cli, cli_layer, printer)).await?;
let config = Box::pin(rendered_config(args, cli_layer, printer)).await?;
if cli.output.format == OutputFormat::Json {
print_json_pretty(&config)?;
return Ok(());

View file

@ -179,7 +179,7 @@ pub(crate) async fn run_doctor(
}],
};
let ctx = match CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer) {
let ctx = match CommandContext::for_target(&args.target, printer, cli_layer) {
Ok(ctx) => ctx,
Err(err) => {
report.sections.push(CheckSection {

View file

@ -37,7 +37,7 @@ pub(crate) async fn run(
require_no_json_override(process_local_json)?;
}
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let built = build_run_manifest(ManifestBuildInput {
workflow: args.workflow.clone(),
cwd: ctx.cwd().to_path_buf(),

View file

@ -1277,7 +1277,7 @@ async fn write_artifact_store_metadata(
settings: &SettingsLayer,
fabro_version: &str,
) -> Result<()> {
let resolved = local_server::server_settings(settings)?;
let resolved = fabro_config::ServerSettings::from_layer(settings)?;
let (object_store, prefix) = serve::build_artifact_object_store(&resolved.server)?;
let artifact_store = ArtifactStore::new(object_store, prefix);
artifact_store.write_metadata(fabro_version).await?;
@ -1796,7 +1796,7 @@ async fn run_install_inner(
.context("failed to parse generated settings.toml")?,
args.storage_dir.as_deref(),
);
local_server::server_settings(&install_settings)?;
fabro_config::ServerSettings::from_layer(&install_settings)?;
// Secrets and auth material
{

View file

@ -51,7 +51,7 @@ pub(crate) async fn execute(
ModelsCommand::List(args) => &args.target,
ModelsCommand::Test(args) => &args.target,
};
let ctx = CommandContext::for_target(target_args, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(target_args, printer, cli_layer)?;
let server = ctx.server().await?;
run_models(command, &server, cli.output.format == OutputFormat::Json).await

View file

@ -14,9 +14,9 @@ pub(super) async fn close_command(
printer: Printer,
) -> Result<()> {
let (record, _run_id) =
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
let creds = super::load_github_credentials_required(cli_layer, printer)?;
fabro_github::close_pull_request(
&creds,

View file

@ -31,7 +31,7 @@ pub(super) async fn create_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run_id).await?.run_id;
let events = client.list_run_events(&run_id, None, None).await?;
@ -86,7 +86,7 @@ pub(super) async fn create_command(
let (owner, repo) = fabro_github::parse_github_owner_repo(&https_url)
.map_err(|err| anyhow::anyhow!("{err}"))?;
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
let creds = super::load_github_credentials_required(cli_layer, printer)?;
let branch_found = fabro_github::branch_exists(
&creds,

View file

@ -29,7 +29,7 @@ pub(super) async fn list_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
let mut entries = Vec::new();
@ -50,7 +50,7 @@ pub(super) async fn list_command(
return Ok(());
}
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
let creds = super::load_github_credentials_required(cli_layer, printer)?;
let futures: Vec<_> = entries
.iter()

View file

@ -14,9 +14,9 @@ pub(super) async fn merge_command(
printer: Printer,
) -> Result<()> {
let (record, _run_id) =
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
let creds = super::load_github_credentials_required(cli_layer, printer)?;
fabro_github::merge_pull_request(
&creds,

View file

@ -42,11 +42,10 @@ pub(crate) async fn dispatch(
reason = "boundary-exempt(pr-api): remove with follow-up #1 when PR ops move server-side"
)]
fn load_github_credentials_required(
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<GitHubCredentials> {
let ctx = CommandContext::base(printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::base(printer, cli_layer)?;
let server_settings = fabro_config::ServerSettings::from_layer(ctx.machine_settings())
.map_err(anyhow::Error::from)?;
let vault = user_config::storage_dir(ctx.machine_settings())
@ -71,11 +70,10 @@ fn load_github_credentials_required(
pub(crate) async fn load_pr_record(
server: &ServerTargetArgs,
run_id: &str,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<(PullRequestRecord, fabro_types::RunId)> {
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(run_id).await?.run_id;
let state = client.get_run_state(&run_id).await?;

View file

@ -14,9 +14,9 @@ pub(super) async fn view_command(
printer: Printer,
) -> Result<()> {
let (record, _run_id) =
super::load_pr_record(&args.server, &args.run_id, cli, cli_layer, printer).await?;
super::load_pr_record(&args.server, &args.run_id, cli_layer, printer).await?;
let creds = super::load_github_credentials_required(cli, cli_layer, printer)?;
let creds = super::load_github_credentials_required(cli_layer, printer)?;
let detail = fabro_github::get_pull_request(
&creds,

View file

@ -22,7 +22,7 @@ pub(crate) async fn execute(
printer: Printer,
) -> anyhow::Result<()> {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
let manifest = build_run_manifest(ManifestBuildInput {

View file

@ -1,7 +1,6 @@
use anyhow::Result;
use fabro_api::types;
use fabro_auth::credential_id_for;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
use fabro_util::terminal::Styles;
@ -12,14 +11,13 @@ use crate::shared::provider_auth;
pub(super) async fn login_command(
args: ProviderLoginArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
) -> Result<()> {
require_no_json_override(process_local_json)?;
let s = Styles::detect_stderr();
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let server = ctx.server().await?;
let credential = if args.api_key_stdin {
provider_auth::authenticate_provider_with_api_key_source(

View file

@ -1,7 +1,6 @@
mod login;
use anyhow::Result;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
@ -9,14 +8,13 @@ use crate::args::{ProviderCommand, ProviderNamespace};
pub(crate) async fn dispatch(
ns: ProviderNamespace,
cli: &CliNamespace,
cli_layer: &CliLayer,
process_local_json: bool,
printer: Printer,
) -> Result<()> {
match ns.command {
ProviderCommand::Login(args) => {
login::login_command(args, cli, cli_layer, process_local_json, printer).await
login::login_command(args, cli_layer, process_local_json, printer).await
}
}
}

View file

@ -151,7 +151,7 @@ draft = true
}
if cli.output.format != OutputFormat::Json {
check_github_app_installation(&args.target, cli, cli_layer, printer).await;
check_github_app_installation(&args.target, cli_layer, printer).await;
}
Ok(created)
@ -159,7 +159,6 @@ draft = true
async fn check_github_app_installation(
target: &ServerTargetArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) {
@ -200,7 +199,7 @@ async fn check_github_app_installation(
return; // Not a GitHub repo — skip silently
};
let ctx = match CommandContext::for_target(target, printer, cli.clone(), cli_layer) {
let ctx = match CommandContext::for_target(target, printer, cli_layer) {
Ok(ctx) => ctx,
Err(err) => {
fabro_util::printerr!(

View file

@ -16,12 +16,12 @@ pub(crate) async fn execute(
printer: Printer,
) -> Result<()> {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let cli_defaults = load_settings_with_storage_dir(None)?;
args.verbose = args.verbose || cli.output.verbosity == OutputVerbosity::Verbose;
let quiet = args.detach;
let prevent_idle_sleep = ctx.cli_settings().exec.prevent_idle_sleep;
let prevent_idle_sleep = ctx.user_settings().cli.exec.prevent_idle_sleep;
let created_run = Box::pin(super::create::create_run(
&ctx,
&args,

View file

@ -41,8 +41,7 @@ pub(crate) async fn cp_command(
local_path,
} => {
let (client, run_id) =
resolve_client_and_run_id(&args.server, &run_prefix, cli, cli_layer, printer)
.await?;
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
let file_count = if args.recursive {
Some(download_recursive(&client, &run_id, &remote_path, &local_path).await?)
@ -73,8 +72,7 @@ pub(crate) async fn cp_command(
remote_path,
} => {
let (client, run_id) =
resolve_client_and_run_id(&args.server, &run_prefix, cli, cli_layer, printer)
.await?;
resolve_client_and_run_id(&args.server, &run_prefix, cli_layer, printer).await?;
let file_count = if args.recursive {
Some(upload_recursive(&client, &run_id, &local_path, &remote_path).await?)
@ -128,11 +126,10 @@ fn parse_direction(src: &str, dst: &str) -> Result<CopyDirection> {
async fn resolve_client_and_run_id(
server: &ServerTargetArgs,
run_prefix: &str,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<(Client, fabro_types::RunId)> {
let ctx = CommandContext::for_target(server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(run_prefix).await?.run_id;
Ok((client.clone_for_reuse(), run_id))

View file

@ -27,7 +27,7 @@ pub(crate) async fn run(
printer: Printer,
) -> Result<()> {
info!(run_id = %args.run, "Showing diff");
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
let state = client.get_run_state(&run_id).await?;

View file

@ -21,7 +21,7 @@ pub(crate) async fn run(
printer: Printer,
) -> Result<()> {
let repo = Repository::discover(".").context("not in a git repository")?;
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run_id).await?.run_id;
let state = client.get_run_state(&run_id).await?;

View file

@ -36,7 +36,7 @@ pub(crate) async fn run(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
info!(run_id = %run_id, "Showing logs");

View file

@ -39,7 +39,7 @@ pub(crate) async fn dispatch(
RunCommands::Create(args) => {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let cli_defaults = load_settings_with_storage_dir(None)?;
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let created_run = Box::pin(create::create_run(
&ctx,
&args,
@ -57,7 +57,7 @@ pub(crate) async fn dispatch(
Ok(())
}
RunCommands::Start(StartArgs { server, run }) => {
let ctx = CommandContext::for_target(&server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&run).await?.run_id;
start::start_run_with_client(client.as_ref(), &run_id, false).await?;
@ -68,7 +68,7 @@ pub(crate) async fn dispatch(
}
RunCommands::Attach(AttachArgs { server, run }) => {
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
let ctx = CommandContext::for_target(&server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&run).await?.run_id;
let exit_code = Box::pin(attach::attach_run_with_client(
@ -113,9 +113,8 @@ pub(crate) async fn dispatch(
let styles: &'static Styles = Box::leak(Box::new(Styles::detect_stderr()));
#[cfg(feature = "sleep_inhibitor")]
let _sleep_guard = {
let ctx =
CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
crate::sleep_inhibitor::guard(ctx.cli_settings().exec.prevent_idle_sleep)
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
crate::sleep_inhibitor::guard(ctx.user_settings().cli.exec.prevent_idle_sleep)
};
Box::pin(resume::resume_command(
args, styles, cli, cli_layer, printer,

View file

@ -15,7 +15,7 @@ pub(crate) async fn run(
process_local_json: bool,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
let expires_in_secs =

View file

@ -19,7 +19,7 @@ pub(crate) async fn resume_command(
cli_layer: &CliLayer,
printer: Printer,
) -> anyhow::Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;

View file

@ -38,7 +38,7 @@ pub(crate) async fn run(
printer: Printer,
) -> Result<()> {
let repo = Repository::discover(".").context("not in a git repository")?;
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run_id).await?.run_id;
let state = client.get_run_state(&run_id).await?;

View file

@ -19,7 +19,7 @@ pub(crate) async fn run(
require_no_json_override(process_local_json)?;
}
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
let ssh = client.create_run_ssh_access(&run_id, args.ttl).await?;

View file

@ -31,7 +31,7 @@ pub(crate) async fn run(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
info!(run_id = %run_id, "Waiting for run to complete");

View file

@ -15,7 +15,7 @@ pub(crate) async fn archive_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
run_bulk(
Action::Archive,
&args.runs,
@ -32,7 +32,7 @@ pub(crate) async fn unarchive_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
run_bulk(
Action::Unarchive,
&args.runs,

View file

@ -1,5 +1,4 @@
use anyhow::Result;
use fabro_types::settings::CliNamespace;
use fabro_types::settings::cli::CliLayer;
use fabro_util::printer::Printer;
use fabro_workflow::run_status::RunStatus;
@ -21,13 +20,8 @@ pub(crate) struct InspectOutput {
pub sandbox: Option<serde_json::Value>,
}
pub(crate) async fn run(
args: &InspectArgs,
cli: &CliNamespace,
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
pub(crate) async fn run(args: &InspectArgs, cli_layer: &CliLayer, printer: Printer) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run = ServerRunSummaryInfo::from_summary(client.resolve_run(&args.run).await?);
let run_id = run.run_id();

View file

@ -24,7 +24,7 @@ pub(crate) async fn list_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let lookup = ServerSummaryLookup::from_client(ctx.server().await?).await?;
let label_filters = parse_label_filters(&args.filter.label);
let filtered = filter_server_runs(

View file

@ -23,7 +23,7 @@ pub(crate) async fn dispatch(
list::list_command(&args, &styles, cli, cli_layer, printer).await
}
RunsCommands::Rm(args) => rm::remove_command(&args, cli, cli_layer, printer).await,
RunsCommands::Inspect(args) => inspect::run(&args, cli, cli_layer, printer).await,
RunsCommands::Inspect(args) => inspect::run(&args, cli_layer, printer).await,
RunsCommands::Archive(args) => {
archive::archive_command(&args, cli, cli_layer, printer).await
}

View file

@ -15,7 +15,7 @@ pub(crate) async fn remove_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
remove_from(args, ctx.server().await?.as_ref(), cli, printer).await
}

View file

@ -16,7 +16,7 @@ pub(crate) async fn dispatch(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&ns.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&ns.target, printer, cli_layer)?;
let server = ctx.server().await?;
match ns.command {
SecretCommand::List(args) => list::list_command(&server, &args, cli, printer).await,

View file

@ -32,7 +32,7 @@ pub(crate) async fn dump_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_target(&args.server, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.server, printer, cli_layer)?;
let client = ctx.server().await?;
let run_id = client.resolve_run(&args.run).await?.run_id;
let state = client.get_run_state(&run_id).await?;

View file

@ -17,7 +17,7 @@ pub(super) async fn df_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
let server = ctx.server().await?;
let json = cli.output.format == OutputFormat::Json;

View file

@ -14,7 +14,7 @@ pub(super) async fn events_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
let server = ctx.server().await?;
let mut stream = server.attach_events(&args.run_ids).await?;
let mut pending = Vec::new();

View file

@ -13,7 +13,7 @@ pub(super) async fn info_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
let server = ctx.server().await?;
let response = server.get_system_info().await?;

View file

@ -17,7 +17,7 @@ pub(super) async fn prune_command(
cli_layer: &CliLayer,
printer: Printer,
) -> Result<()> {
let ctx = CommandContext::for_connection(&args.connection, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_connection(&args.connection, printer, cli_layer)?;
let server = ctx.server().await?;
let response = server
.prune_runs(types::PruneRunsRequest {

View file

@ -19,7 +19,7 @@ pub(crate) async fn run(
cli_layer: &CliLayer,
printer: Printer,
) -> anyhow::Result<()> {
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let built = build_run_manifest(ManifestBuildInput {
workflow: args.workflow.clone(),
cwd: ctx.cwd().to_path_buf(),

View file

@ -23,7 +23,7 @@ pub(crate) async fn version_command(
printer: Printer,
) -> Result<()> {
let client = client_info();
let ctx = CommandContext::for_target(&args.target, printer, cli.clone(), cli_layer)?;
let ctx = CommandContext::for_target(&args.target, printer, cli_layer)?;
let server_target = user_config::resolve_server_target(&args.target, ctx.machine_settings())?;
let server_address = format_server_target(&server_target);
let server_info = match ctx.server().await {

View file

@ -26,12 +26,8 @@ pub(crate) fn bind_request(
resolve_bind_request_from_settings(settings, cli_override)
}
pub(crate) fn server_settings(settings: &SettingsLayer) -> Result<fabro_config::ServerSettings> {
fabro_config::ServerSettings::from_layer(settings).map_err(anyhow::Error::from)
}
pub(crate) fn auth_methods(settings: &SettingsLayer) -> Vec<ServerAuthMethod> {
server_settings(settings)
fabro_config::ServerSettings::from_layer(settings)
.map(|resolved| resolved.server.auth.methods)
.unwrap_or_default()
}

View file

@ -173,9 +173,9 @@ async fn main_inner() -> (String, Result<()>) {
cli: Some(cli_layer.clone()),
..SettingsLayer::default()
});
let cli_settings = match user_config::resolve_cli_settings(&combined_settings) {
Ok(cli_settings) => cli_settings,
Err(err) => return (command_name, Err(err)),
let cli_settings = match fabro_config::UserSettings::from_layer(&combined_settings) {
Ok(settings) => settings.cli,
Err(err) => return (command_name, Err(err.into())),
};
let printer = printer_from_verbosity(cli_settings.output.verbosity);
@ -318,14 +318,7 @@ async fn main_inner() -> (String, Result<()>) {
commands::uninstall::run_uninstall(&args, &cli_settings, printer).await?;
}
Commands::Auth(ns) => {
commands::auth::dispatch(
ns,
&cli_settings,
&cli_layer,
process_local_json,
printer,
)
.await?;
commands::auth::dispatch(ns, &cli_layer, process_local_json, printer).await?;
}
Commands::Pr(ns) => {
Box::pin(commands::pr::dispatch(
@ -353,14 +346,7 @@ async fn main_inner() -> (String, Result<()>) {
commands::upgrade::run_upgrade(args, &cli_settings, printer).await?;
}
Commands::Provider(ns) => {
commands::provider::dispatch(
ns,
&cli_settings,
&cli_layer,
process_local_json,
printer,
)
.await?;
commands::provider::dispatch(ns, &cli_layer, process_local_json, printer).await?;
}
Commands::Sandbox { command } => {
commands::sandbox::dispatch(

View file

@ -30,16 +30,6 @@ pub(crate) fn load_settings_with_config_and_storage_dir(
Ok(apply_storage_dir_override(layer, storage_dir))
}
pub(crate) fn resolve_user_settings(
file: &SettingsLayer,
) -> anyhow::Result<fabro_config::UserSettings> {
fabro_config::UserSettings::from_layer(file).map_err(anyhow::Error::from)
}
pub(crate) fn resolve_cli_settings(file: &SettingsLayer) -> anyhow::Result<CliNamespace> {
resolve_user_settings(file).map(|settings| settings.cli)
}
pub(crate) fn apply_storage_dir_override(
mut layer: SettingsLayer,
storage_dir: Option<&Path>,
@ -68,7 +58,7 @@ fn cli_target_from_settings(settings: &CliNamespace) -> Option<String> {
}
fn configured_server_target(settings: &SettingsLayer) -> Result<Option<ServerTarget>> {
let user_settings = resolve_user_settings(settings)?;
let user_settings = fabro_config::UserSettings::from_layer(settings)?;
let Some(value) = cli_target_from_settings(&user_settings.cli) else {
return Ok(None);
};

View file

@ -2,10 +2,8 @@
clippy::disallowed_methods,
reason = "sync config loading utilities used at startup; not on a Tokio path"
)]
//! Settings resolution entrypoints are owner-first context types:
//! [`ServerSettings`] for current server/runtime config and [`UserSettings`]
//! for current CLI/user config. Stored `SettingsLayer` artifacts still use the
//! per-namespace `resolve_*_from_file` helpers.
//! Resolved settings entrypoints: [`ServerSettings`] for the running server and
//! [`UserSettings`] for the CLI/user perspective.
extern crate self as fabro_config;

View file

@ -1,10 +1,10 @@
use fabro_types::settings::ServerNamespace as ResolvedServerSettings;
use fabro_types::settings::ServerNamespace;
use url::Url;
use crate::server::EnvLookup;
pub(crate) fn resolve_canonical_origin(
resolved: &ResolvedServerSettings,
resolved: &ServerNamespace,
env_lookup: &EnvLookup,
) -> Result<String, String> {
let value = resolved

View file

@ -2,7 +2,7 @@ use anyhow::{Result, anyhow};
use axum::extract::FromRequestParts;
use axum::http::header;
use axum::http::request::Parts;
use fabro_types::settings::{ServerAuthMethod, ServerNamespace as ResolvedServerSettings};
use fabro_types::settings::{ServerAuthMethod, ServerNamespace};
use fabro_types::{IdpIdentity, RunAuthMethod};
use fabro_util::dev_token::validate_dev_token_format;
use hmac::{Hmac, Mac};
@ -51,14 +51,11 @@ pub enum AuthMode {
Disabled,
}
pub fn resolve_auth_mode(settings: &ResolvedServerSettings) -> Result<AuthMode> {
pub fn resolve_auth_mode(settings: &ServerNamespace) -> Result<AuthMode> {
resolve_auth_mode_with_lookup(settings, |name| std::env::var(name).ok())
}
pub fn resolve_auth_mode_with_lookup<F>(
settings: &ResolvedServerSettings,
lookup: F,
) -> Result<AuthMode>
pub fn resolve_auth_mode_with_lookup<F>(settings: &ServerNamespace, lookup: F) -> Result<AuthMode>
where
F: Fn(&str) -> Option<String>,
{
@ -124,7 +121,7 @@ where
}))
}
fn resolve_jwt_issuer<F>(settings: &ResolvedServerSettings, lookup: &F) -> String
fn resolve_jwt_issuer<F>(settings: &ServerNamespace, lookup: &F) -> String
where
F: Fn(&str) -> Option<String>,
{
@ -389,7 +386,7 @@ mod tests {
use tracing_subscriber::{Layer, Registry};
use super::*;
fn settings(source: &str) -> ResolvedServerSettings {
fn settings(source: &str) -> ServerNamespace {
let file = parse_settings_layer(source).expect("fixture should parse");
resolve_server_from_file(&file).expect("fixture should resolve")
}

View file

@ -8,14 +8,14 @@ use clap::Args;
use fabro_config::bind::{self, Bind, BindRequest};
use fabro_config::merge::combine_files;
use fabro_config::user::load_settings_config;
use fabro_config::{ServerSettings as CurrentServerSettings, Storage};
use fabro_config::{ServerSettings, Storage};
use fabro_sandbox::SandboxProvider;
use fabro_types::settings::server::{
GithubIntegrationStrategy, ServerLayer, ServerListenLayer, WebhookStrategy,
};
use fabro_types::settings::{
GithubIntegrationSettings, InterpString, ObjectStoreSettings, ServerListenSettings,
ServerNamespace as ResolvedServerSettings, SettingsLayer,
ServerNamespace, SettingsLayer,
};
use fabro_util::terminal::Styles;
use object_store::ObjectStore;
@ -142,12 +142,12 @@ fn apply_runtime_settings(
settings
}
fn router_web_enabled(settings: &ResolvedServerSettings) -> bool {
fn router_web_enabled(settings: &ServerNamespace) -> bool {
settings.web.enabled
}
async fn resolve_github_webhook_ip_allowlist(
resolved_server_settings: &ResolvedServerSettings,
resolved_server_settings: &ServerNamespace,
github_meta_resolver: &GitHubMetaResolver,
) -> anyhow::Result<Arc<IpAllowlistConfig>> {
let config = resolve_ip_allowlist_config(
@ -167,7 +167,7 @@ async fn resolve_github_webhook_ip_allowlist(
}
async fn resolve_startup_github_webhook_ip_allowlist(
resolved_server_settings: &ResolvedServerSettings,
resolved_server_settings: &ServerNamespace,
github_meta_resolver: &GitHubMetaResolver,
webhook_secret_present: bool,
) -> anyhow::Result<Option<Arc<IpAllowlistConfig>>> {
@ -228,7 +228,7 @@ fn resolve_webhook_preconditions(
}
async fn start_webhook_strategy(
resolved_server_settings: &ResolvedServerSettings,
resolved_server_settings: &ServerNamespace,
state: &Arc<AppState>,
bind_addr: &Bind,
webhook_secret_present: bool,
@ -349,8 +349,8 @@ fn build_object_store_from_settings(
}
}
fn resolve_server_settings(file: &SettingsLayer) -> anyhow::Result<ResolvedServerSettings> {
CurrentServerSettings::from_layer(file)
fn resolve_server_settings(file: &SettingsLayer) -> anyhow::Result<ServerNamespace> {
ServerSettings::from_layer(file)
.map(|settings| settings.server)
.map_err(anyhow::Error::from)
}
@ -391,7 +391,7 @@ fn bind_override_layer(bind: BindRequest) -> SettingsLayer {
}
fn resolved_bind_request(
resolved_server_settings: &ResolvedServerSettings,
resolved_server_settings: &ServerNamespace,
) -> anyhow::Result<BindRequest> {
match &resolved_server_settings.listen {
ServerListenSettings::Unix { path } => Ok(BindRequest::Unix(resolve_interp_path(path)?)),
@ -411,7 +411,7 @@ fn resolve_interp_path(value: &InterpString) -> anyhow::Result<PathBuf> {
}
pub fn build_artifact_object_store(
settings: &ResolvedServerSettings,
settings: &ServerNamespace,
) -> anyhow::Result<(Arc<dyn ObjectStore>, String)> {
let prefix = resolve_interp(&settings.artifacts.prefix)?;
let object_store = build_object_store_from_settings(&settings.artifacts.store)?;
@ -419,7 +419,7 @@ pub fn build_artifact_object_store(
}
fn build_slatedb_store(
settings: &ResolvedServerSettings,
settings: &ServerNamespace,
) -> anyhow::Result<(Arc<dyn ObjectStore>, String, Duration, bool)> {
let prefix = resolve_interp(&settings.slatedb.prefix)?;
let object_store = build_object_store_from_settings(&settings.slatedb.store)?;

View file

@ -40,7 +40,7 @@ pub use fabro_api::types::{
};
use fabro_auth::parse_credential_secret;
use fabro_config::daemon::ServerDaemon;
use fabro_config::{ServerSettings as CurrentServerSettings, Storage};
use fabro_config::{ServerSettings, Storage};
use fabro_interview::{
Answer, ControlInterviewer, Interviewer, Question, QuestionType, WorkerControlEnvelope,
};
@ -573,7 +573,7 @@ pub struct AppState {
pub(crate) server_secrets: ServerSecrets,
pub(crate) provider_credentials: ProviderCredentials,
pub(crate) settings: Arc<RwLock<SettingsLayer>>,
pub(crate) server_settings: RwLock<Arc<CurrentServerSettings>>,
pub(crate) server_settings: RwLock<Arc<ServerSettings>>,
pub(crate) env_lookup: EnvLookup,
http_client: Option<fabro_http::HttpClient>,
shutting_down: AtomicBool,
@ -638,7 +638,7 @@ fn accumulate_model_billing(entry: &mut ModelBillingTotals, usage: &BilledModelU
}
impl AppState {
pub(crate) fn server_settings(&self) -> Arc<CurrentServerSettings> {
pub(crate) fn server_settings(&self) -> Arc<ServerSettings> {
Arc::clone(
&self
.server_settings
@ -780,7 +780,7 @@ impl AppState {
}
pub(crate) fn replace_settings(&self, settings: SettingsLayer) -> anyhow::Result<()> {
let resolved = Arc::new(CurrentServerSettings::from_layer(&settings)?);
let resolved = Arc::new(ServerSettings::from_layer(&settings)?);
resolve_canonical_origin(&resolved.server, &self.env_lookup).map_err(anyhow::Error::msg)?;
*self.settings.write().expect("settings lock poisoned") = settings;
@ -1656,7 +1656,7 @@ fn system_sandbox_provider(settings: &SettingsLayer) -> String {
}
fn resolved_storage_dir(settings: &SettingsLayer) -> Result<PathBuf, String> {
let resolved = CurrentServerSettings::from_layer(settings).map_err(|err| err.to_string())?;
let resolved = ServerSettings::from_layer(settings).map_err(|err| err.to_string())?;
resolved
.server
.storage
@ -1672,7 +1672,7 @@ fn resolved_storage_dir(settings: &SettingsLayer) -> Result<PathBuf, String> {
}
fn resolved_github_settings(settings: &SettingsLayer) -> Result<GithubIntegrationSettings, String> {
let resolved = CurrentServerSettings::from_layer(settings).map_err(|err| err.to_string())?;
let resolved = ServerSettings::from_layer(settings).map_err(|err| err.to_string())?;
Ok(resolved.server.integrations.github)
}
@ -2589,7 +2589,7 @@ pub(crate) fn build_app_state(config: AppStateConfig) -> anyhow::Result<Arc<AppS
let (global_event_tx, _) = broadcast::channel(4096);
let current_server_settings = {
let settings = settings.read().expect("settings lock poisoned");
Arc::new(CurrentServerSettings::from_layer(&settings)?)
Arc::new(ServerSettings::from_layer(&settings)?)
};
let slack_service = {
current_server_settings