app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 5b125f343f1b55436954db44cea73e2050d96915
parent 27f0e7a4e7b5a3fdb0db746572ff51ac6bb352c9
Author: triesap <tyson@radroots.org>
Date:   Sat, 28 Mar 2026 20:02:40 +0000

app: make pending signer cleanup transactional

Diffstat:
Mcrates/remote-signer/src/custody.rs | 48++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 44 insertions(+), 4 deletions(-)

diff --git a/crates/remote-signer/src/custody.rs b/crates/remote-signer/src/custody.rs @@ -12,13 +12,23 @@ pub fn radroots_app_remote_signer_clear_pending_session( path: &Path, remove_client_secret: impl Fn(&str) -> Result<(), String>, ) -> Result<Option<RadrootsAppRemoteSignerSessionRecord>, String> { - let mut state = load_sessions(path)?; + let state = load_sessions(path)?; let Some(record) = state.pending_session().cloned() else { return Ok(None); }; - remove_client_secret(record.client_account_id())?; - let removed = state.remove_pending_session(); - save_sessions(path, &state)?; + let mut next_state = state.clone(); + let removed = next_state.remove_pending_session(); + if removed.is_none() { + return Err("remote signer pending session record cleanup could not complete".to_owned()); + } + save_sessions(path, &next_state)?; + + if let Err(error) = remove_client_secret(record.client_account_id()) { + return Err(format!( + "remote signer pending session record was removed but session secret cleanup needs retry: {error}" + )); + } + Ok(removed) } @@ -332,6 +342,36 @@ mod tests { } #[test] + fn clear_pending_session_leaves_secret_for_retry_when_secret_cleanup_fails() { + let temp = tempfile::tempdir().expect("tempdir"); + let path = temp.path().join("sessions.json"); + let record = write_pending_state(path.as_path()); + let vault = RadrootsNostrSecretVaultMemory::new(); + secret_store_secret(&vault, record.client_account_id(), "deadbeef"); + + let error = radroots_app_remote_signer_clear_pending_session( + path.as_path(), + |_client_account_id| Err("vault unavailable".to_owned()), + ) + .expect_err("cleanup retry"); + + assert!(error.contains("session secret cleanup needs retry")); + assert!( + RadrootsAppRemoteSignerSessionStoreState::load(path.as_path()) + .expect("load") + .sessions + .is_empty() + ); + assert_eq!( + vault + .load_secret_hex(&fixture_account_id(record.client_account_id())) + .expect("load retained secret") + .as_deref(), + Some("deadbeef") + ); + } + + #[test] fn disconnect_selected_remote_signer_leaves_session_for_retry_when_secret_cleanup_fails() { let temp = tempfile::tempdir().expect("tempdir"); let path = temp.path().join("sessions.json");