Repository navigation
Add the pairing-only iPhone app - #295
Conversation
The app pairs an iPhone with a Mac as ADR 0013 specifies: the welcome screen checks the Secure Enclave, Face ID or Touch ID and the iCloud account, explains the wireless-data prompt, asks for notifications, then creates the three keys with the iPhone's protection and the inbox. The scanner reads QR codes only and says to scan only the code in Ask Key on the person's own Mac. The pairing screen shows the code only once the messenger reports it, after a verified challenge and a saved reveal, says that the code appears only there, and confirms with the approval key. Paired Macs can be removed; delivery checks are answered when the app opens or a push arrives, and each one is recorded without content. AskKeyiOSKit holds the logic and views; the app target only starts it. The tests run without an app host, with fakes for keys, keychain and transport and a second messenger as the Mac, and render every screen state in English and Simplified Chinese. scripts/build-ios.sh generates the project with XcodeGen, builds and tests for the simulator, and exports the maintainer's Ad Hoc build; CI runs it in the ios-app job.
- A keychain read that fails at launch shows the welcome screen's keychain message with Try Again instead of offering the reset. - Pushes that arrive before the identity is restored (opening a notification cold-launches the app) are kept and become hints once it is, so the delivery check is recorded as found through the push. - A pairing request that finishes after Cancel is cancelled only when no newer pairing replaced it. - When the account check cannot reach iCloud, for example before the wireless-data prompt is answered, the welcome and list texts give the connection and wireless-data guidance. - build-ios.sh picks a simulator whose runtime the selected Xcode supports.
- A pairing request that finishes after Cancel is also cancelled when a newer pairing sheet is open but has not sent its own request yet. - Test that a build without CloudKitContainerIdentifier never creates a transport.
|
Cross review summary (Opus 5.5 and GPT-6.1-Sol, xhigh, read-only, two rounds; in round 2 each read the other's round 1 report and the executor's response). No P0 or P1 in either round. Round 2: Opus found nothing left to fix; GPT kept one P2 on a round 1 fix (a late cancel skipped in the reverse order), now fixed. Focus as asked for Checked and found correct by both reviewers
Fixed
Not fixed, and why
For #290 (real iPhone, CloudKit Production)
Tests after the fixes: |
"Pairing code" means only the six digits the iPhone shows. The scanner's instruction and its invalid, expired, foreign and key-conflict messages now speak of the QR code in English and Chinese, and the Chinese for "Not reached yet" reads as the Mac not having reached this iPhone yet. A test keeps the scanner texts from calling the QR code a pairing code.
Closes #289
Summary
iOS/, generated with XcodeGen:AskKeyiOSKit(framework) holds the logic and views, the app targetAskKeyiOS(com.sudohg.askkey.ios, iOS 17, iPhone only) only starts it, andAskKeyiOSKitTestsruns without an app host. The kit linksAskKeyDevicesfrom the root package; nothing inSources/changed.biometryCurrentSet) and an empty list in the data protection keychain (this device only, after first unlock, shared access group) and creates the inbox and subscriptions.123 456only when the messenger reportsshowingCode(verified challenge matching the QR code and a saved reveal), says in both states that the code appears only there and is never sent in a notification or a message, offers Confirm (approval key, so every confirmation asks for Face ID or Touch ID with no passcode button) and Cancel (a withdrawal before a challenge), and shows success only after the Mac's verified confirmation. After 60 seconds without a challenge it says pairing failed, to cancel on the Mac and not to enter any code there.scripts/build-ios.sh(build,test,export), the CI jobios-app, an iPhone section indocs/testing.md, and CJK allowlist entries for the two catalogs and thezh-HansInfoPlist.strings.Diff stat
Head
5618999(commitsccc5824app,cce62b0round 1 review fixes,c85c764round 2 review fixes,5618999planner's wording fixes: the scanner says QR code, not pairing code, and the Chinese for "Not reached yet").git diff --stat origin/main: 38 files changed, about 5,600 insertions. New:iOS/(project.yml; app target 5 files; kit 15 Swift files and its String Catalog with 92 strings; tests 10 files),scripts/build-ios.sh. Changed:.github/workflows/ci.yml(+42, new job only),docs/testing.md(+19),scripts/check_hygiene.py(+3),.gitignore(+3). Most of the line count is the two.xcstringsfiles.Tests
bash scripts/build-ios.sh testat5618999: AskKeyiOSKitTests 58 passed, 0 failed, 0 skipped, 28 screens rendered, both locally (Xcode 27.0, iPhone 17e simulator, iOS 27.0, unsigned, no app host) and in CIios-app(Xcode 26.6).mainhas no iPhone tests. The later commits added 5 tests to the 53 listed below: a keychain read failure at launch, a push before launch finishes, a push while running, a build withoutCloudKitContainerIdentifiercreating no transport, and the scanner texts never calling the QR code a pairing code.swift build(local):Build complete!. No SwiftPM source or manifest changed. CIbuild-and-testat5618999:swift test1853 tests, 3 skipped, 0 failures; Automation tests 288 ran, OK.Checks
bash scripts/build-ios.sh build: simulator build, unsigned, succeeds (no warnings with-quiet).df -h /System/Volumes/Datashowed 184 GiB free before building.python3 scripts/check_hygiene.py:Hygiene rules passed: size=0, test-support=0, debug=0, local-path=0, non-ascii-name=0, multica=0, cjk=0.python3 scripts/check_module_deps.py:Module dependency check passed.build-ios.shcatalog check passes: every entry of both catalogs has English and Simplified Chinese with matching placeholders, every key the kit uses exists, and the app catalog holdspush.message.titleandpush.message.body.5618999, run 42775267174:build-and-test,basic-ui-flows,ios-appandrelease-sdk-window-checkall success.askkey-ios-screens. The maintainer reviews the English and Chinese screens before merge.bash scripts/build-ios.sh export(needs the maintainer's team and devices; Verify device pairing on real devices in CloudKit Production #290), the app in a simulator (not launched on the maintainer's Mac), desktop E2E (CI). Signing, the Secure Enclave, Face ID, the keychain access group, CloudKit and push are verified on a real iPhone in Verify device pairing on real devices in CloudKit Production #290.Deviations and questions
The planner accepted deviations 1 and 2 below (the three Scope deviations the cross review listed) on 2026-11-10; no change needed. Reasons: the account check itself needs the CloudKit container, so keys are created in memory first and nothing is stored until it passes; a stored identity exists only after a passed welcome, and the container is created only in a build whose Info.plist names it, which only the signed Ad Hoc export does (CI and tests never reach it: the simulator build has an empty
CloudKitContainerIdentifierand no entitlements, the tests use a fake factory, andCloudKitGateTestschecks that a bundle without the key creates no transport); the push alert keys must be in the app's main bundle, hence a second catalog and a third allowlist entry.AlertMessagesubscription'spush.message.titleandpush.message.bodyin the app's main bundle, so the app target has its own small catalog with those two keys; every UI string is in the kit's catalog, which the hostless tests and renders read. The CJK allowlist therefore has three entries (two catalogs and the strings file) instead of two.CloudKitDeviceTransportneeds the device ID, andCloudKitContainerServiceis internal, so the welcome flow creates the three Secure Enclave keys in memory after the Secure Enclave, Face ID / Touch ID and CloudKit-configuration checks pass, then creates the transport and checks the account. Nothing is stored until the account check passes, and a failed check discards the in-memory keys. At a later launch with a stored identity (which only a passed welcome creates) the transport is created at launch, in a configured build only. A build "carries the iCloud entitlement" when its Info.plist has a non-emptyCloudKitContainerIdentifier, which onlybuild-ios.sh exportsets, together with the signed entitlements.com.sudohg.askkey.ios.devices, and the access group reaches the code through the Info.plist keyKeychainAccessGroup($(AppIdentifierPrefix)com.sudohg.askkey.ios.shared), which the export reads back against the signed entitlement.exportuses automatic signing with-allowProvisioningUpdatesfor the team inASKKEY_APPLE_TEAM_IDand therelease-testingmethod (Ad Hoc's current name), withiCloudContainerEnvironment = Production; it does not take a downloaded profile. If the maintainer prefers manual profiles, that is a small change.com.sudohg.askkey.ios, categoryDevices.remote-notificationbackground mode: in stage 2 checks are answered when the app opens or a push arrives while it runs.Sources/AskKeyDevices/: no changes. The module maps a network failure of the account check (CKError 3 before the wireless-data prompt is answered) totemporarilyUnavailable, so the welcome's "iCloud unavailable" text also gives the connection and wireless-data guidance instead of the module being changed.Review
Two rounds of cross review (Opus 5.5 and GPT-6.1-Sol, xhigh, read-only; round 2 swapped reports): no P0 or P1; every must-fix item fixed in
cce62b0andc85c764. Summary: #295 (comment)