cli

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

commit f8ab66327a7c4dc3b9f885eb3d80d7e4e26972c1
parent 6273de434ee6723d7665bf63e1d5bf58918d492a
Author: triesap <tyson@radroots.org>
Date:   Wed,  1 Jul 2026 22:56:37 +0000

cli: make account create creation-only

Diffstat:
Msrc/ops/exec/basket.rs | 4++--
Msrc/ops/exec/core.rs | 48++++++++++++++++++++++++++++++++++++++----------
Msrc/runtime/account.rs | 26+++-----------------------
Msrc/runtime/sdk.rs | 42+++++++++++++++++++++++++++++++++++++++++-
4 files changed, 84 insertions(+), 36 deletions(-)

diff --git a/src/ops/exec/basket.rs b/src/ops/exec/basket.rs @@ -1507,7 +1507,7 @@ mod tests { let dir = tempdir().expect("tempdir"); let config = sample_config(dir.path()); seed_current_listing(&config); - account::create_or_migrate_default_account(&config).expect("create buyer account"); + account::create_default_account(&config).expect("create buyer account"); let service = OperationAdapter::new(BasketOperationService::new(&config)); create_basket(&service, "basket_quote"); add_listing_item(&service, "basket_quote"); @@ -1561,7 +1561,7 @@ mod tests { let dir = tempdir().expect("tempdir"); let config = sample_config(dir.path()); seed_current_listing(&config); - account::create_or_migrate_default_account(&config).expect("create buyer account"); + account::create_default_account(&config).expect("create buyer account"); let service = OperationAdapter::new(BasketOperationService::new(&config)); create_basket(&service, "basket_dry_run"); add_listing_item(&service, "basket_dry_run"); diff --git a/src/ops/exec/core.rs b/src/ops/exec/core.rs @@ -22,11 +22,10 @@ use crate::out::envelope::OutputWarning; use crate::runtime::RuntimeError; use crate::runtime::account::{ AccountResolution, AccountRuntimeFailure, account_resolution_view, account_summary_view, - attach_identity_secret, clear_default_account, create_or_migrate_default_account, - import_public_identity, preview_account_removal, preview_identity_secret_attachment, - preview_public_identity_import, remove_account, resolve_account_resolution, - resolve_account_selector, secret_backend_status, select_account, snapshot, - unresolved_account_reason, + attach_identity_secret, clear_default_account, create_default_account, import_public_identity, + preview_account_removal, preview_identity_secret_attachment, preview_public_identity_import, + remove_account, resolve_account_resolution, resolve_account_selector, secret_backend_status, + select_account, snapshot, unresolved_account_reason, }; use crate::runtime::config::{PublishTransport, RuntimeConfig, SignerBackend}; use crate::runtime::logging::LoggingState; @@ -300,12 +299,9 @@ impl OperationService<AccountCreateRequest> for CoreOperationService<'_> { })); } - let result = map_runtime(create_or_migrate_default_account(self.config))?; + let result = map_runtime(create_default_account(self.config))?; json_operation_result::<AccountCreateResult>(json!({ - "state": match result.mode { - crate::runtime::account::AccountCreateMode::Created => "created", - crate::runtime::account::AccountCreateMode::Migrated => "migrated", - }, + "state": "created", "account": account_summary_view(&result.account), })) } @@ -1207,6 +1203,7 @@ fn invalid_input(operation_id: &str, message: String) -> OperationAdapterError { #[cfg(test)] mod tests { + use radroots_identity::RadrootsIdentity; use radroots_secret_vault::RadrootsSecretBackend; use serde_json::{Map, Value}; use std::path::{Path, PathBuf}; @@ -1330,6 +1327,37 @@ mod tests { } #[test] + fn account_create_ignores_configured_identity_path() { + let dir = tempdir().expect("tempdir"); + let config = sample_config(dir.path()); + let configured_identity = RadrootsIdentity::generate(); + std::fs::create_dir_all(config.identity.path.parent().expect("identity parent")) + .expect("identity parent dir"); + configured_identity + .save_json(&config.identity.path) + .expect("identity file"); + let logging = LoggingState { + initialized: false, + current_file: None, + }; + let service = OperationAdapter::new(CoreOperationService::new(&config, &logging)); + let create = + OperationRequest::new(OperationContext::default(), AccountCreateRequest::default()) + .expect("account create request"); + let create_result = service.execute(create).expect("account create result"); + let create_envelope = create_result + .to_envelope(OperationContext::default().envelope_context("req_create")) + .expect("account create envelope"); + let configured_identity_id = configured_identity.id().to_string(); + + assert_eq!(create_envelope.result["state"], "created"); + assert_ne!( + create_envelope.result["account"]["id"].as_str(), + Some(configured_identity_id.as_str()) + ); + } + + #[test] fn core_required_account_approvals_return_approval_error() { let dir = tempdir().expect("tempdir"); let config = sample_config(dir.path()); diff --git a/src/runtime/account.rs b/src/runtime/account.rs @@ -164,15 +164,8 @@ pub struct AccountSecretBackendStatus { pub reason: Option<String>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AccountCreateMode { - Created, - Migrated, -} - #[derive(Debug, Clone)] pub struct AccountCreateResult { - pub mode: AccountCreateMode, pub account: AccountRecordView, } @@ -226,22 +219,9 @@ pub struct AccountSigningIdentity { pub identity: RadrootsIdentity, } -pub fn create_or_migrate_default_account( - config: &RuntimeConfig, -) -> Result<AccountCreateResult, RuntimeError> { +pub fn create_default_account(config: &RuntimeConfig) -> Result<AccountCreateResult, RuntimeError> { let manager = account_manager(config)?; - let existing = manager.list_accounts()?; - let (mode, created_account_id) = if existing.is_empty() && config.identity.path.exists() { - ( - AccountCreateMode::Migrated, - manager.migrate_legacy_identity_file(&config.identity.path, None, false)?, - ) - } else { - ( - AccountCreateMode::Created, - manager.generate_identity(None, false)?, - ) - }; + let created_account_id = manager.generate_identity(None, false)?; let snapshot = snapshot(config)?; let account = snapshot_account( @@ -250,7 +230,7 @@ pub fn create_or_migrate_default_account( "created account missing after account create", )?; - Ok(AccountCreateResult { mode, account }) + Ok(AccountCreateResult { account }) } pub fn import_public_identity( diff --git a/src/runtime/sdk.rs b/src/runtime/sdk.rs @@ -1140,7 +1140,7 @@ mod tests { fn materializes_local_account_signer_for_sdk_workflows() { let root = tempdir().expect("tempdir"); let config = sample_config(root.path(), Vec::new()); - let account = account::create_or_migrate_default_account(&config).expect("create account"); + let account = account::create_default_account(&config).expect("create account"); let signer = CliSdkLocalSigner::from_runtime_config(&config).expect("sdk signer"); @@ -1326,6 +1326,46 @@ mod tests { } #[test] + fn cli_account_create_sources_reject_implicit_identity_ingestion() { + let account_source = rust_code_without_non_code( + "src/runtime/account.rs", + crate_source("src/runtime/account.rs").as_str(), + ) + .expect("account source classification"); + let core_source = rust_code_without_non_code( + "src/ops/exec/core.rs", + crate_source("src/ops/exec/core.rs").as_str(), + ) + .expect("core source classification"); + + let removed_create_helper = ["create_or_", "migrate_default_account"].concat(); + let removed_create_mode = ["Account", "Create", "Mode"].concat(); + let removed_import_helper = ["migrate", "_legacy", "_identity", "_file"].concat(); + let removed_identity_probe = ["config.identity.path", ".exists()"].concat(); + for token in [ + removed_create_helper.as_str(), + removed_create_mode.as_str(), + removed_import_helper.as_str(), + removed_identity_probe.as_str(), + ] { + assert!( + !account_source.contains(token), + "CLI account runtime must not contain implicit account-create identity-ingestion token `{token}`" + ); + } + let removed_migrated_output = ["\"", "migr", "ated", "\""].concat(); + for token in [ + removed_create_mode.as_str(), + removed_migrated_output.as_str(), + ] { + assert!( + !core_source.contains(token), + "CLI account create output must not contain removed account-create token `{token}`" + ); + } + } + + #[test] fn migrated_cli_paths_are_guarded_against_workflow_bypasses() { for guard in MIGRATED_CLI_PATH_GUARDS { let source = crate_source(guard.path);