From f120cb4d8db56a7756caf174181ce73d25b22070 Mon Sep 17 00:00:00 2001 From: Chris Izatt Date: Thu, 18 Jun 2026 19:19:38 +0100 Subject: [PATCH 1/3] docs(ios): release-readiness truth pass + add iOS CI lane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the multi-provider release-readiness review. No code/feature changes — just making the tracked docs match the shipped code, and adding a CI gate. Doc truth pass (these are PUBLIC, tracked files): - AppStorePrivacyNotes.md + ios README: APNs is now honest — Release/TestFlight requests notification permission AFTER pairing; delivery requires the user's Mac to have APNs .p8 credentials (TASKWRAITH_APNS_KEY_PATH/KEY_ID/TEAM_ID/ BUNDLE_ID), else NoopApnsPusher drops (no push); the relay does NOT send push. Dropped the aspirational "developer-controlled push relay owns the key" and the dead TASKWRAITH_ENABLE_APNS_REGISTRATION guard. - ios README: removed "thin UI shell"; removed the stale "Not yet wired — incremental run-event streaming" gap (it shipped); corrected the ATS note (only NSAllowsLocalNetworking, no global ArbitraryLoads); archive step now says trust the EXPORTED IPA entitlements (archive-stage dev is expected); crypto review marked completed (2026-06) with the residual MED disclosed. - root README: iOS status → "in TestFlight beta, Mac companion (not standalone)"; push is opt-in + needs Mac APNs creds. iOS CI lane (.github/workflows/ci.yml): new `ios` job (macos-15) runs `swift test --package-path ios/TaskWraithKit` + an unsigned `xcodebuild` of the app target — catches iOS regressions the Electron matrix can't, before any TestFlight upload. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 23 +++++++++ README.md | 11 +++-- ios/TaskWraithApp/AppStorePrivacyNotes.md | 36 ++++++++------ ios/TaskWraithApp/README.md | 59 +++++++++++++++-------- 4 files changed, 90 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 066c4772..2fcf0153 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,29 @@ jobs: swift --version npm run test:swift:bridge + # iOS companion gate — the Electron matrix can't see iOS regressions, and the + # phone is the monetized launch surface. Run the TaskWraithKit Swift tests + # (e2ee interop vectors, session round-trip, composer-shell resolver, guest + # card lifecycle, streaming, …) plus an UNSIGNED simulator build of the app + # target, so a broken iOS build/test fails CI before any TestFlight upload. + ios: + name: iOS (Kit tests + app build) + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Swift package tests (TaskWraithKit) + run: swift test --package-path ios/TaskWraithKit + - name: Generate Xcode project (XcodeGen) + run: | + brew install xcodegen + cd ios/TaskWraithApp && xcodegen generate + - name: Build app target (iOS Simulator, unsigned) + run: | + xcodebuild -project ios/TaskWraithApp/TaskWraith.xcodeproj \ + -scheme TaskWraith \ + -destination 'generic/platform=iOS Simulator' \ + build CODE_SIGNING_ALLOWED=NO + notarized-macos-release: name: Notarized macOS Release runs-on: macos-15 diff --git a/README.md b/README.md index a8c55e88..45bf6184 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,13 @@ agents against developer workspaces. It provides a macOS-focused Electron UI for provider CLIs and SDK-backed workflows while keeping execution, history, and workspace state on the user's machine. -> **iOS companion status:** TaskWraith for iPhone/iPad is pending -> TestFlight/App Store review. Until then, the iOS companion is provisionally -> available from this repository for testers who can sign and provision the app -> with their own Apple Developer team. +> **iOS companion status:** TaskWraith for iPhone/iPad is in **TestFlight beta**. +> It is a **Mac companion** — it pairs with TaskWraith on macOS over an +> end-to-end-encrypted connection to monitor runs, approve actions, and reply +> from the phone; it is not a standalone AI app. Testers can also build it from +> this repository with their own Apple Developer team. Push notifications are +> opt-in after pairing and require APNs credentials on the Mac (see +> `ios/TaskWraithApp/README.md`). diff --git a/ios/TaskWraithApp/AppStorePrivacyNotes.md b/ios/TaskWraithApp/AppStorePrivacyNotes.md index 349a8837..e35e46fb 100644 --- a/ios/TaskWraithApp/AppStorePrivacyNotes.md +++ b/ios/TaskWraithApp/AppStorePrivacyNotes.md @@ -18,20 +18,28 @@ Mac/provider runtime. ## Remote notifications -Release/TestFlight builds do not request APNs registration by default. The -current desktop APNs sender is a local-admin/development feature that requires -an Apple APNs auth key for the companion bundle; that is not a reviewable -consumer App Store design. Keep release registration disabled until one of -these is true: - -- A developer-controlled push relay owns the APNs key and sends metadata-only - wake pushes. -- Distribution is explicitly scoped to local-admin/internal builds with - documented APNs key management outside App Store review. - -Debug builds can opt into local APNs registration for development. Release -builds can only opt in by compiling with `TASKWRAITH_ENABLE_APNS_REGISTRATION` -after the release owner accepts the distribution model. +Release/TestFlight builds request notification permission **after a successful +pairing** (not at cold launch). The user can deny; the app still works without +push (open the app to reconnect / refresh). When granted, the APNs device token +travels over the paired encrypted bridge to the user's own Mac and is stored +there — there is no developer-operated push backend. + +**Push delivery depends on the user's Mac having APNs credentials.** The Mac +sends wake pushes only when an Apple APNs auth key (`.p8`) for the companion +bundle is configured via environment (`TASKWRAITH_APNS_KEY_PATH`, +`TASKWRAITH_APNS_KEY_ID`, `TASKWRAITH_APNS_TEAM_ID`, `TASKWRAITH_APNS_BUNDLE_ID`). +Without those, the Mac uses a no-op pusher: pairing and manual reconnect work, +but **no notifications are delivered**. The relay carries pairing/transport only +— it does not send push. + +APNs payloads carry routing metadata only (pair identifier, coarse reason, +thread/run identifiers, timestamps) — never prompts, commands, diffs, file +paths, filenames, workspace names, model output, or user messages. + +App Store Connect: declare push notifications; the data is the device token + +routing metadata, kept within the user's own device + Mac (no developer +backend). For a consumer launch, do **not** feature push as a hero capability +unless the Mac ships with push pre-configured — present it as optional/advanced. ## Export compliance diff --git a/ios/TaskWraithApp/README.md b/ios/TaskWraithApp/README.md index d8e4f8d6..12c0934d 100644 --- a/ios/TaskWraithApp/README.md +++ b/ios/TaskWraithApp/README.md @@ -4,8 +4,10 @@ SwiftUI app that pairs with TaskWraith on the Mac over the `taskwraith-e2ee-v1` relay transport and renders the remote task feed (approvals, questions, running agents) with action controls — all end-to-end encrypted. -This is the thin UI shell. The substance lives in the `TaskWraithKit` Swift -package next door: +The app target is a thin wrapper; the substance lives in the `TaskWraithKit` +Swift package next door (the companion itself is feature-rich — pairing, +approvals/questions, global + side chats, ensemble roster, diff/file views, +token streaming, composer shells, APNs actions): - **`TaskWraithKit`** — the CryptoKit port of `src/shared/e2ee` + the `RelayTransportClient` and Codable domain models. Validated byte-for-byte @@ -59,9 +61,10 @@ cleartext `ws://` to a LAN/Tailscale relay. Checklist: 4. Run, tap **Scan QR code**, point it at the ghost QR on the Mac, compare the 6-digit codes, confirm on the Mac. Done. -> ⚠️ The ATS `NSAllowsArbitraryLoads` exception is for development only — -> switch the relay to `wss://` (TLS) and remove the exception before any -> TestFlight build, alongside the crypto review noted below. +> ATS is already scoped to `NSAllowsLocalNetworking` only (no global +> `NSAllowsArbitraryLoads`). LAN relays use cleartext `ws://` on the local +> network; off-LAN/remote use requires a `wss://` (TLS) relay — the Tailscale +> serve path documented for desktop 1.5+. ## Pairing locally @@ -74,11 +77,6 @@ cleartext `ws://` to a LAN/Tailscale relay. Checklist: 4. In the app, paste the pairing-code JSON and tap **Pair**. Compare the 6-digit code, then tap **Pair** on the Mac. The task feed appears. -## Not yet wired (intentional scaffold gaps) - -- **Incremental run-event rendering** — the feed renders the projection snapshot; - live `bridge.runEvent` streaming into a transcript view is a follow-up. - ## TestFlight / App Store archive path The generated Xcode project has a shared `TaskWraith` scheme and a Release @@ -93,23 +91,42 @@ TASKWRAITH_APPLE_TEAM_ID=ABCDE12345 ./scripts/archive-testflight.sh Before upload: -1. Confirm the archived entitlement prints `aps-environment = production`. +1. Confirm the **exported IPA** entitlements show `aps-environment = production` + and `get-task-allow = false` (the script prints these). The archive-stage + entitlements may read `development` / `get-task-allow = true` under automatic + signing — that is expected; the export is what ships. 2. Complete the App Store Connect export-compliance questionnaire. This project sets `ITSAppUsesNonExemptEncryption=false`: the app's CryptoKit E2EE uses standard algorithms that qualify for the export-compliance exemption. 3. Read `AppStorePrivacyNotes.md` and make the App Store privacy answers match the selected distribution model. -Remote notifications are intentionally disabled by default in Release builds. -The current Mac-side APNs sender is a local-admin/development feature that -requires an APNs auth key for the companion bundle; that is not suitable for a -consumer TestFlight/App Store review flow. Re-enable Release APNs registration -only after a developer-controlled push service exists, or for an explicitly -internal/local-admin distribution by compiling with -`TASKWRAITH_ENABLE_APNS_REGISTRATION`. +## Push notifications (post-pairing opt-in; delivery needs Mac credentials) + +Release/TestFlight builds request notification permission **after a successful +pairing**, and register the APNs token to the user's paired Mac. The app works +fine if the user denies (open to reconnect/refresh). + +Delivery requires the **Mac** to have APNs credentials configured — set these in +the Mac's environment before `npm run dev` / the packaged app launch: + +```sh +TASKWRAITH_APNS_KEY_PATH=~/.appstoreconnect/private_keys/AuthKey_XXXXXXXXXX.p8 +TASKWRAITH_APNS_KEY_ID=XXXXXXXXXX # 10-char Key ID +TASKWRAITH_APNS_TEAM_ID=8CZML8FK2D # 10-char Team ID +TASKWRAITH_APNS_BUNDLE_ID=com.taskwraith.companion +``` + +Without these the Mac uses a no-op pusher (pairing + manual reconnect still +work; no pushes delivered). The relay does not send push. For a consumer App +Store listing, don't market push as a hero feature unless the Mac ships with +push pre-configured — see `AppStorePrivacyNotes.md`. ## Security -The E2EE core is security-sensitive. Recommend an independent crypto review of -`TaskWraithKit` (and the shared `src/shared/e2ee`) before a public App Store -submission. +The E2EE core is security-sensitive. An independent crypto review of +`TaskWraithKit` (and the shared `src/shared/e2ee`) was completed 2026-06; +CRITICAL/HIGH findings were fixed and the results are tracked in +`docs/security/e2ee-review-findings.md` (one residual MED — silent identity +regeneration if the Keychain/`safeStorage` is unavailable — is documented there +and accepted for the companion-beta scope). From 6161ac49c4366d3ed51998851531b2826b1df5aa Mon Sep 17 00:00:00 2001 From: Chris Izatt Date: Thu, 18 Jun 2026 19:30:00 +0100 Subject: [PATCH 2/3] ci(ios): run the iOS lane on macos-26 (iOS 26 SDK for glassEffect) The first run of the new iOS job failed: TaskWraithUI uses the iOS-26 Liquid Glass API `.glassEffect(.regular)` (TWSharedViews.swift), which the macos-15 runner's Xcode can't compile ("no member 'glassEffect'"). Point the lane at the macos-26 runner (matches the local dev toolchain) + print the toolchain. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fcf0153..2de26403 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,9 +57,14 @@ jobs: # target, so a broken iOS build/test fails CI before any TestFlight upload. ios: name: iOS (Kit tests + app build) - runs-on: macos-15 + # macOS 26 runner: TaskWraithUI uses the iOS-26 "Liquid Glass" API + # (`.glassEffect(.regular)`), so the toolchain needs the iOS 26 SDK — + # older runner Xcodes fail with "no member 'glassEffect'". + runs-on: macos-26 steps: - uses: actions/checkout@v4 + - name: Toolchain versions + run: xcodebuild -version && swift --version - name: Swift package tests (TaskWraithKit) run: swift test --package-path ios/TaskWraithKit - name: Generate Xcode project (XcodeGen) From c7ba49cc87d10a9faff6bb8ecf3c2f7a603c116c Mon Sep 17 00:00:00 2001 From: Chris Izatt Date: Thu, 18 Jun 2026 19:43:30 +0100 Subject: [PATCH 3/3] =?UTF-8?q?ci:=20drop=20the=20iOS=20lane=20from=20this?= =?UTF-8?q?=20PR=20(toolchain=20drift=20=E2=80=94=20handle=20separately)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new iOS lane surfaced a real but separate issue: the GitHub macos-26 runner ships Xcode 26.5 / Swift 6.3.2, whose region-based isolation rejects `sending 'params'` in RemoteSessionModel.swift (1613, 2620) as an ERROR — while local dev is Xcode 26.0 / Swift 6.2.4, which does not. So the Kit compiles + ships locally but not on the newest CI toolchain. Keep this PR to the doc truth pass (already green) and decide the iOS-lane toolchain approach separately (pin CI Xcode to 26.0 to match local, or make the code Swift-6.3-clean). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2de26403..066c4772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,34 +50,6 @@ jobs: swift --version npm run test:swift:bridge - # iOS companion gate — the Electron matrix can't see iOS regressions, and the - # phone is the monetized launch surface. Run the TaskWraithKit Swift tests - # (e2ee interop vectors, session round-trip, composer-shell resolver, guest - # card lifecycle, streaming, …) plus an UNSIGNED simulator build of the app - # target, so a broken iOS build/test fails CI before any TestFlight upload. - ios: - name: iOS (Kit tests + app build) - # macOS 26 runner: TaskWraithUI uses the iOS-26 "Liquid Glass" API - # (`.glassEffect(.regular)`), so the toolchain needs the iOS 26 SDK — - # older runner Xcodes fail with "no member 'glassEffect'". - runs-on: macos-26 - steps: - - uses: actions/checkout@v4 - - name: Toolchain versions - run: xcodebuild -version && swift --version - - name: Swift package tests (TaskWraithKit) - run: swift test --package-path ios/TaskWraithKit - - name: Generate Xcode project (XcodeGen) - run: | - brew install xcodegen - cd ios/TaskWraithApp && xcodegen generate - - name: Build app target (iOS Simulator, unsigned) - run: | - xcodebuild -project ios/TaskWraithApp/TaskWraith.xcodeproj \ - -scheme TaskWraith \ - -destination 'generic/platform=iOS Simulator' \ - build CODE_SIGNING_ALLOWED=NO - notarized-macos-release: name: Notarized macOS Release runs-on: macos-15