Skip to content

fix(ui): detect awaiting sync for owner imports via config signature#200

Merged
sanity merged 2 commits intomainfrom
fix/owner-import-sync
Mar 26, 2026
Merged

fix(ui): detect awaiting sync for owner imports via config signature#200
sanity merged 2 commits intomainfrom
fix/owner-import-sync

Conversation

@sanity
Copy link
Copy Markdown
Contributor

@sanity sanity commented Mar 26, 2026

Problem

When a room owner imports their identity on a new device, is_awaiting_initial_sync() returned false immediately due to an owner bypass (if is_owner { return false }). This caused a cascade of failures:

  1. No GET-first flow: The room_synchronizer skipped the GET and PUT the default state (unsigned config) directly to the contract
  2. No "syncing" indicator: The message input appeared immediately, letting the owner send messages before sync
  3. GET response didn't replace state: The handler tried to merge instead of wholesale-replacing the placeholder state
  4. Every UPDATE rejected: The contract's validate_state failed with "State verification failed: Invalid signature: signature error" because the default AuthorizedConfigurationV1 is signed by SigningKey([0; 32]), not the real owner

User report from Ivvor on Matrix: messages sent after owner identity import all fail, with only pre-export messages visible via riverctl message list.

Approach

Replace the ownership-based heuristic with a direct check: verify whether the configuration signature is valid against the owner's key.

  • Default stateAuthorizedConfigurationV1::default() is signed by SigningKey([0; 32])verify_signature(&owner_vk) returns Err → awaiting sync = true
  • Newly created room → config signed by real owner at creation → verify_signature(&owner_vk) returns Ok → awaiting sync = false
  • Synced room → config from network, signed by owner → awaiting sync = false

This works correctly for both owner and non-owner imports without special-casing.

Testing

  • cargo check -p river-ui --target wasm32-unknown-unknown --features no-sync — compiles
  • cargo test -p river-core — 181 tests pass
  • Verified AuthorizedConfigurationV1::default() uses SigningKey([0; 32]) (configuration.rs:164-169)
  • Verified create_new_room_with_name signs config with real owner key (room_data.rs:748)

[AI-assisted - Claude]

The previous is_awaiting_initial_sync() had an owner bypass that returned
false immediately for room owners. This caused owner identity imports to
skip the GET-first sync flow, PUT the default state (unsigned config)
directly to the contract, and allow message sends before sync completed.
Every UPDATE then failed with "State verification failed: Invalid
signature: signature error" because the default AuthorizedConfigurationV1
is signed by SigningKey([0; 32]), not the real owner.

Fix: check whether the configuration signature verifies against the
owner's key instead of using ownership as a proxy for "already synced".
This correctly detects the placeholder state for both owner and non-owner
imports, while newly-created rooms pass (their config is signed by the
real owner at creation time).

Updated the test to cover the owner-import case (the exact bug) using
AuthorizedConfigurationV1::default() for imported rooms instead of a
properly-signed config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sanity sanity force-pushed the fix/owner-import-sync branch from 659a6c3 to 94d7dd7 Compare March 26, 2026 18:52
The comment referenced the old is_awaiting_initial_sync logic ("returns
false once members are populated"). Updated to reflect the new
signature-based check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sanity sanity merged commit f643208 into main Mar 26, 2026
4 checks passed
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.

1 participant