Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,46 @@ jobs:
if-no-files-found: error
retention-days: 7

ios-app:
# The pairing-only iPhone app (#289). release-gate.py does not require
# this job, but a PR that changes the app needs it green.
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Report toolchain and simulators
run: |
xcodebuild -version
xcrun simctl list runtimes
- name: Restore SwiftPM dependency cache
id: swiftpm-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/Library/Caches/org.swift.swiftpm
key: swiftpm-${{ github.job }}-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Package.resolved') }}
- name: Resolve package dependencies
run: swift package resolve
- name: Save SwiftPM dependency cache
if: steps.swiftpm-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/Library/Caches/org.swift.swiftpm
key: ${{ steps.swiftpm-cache.outputs.cache-primary-key }}
- name: Install XcodeGen
run: brew list xcodegen || brew install xcodegen
- name: Build the app for the iOS Simulator
run: bash scripts/build-ios.sh build
- name: Run AskKeyiOSKitTests on a simulator and render the screens
run: bash scripts/build-ios.sh test
- name: Upload the rendered screens
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: askkey-ios-screens
path: .build/ios/screens/
if-no-files-found: error
retention-days: 14

release-sdk-window-check:
# The preview image is informational; the two macos-latest jobs remain required.
runs-on: xcode-27
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ reviews/

# Website build tooling
node_modules/

# Generated iPhone project (scripts/build-ios.sh); build output goes to .build/ios
iOS/*.xcodeproj/
17 changes: 17 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,23 @@ Before building, [cloudkit-signing.py](../scripts/cloudkit-signing.py) copies th

When the App carries `Contents/embedded.provisionprofile`, `package-release.sh` checks the staged copy before notarizing, also with `--no-notarize`: `ASKKEY_APPLE_TEAM_ID` is then required, the profile must still pass the checks above for the App's leaf certificate and be the one the signature sealed, the Info.plist must declare the container, and the signed entitlements must be exactly the set above. The DMG keeps the profile.

## iPhone app

The pairing-only iPhone app (#289, ADR 0013 → iPhone app) lives in `iOS/`. [build-ios.sh](../scripts/build-ios.sh) generates `iOS/AskKeyiOS.xcodeproj` from `iOS/project.yml` with XcodeGen (the project is never committed), checks that both String Catalogs have English and Simplified Chinese with matching placeholders and that every key the kit uses exists, and keeps its build output in `.build/ios`. It needs XcodeGen, an iOS Simulator runtime and at least 80 GiB free:

```bash
bash scripts/build-ios.sh build
bash scripts/build-ios.sh test
```

| Mode | Coverage and limits |
| --- | --- |
| `build` | Builds the app target `AskKeyiOS` for the iOS Simulator, unsigned. The build has no CloudKit container in its Info.plist, so it never creates one. |
| `test` | Runs `AskKeyiOSKitTests` on the newest available iPhone simulator, without an app host, and writes the rendered screens to `.build/ios/screens` (each screen state in English and Simplified Chinese). The tests drive the real `DeviceMessenger` with software keys, an in-memory keychain and an in-memory account; the Mac side is a second messenger. They do not run the Secure Enclave, Face ID, the keychain, CloudKit or push. |
| `export` | The maintainer's Ad Hoc build for #290: `ASKKEY_APPLE_TEAM_ID=<team> bash scripts/build-ios.sh export` archives with automatic signing for that team (`-allowProvisioningUpdates`), puts `CloudKitContainerIdentifier` in the Info.plist, exports with the Production CloudKit environment, and reads back the signed entitlements (application identifier, team, `aps-environment = production`, the container and CloudKit service, the keychain access group) and the Info.plist. Agents do not run it. |

The CI job `ios-app` runs `build` and `test` on `macos-latest` and uploads the screens as the `askkey-ios-screens` artifact; `release-gate.py` does not require it, but a PR that changes the app needs it green and the maintainer reviews the English and Chinese screens before merge. Do not launch the app in a simulator on the maintainer's Mac. Signing, the Secure Enclave, Face ID, the keychain access group, CloudKit and push are verified on a real iPhone in #290.

## Pre-release checks on the maintainer's Mac

CI cannot check focus, Touch ID or real clients. Before notarizing, review the release diff for these behaviors, then install the signed `Release` build at `/Applications/Ask Key.app` and walk through this list once with the maintainer. The planner triggers agent requests itself (for example `askkey run --wait-for-approval` with a harmless command that prints no values, or a Codex session), one request at a time with explicit instructions; delay requests that coincide with screen lock so the maintainer never waits at the lock screen. The maintainer only touches the sensor, clicks decisions and reports what they see. Notarize the same build afterwards; its signature hash must match the one tested. Device checks remain `NOT_VERIFIED` until performed; passing synthetic tests or CI is not device verification.
Expand Down
20 changes: 20 additions & 0 deletions iOS/AskKeyiOS/AskKeyiOS.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.sudohg.askkey</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.sudohg.askkey.ios.shared</string>
</array>
</dict>
</plist>
14 changes: 14 additions & 0 deletions iOS/AskKeyiOS/AskKeyiOSApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import AskKeyiOSKit
import SwiftUI

/// Only starts the app; every behavior lives in `AskKeyiOSKit`.
@main
struct AskKeyiOSApp: App {
@UIApplicationDelegateAdaptor(AskKeyAppDelegate.self) private var delegate

var body: some Scene {
WindowGroup {
AskKeyRootView(model: delegate.model)
}
}
}
49 changes: 49 additions & 0 deletions iOS/AskKeyiOS/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Ask Key</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Ask Key</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CloudKitContainerIdentifier</key>
<string>$(CLOUDKIT_CONTAINER_IDENTIFIER)</string>
<key>KeychainAccessGroup</key>
<string>$(AppIdentifierPrefix)com.sudohg.askkey.ios.shared</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Ask Key uses the camera only to scan the pairing QR code that Ask Key shows on your own Mac.</string>
<key>NSFaceIDUsageDescription</key>
<string>Ask Key uses Face ID to confirm that you approve pairing this iPhone with a Mac.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
</dict>
</plist>
22 changes: 22 additions & 0 deletions iOS/AskKeyiOS/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"sourceLanguage": "en",
"strings": {
"push.message.body": {
"comment": "Body of the alert CloudKit shows for a message in this iPhone's inbox. Neutral: Ask Key never asks the person to act on a notification's text.",
"extractionState": "manual",
"localizations": {
"en": { "stringUnit": { "state": "translated", "value": "A paired device sent a message." } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "一台已配对的设备发来一条消息。" } }
}
},
"push.message.title": {
"comment": "Title of the alert CloudKit shows for a message in this iPhone's inbox.",
"extractionState": "manual",
"localizations": {
"en": { "stringUnit": { "state": "translated", "value": "Ask Key" } },
"zh-Hans": { "stringUnit": { "state": "translated", "value": "请旨" } }
}
}
},
"version": "1.0"
}
4 changes: 4 additions & 0 deletions iOS/AskKeyiOS/Resources/zh-Hans.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"CFBundleDisplayName" = "请旨";
"CFBundleName" = "请旨";
"NSCameraUsageDescription" = "请旨只用相机扫描你自己的 Mac 上请旨显示的配对二维码。";
"NSFaceIDUsageDescription" = "请旨用面容 ID 确认你同意这台 iPhone 与 Mac 配对。";
Loading
Loading