## What changed - adds production iOS identity, localized storefront metadata, native privacy declarations, App Store-safe artwork, and account-deletion UX - mirrors the live Danish Google Play title, short description, and long description in the App Store metadata source - generates Android launcher/store icons from the opaque iOS marketing master so both platforms use the same white background - adds guarded GitHub Actions workflows for storefront readiness, credential health, signed TestFlight uploads, and App Store candidate preparation - adds pinned Fastlane configuration with a committed dependency lock, release manifest tooling, and an operational App Store runbook - preserves the upstream iOS safe-area implementation while retaining opaque App Store icon assets ## Why The repository previously supported development-signed device bundles but had no production App Store identity, reproducible storefront source of truth, or protected signed-release pipeline. Apple also requires in-app account deletion for apps that support account creation. The Android icon master was transparent, which rendered as black on dark store/device surfaces. ## Impact Automation remains fail-closed behind `APP_STORE_AUTOMATION_ENABLED=false`. No build can upload to TestFlight or change App Store metadata until the switch is deliberately enabled after merge and the remaining release gates are satisfied. ## Validation - focused App Store, iOS icon, and cross-platform icon-background tests pass - every generated Android store/launcher icon is opaque with pure-white corners; iOS marketing artwork is checked the same way - Android icon drift check passes for all 19 generated files - production Vite build and the broader focused release checks completed successfully - storefront metadata is valid; only the two expected screenshot-set warnings remain - App Store Readiness is green at head `4445fecc` - Apple Distribution certificate and App Store profile were independently verified for `HP3FJ4GVL7.io.truckwash.app` - live App Store Connect API authentication succeeded for app `6792777794` - App Store record, free Denmark-only availability, and automatic `Internal QA` TestFlight group are configured - EU trader status, Content Rights, 4+ age rating, and the published App Privacy label are completed in App Store Connect - iPhone and iPad accessibility declarations are configured honestly as pre-release drafts ## Remaining external gates - reviewed iPhone and iPad screenshot sets are still required - an App Review login must be supplied without creating or exposing customer credentials - the first signed TestFlight candidate must run after merge and deliberate automation enablement
204 lines
8.8 KiB
Markdown
204 lines
8.8 KiB
Markdown
# Apple App Store Release Runbook
|
||
|
||
This is the operating runbook for the public iOS application and its signed
|
||
GitHub Actions delivery. Public review submission and public release remain
|
||
human actions in App Store Connect.
|
||
|
||
## Storefront record
|
||
|
||
Create or reconcile one App Store Connect record:
|
||
|
||
| Setting | Value |
|
||
| --- | --- |
|
||
| Name | Truck Wash Kundeportal |
|
||
| Bundle ID | `io.truckwash.app` |
|
||
| SKU | `truckwash-ios` |
|
||
| Primary language | Danish |
|
||
| Category | Business |
|
||
| Price | Free |
|
||
| Availability | Denmark only |
|
||
| Support URL | `https://truckwash.io/support` |
|
||
| Privacy URL | `https://truckwash.io/privacy-policy` |
|
||
| Marketing URL | `https://truckwash.io/` |
|
||
| Release | Manual after approval |
|
||
|
||
Use the standard Apple EULA and do not configure in-app purchases. Payments in
|
||
the product cover physical truck-wash services. Keep iPhone and iPad enabled;
|
||
disable Apple-silicon Mac and Vision Pro compatibility until those targets have
|
||
been tested deliberately.
|
||
|
||
The Account Holder or Admin must complete these console-only items before the
|
||
first candidate:
|
||
|
||
- Accept current Apple developer and business agreements.
|
||
- Verify Truck Wash ApS's EU Digital Services Act trader identity and contact
|
||
information.
|
||
- Complete the current age-rating questionnaire. Do not hard-code an expected
|
||
rating in automation.
|
||
- Approve the privacy data matrix and enter matching App Privacy answers,
|
||
including third-party SDK behavior.
|
||
- Decide export compliance after reviewing the final binary. Only add
|
||
`ITSAppUsesNonExemptEncryption=false` when the exempt determination is
|
||
approved.
|
||
- Complete accessibility declarations only for behavior verified on devices.
|
||
- Store a durable, sanitized review account in App Store Connect. Never commit
|
||
its password, OTP seed, or recovery data.
|
||
|
||
Review notes must explain customer and driver login, the review account's 2FA
|
||
path, QR/hardware behavior, camera/location denial fallbacks, and the physical
|
||
service payment model.
|
||
|
||
## Metadata and assets in Git
|
||
|
||
`fastlane/metadata/da-DK/` is the Danish storefront source of truth.
|
||
`ios/release.json` is the release-version source of truth. Its version is
|
||
numeric `X.Y.Z`; its bundle ID must remain `io.truckwash.app`.
|
||
|
||
Run the readiness validation locally:
|
||
|
||
```sh
|
||
npm run mobile:ios:storefront:check
|
||
```
|
||
|
||
Readiness mode validates all present assets and reports missing screenshot sets
|
||
as warnings. A candidate tag runs strict mode and requires exactly six reviewed
|
||
images in each set:
|
||
|
||
- `fastlane/screenshots/da-DK/iphone-6.9-01-*.png` through `06`, 1320×2868.
|
||
- `fastlane/screenshots/da-DK/ipad-13-01-*.png` through `06`, 2064×2752.
|
||
|
||
Use Xcode 26 simulators and the real Capacitor app. Capture dashboard, booking,
|
||
self-wash/QR, vehicles, orders/history, and invoices. Screenshots must contain
|
||
sanitized fixture data, no alpha channel, no real customer data, and no
|
||
placeholder content. Linux CI cannot honestly synthesize authenticated native
|
||
captures; capture and approve them on a controlled macOS machine before tagging.
|
||
|
||
The validator also rejects the known default Capacitor icon and splash artwork.
|
||
Native permission strings must exist in Danish and English and are included via
|
||
the `InfoPlist.strings` Xcode variant group.
|
||
|
||
## Apple identities and GitHub configuration
|
||
|
||
Create:
|
||
|
||
1. A dedicated App Store Connect team API key named `GitHub App Store CI` with
|
||
the App Manager role. Team JWTs use the account issuer ID in the `iss` claim.
|
||
2. A dedicated Apple Distribution certificate for CI.
|
||
3. An App Store distribution provisioning profile for `io.truckwash.app`.
|
||
4. An internal TestFlight group named `Internal QA` with automatic distribution.
|
||
|
||
Configure two GitHub environments:
|
||
|
||
- `app-store-signing`, branch policy limited to protected `master`.
|
||
- `app-store-candidate`, tag policy limited to protected `ios-v*` tags.
|
||
|
||
Private repositories on the Team plan cannot rely on environment required
|
||
reviewers. Protect `ios-v*` creation/update/deletion with a repository ruleset
|
||
limited to release managers. Manual App Review submission is the final human
|
||
approval.
|
||
|
||
Environment secrets:
|
||
|
||
- `IOS_DISTRIBUTION_CERTIFICATE_P12_BASE64`
|
||
- `IOS_DISTRIBUTION_CERTIFICATE_PASSWORD`
|
||
- `IOS_APP_STORE_PROFILE_BASE64`
|
||
- `APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64`
|
||
|
||
Environment variables:
|
||
|
||
- `APPLE_TEAM_ID=HP3FJ4GVL7`
|
||
- `IOS_BUNDLE_ID=io.truckwash.app`
|
||
- `IOS_SCHEME=App`
|
||
- `IOS_PROJECT=ios/App/App.xcodeproj`
|
||
- `APP_STORE_CONNECT_API_KEY_ID`
|
||
- `APP_STORE_CONNECT_APP_ID` (Apple's numeric app resource ID)
|
||
- `TESTFLIGHT_INTERNAL_GROUP_ID` (Apple's beta-group resource ID)
|
||
- `APP_STORE_CONNECT_ISSUER_ID=074cc671-edc3-403d-b85f-98470f3b16bd`
|
||
|
||
The repository variable `APP_STORE_AUTOMATION_ENABLED` is the authoritative
|
||
activation switch. Missing or any value other than `true` makes all signing,
|
||
credential-health, and candidate workflows succeed as safe no-ops without
|
||
selecting an App Store environment or reading Apple secrets.
|
||
|
||
## Enablement and first canary
|
||
|
||
Keep `APP_STORE_AUTOMATION_ENABLED=false` while configuring Apple/GitHub state.
|
||
Then:
|
||
|
||
1. Merge all product-readiness work and confirm `App Store Readiness` passes.
|
||
After its first successful default-branch run, add that job to the protected
|
||
master ruleset's required status checks.
|
||
2. Verify the privacy policy, account-deletion flow, icons, localized permission
|
||
copy, and privacy manifest on a device.
|
||
3. Set the repository switch to `true` during a controlled release window.
|
||
4. Dispatch `iOS Internal TestFlight` from the `master` workflow definition,
|
||
supplying the full current master SHA and confirmation
|
||
`UPLOAD IOS INTERNAL BUILD`.
|
||
5. Confirm the workflow validates Xcode 26.3/iOS 26, certificate/profile
|
||
identity and expiry, the signed IPA, App Store processing, and exact Internal
|
||
QA assignment.
|
||
6. Install the result on a clean supported iPhone and iPad. Verify fresh install,
|
||
upgrade, login, resume, offline/reconnect, permission allow/deny, booking,
|
||
self-wash/QR, vehicles, orders, invoices, support/privacy, and account
|
||
deletion.
|
||
7. Leave the switch enabled only after the canary is accepted.
|
||
|
||
If the first live credential attempt fails, set the repository switch back to
|
||
`false` before investigating. This avoids red master releases while credentials
|
||
are incomplete.
|
||
|
||
## Continuous TestFlight delivery
|
||
|
||
`Frontend Release` publishes a signed-by-CI evidence artifact only after the
|
||
production deployment, public live gate, credentialed live gate, Release
|
||
Manager gate, and server-version update all pass for current `master`.
|
||
|
||
`iOS Internal TestFlight` consumes that exact proof. It refuses a stale SHA,
|
||
uses `/Applications/Xcode_26.3.app`, requires an iOS 26 SDK, queries App Store
|
||
Connect for the next build number under serialized concurrency, signs and
|
||
inspects the IPA, uploads through pinned Fastlane, waits for processing, and
|
||
idempotently assigns the exact build to Internal QA.
|
||
|
||
Outputs include:
|
||
|
||
- Signed IPA, retained for 30 days.
|
||
- dSYMs, SHA-256 checksums, and `ios-release-manifest.json`, retained for 90
|
||
days.
|
||
- Source SHA, marketing/build versions, App Store build ID, Xcode/SDK versions,
|
||
and workflow identity in the manifest.
|
||
|
||
Every successful future Frontend Release for current `master` triggers this
|
||
delivery automatically. Stale or proofless releases do not sign or upload.
|
||
|
||
## Select a public candidate
|
||
|
||
1. Verify the desired TestFlight build on iPhone and iPad.
|
||
2. Confirm its commit's `ios/release.json` contains the public version.
|
||
3. Create a new protected tag such as `ios-v1.0.0` on that exact commit. Never
|
||
move or reuse an existing release tag.
|
||
4. `iOS App Store Candidate` locates the release manifest for that exact SHA,
|
||
verifies the exact processed App Store build, enforces complete screenshots,
|
||
synchronizes Danish metadata, attaches the existing build, and reads it back.
|
||
It does not rebuild, submit for review, or release publicly.
|
||
5. In App Store Connect, review the rendered product page, review account,
|
||
privacy/export/age answers, and candidate build. Submit manually.
|
||
6. Release the first Denmark version manually after approval. Use phased release
|
||
for later updates unless there is a reason not to.
|
||
7. Merge the next `ios/release.json` version bump before further delivery after
|
||
Apple closes the released version to new builds.
|
||
|
||
## Rotation and recovery
|
||
|
||
`iOS Credential Health` runs every Monday and fails when certificate/profile
|
||
identity drifts or either expires within 30 days. Rotate one credential at a
|
||
time, keep automation disabled during rotation, and repeat the canary.
|
||
|
||
- Bad TestFlight build: expire it, fix master, and produce a new build number.
|
||
- Bad candidate: detach it in App Store Connect and tag a corrected tested SHA
|
||
with a new version; never move the tag.
|
||
- Bad phased update: pause the phase.
|
||
- Compromised key/certificate: disable automation, revoke it in Apple, rotate
|
||
GitHub secrets, inspect audit logs, and run a fresh canary.
|
||
- Public emergency: remove from sale only when necessary and prepare an
|
||
expedited corrective version.
|