Appearance
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.keryxappunder team8P59575P2K(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.entitlementstoproductionfor 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
| Field | Value |
|---|---|
| Bundle ID | com.sourcectl.keryxapp |
| Development team | 8P59575P2K (verify ownership in #114) |
| Publisher | sourcectl (P0-01) |
| Store product name | Keryx for SafeCall |
| Minimum iOS | 17.0 (P0-04) |
| Android application ID | com.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.
| Artifact | Custody | In Git |
|---|---|---|
| Distribution certificate + private key | Keychain + NAS secrets/ (secret-store.md) | Never |
| App Store provisioning profile | Keychain (Xcode can re-download) | Never |
APNs auth key (.p8) | NAS secrets/ | Never |
| ExportOptions.plist | Committed (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-connectteamID:8P59575P2KuploadSymbols: truemanageAppVersionAndBuildNumber: false (keeppubspec1.2.0+N; Xcode otherwise can rewriteCFBundleVersionfrom 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:
- Distribution certificate installed (Xcode → Settings → Accounts → Manage Certificates, or import the NAS-share
.p12— secret-store.md). - App Store provisioning profile for
com.sourcectl.keryxapp(Xcode downloads automatically when team is configured). - APNs auth key (
.p8) stored in NASsecrets/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:
- Open Xcode → Settings (⌘,) → Accounts.
- + → Apple ID. Sign in with the Apple ID that is a member of SOURCE CONTROL SINGLE MEMBER P.C.
- 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. - 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.
- Open Keychain Access → My Certificates.
- Find Apple Distribution for SOURCE CONTROL / team
8P59575P2K. - 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. - OA-110-cert-backup is done (both Macs’
.p12files on the NAS share, 1 September 2026; passwords in Apple Passwords, not the repo).
On MiniVan-3:
- Download the GLaDOS
.p12from the NAS share onto the Mac. Double-click it (orsecurity import path/to/cert.p12 -k ~/Library/Keychains/login.keychain-db). - Enter the vault item password. Confirm Always Trust is not required; default trust is enough.
- Xcode → Settings → Accounts → team
8P59575P2K→ Download Manual Profiles. Xcode pulls the App Store profile forcom.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.
- Xcode → Settings → Accounts →
8P59575P2K→ Manage Certificates… → + → Apple Distribution. - Open
keryx/ios/Runner.xcworkspace(the workspace, not the.xcodeproj). - Select the Runner target → Signing & Capabilities. Team =
8P59575P2K, Automatically manage signing on. Bundle ID must staycom.sourcectl.keryxapp. - 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 codesigningYou 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:
- Apple Developer Program includes iOS App Store distribution (not Mac Developer ID only).
- App ID
com.sourcectl.keryxappis registered under this team with Push Notifications and Background Modes → Remote notifications. - Certificates: Apple Development and Apple Distribution exist for team
8P59575P2K(Manage Certificates…). - App Store provisioning profile for
com.sourcectl.keryxappis present (Xcode downloads automatically with-allowProvisioningUpdateswhen 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.
| Field | Value |
|---|---|
| Team | 8P59575P2K — SOURCE CONTROL SINGLE MEMBER P.C. |
| Requested App ID | com.sourcectl.keryx (unavailable; not released) |
| Registered App ID | com.sourcectl.keryxapp |
| Support case | 102915938031 — Apple Developer Support |
| Android application ID | com.sourcectl.keryx (unchanged) |
Support thread summary (case 102915938031)
| Date | From | Summary |
|---|---|---|
| 2026-06-18 | Apple (Thai) | Asked which Team ID currently holds the bundle ID |
| 2026-06-19 | Thomas | Cannot see holding Team ID; requests release of com.sourcectl.keryx for sourcectl account |
| 2026-06-23 | Apple (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-26 | Thomas | Clarified: 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-29 | Apple (Jenn) | Identifier cannot be released or changed; suggested com.sourcectl.keryxapp (among others) |
| 2026-08-29 | Thomas | Registered 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.
- Confirm App ID
com.sourcectl.keryxappexists on team8P59575P2K. - 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.
- Create Apple Distribution certificate if missing; let Xcode download the App Store profile.
- 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). cd keryx && flutter analyze && flutter testbun run build:keryx-ipa && bun run verify:keryx-ipa -- --expect-team=8P59575P2K- 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.
| Role | Responsibility |
|---|---|
| Primary custodian | Maintains distribution cert/profile in keychain; runs first signed IPA build; registers App ID capabilities |
| Backup custodian | Holds cert/profile/APNs-key exports in NAS secrets/ (secret-store.md); performs recovery drill |
| Account Holder | Apple Developer Program legal entity; approves cert revocation and role changes |
| Release authority | Approves rotation/revocation and ASC upload |
| Audit log | NAS 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-ipareports non-productionaps-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-ipaOr from repo root (preferred — prefixes PATH so Xcode uses /usr/bin/rsync):
bash
bun run build:keryx-ipa
bun run verify:keryx-ipaIf 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=8P59575P2KDefault IPA path: first *.ipa under keryx/build/ios/ipa/.
Verification checklist
Archive in controlled evidence (not git):
- [ ]
verify:keryx-ipaexit 0 - [ ]
aps-environment = productionin 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):
- Install Xcode (not only Command Line Tools). Open it once, accept the license. Then:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developerandsudo xcodebuild -runFirstLaunch. - Download the iOS platform. Xcode 26 does not include it until you install it: Xcode → Settings → Components → iOS, or
xcodebuild -downloadPlatform iOS. Confirm withxcodebuild -showsdks(-sdk iphoneosmust appear). Without this, Flutter reportsNo Xcode build settings have been found(MiniVan-3, 30 August 2026). The CocoaPods vs Swift Package Manager note is the same as GLaDOS; do notpod deintegratefor the recovery drill. - Check out the same commit as the primary IPA (or the current release tag per versioning.md). Confirm
keryx/ios/Runner.xcodeproj/project.pbxprojexists. Do not runflutter create— that overwrites the App Store bundle ID and signing. - 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 personal79UF8SKVMS). Import the NAS-share.p12(OA-110-cert-backup is done). Confirm withsecurity find-identity -v -p codesigning. - From repo root:
bun run check:keryx-ios. - Run
bun run build:keryx-ipa. - Run
bun run verify:keryx-ipa -- --expect-team=8P59575P2K— team andaps-environment=productionmust match the primary build. - 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 work | Owner 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.
Related documents
- Internal TestFlight — first internal upload
- Publishing Keryx — Apple App Store runbook
- Secret store — NAS CT 500 LAN share;
.p12/.p8backup - NAS unlock after reboot — gocryptfs after CT 500 restarts
- Android signing — P3-01 Play counterpart
- Apple privacy compliance — privacy manifests
- iOS delivery architecture — APNs delivery