lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 8d63f0086c607ba3f7d02e1aca2b65c9f80c0c2e
parent 8332f0dc68b28b3a002d47b09e1931b191c3242d
Author: triesap <tyson@radroots.org>
Date:   Tue,  4 Aug 2026 00:12:02 +0000

runtime: finalize removal and startup safety

- derive credential deletion impact from the local binding
- retain cascade-backed account-owned data cleanup
- convert missing or invalid icon resources into safe UI failure
- cover binding-aware impact and resource decoding failure

Diffstat:
Mcrates/studio_application/src/app_core.rs | 45+++++++++++++++++++++++++++++++++++++++------
1 file changed, 39 insertions(+), 6 deletions(-)

diff --git a/crates/studio_application/src/app_core.rs b/crates/studio_application/src/app_core.rs @@ -141,15 +141,17 @@ impl AppCore { now: UnixTimestamp, ) -> Result<RemovalConfirmationToken, SafeError> { let mut state = self.lock_state(); - if !state + let Some(account) = state .state_machine .snapshot() .accounts() .iter() - .any(|account| account.public_key() == public_key) - { + .find(|account| account.public_key() == public_key) + else { return Err(account_not_found()); - } + }; + let deletes_local_credential = account.signer().availability() + != radroots_studio_domain::BindingAvailability::CredentialMissing; let id = state.next_removal_token; state.next_removal_token = id.checked_add(1).ok_or_else(invalid_application_state)?; let revision = state.state_machine.snapshot().revision(); @@ -160,7 +162,7 @@ impl AppCore { ) .ok_or_else(invalid_application_state)?; let impact = RemovalImpact { - deletes_local_credential: true, + deletes_local_credential, signs_out: state .state_machine .snapshot() @@ -241,7 +243,12 @@ const fn account_not_found() -> SafeError { #[cfg(test)] mod tests { - use crate::{AppCore, AppLifecycle, RelayConfiguration}; + use radroots_studio_domain::{ + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, UnixTimestamp, + }; + + use crate::{AppCore, AppLifecycle, RelayConfiguration, StateTransition}; #[test] fn bootstrap_is_idempotent_and_advances_only_once() { @@ -264,4 +271,30 @@ mod tests { assert_eq!(first.snapshot().revision().value(), 1); assert_eq!(second.snapshot().revision().value(), 0); } + + #[test] + fn removal_impact_matches_missing_local_binding() { + let core = AppCore::in_memory(RelayConfiguration::default()); + let public_key = PublicKey::from_bytes([9; 32]); + let account = AccountSummary::new( + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::CredentialMissing), + None, + AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("time")), + None, + ) + .expect("account"); + core.apply_transition(StateTransition::BootstrapRegistry { + accounts: vec![account], + selected: Some(public_key), + }) + .expect("registry"); + + let removal = core + .issue_removal_token(public_key, UnixTimestamp::from_seconds(2).expect("time")) + .expect("removal"); + + assert!(!removal.impact().deletes_local_credential()); + assert!(!removal.impact().signs_out()); + } }