Skip to content

Sync exchange v2: ensure implicit aborts go to terminal state#8990

Open
CDRussell wants to merge 2 commits into
developfrom
feature/craig/fix/sync_exchange_v2_tidy_terminal_states
Open

Sync exchange v2: ensure implicit aborts go to terminal state#8990
CDRussell wants to merge 2 commits into
developfrom
feature/craig/fix/sync_exchange_v2_tidy_terminal_states

Conversation

@CDRussell

@CDRussell CDRussell commented Jun 24, 2026

Copy link
Copy Markdown
Member

Task/Issue URL: https://app.asana.com/1/137249556945/project/1203822806345703/task/1215724271421988?focus=true

Description

Per the Transport Tech Design ("other error/incompatibility/bug in the other party is detected" → tear down), implicit aborts in the v2 SM now drive to a terminal state instead of leaving the session in a non-terminal one. Previously the runner only tore down on isTerminal(), so out-of-sequence messages let sessions linger until the 5-min client timed out.

Now, if the peer sends an unexpected/out-of-sequence message then treated as UNEXPECTED_EVENT rather than the generic PAIRING_FAILED

Finally, if there is a terminal error in the manual code entry screen, after the user sees the error message they are taken back to sync settings screen (instead of returned only to the barcode scanning screen on a bad session).

Steps to test this PR

Note

Requires the Python pairing reference client

For each scenario below, start with no existing sync account before testing.

1. response-without-hello — exercises the new Bootstrapped → Aborted path

Direction: Android = Presenter (shows code), Python = Scanner (pastes Android's code).

  • On Android: Sync settings → Sync With Another Device → Copy Text Code (Android presents).
  • In a terminal, run the broken Scanner:
      poetry run pairing-client \
        --server-url https://sync.duckduckgo.com/ \
        --broken-mode response-without-hello \
        --kind 3party
  • When Python prompts Paste the other device's code:, paste Android's code
  • Expected on Android: dialog "Sync failed. Please try again." immediately (no 5-min wait)

2. duplicate-hello — sanity check on second-hello-in-Negotiating teardown

Direction: Android = Presenter, Python = Scanner.

  • Android: same as scenario 1 (copy code from Android and paste into python companion).
  • Python:
      poetry run pairing-client \
        --server-url https://sync.duckduckgo.com/ \
        --broken-mode duplicate-hello \
        --kind 3party
  • Paste Android's code into Python when prompted.
  • Expected on Android: dialog "Sync failed. Please try again."

3. hello-from-presenter — same teardown contract, opposite role assignment

Direction: Android = Scanner (pastes Python's code), Python = Presenter (shows code).

  • In a terminal, run the broken Presenter:
      poetry run pairing-client \
        --server-url https://sync.duckduckgo.com/ \
        --broken-mode hello-from-presenter \
        --kind 3party --has-account 3party
  • Python prints https://duckduckgo.com/sync/pairing/#&code2=… — copy this URL.
  • On Android: Sync settings → Sync With Another Device → Manually Enter Code and paste Python's URL.
  • Expected on Android: dialog "Sync failed. Please try again."

Note

Medium Risk
Changes sync v2 pairing session teardown and user-visible error classification; impact is limited to the connect/pairing path but wrong mapping would confuse users or leave sessions uncleared.

Overview
V2 exchange state machine now moves implicit aborts and out-of-sequence local triggers into terminal states (Aborted, Host.Aborted, Joiner.AbortedLocal) instead of leaving the session stuck in non-terminal states, so the runner can tear down per session lifecycle. Late messages on already-terminal states stay defensive (state unchanged).

RealSyncCodeDispatcher maps those terminals more precisely: Joiner.AbortedLocal and Host.Aborted use PAIRING_CANCELLED only when the user explicitly denied; wire-driven protocol errors use PAIRING_FAILED (generic "Sync failed" copy) instead of cancellation messaging.

Tests in ExchangeV2StateMachineTest and RealSyncCodeDispatcherTest assert terminal transitions and the new outcome codes for present/linking flows.

Reviewed by Cursor Bugbot for commit 4a41c40. Configure here.

CDRussell commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch 8 times, most recently from 3b7c4c3 to c4c0727 Compare July 3, 2026 14:54
@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch from c4c0727 to f0ff4aa Compare July 3, 2026 15:40
@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch from f78e252 to f94c91c Compare July 3, 2026 18:33
@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch from f94c91c to 9255d4f Compare July 3, 2026 23:25

@SuppressLint("DenyListedApi")
@RunWith(AndroidJUnit4::class)
class RealSyncCodeDispatcherTest {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Revisited most of this test class as I didn't like the previous use of coroutines (wasn't using Turbine), nor the feature flag interactions. Resulted in a noisier diff but it's leaving it in a better state.

@CDRussell CDRussell requested a review from cmonfortep July 3, 2026 23:28
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