feat(Where): add scheduled encrypted backups - #306
Open
kyleve wants to merge 13 commits into
Open
Conversation
Add a focused data-oriented keychain boundary with atomic create-only insertion for stable synchronized secrets, then keep Ledger's string API through a thin adapter.
Add AES-GCM backup envelopes, synchronized recovery keys, iCloud/local storage with coordinated retention, scheduling seams, preferences, cancellation-aware export serialization, and focused core coverage.
Register background processing after App Intents dependencies, gate launch on protected data, wire due checks through the session, and declare the iCloud Documents, backup type, and background-task capabilities.
Explain and configure automatic backups on the Data page, reveal and securely copy the recovery key, list reachable encrypted files and partial iCloud failures, and accept authenticated .wherebackup restores during onboarding.
…tion Keep immutable synchronized recovery keys and resolve restore keys by envelope identifier. Authenticate archives before retention; coordinate individual file accesses and separate durable writes from cleanup. Own cancellable execution and drain scheduling before retiring a scope. Reject stale success metadata after reset.
Use first-unlock file protection instead of the ordinary screen-lock flag. Bound background launch waits, preserve recording on expiration, and cancel automatic scheduling on recording changes and session retirement. Track the signed-device validation still required before release.
Invalidate pending reveals and reject late backup success after reset. Explain the synchronized key collection and copying a key from each recording device. Review Data-page references and seed revealed-key fixtures at both measurement and capture readiness.
Add bounded PlusCal models for lifecycle ownership, recovery-key publication, and authenticated retention. Check six candidate configurations and thirteen negative or reachability controls. This is verification groundwork: candidate models describe the accompanying Swift fixes. The review retention control reproduces deletion with a damaged keeper; the candidate requires coordinated revalidation of all keepers.
Put protected-data preparation at the start of the shared launch plan so RootView promotion cannot bypass it. Construct the sidecar without file I/O and prepare it only after the cancellation-aware first-unlock barrier opens. Preserve first-unlock availability across ordinary relocks. Add regression tests and architecture rules for sidecar ownership and preparation.
Reproduce the model-found stale-keeper race in Swift tests. Hold read intents for every retained archive alongside the deletion intent, then recheck candidate and keeper digests before removing anything. Preflight iCloud eviction before content coordination, preserve partial listings, and give catalog reads their own cancellable I/O context outside the storage actor.
Give execution owners explicit cancellation authority. A disappearing Data page drains the shared result and records committed success without cancelling an export started by another trigger. Cancel view-owned catalog requests and observations, reject stale completions, and keep recovery-key hiding in the lifecycle wrapper. Snapshot the shared display child so rehosting does not mutate the revealed-key fixture. Verified 2,036 unit tests, 17 architecture rules, and two fresh comparisons of the three affected snapshot suites without changing references.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Why
Residency history is important and difficult to reconstruct. Automatic encrypted copies provide recovery when app data is damaged or lost.
Design decisions
Review focus
BackupSettingsSectionowns lifecycle actions; its shared display child supplies deterministic snapshots without disabling production hiding behavior.Formal verification scope
The three backup specifications include Swift mappings, explicit bounds, safety properties, liveness assumptions, and negative controls. All 19 configurations produced their expected verdicts: six passing state-space checks and thirteen counterexample or reachability controls.
The models cover first-unlock admission, cancellation ownership, drain-before-retirement, generation-checked success, independent key creation and synchronization, and authenticated retention. They do not prove the Swift implementation or iOS services. In particular, coordinated retention assumes a consistent local filesystem view; it is not a distributed transaction across delayed iCloud replicas. Cryptography, permanent device loss, and live-service behavior require separate validation.
Compatibility
Manual
.zipexports and the inner backup schema are unchanged. Automatic.wherebackupfiles wrap that ZIP in a versioned authenticated container.The previous synchronized recovery-key account is preserved and imported into the collection without replacement. Erasing Where data does not erase recovery keys.
Testing
./ide --no-open,./sync-agents,./swiftformat --lint,./xcstrings --lint, and./attribution --checkpassed. The changed.bumperSwift files also passed explicit formatting lint../test --all --no-generatepassed 2,036 tests against the final code. Coverage includes first-unlock waiters, deferred sidecar loading, cancellation ownership, disappeared views, blocked catalog reads, partial iCloud listings, and changed or missing retention keepers../tla-check AutomaticBackupLifecycle,./tla-check RecoveryKeyPublication, and./tla-check AutomaticBackupRetentionpassed all 19 expected verdicts. The largest retention case explored 2,661,340 distinct states; the three-device key case explored 863,441../test --snapshots --no-generate --skip-architecture --review --only WhereUISnapshotTests/BackupSettingsSectionSnapshotTests --only WhereUISnapshotTests/DataSettingsViewSnapshotTests --only WhereUISnapshotTests/RootViewSnapshotTestspassed all three suites and 24 captures. A fresh--no-buildcomparison passed again. No reference images changed in this follow-up. All 20 backup/Data captures matched byte for byte; each run reported one tolerated, one-channel, one-pixel RootView difference.mise exec -- tuist test Ledger-macOS-Tests --no-selective-testing -- -destination 'platform=macOS'passed during the earlier feature validation. It was not repeated for this follow-up because Ledger and KeychainKit did not change. Unaffected image-snapshot suites were not repeated.Release follow-up
Signed-device validation remains required for reboot/first-unlock behavior, later locked recording, background expiration, independent offline key creation and synchronization, cross-device restore, and iCloud Drive download/fallback. Use disposable test data. These hardware and live-service checks cannot be proved by simulator tests or bounded models; they are tracked in Where/TODOs.md.