Skip to content

fix(station): keep the disaster recovery committee from becoming empty - #677

Open
sea-snake wants to merge 2 commits into
mainfrom
fix/keep-disaster-recovery-committee-non-empty
Open

sea-snake wants to merge 2 commits into
mainfrom
fix/keep-disaster-recovery-committee-non-empty

Conversation

@sea-snake

@sea-snake sea-snake commented Oct 23, 2026 •

Copy link
Copy Markdown

Summary

The station mirrors its disaster recovery committee to the upgrader. The upgrader does not accept an empty committee: it rejects the update and keeps the committee it already has. The station did not account for this, and it decoded the upgrader's reply as (), so an error reply was treated as a successful sync.

This change keeps the station's committee non-empty and in sync with the upgrader, and reports a failed sync:

  • SetDisasterRecovery now requires a committee (committee: None is refused) whose user group has at least one active member. The check runs when the request is created and again when it executes, so it applies to the state at execution time. The Candid input is unchanged, and installing a station without a committee still works.
  • edit_user refuses a change that takes the last active member out of the configured committee, either by removing the committee group or by setting the user to inactive. remove_admin applies the same check when the committee group is the admin group. Changes that do not remove an active member, including edits on a station whose committee already has no active member, are unaffected.
  • Two new DisasterRecoveryError variants: MISSING_COMMITTEE and COMMITTEE_WITHOUT_ACTIVE_MEMBERS.
  • The committee is synced to the upgrader when a member's identities or name change. The upgrader keeps its own copy of these fields, and until now only joining or leaving the committee triggered a sync.
  • sync_committee and sync_accounts_and_assets decode the upgrader's reply as ApiResult and turn an Err into CommitteeSyncFailed / AccountSyncFailed.

The upgrader and the wallet frontend are unchanged.

Tests

  • New station unit tests in services/user.rs and factories/requests/set_disaster_recovery.rs. The 7 refusal tests fail with the new checks disabled and pass with them; the 4 tests for changes that stay allowed pass either way.
  • cargo test -p station --lib: 420 passed, 0 failed.
  • cargo fmt --check, cargo clippy -p station --tests -- -D warnings and cargo clippy -p integration-tests --tests -- -D warnings pass.
  • New integration test test_set_disaster_recovery_requires_committee: a SetDisasterRecovery request without a committee is rejected with a validation error and the upgrader's committee is unchanged.
  • New integration test test_committee_member_identity_change_is_synced: after a committee member's identity is changed from A to B, the upgrader's committee lists B and not A, a disaster recovery request from A is rejected and one from B is accepted. It fails with the new sync condition disabled and passes with it.
  • Locally (macOS arm64) via ./scripts/run-integration-tests.sh: the disaster_recovery filter passed 14 of 14 on the first commit, and the new identity test passed on the second commit.

🤖 Generated with Claude Code

The upgrader does not accept an empty disaster recovery committee, so the
station now refuses changes that would leave the committee unset or
without active members:

- SetDisasterRecovery requires a committee whose user group has an active
  member, checked at creation and again at execution.
- Editing a user, or removing the admin role, fails when it would take the
  last active member out of the committee.

The upgrader's reply to the committee and account syncs is decoded as its
result type, so an error reply is reported as a failed sync.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sea-snake
sea-snake requested a review from a team as a code owner October 23, 2026 13:39
Copilot AI balanced review requested due to automatic review settings October 23, 2026 13:39
@zeropath-ai

zeropath-ai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

✅ No security or compliance issues detected. Reviewed everything up to 4854f72.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► core/station/impl/src/errors/disaster_recovery.rs
(Add MissingCommittee and CommitteeWithoutActiveMembers errors and propagate details)
Enhancement ► core/station/impl/src/factories/requests/set_disaster_recovery.rs
(Update request creation to use station_api types, validate committee, pass through new input type, add tests)
Enhancement ► core/station/impl/src/services/disaster_recovery.rs
(Adapt to ApiResult and improved error handling for account/committee sync)
Enhancement ► core/station/impl/src/services/system.rs
(Add disaster recovery committee assertion and storage helpers; adjust initialization paths to store committee)
Enhancement ► core/station/impl/src/services/user.rs
(Add validation to ensure committee has active members on user changes; provide helper to enforce active member constraint)
Enhancement ► tests/integration/src/disaster_recovery_tests.rs
(Update integration tests for disaster recovery flow: require committee on creation, committee changes, and identity-related tests)
Enhancement ► core/station/impl/src/services/system.rs
(Store and validate disaster recovery committee during various init paths)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The membership guards and synchronization reply handling match the intended behavior, with regression coverage and no identified blocking issues.

No open issues 🎉

What changed in this PR

This PR prevents station operations from emptying the disaster recovery committee and makes upgrader synchronization failures visible, while preserving installation without a committee.

Changes:

  • Validate committee membership when requests are created and executed.
  • Block user changes that remove the last active committee member.
  • Decode upgrader error replies and add regression tests for committee safeguards.
File Description
tests/​integration/​src/​disaster_recovery_tests.rs Tests rejection of requests without a committee.
core/​station/​impl/​src/​services/​user.rs Guards the last active committee member and tests allowed and rejected edits.
core/​station/​impl/​src/​services/​system.rs Validates committee changes while preserving initialization behavior.
core/​station/​impl/​src/​services/​disaster_recovery.rs Propagates upgrader synchronization errors.
core/​station/​impl/​src/​factories/​requests/​set_disaster_recovery.rs Validates requests at creation and execution, with unit tests.
core/​station/​impl/​src/​errors/​disaster_recovery.rs Adds errors for missing and inactive committees.

Review effort: Balanced

…hange

The upgrader keeps its own copy of each committee member's identities and
name. Editing those fields on a user who stays in the committee now
triggers a committee sync, so the upgrader's copy matches the station.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants