cli

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

commit e23ea523c84b190abb9b80ad2821e99b801ff3c2
parent 9d8004ea1dad724340952ecfe0e3e02d073fd6e5
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Apr 2026 07:23:29 +0000

cli: classify account binding mismatch

Diffstat:
Msrc/operation_adapter.rs | 38++++++++++++++++++++++++++++++++++++++
Mtests/signer_runtime_modes.rs | 39+++++++++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+), 0 deletions(-)

diff --git a/src/operation_adapter.rs b/src/operation_adapter.rs @@ -324,6 +324,11 @@ pub enum OperationAdapterError { operation_id: String, message: String, }, + #[error("account mismatch for `{operation_id}`: {message}")] + AccountMismatch { + operation_id: String, + message: String, + }, #[error("signer unconfigured for `{operation_id}`: {message}")] SignerUnconfigured { operation_id: String, @@ -432,6 +437,16 @@ impl OperationAdapterError { message, CliExitCode::SignerUnavailable, ), + Self::AccountMismatch { + operation_id, + message, + } => runtime_output_error( + "account_mismatch", + operation_id, + "account", + message, + CliExitCode::AuthorizationFailed, + ), Self::SignerUnconfigured { operation_id, message, @@ -535,6 +550,19 @@ fn classify_runtime_failure( if contains_any( &lowered, &[ + "account mismatch", + "selected local account", + "cannot sign listing seller_pubkey", + ], + ) { + return OperationAdapterError::AccountMismatch { + operation_id: operation_id.to_owned(), + message, + }; + } + if contains_any( + &lowered, + &[ "no account", "no local account", "account selector", @@ -1325,6 +1353,16 @@ mod tests { ( OperationAdapterError::unconfigured( "listing.publish", + "selected local account pubkey `b` cannot sign listing seller_pubkey `a`" + .to_owned(), + ), + "account_mismatch", + "account", + 5, + ), + ( + OperationAdapterError::unconfigured( + "listing.publish", "signer.remote_nip46 binding is missing".to_owned(), ), "signer_unconfigured", diff --git a/tests/signer_runtime_modes.rs b/tests/signer_runtime_modes.rs @@ -342,6 +342,45 @@ fn local_listing_publish_signs_with_selected_account_without_remote_fallback() { } #[test] +fn local_listing_publish_fails_when_selected_account_does_not_match_seller() { + let sandbox = RadrootsCliSandbox::new(); + let first = sandbox.json_success(&["--format", "json", "account", "create"]); + let first_account_id = first["result"]["account"]["id"] + .as_str() + .expect("first account id"); + let listing_file = create_listing_draft(&sandbox, "local-mismatch"); + make_listing_publishable(&listing_file, "AAAAAAAAAAAAAAAAAAAAAw"); + let second = sandbox.json_success(&["--format", "json", "account", "create"]); + let second_account_id = second["result"]["account"]["id"] + .as_str() + .expect("second account id"); + assert_ne!(first_account_id, second_account_id); + + let (output, value) = sandbox.json_output(&[ + "--format", + "json", + "--account-id", + second_account_id, + "--approval-token", + "approve", + "listing", + "publish", + listing_file.to_string_lossy().as_ref(), + ]); + + assert!(!output.status.success()); + assert_eq!(value["operation_id"], "listing.publish"); + assert_eq!(value["result"], serde_json::Value::Null); + assert_eq!(value["errors"][0]["code"], "account_mismatch"); + assert_eq!(value["errors"][0]["exit_code"], 5); + assert_eq!(value["errors"][0]["detail"]["class"], "account"); + assert_contains( + &value["errors"][0]["message"], + "cannot sign listing seller_pubkey", + ); +} + +#[test] fn watch_only_listing_publish_fails_as_account_watch_only() { let sandbox = RadrootsCliSandbox::new(); let public_identity = identity_public(12);