commit cf21782c9a1ff6ffddc79ed0b71937e357b37a85
parent ecbd9d0c8e3a323402d62dc178aafabaef97857f
Author: triesap <tyson@radroots.org>
Date: Sat, 25 Apr 2026 06:19:26 +0000
runtime: make workspace config repo-local only
Diffstat:
9 files changed, 63 insertions(+), 47 deletions(-)
diff --git a/src/commands/doctor.rs b/src/commands/doctor.rs
@@ -95,7 +95,11 @@ pub fn report(
fn config_check(config: &RuntimeConfig) -> EvaluatedCheck {
let detail = match (
config.paths.app_config_path.exists(),
- config.paths.workspace_config_path.exists(),
+ config
+ .paths
+ .workspace_config_path
+ .as_ref()
+ .is_some_and(|path| path.exists()),
) {
(false, false) => "defaults active".to_owned(),
(true, false) => "app config root present".to_owned(),
diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs
@@ -46,7 +46,11 @@ pub fn show(
},
config_files: ConfigFilesRuntimeView {
user_present: config.paths.app_config_path.exists(),
- workspace_present: config.paths.workspace_config_path.exists(),
+ workspace_present: config
+ .paths
+ .workspace_config_path
+ .as_ref()
+ .is_some_and(|path| path.exists()),
},
paths: PathsRuntimeView {
profile: config.paths.profile.clone(),
@@ -64,7 +68,12 @@ pub fn show(
shared_accounts_namespace: config.paths.shared_accounts_namespace.clone(),
shared_identities_namespace: config.paths.shared_identities_namespace.clone(),
app_config_path: config.paths.app_config_path.display().to_string(),
- workspace_config_path: config.paths.workspace_config_path.display().to_string(),
+ workspace_config_enabled: config.paths.workspace_config_path.is_some(),
+ workspace_config_path: config
+ .paths
+ .workspace_config_path
+ .as_ref()
+ .map(|path| path.display().to_string()),
app_data_root: config.paths.app_data_root.display().to_string(),
app_logs_root: config.paths.app_logs_root.display().to_string(),
shared_accounts_data_root: config.paths.shared_accounts_data_root.display().to_string(),
diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs
@@ -348,7 +348,8 @@ pub struct PathsRuntimeView {
pub shared_accounts_namespace: String,
pub shared_identities_namespace: String,
pub app_config_path: String,
- pub workspace_config_path: String,
+ pub workspace_config_enabled: bool,
+ pub workspace_config_path: Option<String>,
pub app_data_root: String,
pub app_logs_root: String,
pub shared_accounts_data_root: String,
diff --git a/src/render/mod.rs b/src/render/mod.rs
@@ -1129,7 +1129,10 @@ fn render_config_show(
let workspace_config = format!(
"{} ยท {}",
present_absent(view.config_files.workspace_present),
- view.paths.workspace_config_path
+ view.paths
+ .workspace_config_path
+ .as_deref()
+ .unwrap_or("disabled for interactive_user")
);
let allowed_profiles = view.paths.allowed_profiles.join(", ");
render_pairs(
@@ -4334,8 +4337,7 @@ mod tests {
shared_accounts_namespace: "shared/accounts".into(),
shared_identities_namespace: "shared/identities".into(),
app_config_path: "/home/tester/.radroots/config/apps/cli/config.toml".into(),
- workspace_config_path: "/workspace/infra/local/runtime/radroots/config.toml"
- .into(),
+ workspace_config_path: None,
app_data_root: "/home/tester/.radroots/data/apps/cli".into(),
app_logs_root: "/home/tester/.radroots/logs/apps/cli".into(),
shared_accounts_data_root: "/home/tester/.radroots/data/shared/accounts".into(),
@@ -4409,10 +4411,8 @@ mod tests {
assert_eq!(view.paths.profile, "interactive_user");
assert_eq!(view.paths.app_namespace, "apps/cli");
assert_eq!(view.paths.shared_accounts_namespace, "shared/accounts");
- assert_eq!(
- view.paths.workspace_config_path,
- "/workspace/infra/local/runtime/radroots/config.toml"
- );
+ assert!(!view.paths.workspace_config_enabled);
+ assert_eq!(view.paths.workspace_config_path, None);
assert_eq!(view.account.selector.as_deref(), Some("acct_demo"));
assert!(
view.account
@@ -4490,8 +4490,7 @@ mod tests {
shared_identities_namespace: "shared/identities".into(),
app_config_path: "/home/tester/.radroots/config/apps/cli/config.toml"
.into(),
- workspace_config_path:
- "/workspace/infra/local/runtime/radroots/config.toml".into(),
+ workspace_config_path: None,
app_data_root: "/home/tester/.radroots/data/apps/cli".into(),
app_logs_root: "/home/tester/.radroots/logs/apps/cli".into(),
shared_accounts_data_root: "/home/tester/.radroots/data/shared/accounts"
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -467,7 +467,12 @@ impl RuntimeConfig {
) -> Result<Self, RuntimeError> {
let paths = resolve_paths(env, env_file)?;
let migration = resolve_migration(paths.clone(), env);
- let workspace_config = load_cli_config_file(paths.workspace_config_path.as_path())?;
+ let workspace_config = paths
+ .workspace_config_path
+ .as_deref()
+ .map(load_cli_config_file)
+ .transpose()?
+ .flatten();
let app_config = load_cli_config_file(paths.app_config_path.as_path())?;
let account_secret_backend = resolve_account_secret_backend(args, env, env_file)?
.unwrap_or(RadrootsSecretBackend::HostVault(
@@ -1521,9 +1526,7 @@ mod tests {
app_config_path: PathBuf::from(
"/home/tester/.radroots/config/apps/cli/config.toml"
),
- workspace_config_path: PathBuf::from(
- "/workspaces/radroots-cli/infra/local/runtime/radroots/config.toml"
- ),
+ workspace_config_path: None,
app_data_root: PathBuf::from("/home/tester/.radroots/data/apps/cli"),
app_logs_root: PathBuf::from("/home/tester/.radroots/logs/apps/cli"),
shared_accounts_data_root: PathBuf::from(
@@ -2164,10 +2167,7 @@ target = "https://rpc.workspace.test/jsonrpc"
resolved.paths.shared_identities_namespace,
"shared/identities"
);
- assert_eq!(
- resolved.paths.workspace_config_path,
- PathBuf::from("/workspaces/radroots-cli/infra/local/runtime/radroots/config.toml")
- );
+ assert_eq!(resolved.paths.workspace_config_path, None);
assert_eq!(
resolved.paths.app_data_root,
PathBuf::from("/home/tester/.radroots/data/apps/cli")
@@ -2276,7 +2276,9 @@ target = "https://rpc.workspace.test/jsonrpc"
);
assert_eq!(
resolved.paths.workspace_config_path,
- PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/config.toml")
+ Some(PathBuf::from(
+ "/workspaces/radroots-cli/.local/radroots/dev/config.toml"
+ ))
);
assert_eq!(
resolved.paths.app_data_root,
@@ -2337,7 +2339,9 @@ RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT=.local/radroots/dev
);
assert_eq!(
resolved.paths.workspace_config_path,
- PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/config.toml")
+ Some(PathBuf::from(
+ "/workspaces/radroots-cli/.local/radroots/dev/config.toml"
+ ))
);
}
diff --git a/src/runtime/farm_config.rs b/src/runtime/farm_config.rs
@@ -128,10 +128,16 @@ pub fn user_config_path(paths: &PathsConfig) -> Result<PathBuf, RuntimeError> {
}
pub fn workspace_config_path(paths: &PathsConfig) -> Result<PathBuf, RuntimeError> {
- let Some(parent) = paths.workspace_config_path.parent() else {
+ let Some(path) = paths.workspace_config_path.as_ref() else {
+ return Err(RuntimeError::Config(format!(
+ "workspace farm config requires repo_local path profile, got `{}`",
+ paths.profile
+ )));
+ };
+ let Some(parent) = path.parent() else {
return Err(RuntimeError::Config(format!(
"workspace config path {} has no parent directory",
- paths.workspace_config_path.display()
+ path.display()
)));
};
Ok(parent.join("config/apps/cli").join(FARM_CONFIG_FILE_NAME))
@@ -350,19 +356,21 @@ mod tests {
use tempfile::tempdir;
fn sample_paths(profile: &str, root: &Path) -> PathsConfig {
+ let repo_local_root = root.join("infra/local/runtime/radroots");
PathsConfig {
profile: profile.to_owned(),
profile_source: "test".to_owned(),
allowed_profiles: vec!["interactive_user".to_owned(), "repo_local".to_owned()],
root_source: "test".to_owned(),
- repo_local_root: Some(root.join("infra/local/runtime/radroots")),
+ repo_local_root: Some(repo_local_root.clone()),
repo_local_root_source: Some("test".to_owned()),
subordinate_path_override_source: "test".to_owned(),
app_namespace: "apps/cli".to_owned(),
shared_accounts_namespace: "shared/accounts".to_owned(),
shared_identities_namespace: "shared/identities".to_owned(),
app_config_path: root.join("home/.radroots/config/apps/cli/config.toml"),
- workspace_config_path: root.join("workspace/infra/local/runtime/radroots/config.toml"),
+ workspace_config_path: (profile == "repo_local")
+ .then(|| repo_local_root.join("config.toml")),
app_data_root: root.join("home/.radroots/data/apps/cli"),
app_logs_root: root.join("home/.radroots/logs/apps/cli"),
shared_accounts_data_root: root.join("home/.radroots/data/shared/accounts"),
@@ -463,7 +471,7 @@ mod tests {
let paths = sample_paths("repo_local", dir.path());
let document = sample_document(FarmConfigScope::Workspace);
let expected_path = PathBuf::from(dir.path())
- .join("workspace/infra/local/runtime/radroots/config/apps/cli/farm.toml");
+ .join("infra/local/runtime/radroots/config/apps/cli/farm.toml");
let written_path =
write(&paths, FarmConfigScope::Workspace, &document).expect("write workspace config");
diff --git a/src/runtime/paths.rs b/src/runtime/paths.rs
@@ -10,7 +10,6 @@ use crate::runtime::{
config::{EnvFileValues, Environment},
};
-const DEFAULT_WORKSPACE_CONFIG_PATH: &str = "infra/local/runtime/radroots/config.toml";
const CLI_DEFAULT_PROFILE: &str = "interactive_user";
const CLI_REPO_LOCAL_PROFILE: &str = "repo_local";
const CLI_APP_NAMESPACE_VALUE: &str = "cli";
@@ -34,7 +33,7 @@ pub struct PathsConfig {
pub shared_accounts_namespace: String,
pub shared_identities_namespace: String,
pub app_config_path: PathBuf,
- pub workspace_config_path: PathBuf,
+ pub workspace_config_path: Option<PathBuf>,
pub app_data_root: PathBuf,
pub app_logs_root: PathBuf,
pub shared_accounts_data_root: PathBuf,
@@ -51,8 +50,7 @@ pub(crate) fn resolve_paths(
let (profile, profile_label, profile_source) = resolve_cli_path_profile(env, env_file)?;
let override_selection =
resolve_cli_path_overrides(current_dir.as_path(), env, env_file, profile)?;
- let workspace_config_path =
- resolve_workspace_config_path(current_dir.as_path(), profile, &override_selection)?;
+ let workspace_config_path = resolve_workspace_config_path(profile, &override_selection)?;
let resolved = resolver
.resolve(profile, &override_selection.overrides)
.map_err(|err| RuntimeError::Config(format!("resolve Radroots path roots: {err}")))?;
@@ -193,16 +191,15 @@ fn normalize_explicit_path_root(current_dir: &Path, value: &str) -> PathBuf {
}
fn resolve_workspace_config_path(
- current_dir: &Path,
profile: RadrootsPathProfile,
override_selection: &CliPathOverrideSelection,
-) -> Result<PathBuf, RuntimeError> {
+) -> Result<Option<PathBuf>, RuntimeError> {
match profile {
- RadrootsPathProfile::InteractiveUser => Ok(current_dir.join(DEFAULT_WORKSPACE_CONFIG_PATH)),
+ RadrootsPathProfile::InteractiveUser => Ok(None),
RadrootsPathProfile::RepoLocal => override_selection
.repo_local_root
.as_ref()
- .map(|root| root.join(DEFAULT_CONFIG_FILE_NAME))
+ .map(|root| Some(root.join(DEFAULT_CONFIG_FILE_NAME)))
.ok_or_else(|| {
RuntimeError::Config(format!(
"{ENV_CLI_PATHS_REPO_LOCAL_ROOT} must be resolved when {ENV_CLI_PATHS_PROFILE}=repo_local"
diff --git a/src/runtime/provider.rs b/src/runtime/provider.rs
@@ -768,9 +768,7 @@ mod tests {
shared_accounts_namespace: "shared/accounts".into(),
shared_identities_namespace: "shared/identities".into(),
app_config_path: PathBuf::from("/tmp/config/apps/cli/config.toml"),
- workspace_config_path: PathBuf::from(
- "/tmp/workspace/infra/local/runtime/radroots/config.toml",
- ),
+ workspace_config_path: None,
app_data_root: PathBuf::from("/tmp/data"),
app_logs_root: PathBuf::from("/tmp/logs"),
shared_accounts_data_root: PathBuf::from("/tmp/shared/accounts"),
diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs
@@ -104,7 +104,6 @@ fn binding_by_capability<'a>(json: &'a Value, capability_id: &str) -> &'a Value
#[test]
fn config_show_json_reports_default_bootstrap_state() {
let dir = tempdir().expect("tempdir");
- let canonical_root = dir.path().canonicalize().expect("canonical tempdir");
let output = runtime_show_command_in(dir.path())
.args(["--json", "config", "show"])
.output()
@@ -147,13 +146,8 @@ fn config_show_json_reports_default_bootstrap_state() {
.display()
.to_string()
);
- assert_eq!(
- json["paths"]["workspace_config_path"],
- canonical_root
- .join("infra/local/runtime/radroots/config.toml")
- .display()
- .to_string()
- );
+ assert_eq!(json["paths"]["workspace_config_enabled"], false);
+ assert_eq!(json["paths"]["workspace_config_path"], Value::Null);
assert_eq!(
json["paths"]["app_data_root"],
data_root(dir.path()).join("apps/cli").display().to_string()
@@ -471,6 +465,7 @@ fn config_show_json_reports_repo_local_paths_when_requested() {
json["paths"]["workspace_config_path"],
repo_local_root.join("config.toml").display().to_string()
);
+ assert_eq!(json["paths"]["workspace_config_enabled"], true);
assert_eq!(
json["paths"]["app_data_root"],
repo_local_root.join("data/apps/cli").display().to_string()
@@ -526,6 +521,7 @@ fn config_show_json_reads_repo_local_workspace_config_from_explicit_root() {
json["paths"]["workspace_config_path"],
repo_local_root.join("config.toml").display().to_string()
);
+ assert_eq!(json["paths"]["workspace_config_enabled"], true);
assert_eq!(json["config_files"]["workspace_present"], true);
assert_eq!(json["relay"]["count"], 1);
assert_eq!(json["relay"]["urls"][0], "wss://relay.repo-local");