commit 1265c34e656243fb3288d463f45c2e9ab7eb5563
parent 35ea3c747c4ab9d5aa06711db7fb78f126e69dee
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Apr 2026 16:55:37 +0000
runtime: report path provenance
Diffstat:
3 files changed, 185 insertions(+), 15 deletions(-)
diff --git a/src/app/config.rs b/src/app/config.rs
@@ -389,14 +389,14 @@ impl Settings {
mod tests {
use std::path::PathBuf;
- use crate::app::paths::{
- default_runtime_paths_for_process, resolve_runtime_paths_with_resolver,
- runtime_contract_with_resolver,
- };
use super::{
BridgeConfig, BridgeDeliveryPolicy, Configuration, Nip46Config, RpcConfig,
load_settings_from_path_with_resolver,
};
+ use crate::app::paths::{
+ default_runtime_paths_for_process, resolve_runtime_paths_with_resolver,
+ runtime_contract_with_resolver,
+ };
use radroots_runtime::RadrootsNostrServiceConfig;
use radroots_runtime_paths::{
RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform,
@@ -642,6 +642,21 @@ bearer_token = "change-me"
.expect("interactive-user contract");
assert_eq!(contract.active_profile, "interactive_user");
+ assert_eq!(contract.path_overrides.profile_source, "caller");
+ assert_eq!(contract.path_overrides.root_source, "host_defaults");
+ assert_eq!(contract.path_overrides.repo_local_root, None);
+ assert_eq!(contract.path_overrides.repo_local_root_source, None);
+ assert_eq!(
+ contract.path_overrides.subordinate_path_override_source,
+ "config_artifact"
+ );
+ assert_eq!(
+ contract.path_overrides.subordinate_path_override_keys,
+ vec![
+ "config.service.logs_dir".to_owned(),
+ "config.bridge.state_path".to_owned(),
+ ]
+ );
assert_eq!(
contract.allowed_profiles,
vec![
diff --git a/src/app/paths.rs b/src/app/paths.rs
@@ -15,6 +15,9 @@ const RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV: &str = "RADROOTSD_PATHS_REPO_LOCAL_RO
const RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND: &str = "encrypted_file";
const RADROOTSD_ALLOWED_PROFILES: [&str; 3] = ["interactive_user", "service_host", "repo_local"];
const RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS: [&str; 1] = ["encrypted_file"];
+const SUBORDINATE_PATH_OVERRIDE_SOURCE: &str = "config_artifact";
+const SUBORDINATE_PATH_OVERRIDE_KEYS: [&str; 2] =
+ ["config.service.logs_dir", "config.bridge.state_path"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RadrootsdRuntimePaths {
@@ -28,6 +31,7 @@ pub(crate) struct RadrootsdRuntimePaths {
pub struct RadrootsdRuntimeContractOutput {
pub active_profile: String,
pub allowed_profiles: Vec<String>,
+ pub path_overrides: RadrootsdRuntimePathOverrideContractOutput,
pub default_shared_secret_backend: String,
pub allowed_shared_secret_backends: Vec<String>,
pub canonical_config_path: PathBuf,
@@ -36,6 +40,25 @@ pub struct RadrootsdRuntimeContractOutput {
pub canonical_bridge_state_path: PathBuf,
}
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct RadrootsdRuntimePathOverrideContractOutput {
+ pub profile_source: String,
+ pub root_source: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub repo_local_root: Option<PathBuf>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub repo_local_root_source: Option<String>,
+ pub subordinate_path_override_source: String,
+ pub subordinate_path_override_keys: Vec<String>,
+}
+
+struct RadrootsdRuntimePathSelection {
+ profile: RadrootsPathProfile,
+ profile_source: String,
+ repo_local_root: Option<PathBuf>,
+ repo_local_root_source: Option<String>,
+}
+
fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> {
match value {
"interactive_user" => Ok(RadrootsPathProfile::InteractiveUser),
@@ -48,16 +71,33 @@ fn parse_path_profile(value: &str) -> Result<RadrootsPathProfile> {
}
pub(crate) fn process_path_selection() -> Result<(RadrootsPathProfile, Option<PathBuf>)> {
+ let selection = process_path_selection_with_sources()?;
+ Ok((selection.profile, selection.repo_local_root))
+}
+
+fn process_path_selection_with_sources() -> Result<RadrootsdRuntimePathSelection> {
let profile = match std::env::var(RADROOTSD_PATHS_PROFILE_ENV) {
- Ok(value) => parse_path_profile(&value)?,
- Err(std::env::VarError::NotPresent) => RadrootsPathProfile::InteractiveUser,
+ Ok(value) => (
+ parse_path_profile(&value)?,
+ format!("process_env:{RADROOTSD_PATHS_PROFILE_ENV}"),
+ ),
+ Err(std::env::VarError::NotPresent) => {
+ (RadrootsPathProfile::InteractiveUser, "default".to_owned())
+ }
Err(std::env::VarError::NotUnicode(_)) => {
bail!("{RADROOTSD_PATHS_PROFILE_ENV} must be valid utf-8 when set")
}
};
- let repo_local_root =
- std::env::var_os(RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV).map(PathBuf::from);
- Ok((profile, repo_local_root))
+ let repo_local_root_raw = std::env::var_os(RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV);
+ let repo_local_root = repo_local_root_raw.as_ref().map(PathBuf::from);
+ Ok(RadrootsdRuntimePathSelection {
+ profile: profile.0,
+ profile_source: profile.1,
+ repo_local_root,
+ repo_local_root_source: repo_local_root_raw
+ .as_ref()
+ .map(|_| format!("process_env:{RADROOTSD_PATHS_REPO_LOCAL_ROOT_ENV}")),
+ })
}
fn path_overrides_for(
@@ -124,12 +164,8 @@ pub fn default_identity_path_for_process() -> Result<PathBuf> {
#[cfg_attr(test, allow(dead_code))]
pub fn runtime_contract_for_process() -> Result<RadrootsdRuntimeContractOutput> {
- let (profile, repo_local_root) = process_path_selection()?;
- runtime_contract_with_resolver(
- &RadrootsPathResolver::current(),
- profile,
- repo_local_root.as_deref(),
- )
+ let selection = process_path_selection_with_sources()?;
+ runtime_contract_with_selection(&RadrootsPathResolver::current(), &selection)
}
pub(crate) fn runtime_contract_with_resolver(
@@ -137,6 +173,23 @@ pub(crate) fn runtime_contract_with_resolver(
profile: RadrootsPathProfile,
repo_local_root: Option<&Path>,
) -> Result<RadrootsdRuntimeContractOutput> {
+ runtime_contract_with_selection(
+ resolver,
+ &RadrootsdRuntimePathSelection {
+ profile,
+ profile_source: "caller".to_owned(),
+ repo_local_root: repo_local_root.map(Path::to_path_buf),
+ repo_local_root_source: repo_local_root.map(|_| "caller".to_owned()),
+ },
+ )
+}
+
+fn runtime_contract_with_selection(
+ resolver: &RadrootsPathResolver,
+ selection: &RadrootsdRuntimePathSelection,
+) -> Result<RadrootsdRuntimeContractOutput> {
+ let profile = selection.profile;
+ let repo_local_root = selection.repo_local_root.as_deref();
let paths = resolve_runtime_paths_with_resolver(resolver, profile, repo_local_root)?;
Ok(RadrootsdRuntimeContractOutput {
active_profile: profile.to_string(),
@@ -144,6 +197,17 @@ pub(crate) fn runtime_contract_with_resolver(
.into_iter()
.map(str::to_owned)
.collect(),
+ path_overrides: RadrootsdRuntimePathOverrideContractOutput {
+ profile_source: selection.profile_source.clone(),
+ root_source: root_source_for_profile(profile).to_owned(),
+ repo_local_root: selection.repo_local_root.clone(),
+ repo_local_root_source: selection.repo_local_root_source.clone(),
+ subordinate_path_override_source: SUBORDINATE_PATH_OVERRIDE_SOURCE.to_owned(),
+ subordinate_path_override_keys: SUBORDINATE_PATH_OVERRIDE_KEYS
+ .into_iter()
+ .map(str::to_owned)
+ .collect(),
+ },
default_shared_secret_backend: RADROOTSD_DEFAULT_SHARED_SECRET_BACKEND.to_owned(),
allowed_shared_secret_backends: RADROOTSD_ALLOWED_SHARED_SECRET_BACKENDS
.into_iter()
@@ -155,3 +219,12 @@ pub(crate) fn runtime_contract_with_resolver(
canonical_bridge_state_path: paths.bridge_state_path,
})
}
+
+fn root_source_for_profile(profile: RadrootsPathProfile) -> &'static str {
+ match profile {
+ RadrootsPathProfile::InteractiveUser => "host_defaults",
+ RadrootsPathProfile::ServiceHost => "service_host_defaults",
+ RadrootsPathProfile::RepoLocal => "repo_local_root",
+ RadrootsPathProfile::MobileNative => "mobile_native_defaults",
+ }
+}
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
@@ -50,13 +50,18 @@ enum RunWaitOutcome {
struct RadrootsdRuntimeStartupReport {
active_profile: String,
config_path: PathBuf,
+ config_path_source: String,
canonical_config_path: PathBuf,
logs_dir: PathBuf,
+ logs_dir_source: String,
canonical_logs_dir: PathBuf,
identity_path: PathBuf,
+ identity_path_source: String,
canonical_identity_path: PathBuf,
bridge_state_path: PathBuf,
+ bridge_state_path_source: String,
canonical_bridge_state_path: PathBuf,
+ path_overrides: paths::RadrootsdRuntimePathOverrideContractOutput,
default_shared_secret_backend: String,
allowed_shared_secret_backends: Vec<String>,
}
@@ -159,33 +164,89 @@ fn runtime_startup_report(
.config
.clone()
.unwrap_or_else(|| contract.canonical_config_path.clone()),
+ config_path_source: cli_or_profile_path_source(
+ args.service.config.is_some(),
+ &args
+ .service
+ .config
+ .clone()
+ .unwrap_or_else(|| contract.canonical_config_path.clone()),
+ &contract.canonical_config_path,
+ ),
canonical_config_path: contract.canonical_config_path.clone(),
logs_dir: PathBuf::from(settings.config.service.logs_dir.as_str()),
+ logs_dir_source: config_or_profile_path_source(
+ &PathBuf::from(settings.config.service.logs_dir.as_str()),
+ &contract.canonical_logs_dir,
+ ),
canonical_logs_dir: contract.canonical_logs_dir.clone(),
identity_path: args
.service
.identity
.clone()
.unwrap_or_else(|| contract.canonical_identity_path.clone()),
+ identity_path_source: cli_or_profile_path_source(
+ args.service.identity.is_some(),
+ &args
+ .service
+ .identity
+ .clone()
+ .unwrap_or_else(|| contract.canonical_identity_path.clone()),
+ &contract.canonical_identity_path,
+ ),
canonical_identity_path: contract.canonical_identity_path.clone(),
bridge_state_path: settings.config.bridge.state_path.clone(),
+ bridge_state_path_source: config_or_profile_path_source(
+ &settings.config.bridge.state_path,
+ &contract.canonical_bridge_state_path,
+ ),
canonical_bridge_state_path: contract.canonical_bridge_state_path.clone(),
+ path_overrides: contract.path_overrides.clone(),
default_shared_secret_backend: contract.default_shared_secret_backend.clone(),
allowed_shared_secret_backends: contract.allowed_shared_secret_backends.clone(),
}
}
+fn cli_or_profile_path_source(
+ is_cli_arg: bool,
+ actual_path: &PathBuf,
+ canonical_path: &PathBuf,
+) -> String {
+ if is_cli_arg {
+ "cli_arg".to_owned()
+ } else {
+ config_or_profile_path_source(actual_path, canonical_path)
+ }
+}
+
+fn config_or_profile_path_source(actual_path: &PathBuf, canonical_path: &PathBuf) -> String {
+ if actual_path == canonical_path {
+ "profile_default".to_owned()
+ } else {
+ "config_artifact".to_owned()
+ }
+}
+
#[cfg(not(test))]
fn log_runtime_startup_report(report: &RadrootsdRuntimeStartupReport) {
info!(
active_profile = report.active_profile.as_str(),
+ profile_source = report.path_overrides.profile_source.as_str(),
+ root_source = report.path_overrides.root_source.as_str(),
+ repo_local_root = ?report.path_overrides.repo_local_root,
+ repo_local_root_source = ?report.path_overrides.repo_local_root_source,
+ subordinate_path_override_source = report.path_overrides.subordinate_path_override_source.as_str(),
config_path = %report.config_path.display(),
+ config_path_source = report.config_path_source.as_str(),
canonical_config_path = %report.canonical_config_path.display(),
logs_dir = %report.logs_dir.display(),
+ logs_dir_source = report.logs_dir_source.as_str(),
canonical_logs_dir = %report.canonical_logs_dir.display(),
identity_path = %report.identity_path.display(),
+ identity_path_source = report.identity_path_source.as_str(),
canonical_identity_path = %report.canonical_identity_path.display(),
bridge_state_path = %report.bridge_state_path.display(),
+ bridge_state_path_source = report.bridge_state_path_source.as_str(),
canonical_bridge_state_path = %report.canonical_bridge_state_path.display(),
default_shared_secret_backend = report.default_shared_secret_backend.as_str(),
allowed_shared_secret_backends = ?report.allowed_shared_secret_backends,
@@ -499,6 +560,17 @@ mod tests {
"service_host".to_string(),
"repo_local".to_string(),
],
+ path_overrides: paths::RadrootsdRuntimePathOverrideContractOutput {
+ profile_source: "caller".to_string(),
+ root_source: "host_defaults".to_string(),
+ repo_local_root: None,
+ repo_local_root_source: None,
+ subordinate_path_override_source: "config_artifact".to_string(),
+ subordinate_path_override_keys: vec![
+ "config.service.logs_dir".to_string(),
+ "config.bridge.state_path".to_string(),
+ ],
+ },
default_shared_secret_backend: "encrypted_file".to_string(),
allowed_shared_secret_backends: vec!["encrypted_file".to_string()],
canonical_config_path: PathBuf::from(
@@ -757,21 +829,26 @@ mod tests {
RadrootsdRuntimeStartupReport {
active_profile: "interactive_user".to_string(),
config_path: PathBuf::from("/tmp/radrootsd/config.toml"),
+ config_path_source: "cli_arg".to_string(),
canonical_config_path: PathBuf::from(
"/home/treesap/.radroots/config/services/radrootsd/config.toml"
),
logs_dir: PathBuf::from("/tmp/radrootsd/logs"),
+ logs_dir_source: "config_artifact".to_string(),
canonical_logs_dir: PathBuf::from(
"/home/treesap/.radroots/logs/services/radrootsd"
),
identity_path: PathBuf::from("/tmp/radrootsd/identity.secret.json"),
+ identity_path_source: "cli_arg".to_string(),
canonical_identity_path: PathBuf::from(
"/home/treesap/.radroots/secrets/services/radrootsd/identity.secret.json"
),
bridge_state_path: PathBuf::from("/tmp/radrootsd/bridge-jobs.json"),
+ bridge_state_path_source: "config_artifact".to_string(),
canonical_bridge_state_path: PathBuf::from(
"/home/treesap/.radroots/data/services/radrootsd/bridge/bridge-jobs.json"
),
+ path_overrides: sample_runtime_contract().path_overrides,
default_shared_secret_backend: "encrypted_file".to_string(),
allowed_shared_secret_backends: vec!["encrypted_file".to_string()],
}
@@ -795,12 +872,17 @@ mod tests {
let report = runtime_startup_report(&args, &settings, &contract);
assert_eq!(report.config_path, contract.canonical_config_path);
+ assert_eq!(report.config_path_source, "profile_default");
assert_eq!(report.logs_dir, contract.canonical_logs_dir);
+ assert_eq!(report.logs_dir_source, "profile_default");
assert_eq!(report.identity_path, contract.canonical_identity_path);
+ assert_eq!(report.identity_path_source, "profile_default");
assert_eq!(
report.bridge_state_path,
contract.canonical_bridge_state_path
);
+ assert_eq!(report.bridge_state_path_source, "profile_default");
+ assert_eq!(report.path_overrides, contract.path_overrides);
assert_eq!(report.default_shared_secret_backend, "encrypted_file");
assert_eq!(
report.allowed_shared_secret_backends,