radrootsd

JSON-RPC bridge for Radroots event publishing
git clone https://radroots.dev/git/radrootsd.git
Log | Files | Refs | README | LICENSE

commit 1265c34e656243fb3288d463f45c2e9ab7eb5563
parent 35ea3c747c4ab9d5aa06711db7fb78f126e69dee
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Apr 2026 16:55:37 +0000

runtime: report path provenance

Diffstat:
Msrc/app/config.rs | 23+++++++++++++++++++----
Msrc/app/paths.rs | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/app/runtime.rs | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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,