Fixes#197 (M5-S4). AccountsGrpcService::add_account writes the account's password to the keyring vault (set_secret) before persisting the account row via save_account. If set_secret succeeds but save_account then fails (disk full, DB locked, pool closed, etc.), the credential was orphaned in the keyring forever -- unreferenced by any account row, invisible to ListAccounts.
On a save_account failure, add_account now calls SecretManager::delete_secret on the just-written key before returning.
The RPC still returns the original persistence-failure Status::internal, never a fabricated success and never a rollback-only error masking the real cause.
If the rollback delete itself fails, that failure is appended to the error message (... (and failed to roll back the credential: {rollback_err})) and logged via tracing::warn!, but the primary error code/meaning stays "persistence failed."
Updated the doc comment above add_account to describe the two-step commit-with-rollback behavior instead of the previous half-true "if this fails, nothing else happens" description.
Tests
Two new tests near the existing add_account_* tests in crates/nunciod/src/grpc.rs:
add_account_rolls_back_keyring_secret_when_save_account_fails: forces a genuine save_account failure by calling DatabaseEngine::close() (closing the connection pool) before making the add_account RPC call, then asserts secrets.get_secret(...) returns Err (secret rolled back, not orphaned).
add_account_rollback_preserves_the_original_persistence_error: same forced-failure setup, asserts the surfaced error message describes the persistence failure ("failed to persist account") and is not the unrelated set_secret failure message.
All existing add_account_* tests pass unmodified (happy-path behavior unchanged).
New tests force a real save_account failure via DatabaseEngine::close(), not a simulated one
Existing add_account_* tests pass unmodified
## Summary
Fixes #197 (M5-S4). `AccountsGrpcService::add_account` writes the account's password to the keyring vault (`set_secret`) before persisting the account row via `save_account`. If `set_secret` succeeds but `save_account` then fails (disk full, DB locked, pool closed, etc.), the credential was orphaned in the keyring forever -- unreferenced by any account row, invisible to `ListAccounts`.
- On a `save_account` failure, `add_account` now calls `SecretManager::delete_secret` on the just-written key before returning.
- The RPC still returns the original persistence-failure `Status::internal`, never a fabricated success and never a rollback-only error masking the real cause.
- If the rollback delete itself fails, that failure is appended to the error message (`... (and failed to roll back the credential: {rollback_err})`) and logged via `tracing::warn!`, but the primary error code/meaning stays "persistence failed."
- Updated the doc comment above `add_account` to describe the two-step commit-with-rollback behavior instead of the previous half-true "if this fails, nothing else happens" description.
## Tests
Two new tests near the existing `add_account_*` tests in `crates/nunciod/src/grpc.rs`:
- `add_account_rolls_back_keyring_secret_when_save_account_fails`: forces a genuine `save_account` failure by calling `DatabaseEngine::close()` (closing the connection pool) before making the `add_account` RPC call, then asserts `secrets.get_secret(...)` returns `Err` (secret rolled back, not orphaned).
- `add_account_rollback_preserves_the_original_persistence_error`: same forced-failure setup, asserts the surfaced error message describes the persistence failure (`"failed to persist account"`) and is not the unrelated `set_secret` failure message.
All existing `add_account_*` tests pass unmodified (happy-path behavior unchanged).
## Gate output
```
cargo fmt --all -- --check -> clean, no output
cargo check-all -> Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.29s (zero warnings)
cargo test-all -> 400 passed; 0 failed; 0 ignored (across all workspace crates)
```
Notable subset (`nunciod` lib): `112 passed; 0 failed; 0 ignored`, including the two new rollback tests.
## Test plan
- [x] `cargo fmt --all -- --check`
- [x] `cargo check-all` (clippy `--all-targets --workspace -D warnings`)
- [x] `cargo test-all` (400 passed, 0 failed, 0 ignored)
- [x] New tests force a real `save_account` failure via `DatabaseEngine::close()`, not a simulated one
- [x] Existing `add_account_*` tests pass unmodified
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Fixes #197 (M5-S4).
AccountsGrpcService::add_accountwrites the account's password to the keyring vault (set_secret) before persisting the account row viasave_account. Ifset_secretsucceeds butsave_accountthen fails (disk full, DB locked, pool closed, etc.), the credential was orphaned in the keyring forever -- unreferenced by any account row, invisible toListAccounts.save_accountfailure,add_accountnow callsSecretManager::delete_secreton the just-written key before returning.Status::internal, never a fabricated success and never a rollback-only error masking the real cause.... (and failed to roll back the credential: {rollback_err})) and logged viatracing::warn!, but the primary error code/meaning stays "persistence failed."add_accountto describe the two-step commit-with-rollback behavior instead of the previous half-true "if this fails, nothing else happens" description.Tests
Two new tests near the existing
add_account_*tests incrates/nunciod/src/grpc.rs:add_account_rolls_back_keyring_secret_when_save_account_fails: forces a genuinesave_accountfailure by callingDatabaseEngine::close()(closing the connection pool) before making theadd_accountRPC call, then assertssecrets.get_secret(...)returnsErr(secret rolled back, not orphaned).add_account_rollback_preserves_the_original_persistence_error: same forced-failure setup, asserts the surfaced error message describes the persistence failure ("failed to persist account") and is not the unrelatedset_secretfailure message.All existing
add_account_*tests pass unmodified (happy-path behavior unchanged).Gate output
Notable subset (
nunciodlib):112 passed; 0 failed; 0 ignored, including the two new rollback tests.Test plan
cargo fmt --all -- --checkcargo check-all(clippy--all-targets --workspace -D warnings)cargo test-all(400 passed, 0 failed, 0 ignored)save_accountfailure viaDatabaseEngine::close(), not a simulated oneadd_account_*tests pass unmodified