Skip to content

Bridge iOS companion boundary

Status: local 0.0.4 implementation proposal and testable skeleton.

Scope

The iOS companion is a notification client for the existing Bridge. It is not an alternative project store, an agent runtime, or a place to persist source code. The iPhone owns the notification permission and APNs device lifecycle; the server owns project authorization, encrypted token storage and delivery policy.

The first release includes:

  1. OAuth 2.1 Authorization Code with PKCE through the system browser.
  2. Keychain storage for the short-lived access token, refresh token and local device id.
  3. Explicit APNs environment (production in Release, development in Debug).
  4. Registration, preferences, and revocation against the Bridge API.
  5. Safe UI states that never render access tokens, refresh tokens, APNs device tokens, or authorization headers.

Apple Watch is intentionally covered by iPhone notification mirroring. A watchOS target is not part of this boundary.

OAuth and binding

The app uses a registered public client id and the redirect URI eu.heyaira.bridge://oauth/callback. Each authorization request includes a fresh PKCE verifier, S256 challenge, state, and the HeyAira MCP resource. The callback state is checked before code exchange. The app has no client secret.

The Bridge API receives only the OAuth access token over TLS. The server validates the token hash, resource, expiry, revocation state and bound instance before resolving the project actor. This prevents a mobile client from selecting a project by request body alone.

Mobile API lifecycle

OAuth/PKCE -> permission -> APNs registration
    -> POST /bridge/v1/mobile/devices
    -> PUT  /bridge/v1/mobile/devices/{id}/preferences
    -> POST /bridge/v1/mobile/devices/{id}/revoke

Notification list (main screen)
    -> GET  /bridge/v1/mobile/notifications   (filters, cursor paging)
    -> POST /bridge/v1/mobile/notifications/read

The app opens on the notification list, grouped by day. Severity, event type, project and unread-only filters are remembered on the device (UserDefaults, no secrets). Tapping a row expands it in place (one open at a time) and marks it read; pull to refresh reloads from the top; the app icon badge follows the server's unread count. Tapping a push opens the list with that item expanded. Connection, environment and preferences live on a separate Settings screen. The app has four text tabs: Inbox, Tasks, Memory and Sources. Each is a list whose rows open a separate detail screen (no inline expand). Tasks, Memory and Sources read the same Work Center routes as the Mac Bridge app (/bridge/v1/projects|tasks|memory|receipts|sources) with the app's project token, which the server treats as a single-project, read-only principal. Requested scopes are memory:read tasks:read documents:read notifications:write offline_access; documents:read covers approved Git document references. notifications:write is the only write the app asks for: registering this phone, changing its notification preferences, revoking it and marking notifications read. A sign-in from before that scope existed keeps working: a grant issued to the verified HeyAira app client (every registered callback is eu.heyaira.bridge://oauth/callback) may make these writes without the scope, so a rotated APNs token is still registered. Either way the signed-in identity must still be a contributor, admin or owner of the project; a viewer or a removed membership is refused. A sign-in from before that scope was added still shows receipts; the Sources tab asks to sign in again for Git documents. Sessions are processes on the Mac and are not shown.

The raw APNs token is held in memory only for the registration request. The server-side mobile token vault stores the encrypted token. Sign-out revokes the server record and deletes the local device id and OAuth tokens.

Verification boundary

The local contract tests are deterministic and use synthetic configuration. They verify the source-level security boundary and endpoint contract. The notification-list logic (paging, filters, accordion, mark-read, push focus) is verified without Xcode by ios/HeyAiraBridge/Tests/run-inbox-logic-check.sh, which compiles the Foundation-only sources with swiftc. The SwiftUI views need a real Xcode build. A real Xcode build, device registration, APNs delivery, TestFlight upload and App Store release require a host with Xcode, an explicitly registered OAuth client, Apple signing access and a separate deployment authorization. None of those credentials or external side effects belong in this repository or in a work receipt.

Onboarding acceptance sequence

The first device acceptance is intentionally separate from APNs delivery:

  1. Build the Debug target with full Xcode and install it on an iPhone.
  2. Complete OAuth Authorization Code + PKCE in the system browser.
  3. Verify that the callback returns through eu.heyaira.bridge://oauth/callback and that no client secret is present.
  4. Verify a restored session after relaunch; tokens must remain in Keychain.
  5. Grant notifications and register the Debug APNs token as sandbox at /bridge/v1/mobile/devices.
  6. Inspect the safe registration response in the app; it must not contain the raw device token.
  7. Only then run the APNs sandbox delivery test. Production APNs is a separate later acceptance with the Release entitlement.

On the current development host, only CommandLineTools are installed. Swift source parsing and repository contract tests can run there, but xcodebuild, code signing, app installation and real device registration remain unverified until full Xcode and an iPhone are available.