Skip to content

Keryx iOS distribution signing

Approved: 26 August 2026

This document is the P3-02 signing packet for Keryx for SafeCall on the Apple App Store. It records distribution signing identity, production APNs entitlement proof, ExportOptions, IPA verification, custody, recovery drill, and evidence handoff.

It does not claim CI signing gates (#112), store record verification (#114), App Store Connect form paste (#125), TestFlight functional soak, or that APNs device-token delivery is implemented (#97 policy only).

Purpose and non-goals

Purpose

  • Approve iOS distribution signing for com.sourcectl.keryxapp under team 8P59575P2K (verify against sourcectl Apple Developer account in #114).
  • Require App Store IPA builds to use distribution certificate + App Store provisioning profile — never ad-hoc or development export for release.
  • Document custodians, certificate/profile/APNs-key backup, rotation triggers, and recovery drill steps.
  • Provide verification commands and evidence archive requirements for #110 close.

Non-goals

  • Committing .p12, .p8, .mobileprovision, or App Store Connect API keys.
  • Changing source Runner.entitlements to production for local debug builds.
  • Replacing SafeCall Nav (mobile/) signing — out of scope unless requested.
  • Automating CI secret injection (#112).
  • TestFlight upload or App Store submission without separate release authority.

Package identity

FieldValue
Bundle IDcom.sourcectl.keryxapp
Development team8P59575P2K (verify ownership in #114)
Publishersourcectl (P0-01)
Store product nameKeryx for SafeCall
Minimum iOS17.0 (P0-04)
Android application IDcom.sourcectl.keryx (unchanged; Play/Firebase)

Distribution signing model

Apple distribution signing uses a Distribution certificate and an App Store provisioning profile tied to the App ID. The signed IPA (or archive) is authoritative for entitlements — not the source Runner.entitlements file alone.

ArtifactCustodyIn Git
Distribution certificate + private keyKeychain + NAS secrets/ (secret-store.md)Never
App Store provisioning profileKeychain (Xcode can re-download)Never
APNs auth key (.p8)NAS secrets/Never
ExportOptions.plistCommitted (team ID, method only)Yes

Source keryx/ios/Runner/Runner.entitlements may keep aps-environment = development for local debug builds. Store IPAs must prove aps-environment = production via embedded provisioning profile inspection (bun run verify:keryx-ipa).

Required App ID capabilities

Register or verify in Apple Developer → Identifiers → com.sourcectl.keryxapp:

  • Push Notifications
  • Background Modes → Remote notifications (via Xcode capability / profile)

See also Apple privacy compliance and iOS delivery architecture.

Export options (committed)

Committed file: keryx/ios/ExportOptions.plist

  • method: app-store-connect
  • teamID: 8P59575P2K
  • uploadSymbols: true
  • manageAppVersionAndBuildNumber: false (keep pubspec 1.2.0+N; Xcode otherwise can rewrite CFBundleVersion from App Store Connect on export)

No provisioning profile UUIDs or certificate names are committed. Xcode automatic signing selects the distribution profile at export time.

Local signing inputs

Release IPA builds require a valid distribution identity in the build machine keychain:

  1. Distribution certificate installed (Xcode → Settings → Accounts → Manage Certificates, or import the NAS-share .p12secret-store.md).
  2. App Store provisioning profile for com.sourcectl.keryxapp (Xcode downloads automatically when team is configured).
  3. APNs auth key (.p8) stored in NAS secrets/ for server-side push — not required for IPA build, but required for production delivery (#119). See secret-store.md.

Without distribution signing, flutter build ipa --release fails at Xcode export — intentional fail-closed behavior.

Second Mac: Xcode login, cert, and profile

Apple does not put the Distribution private key in git or in the Developer portal download. MiniVan-3 (or any recovery Mac) needs the sourcectl team in Xcode and a Distribution identity in its keychain. Prefer Path A now that OA-110-cert-backup is done (both .p12 files on the NAS share, 1 September 2026). Path B remains valid if you mint a new Distribution identity.

Do not add the personal team 79UF8SKVMS (Thomas Minitsios). That team holds the un-releasable com.sourcectl.keryx development App ID.

1. Sign Xcode into team 8P59575P2K (both paths)

On the recovery Mac:

  1. Open XcodeSettings (⌘,) → Accounts.
  2. +Apple ID. Sign in with the Apple ID that is a member of SOURCE CONTROL SINGLE MEMBER P.C.
  3. In the account list, select that Apple ID. The team row must show Team ID 8P59575P2K. If you only see a personal team, the Apple ID is not on the sourcectl Developer Program — add it in developer.apple.com → Users and Access (Account Holder), then retry.
  4. Click Manage Certificates… and note whether Apple Distribution appears and whether it is in the keychain on this Mac.

2a. Path A — restore the GLaDOS identity (NAS .p12)

On GLaDOS (once, then store the file in NAS secrets/ — never git). Full steps: secret-store.md §5. If Keychain only shows Developer ID identities, that is not Apple Distribution — recreate Distribution in Xcode Manage Certificates first.

  1. Open Keychain AccessMy Certificates.
  2. Find Apple Distribution for SOURCE CONTROL / team 8P59575P2K.
  3. Expand it so the private key is visible. Select the certificate (the key comes with it). File → Export Items….p12. Password goes in the vault item only.
  4. OA-110-cert-backup is done (both Macs’ .p12 files on the NAS share, 1 September 2026; passwords in Apple Passwords, not the repo).

On MiniVan-3:

  1. Download the GLaDOS .p12 from the NAS share onto the Mac. Double-click it (or security import path/to/cert.p12 -k ~/Library/Keychains/login.keychain-db).
  2. Enter the vault item password. Confirm Always Trust is not required; default trust is enough.
  3. Xcode → Settings → Accounts → team 8P59575P2KDownload Manual Profiles. Xcode pulls the App Store profile for com.sourcectl.keryxapp.

2b. Path B — new Distribution cert on MiniVan-3 (no .p12 yet)

Use this only if Path A is not available. It creates a second Apple Distribution certificate on the team (Apple allows more than one). GLaDOS keeps its own cert.

  1. Xcode → Settings → Accounts → 8P59575P2KManage Certificates…+Apple Distribution.
  2. Open keryx/ios/Runner.xcworkspace (the workspace, not the .xcodeproj).
  3. Select the Runner target → Signing & Capabilities. Team = 8P59575P2K, Automatically manage signing on. Bundle ID must stay com.sourcectl.keryxapp.
  4. Xcode creates or downloads the App Store provisioning profile. You do not need a physical iPhone for an App Store archive.

3. Confirm before build:keryx-ipa

bash
security find-identity -v -p codesigning

You need a line containing Apple Distribution and team 8P59575P2K. Apple Development alone is not enough for flutter build ipa --release export.

Then bun run check:keryx-ios and bun run build:keryx-ipa.

Prerequisites (operator)

Before the first archive succeeds, verify in Xcode → Settings → Accounts → team 8P59575P2K:

  1. Apple Developer Program includes iOS App Store distribution (not Mac Developer ID only).
  2. App ID com.sourcectl.keryxapp is registered under this team with Push Notifications and Background Modes → Remote notifications.
  3. Certificates: Apple Development and Apple Distribution exist for team 8P59575P2K (Manage Certificates…).
  4. App Store provisioning profile for com.sourcectl.keryxapp is present (Xcode downloads automatically with -allowProvisioningUpdates when automatic signing is enabled).

App ID registration — resolved 29 August 2026

Status: resolved. Apple will not release com.sourcectl.keryx. The registered iOS App ID is com.sourcectl.keryxapp (P0-01 identifier amendment the same date). #110 stays open until evidence is in the controlled store and the ASC app record exists (operator-actions.md OA-110-evidence, OA-110-asc). Primary and recovery IPAs are verified.

FieldValue
Team8P59575P2K — SOURCE CONTROL SINGLE MEMBER P.C.
Requested App IDcom.sourcectl.keryx (unavailable; not released)
Registered App IDcom.sourcectl.keryxapp
Support case102915938031 — Apple Developer Support
Android application IDcom.sourcectl.keryx (unchanged)

Support thread summary (case 102915938031)

DateFromSummary
2026-06-18Apple (Thai)Asked which Team ID currently holds the bundle ID
2026-06-19ThomasCannot see holding Team ID; requests release of com.sourcectl.keryx for sourcectl account
2026-06-23Apple (Audra)Misread issue as App Store app-bundle membership (multi-app SKU), not App ID registration; sent irrelevant checklist (IPA, App Review, etc.)
2026-08-26ThomasClarified: new App ID registration at Certificates, Identifiers & Profiles; reverse-DNS com.sourcectl.keryx from domain sourcectl.com + app name keryx; identifier unavailable though never registered on this account
2026-08-29Apple (Jenn)Identifier cannot be released or changed; suggested com.sourcectl.keryxapp (among others)
2026-08-29ThomasRegistered App ID com.sourcectl.keryxapp under team 8P59575P2K

Operator checklist (capabilities, ASC, IPA)

Tracked in operator-actions.md (OA-110-*). Do not close #110 until those blocking rows are done.

  1. Confirm App ID com.sourcectl.keryxapp exists on team 8P59575P2K.
  2. Enable Push Notifications (standard APNs only; not Broadcast) and, in Xcode → Runner → Signing & Capabilities, Background Modes → Remote notifications. No other App Services are approved for launch.
  3. Create Apple Distribution certificate if missing; let Xcode download the App Store profile.
  4. Create the App Store Connect iOS app record if missing: Keryx for SafeCall, bundle com.sourcectl.keryxapp, English primary, Czech Republic only. Do not paste full listing copy (#125).
  5. cd keryx && flutter analyze && flutter test
  6. bun run build:keryx-ipa && bun run verify:keryx-ipa -- --expect-team=8P59575P2K
  7. Recovery drill on second authorized Mac; archive evidence; close #110.

Custody matrix

Personal contact values live in controlled store records (#114); this packet defines roles only.

RoleResponsibility
Primary custodianMaintains distribution cert/profile in keychain; runs first signed IPA build; registers App ID capabilities
Backup custodianHolds cert/profile/APNs-key exports in NAS secrets/ (secret-store.md); performs recovery drill
Account HolderApple Developer Program legal entity; approves cert revocation and role changes
Release authorityApproves rotation/revocation and ASC upload
Audit logNAS evidence/ — IPA checksum, entitlements dump, operator, date, commit

Rotation / revocation triggers

  • Suspected certificate, profile, or APNs-key compromise.
  • Custodian offboarding without backup transfer.
  • Distribution profile expiry without renewal.
  • verify:keryx-ipa reports non-production aps-environment.
  • Apple Developer membership lapse.

After rotation: new distribution cert → regenerate App Store profile → archive new signing identity → recovery drill on backup path.

Build and verify release IPA

bash
cd keryx
flutter analyze && flutter test
flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
cd ..
bun run verify:keryx-ipa

Or from repo root (preferred — prefixes PATH so Xcode uses /usr/bin/rsync):

bash
bun run build:keryx-ipa
bun run verify:keryx-ipa

If export fails with Copy failed and Homebrew rsync is ahead of /usr/bin/rsync on PATH, Xcode's --extended-attributes option is rejected. bun run build:keryx-ipa forces the system rsync; a raw flutter build ipa does not.

Optional team gate:

bash
bun run verify:keryx-ipa -- --expect-team=8P59575P2K

Default IPA path: first *.ipa under keryx/build/ios/ipa/.

Verification checklist

Archive in controlled evidence (not git):

  • [ ] verify:keryx-ipa exit 0
  • [ ] aps-environment = production in embedded provisioning profile
  • [ ] Bundle ID com.sourcectl.keryxapp
  • [ ] MinimumOSVersion ≥ 17.0
  • [ ] Version and build number match intended release
  • [ ] Signing team 8P59575P2K; distribution identity recorded
  • [ ] IPA SHA-256 checksum recorded
  • [ ] Source commit hash and build timestamp recorded
  • [ ] Remote-notification background mode present in entitlements
  • [ ] App ID capabilities verified in Developer Portal (screenshot or ticket ref)

Recovery drill procedure

Second authorized Mac (or restored keychain from the NAS share):

  1. Install Xcode (not only Command Line Tools). Open it once, accept the license. Then: sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer and sudo xcodebuild -runFirstLaunch.
  2. Download the iOS platform. Xcode 26 does not include it until you install it: Xcode → Settings → Components → iOS, or xcodebuild -downloadPlatform iOS. Confirm with xcodebuild -showsdks (-sdk iphoneos must appear). Without this, Flutter reports No Xcode build settings have been found (MiniVan-3, 30 August 2026). The CocoaPods vs Swift Package Manager note is the same as GLaDOS; do not pod deintegrate for the recovery drill.
  3. Check out the same commit as the primary IPA (or the current release tag per versioning.md). Confirm keryx/ios/Runner.xcodeproj/project.pbxproj exists. Do not run flutter create — that overwrites the App Store bundle ID and signing.
  4. Put a Distribution identity and App Store profile on this Mac — see ios-signing.md (Second Mac: Xcode login, cert, and profile). Sign Xcode into team 8P59575P2K (not personal 79UF8SKVMS). Import the NAS-share .p12 (OA-110-cert-backup is done). Confirm with security find-identity -v -p codesigning.
  5. From repo root: bun run check:keryx-ios.
  6. Run bun run build:keryx-ipa.
  7. Run bun run verify:keryx-ipa -- --expect-team=8P59575P2K — team and aps-environment=production must match the primary build.
  8. Record: date, operator, commit, IPA checksum, entitlements dump, environment ID in controlled audit log.

Failure to reproduce production entitlement blocks #110 close.

Result (30 August 2026): MiniVan-3 Path B recovery passed. verify:keryx-ipa -- --expect-team=8P59575P2K: SHA-256 a924af769b8fe27d6d942f74772bfa27c77707c1ae83aae10619a979e7a49f96, aps-environment=production. Copy that checksum into the evidence store (OA-110-evidence). Both Macs’ Distribution .p12 files are on the NAS share (OA-110-cert-backup, 1 September 2026).

Evidence handoff

Remaining workOwner issue
Reproducible AAB/IPA pipeline command#111 — recorded 1 September 2026 (bun run release:keryx -- --contracts)
CI signing secrets + verify gate#112
Store records, roles, evidence archive#114
App Store Connect paste and submission#125
APNs token registration / delivery proof#97 implementation

Launch posture

P3-02 / #110 is complete: distribution signing is documented, ExportOptions are committed, a verified release IPA proves production APNs entitlement, custody is documented (NAS .p12 backup), and recovery is proven on a second authorized environment.

It does not mean TestFlight promotion, App Store submission, CI automation, APNs token delivery, or that every ASC form is filled.