cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 3f856c6368cf5841c4ceac5e4860b042a38bcb1d
parent 3a292865c8260e4b15731c224a25eb754be0ad0d
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Apr 2026 16:08:31 +0000

runtime: align cli contract reporting

Diffstat:
Msrc/commands/identity.rs | 16++++++++--------
Msrc/commands/runtime.rs | 9+++++++++
Msrc/domain/runtime.rs | 11+++++++++++
Msrc/render/mod.rs | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/runtime/accounts.rs | 1+
Msrc/runtime/config.rs | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/runtime/signer.rs | 13+++++++------
Mtests/identity_commands.rs | 4++--
Mtests/runtime_show.rs | 28++++++++++++++++++++++++++++
Mtests/signer_status.rs | 2+-
10 files changed, 226 insertions(+), 40 deletions(-)

diff --git a/src/commands/identity.rs b/src/commands/identity.rs @@ -4,8 +4,8 @@ use crate::domain::runtime::{ }; use crate::runtime::RuntimeError; use crate::runtime::accounts::{ - AccountCreateMode, AccountRecordView, create_or_migrate_selected_account, resolve_account, - select_account, snapshot, + AccountCreateMode, AccountRecordView, SHARED_ACCOUNT_STORE_SOURCE, + create_or_migrate_selected_account, resolve_account, select_account, snapshot, }; use crate::runtime::config::RuntimeConfig; @@ -18,8 +18,8 @@ pub fn init(config: &RuntimeConfig) -> Result<AccountNewView, RuntimeError> { AccountCreateMode::Migrated => "migrated".to_owned(), }, source: match result.mode { - AccountCreateMode::Created => "local account store · local first".to_owned(), - AccountCreateMode::Migrated => "legacy identity import · local first".to_owned(), + AccountCreateMode::Created => SHARED_ACCOUNT_STORE_SOURCE.to_owned(), + AccountCreateMode::Migrated => "legacy shared identity import · local first".to_owned(), }, public_identity: IdentityPublicView::from_public_identity( &result.account.record.public_identity, @@ -36,7 +36,7 @@ pub fn show(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> { let view = match resolve_account(config)? { Some(account) => AccountWhoamiView { state: "ready".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), reason: None, public_identity: Some(IdentityPublicView::from_public_identity( &account.record.public_identity, @@ -45,7 +45,7 @@ pub fn show(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> { }, None => AccountWhoamiView { state: "unconfigured".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), reason: Some(format!( "no local account is selected in {}", config.account.store_path.display() @@ -83,7 +83,7 @@ pub fn list(config: &RuntimeConfig) -> Result<CommandOutput, RuntimeError> { }; Ok(CommandOutput::success(CommandView::AccountList( AccountListView { - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), count: accounts.len(), accounts, actions, @@ -95,7 +95,7 @@ pub fn use_account(config: &RuntimeConfig, selector: &str) -> Result<AccountUseV let account = select_account(config, selector)?; Ok(AccountUseView { state: "active".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), active_account_id: account.record.account_id.to_string(), account: account_summary(&account), }) diff --git a/src/commands/runtime.rs b/src/commands/runtime.rs @@ -26,6 +26,10 @@ pub fn show( }, paths: PathsRuntimeView { profile: config.paths.profile.clone(), + allowed_profiles: config.paths.allowed_profiles.clone(), + app_namespace: config.paths.app_namespace.clone(), + 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(), app_data_root: config.paths.app_data_root.display().to_string(), @@ -58,6 +62,11 @@ pub fn show( secrets_dir: config.account.secrets_dir.display().to_string(), identity_path: config.identity.path.display().to_string(), secret_backend: AccountSecretRuntimeView { + contract_default_backend: config.account_secret_contract.default_backend.clone(), + contract_default_fallback: config.account_secret_contract.default_fallback.clone(), + allowed_backends: config.account_secret_contract.allowed_backends.clone(), + host_vault_policy: config.account_secret_contract.host_vault_policy.clone(), + uses_protected_store: config.account_secret_contract.uses_protected_store, configured_primary: secret_backend.configured_primary, configured_fallback: secret_backend.configured_fallback, state: secret_backend.state, diff --git a/src/domain/runtime.rs b/src/domain/runtime.rs @@ -146,6 +146,10 @@ pub struct LoggingRuntimeView { #[derive(Debug, Clone, Serialize)] pub struct PathsRuntimeView { pub profile: String, + pub allowed_profiles: Vec<String>, + pub app_namespace: String, + pub shared_accounts_namespace: String, + pub shared_identities_namespace: String, pub app_config_path: String, pub workspace_config_path: String, pub app_data_root: String, @@ -167,6 +171,13 @@ pub struct AccountRuntimeView { #[derive(Debug, Clone, Serialize)] pub struct AccountSecretRuntimeView { + pub contract_default_backend: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub contract_default_fallback: Option<String>, + pub allowed_backends: Vec<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_vault_policy: Option<String>, + pub uses_protected_store: bool, pub configured_primary: String, #[serde(skip_serializing_if = "Option::is_none")] pub configured_fallback: Option<String>, diff --git a/src/render/mod.rs b/src/render/mod.rs @@ -496,11 +496,22 @@ fn render_config_show( present_absent(view.config_files.workspace_present), view.paths.workspace_config_path ); + let allowed_profiles = view.paths.allowed_profiles.join(", "); render_pairs( stdout, - "config roots", + "runtime roots", &[ ("profile", view.paths.profile.as_str()), + ("allowed profiles", allowed_profiles.as_str()), + ("app namespace", view.paths.app_namespace.as_str()), + ( + "shared accounts namespace", + view.paths.shared_accounts_namespace.as_str(), + ), + ( + "shared identities namespace", + view.paths.shared_identities_namespace.as_str(), + ), ("app config", user_config.as_str()), ("workspace config", workspace_config.as_str()), ("app data root", view.paths.app_data_root.as_str()), @@ -536,14 +547,30 @@ fn render_config_show( ("store path", view.account.store_path.as_str()), ("secrets dir", view.account.secrets_dir.as_str()), ( - "secret backend", + "contract default secret backend", + view.account.secret_backend.contract_default_backend.as_str(), + ), + ( + "configured secret backend", view.account.secret_backend.configured_primary.as_str(), ), ("identity path", view.account.identity_path.as_str()), ]; + if let Some(fallback) = &view.account.secret_backend.contract_default_fallback { + account_rows.push(("contract default fallback", fallback.as_str())); + } if let Some(fallback) = &view.account.secret_backend.configured_fallback { - account_rows.push(("secret fallback", fallback.as_str())); + account_rows.push(("configured secret fallback", fallback.as_str())); + } + let allowed_backends = view.account.secret_backend.allowed_backends.join(", "); + account_rows.push(("allowed secret backends", allowed_backends.as_str())); + if let Some(policy) = &view.account.secret_backend.host_vault_policy { + account_rows.push(("host vault policy", policy.as_str())); } + account_rows.push(( + "uses protected store", + yes_no(view.account.secret_backend.uses_protected_store), + )); account_rows.push(("secret status", view.account.secret_backend.state.as_str())); if let Some(active_backend) = &view.account.secret_backend.active_backend { account_rows.push(("active secret backend", active_backend.as_str())); @@ -2059,9 +2086,9 @@ mod tests { RelayEntryView, RelayListView, }; use crate::runtime::config::{ - AccountConfig, IdentityConfig, LocalConfig, LoggingConfig, MycConfig, OutputConfig, - OutputFormat, PathsConfig, RelayConfig, RelayConfigSource, RelayPublishPolicy, RpcConfig, - RuntimeConfig, SignerBackend, SignerConfig, Verbosity, + AccountConfig, AccountSecretContractConfig, IdentityConfig, LocalConfig, LoggingConfig, + MycConfig, OutputConfig, OutputFormat, PathsConfig, RelayConfig, RelayConfigSource, + RelayPublishPolicy, RpcConfig, RuntimeConfig, SignerBackend, SignerConfig, Verbosity, }; use crate::runtime::logging::LoggingState; use radroots_secret_vault::RadrootsSecretBackend; @@ -2078,6 +2105,10 @@ mod tests { }, paths: PathsConfig { profile: "interactive_user".into(), + allowed_profiles: vec!["interactive_user".into()], + app_namespace: "apps/cli".into(), + 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/.radroots/config.toml".into(), app_data_root: "/home/tester/.radroots/data/apps/cli".into(), @@ -2100,6 +2131,18 @@ mod tests { secret_backend: RadrootsSecretBackend::EncryptedFile, secret_fallback: None, }, + account_secret_contract: AccountSecretContractConfig { + default_backend: "host_vault".into(), + default_fallback: Some("encrypted_file".into()), + allowed_backends: vec![ + "host_vault".into(), + "encrypted_file".into(), + "memory".into(), + "plaintext_file".into(), + ], + host_vault_policy: Some("desktop".into()), + uses_protected_store: true, + }, identity: IdentityConfig { path: "/home/tester/.radroots/secrets/shared/identities/default.json".into(), }, @@ -2134,6 +2177,8 @@ mod tests { .expect("runtime show"); assert_eq!(view.output.format, "human"); 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/.radroots/config.toml" @@ -2146,6 +2191,7 @@ mod tests { ); assert_eq!(view.relay.count, 2); assert_eq!(view.relay.publish_policy, "any"); + assert_eq!(view.account.secret_backend.contract_default_backend, "host_vault"); assert!( view.local .replica_db_path @@ -2187,6 +2233,10 @@ mod tests { }, paths: PathsConfig { profile: "interactive_user".into(), + allowed_profiles: vec!["interactive_user".into()], + app_namespace: "apps/cli".into(), + 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/.radroots/config.toml".into(), @@ -2211,6 +2261,18 @@ mod tests { secret_backend: RadrootsSecretBackend::EncryptedFile, secret_fallback: None, }, + account_secret_contract: AccountSecretContractConfig { + default_backend: "host_vault".into(), + default_fallback: Some("encrypted_file".into()), + allowed_backends: vec![ + "host_vault".into(), + "encrypted_file".into(), + "memory".into(), + "plaintext_file".into(), + ], + host_vault_policy: Some("desktop".into()), + uses_protected_store: true, + }, identity: IdentityConfig { path: "/home/tester/.radroots/secrets/shared/identities/default.json" .into(), @@ -2257,7 +2319,7 @@ mod tests { #[test] fn account_list_ndjson_emits_one_json_object_per_account() { let output = CommandOutput::success(CommandView::AccountList(AccountListView { - source: "local account store · local first".to_owned(), + source: "shared account store · local first".to_owned(), count: 2, accounts: vec![ crate::domain::runtime::AccountSummaryView { diff --git a/src/runtime/accounts.rs b/src/runtime/accounts.rs @@ -30,6 +30,7 @@ const ENCRYPTED_FILE_MASTER_KEY_FILE: &str = ".vault.key"; const ENCRYPTED_FILE_SECRET_SUFFIX: &str = ".secret.json"; const PLAINTEXT_FILE_SECRET_SUFFIX: &str = ".secret"; const WRAPPED_KEY_VERSION: u8 = 1; +pub const SHARED_ACCOUNT_STORE_SOURCE: &str = "shared account store · local first"; #[derive(Debug, Clone)] pub struct AccountSnapshot { diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -3,10 +3,9 @@ use std::fs; use std::path::Path; use std::path::PathBuf; -use radroots_identity::DEFAULT_IDENTITY_PATH; use radroots_runtime_paths::{ DEFAULT_CONFIG_FILE_NAME, RadrootsPathOverrides, RadrootsPathProfile, RadrootsPathResolver, - RadrootsRuntimeNamespace, + RadrootsRuntimeNamespace, default_shared_identity_path, }; use radroots_secret_vault::{RadrootsHostVaultPolicy, RadrootsSecretBackend}; use serde::Deserialize; @@ -24,6 +23,21 @@ const DEFAULT_LOCAL_BACKUPS_DIR: &str = "backups"; const DEFAULT_LOCAL_EXPORTS_DIR: &str = "exports"; const DEFAULT_SHARED_ACCOUNTS_STORE_FILE: &str = "store.json"; const DEFAULT_RPC_URL: &str = "http://127.0.0.1:7070"; +const CLI_PROFILE: &str = "interactive_user"; +const CLI_APP_NAMESPACE_VALUE: &str = "cli"; +const SHARED_ACCOUNTS_NAMESPACE_VALUE: &str = "accounts"; +const SHARED_IDENTITIES_NAMESPACE_VALUE: &str = "identities"; +const CLI_HOST_VAULT_POLICY: &str = "desktop"; +const CLI_DEFAULT_SECRET_BACKEND: &str = "host_vault"; +const CLI_DEFAULT_SECRET_FALLBACK: &str = "encrypted_file"; +const CLI_ALLOWED_PROFILES: &[&str] = &[CLI_PROFILE]; +const CLI_ALLOWED_SHARED_SECRET_BACKENDS: &[&str] = &[ + "host_vault", + "encrypted_file", + "memory", + "plaintext_file", +]; +const CLI_USES_PROTECTED_STORE: bool = true; const ENV_FILE_PATH: &str = "RADROOTS_ENV_FILE"; const ENV_OUTPUT: &str = "RADROOTS_OUTPUT"; const ENV_CLI_LOG_FILTER: &str = "RADROOTS_CLI_LOGGING_FILTER"; @@ -125,6 +139,15 @@ pub struct AccountConfig { pub secret_fallback: Option<RadrootsSecretBackend>, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountSecretContractConfig { + pub default_backend: String, + pub default_fallback: Option<String>, + pub allowed_backends: Vec<String>, + pub host_vault_policy: Option<String>, + pub uses_protected_store: bool, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SignerBackend { Local, @@ -211,6 +234,7 @@ pub struct RuntimeConfig { pub paths: PathsConfig, pub logging: LoggingConfig, pub account: AccountConfig, + pub account_secret_contract: AccountSecretContractConfig, pub identity: IdentityConfig, pub signer: SignerConfig, pub relay: RelayConfig, @@ -222,6 +246,10 @@ pub struct RuntimeConfig { #[derive(Debug, Clone, PartialEq, Eq)] pub struct PathsConfig { pub profile: String, + pub allowed_profiles: Vec<String>, + pub app_namespace: String, + pub shared_accounts_namespace: String, + pub shared_identities_namespace: String, pub app_config_path: PathBuf, pub workspace_config_path: PathBuf, pub app_data_root: PathBuf, @@ -342,6 +370,16 @@ impl RuntimeConfig { secret_backend: account_secret_backend, secret_fallback: account_secret_fallback, }, + account_secret_contract: AccountSecretContractConfig { + default_backend: CLI_DEFAULT_SECRET_BACKEND.to_owned(), + default_fallback: Some(CLI_DEFAULT_SECRET_FALLBACK.to_owned()), + allowed_backends: CLI_ALLOWED_SHARED_SECRET_BACKENDS + .iter() + .map(|value| (*value).to_owned()) + .collect(), + host_vault_policy: Some(CLI_HOST_VAULT_POLICY.to_owned()), + uses_protected_store: CLI_USES_PROTECTED_STORE, + }, identity: IdentityConfig { path: args .identity_path @@ -400,26 +438,39 @@ impl RuntimeConfig { fn resolve_paths(env: &dyn Environment) -> Result<PathsConfig, RuntimeError> { let current_dir = env.current_dir()?; let resolver = env.path_resolver(); + let profile = RadrootsPathProfile::InteractiveUser; + let overrides = RadrootsPathOverrides::default(); let resolved = resolver - .resolve( - RadrootsPathProfile::InteractiveUser, - &RadrootsPathOverrides::default(), - ) + .resolve(profile, &overrides) .map_err(|err| RuntimeError::Config(format!("resolve Radroots path roots: {err}")))?; - let app_namespace = RadrootsRuntimeNamespace::app("cli") + let app_namespace = RadrootsRuntimeNamespace::app(CLI_APP_NAMESPACE_VALUE) .map_err(|err| RuntimeError::Config(format!("resolve cli namespace: {err}")))?; - let shared_accounts_namespace = RadrootsRuntimeNamespace::shared("accounts") + let shared_accounts_namespace = RadrootsRuntimeNamespace::shared(SHARED_ACCOUNTS_NAMESPACE_VALUE) .map_err(|err| RuntimeError::Config(format!("resolve shared accounts namespace: {err}")))?; + let shared_identity_namespace = + RadrootsRuntimeNamespace::shared(SHARED_IDENTITIES_NAMESPACE_VALUE).map_err(|err| { + RuntimeError::Config(format!("resolve shared identities namespace: {err}")) + })?; let app_paths = resolved.namespaced(&app_namespace); let shared_accounts_paths = resolved.namespaced(&shared_accounts_namespace); - let default_identity_path = resolved - .secrets - .join("shared") - .join("identities") - .join(DEFAULT_IDENTITY_PATH); + let default_identity_path = default_shared_identity_path(&resolver, profile, &overrides) + .map_err(|err| RuntimeError::Config(format!("resolve shared identity path: {err}")))?; Ok(PathsConfig { - profile: RadrootsPathProfile::InteractiveUser.to_string(), + profile: CLI_PROFILE.to_owned(), + allowed_profiles: CLI_ALLOWED_PROFILES + .iter() + .map(|value| (*value).to_owned()) + .collect(), + app_namespace: app_namespace.relative_path().display().to_string(), + shared_accounts_namespace: shared_accounts_namespace + .relative_path() + .display() + .to_string(), + shared_identities_namespace: shared_identity_namespace + .relative_path() + .display() + .to_string(), app_config_path: app_paths.config.join(DEFAULT_CONFIG_FILE_NAME), workspace_config_path: current_dir.join(DEFAULT_WORKSPACE_CONFIG_PATH), app_data_root: app_paths.data, @@ -867,8 +918,9 @@ fn parse_bool_env(key: &str, value: &str) -> Result<bool, RuntimeError> { #[cfg(test)] mod tests { use super::{ - AccountConfig, EnvFileValues, Environment, OutputConfig, OutputFormat, PathsConfig, - RelayConfigSource, RelayPublishPolicy, RuntimeConfig, SignerBackend, Verbosity, + AccountConfig, AccountSecretContractConfig, EnvFileValues, Environment, OutputConfig, + OutputFormat, PathsConfig, RelayConfigSource, RelayPublishPolicy, RuntimeConfig, + SignerBackend, Verbosity, parse_env_file_values, }; use crate::cli::CliArgs; @@ -968,6 +1020,10 @@ mod tests { resolved.paths, PathsConfig { profile: "interactive_user".to_owned(), + allowed_profiles: vec!["interactive_user".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: PathBuf::from( "/home/tester/.radroots/config/apps/cli/config.toml" ), @@ -1005,6 +1061,21 @@ mod tests { secret_fallback: Some(RadrootsSecretBackend::EncryptedFile), } ); + assert_eq!( + resolved.account_secret_contract, + AccountSecretContractConfig { + default_backend: "host_vault".to_owned(), + default_fallback: Some("encrypted_file".to_owned()), + allowed_backends: vec![ + "host_vault".to_owned(), + "encrypted_file".to_owned(), + "memory".to_owned(), + "plaintext_file".to_owned(), + ], + host_vault_policy: Some("desktop".to_owned()), + uses_protected_store: true, + } + ); assert_eq!(resolved.signer.backend, SignerBackend::Local); assert_eq!( resolved.relay.urls, @@ -1261,6 +1332,9 @@ RADROOTS_CLI_LOGGING_STDOUT=false resolved.paths.app_config_path, PathBuf::from("/home/tester/.radroots/config/apps/cli/config.toml") ); + assert_eq!(resolved.paths.app_namespace, "apps/cli"); + assert_eq!(resolved.paths.shared_accounts_namespace, "shared/accounts"); + assert_eq!(resolved.paths.shared_identities_namespace, "shared/identities"); assert_eq!( resolved.paths.workspace_config_path, PathBuf::from("/workspaces/radroots-cli/.radroots/config.toml") diff --git a/src/runtime/signer.rs b/src/runtime/signer.rs @@ -1,5 +1,6 @@ use crate::domain::runtime::{IdentityPublicView, LocalSignerStatusView, SignerStatusView}; use crate::runtime::config::{RuntimeConfig, SignerBackend}; +use crate::runtime::accounts::SHARED_ACCOUNT_STORE_SOURCE; use radroots_nostr_accounts::prelude::RadrootsNostrSelectedAccountStatus; use radroots_nostr_signer::prelude::{ RadrootsNostrLocalSignerAvailability, RadrootsNostrLocalSignerCapability, @@ -19,7 +20,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { return SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "unavailable".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: None, reason: secret_backend.reason, local: None, @@ -31,7 +32,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { return SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "error".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: None, reason: secret_backend.reason, local: None, @@ -60,7 +61,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "ready".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: Some(local.account_id.to_string()), reason: None, local: Some(LocalSignerStatusView { @@ -79,7 +80,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { Ok(RadrootsNostrSelectedAccountStatus::PublicOnly { account }) => SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "unconfigured".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: Some(account.account_id.to_string()), reason: Some(format!( "local account {} is present but not secret-backed", @@ -99,7 +100,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { Ok(RadrootsNostrSelectedAccountStatus::NotConfigured) => SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "unconfigured".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: None, reason: Some(format!( "no local account is selected in {}", @@ -111,7 +112,7 @@ fn resolve_local_signer_status(config: &RuntimeConfig) -> SignerStatusView { Err(error) => SignerStatusView { mode: config.signer.backend.as_str().to_owned(), state: "error".to_owned(), - source: "local account store · local first".to_owned(), + source: SHARED_ACCOUNT_STORE_SOURCE.to_owned(), account_id: None, reason: Some(error.to_string()), local: None, diff --git a/tests/identity_commands.rs b/tests/identity_commands.rs @@ -70,7 +70,7 @@ fn account_new_json_creates_local_account_store_entry() { let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); let json: Value = serde_json::from_str(stdout.as_str()).expect("json output"); assert_eq!(json["state"], "created"); - assert_eq!(json["source"], "local account store · local first"); + assert_eq!(json["source"], "shared account store · local first"); assert!(json["account"]["id"].is_string()); assert_eq!(json["account"]["signer"], "local"); assert_eq!(json["account"]["is_default"], true); @@ -161,7 +161,7 @@ fn account_whoami_json_reads_selected_account() { let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); let json: Value = serde_json::from_str(stdout.as_str()).expect("json output"); assert_eq!(json["state"], "ready"); - assert_eq!(json["source"], "local account store · local first"); + assert_eq!(json["source"], "shared account store · local first"); assert!(json["account"]["id"].is_string()); assert_eq!(json["account"]["signer"], "local"); assert_eq!(json["account"]["is_default"], true); diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs @@ -104,6 +104,10 @@ fn config_show_json_reports_default_bootstrap_state() { assert_eq!(json["output"]["color"], true); assert_eq!(json["output"]["dry_run"], false); assert_eq!(json["paths"]["profile"], "interactive_user"); + assert_eq!(json["paths"]["allowed_profiles"][0], "interactive_user"); + assert_eq!(json["paths"]["app_namespace"], "apps/cli"); + assert_eq!(json["paths"]["shared_accounts_namespace"], "shared/accounts"); + assert_eq!(json["paths"]["shared_identities_namespace"], "shared/identities"); assert_eq!( json["paths"]["app_config_path"], config_root(dir.path()) @@ -178,6 +182,30 @@ fn config_show_json_reports_default_bootstrap_state() { .to_string() ); assert_eq!( + json["account"]["secret_backend"]["contract_default_backend"], + "host_vault" + ); + assert_eq!( + json["account"]["secret_backend"]["contract_default_fallback"], + "encrypted_file" + ); + assert_eq!( + json["account"]["secret_backend"]["allowed_backends"][0], + "host_vault" + ); + assert_eq!( + json["account"]["secret_backend"]["allowed_backends"][1], + "encrypted_file" + ); + assert_eq!( + json["account"]["secret_backend"]["host_vault_policy"], + "desktop" + ); + assert_eq!( + json["account"]["secret_backend"]["uses_protected_store"], + true + ); + assert_eq!( json["account"]["secret_backend"]["configured_primary"], "host_vault" ); diff --git a/tests/signer_status.rs b/tests/signer_status.rs @@ -66,7 +66,7 @@ fn signer_status_reports_local_ready_when_account_exists() { let json: Value = serde_json::from_str(stdout.as_str()).expect("json output"); assert_eq!(json["mode"], "local"); assert_eq!(json["state"], "ready"); - assert_eq!(json["source"], "local account store · local first"); + assert_eq!(json["source"], "shared account store · local first"); assert_eq!(json["account_id"], json["local"]["account_id"]); assert_eq!(json["reason"], Value::Null); assert_eq!(json["local"]["availability"], "secret_backed");