Skip to content

feat(sync): keep only the devices the user chooses on a restore - #1831

Open
A-EDev wants to merge 3 commits into
mainfrom
feat/sync2-restore-choose-devices
Open

A-EDev wants to merge 3 commits into
mainfrom
feat/sync2-restore-choose-devices

Conversation

@A-EDev

@A-EDev A-EDev commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A restore with the recovery words now asks which of the group's devices the user still has, with none ticked. It signs out the rest on the first sync, and seals nothing before that. This follows flow-sync #96, spec/protocol.md Recovery.

  • linking/Recovery.kt: the restore is split into a preview that writes nothing (roster, current key, device names through the history chain) and the commit. finishRestore reads every log without saving or writing, removes all current devices except the chosen ones, works that list out at removal time, and clears the saved choice once the roster reads back without them.
  • GroupRecord.restoreKeep: the saved choice, in Room version 10 by AutoMigration. A crash or a lost connection resumes at the removal.
  • Session.sealingKey(): the one place batches, acks, snapshots, store logins and live messages get their key. It refuses while the removal is pending. LiveChannel.send returns null during that time.
  • engine/EpochKeys.kt: the history chain walk moved out of Session, so the preview uses the same code.
  • UI: a new step in LinkedSetup with one checkbox per device, a new error string and a nightly note.
  • Test vectors refreshed from flow-sync a7a1f56.

A read of a store now has a total limit, as on Desktop: store/ReadBudget.kt for pages and log objects, and the roster's own limit for roster pages. Past it, an ordinary sync goes on with what it read. A restored device doesn't remove: it stays pending, says the restore isn't finished, and the next sync goes on from what it holds, as flow-sync #96 asks in Recovery step 6. Android reads into its local store first, so the place is the saved server cursor and the held objects, and the cuts come out the same as one uninterrupted read. A log that stops at an object the store lacks, or at a bad copy, ends there and the removal goes ahead.

If the removal has to replace the recovery key, because a device being removed set it, the new words go to LinkedSync.replacedWords and the key is marked unsaved before the roster is read again, so a failed read doesn't lose them. They stay in memory only: after the process ends, the saved flag still shows the warning, and Replace recovery words makes new ones.

Related issue

None. Follows flow-sync #96, which should land first.

Change type

  • Bug fix
  • Feature
  • Refactor or maintenance
  • Build, packaging, or CI
  • Documentation

Validation

All Gradle commands ran with --offline -Dorg.gradle.jvmargs="-Xmx4096m -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8 -Djava.net.preferIPv6Addresses=true".

  • ./gradlew :app:assembleGithubDebug: not run locally, left to CI
  • ./gradlew :app:testGithubDebugUnitTest: on 802336f, 5,424 tests, 0 failed, 46 skipped. An earlier run of the same code had one failure, LanSyncTest.a_listener_takes_its_last_port_only_while_it_is_free, in code this PR doesn't touch, while other builds ran on this machine; it passed on the rerun. Not rerun in full on the tip, left to CI.
  • :app:testGithubDebugUnitTest --tests 'io.github.aedev.flow.sync2.*' --tests 'io.github.aedev.flow.sync.*' --tests 'io.github.aedev.flow.ui.screens.sync.*' on the tip: 514 tests, 0 failed.
  • ./gradlew ktlintCheck: passed
  • I ran any additional flavor-specific build or test tasks affected by this change.
  • I manually tested the affected behavior on an Android device or emulator.

New tests: RestoreChoiceTest (25) and two in LinkedSetupTest, one in ServerStoreTest. Each of these changes made at least one test fail when tried: the sealing check off, the live check off, the removal dropped, the cut taken from saved positions, the choice ignored, the removal attempted offline, the sync not finishing the restore, the choice not saved, the saved choice cleared at the wrong time, the new words held only after the re-read, the key marked unsaved only after the re-read or never, and the sync dropping the words. So did these, on the read limit: the removal not held by a read that stopped on it, every sync failing on such a read, a missing object holding the removal too, no limit on pages, on file logs or on roster pages, a stopped read losing its place, a stop not reported on pages or on files, and cuts from saved positions.

Test device and Android version: none. Nothing ran on a device or an emulator, so the new screen has only been seen by Compose tests.

Risk and compatibility

Room LinkedDatabase 9 to 10, one nullable column. Other devices read these entries as an ordinary restore, recovery change and removal, so they need no update. On WebDAV a removal asks for a new login, as any removal does.

A store that keeps a read at its limit keeps a restore pending, sealing nothing, until that stops; each sync still ends. On this PR alone, an object over the size limit fails the whole sync, so nothing is removed. #1830 makes such an object stop only its sender's log, and from then on a restore cuts there like a gap and goes ahead. The two tests in RestoreChoiceTest that assert today's behaviour change with whichever of the two lands second.

  • The change does not introduce secrets, private data, or unexpected telemetry.
  • New user-facing text uses Android string resources.
  • Dependency and lockfile changes are intentional and limited to this PR.
  • Room schema changes include the required version bump and migration, or this PR does not change the Room schema.
  • Breaking changes and upgrade steps are clearly documented.

Copilot AI balanced review requested due to automatic review settings November 7, 2026 14:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions github-actions Bot added documentation Improvements or additions to documentation ui Compose UI, theming, or Material 3 surfaces labels Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation ui Compose UI, theming, or Material 3 surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants