Skip to content

iOS appRunbooks · pinned from gurbani-soul-ios

Runbook: App Store submission (go / no-go)

7 min readUpdated

Page actions

ChatGPT and Claude are third-party services. The link gives them this page's public address and a short instruction. This site records nothing.

The order matters: the web pages must be live before the binary is submitted, and the binary must be an App Store channel build. Nothing here is improvised — each step names the command or the file that proves it. Audit and plan: 2026-09-20.

0. What cannot be undone

  • There is no binary rollback on iOS. A bad release is fixed by a new build (ios-hotfix).
  • The EU trader details (address, phone, email) become public once declared.
  • The app name can be changed only with a new version once it has been submitted.

1. Machine-checkable gates (an agent or CI closes these)

#GateProof
M1The platform release vX.Y.Z is live and this app vendors itplatform: /api/health.commit == the vX.Y.Z tag commit; here: vendor.lock.json platform ref vX.Y.Z, make gates green
M2/privacy and /support are live on production and name Gurbani Soul@smoke specs in the deploy gate; uptime workflow green
M3Repo gates green on the RC commitmake gates (vendored inputs, versions, iOS source/listing/archive gates)
M4The RC is an App Store channel build from green trunkmake testflight TEAM_ID=… BUILD=N CHANNEL=appstore UPLOAD=1 — refuses a dirty tree, an off-trunk commit, red CI, Xcode < 26, or an unsigned Nitnem review
M5The ledger row is committedios/testflight-builds.json has the build with channel: appstore, sdk: iphoneos26.x; PR merged
M6This version may be submittedmake appstore-preflight exits 0
M7Listing text is within limits and truelisting lint (part of M3); paste from docs/ios/app-store-listing.md only
M8App Store Connect holds exactly the listing docmake asc-diff prints RESULT: NO DRIFT (GET only; run after pasting and again on submission day)

2. Owner gates (only a person can close these — record name + date in the build log)

#GateWhere
H1Scholar review of the Nitnem non-SGGS text → REVIEWED: true, and NitnemReview.extraTextReviewed = true in the same commitios/Resources/NITNEM-REVIEW.md, docs/nitnem/review-pack/
H2G3 — Granthi / scholar acceptance of the ੴ icon treatment and the default saroop renderingplatform docs/brand/gurbani-soul-brand-book.md
H3G4 — legal / trade-dress clearance; the source edition’s publisher noted; the ShabadOS 4.8.7 licence text recordedbrand book; NOTICE.md
H4Charter S (scripture fidelity) signed on the RC build numberdocs/ios/testflight-test-plan.md
H5Hardware pass on the RC (below)ios/App/Tests/UI/A11Y_CHECKLIST.md
H6App Store Connect forms (below)docs/ios/app-store-listing.md
H7A monitored support address exists and is the one in the listingplatform support-inbox
H8Rulesets applied so the iOS parity + app checks are requiredscripts/gh/apply_rulesets.sh
H9Submit for review; after approval, Release manually (7-day phased release applies from the first update)App Store Connect

H5 — hardware pass (signed RC build, real devices)

  • ☐ Oldest supported iPhone on iOS 17.x: first launch after install — note the seconds spent on “Verifying scripture integrity…” (budget: under 5 s; the app stays responsive throughout).
  • ☐ Turn a Nitnem reminder on → the system permission prompt appears → at the chosen time a banner with sound arrives; tapping it opens Nitnem.
  • ☐ Live Activity: More → turn it on → open a bani, stay 20 s, lock the device → progress shows; tapping the Lock Screen banner returns to that bani; it ends when the bani is left.
  • ☐ Reader: scroll down an Ang, swipe to the next, swipe back — is the scroll position kept? (Record the answer; the pager’s neighbour handling is untouched pending this check.)
  • ☐ Change the accent in Themes, return to the Reader — the open Ang uses the new accent at once.
  • ☐ iPad: portrait + landscape, Split View ⅓ and ½, Stage Manager resize.
  • ☐ A11Y_CHECKLIST.md: VoiceOver, largest accessibility text size, Reduce Motion, Increase Contrast, Bold Text.
  • ☐ Widgets (all three) populate after the first app launch; More → About → Integrity shows “Widget App Group available”.
  • ☐ Airplane Mode for the whole session: nothing degrades.
  • ☐ Storage almost full: the app launches and reads; saving a verse fails gracefully.

H6 — App Store Connect

Paste from make asc-sheet (every field in console order, exactly as the reviewed listing doc says, markdown stripped). When done, make asc-diff (with the API key env from CLAUDE.local / the TestFlight command) reads the console — GET only, it never writes — and must print RESULT: NO DRIFT. It does not see App Privacy, DSA, Mac/Vision Pro, agreements or the age-rating answers; tick those by hand.

  • ☐ Name available; subtitle, promo text, keywords, description pasted from the listing doc.
  • ☐ Support / Marketing / Privacy URLs = the “Submit this” column (they must resolve today).
  • ☐ App Privacy: Data Not Collected.
  • ☐ Age rating questionnaire → 4+. Read the live form; answer what it asks.
  • ☐ New app record / version: create the App Store version with the string exactly matching the binary’s MARKETING_VERSION (a new app record defaults to 1.0; the build is not selectable until the version string matches). Submit the build whose version equals the live site (/api/meta.version on gurbanisoul.com) — one number everywhere. Confirm with python3 scripts/release/check_release_complete.py X.Y.Z in the sggs-platform repository (the script lives there) before selecting. Any earlier superseded upload (e.g. a prior patch) is left unsubmitted; Apple expires it. Select the processed build once it leaves “Processing”.
  • ☐ App Information: Primary category Reference, Secondary Education; Content Rights.
  • ☐ Content rights: Yes, contains third-party content, and I have the rights (list from the doc).
  • ☐ Export compliance: answered by ITSAppUsesNonExemptEncryption = false.
  • ☐ EU DSA trader status declared and verified (the contact details become public).
  • ☐ Accessibility labels: declare only what H5 proved.
  • ☐ Availability: all territories. Price: free. Agreements: nothing pending under Business.
  • ☐ Untick “Make this app available on Mac” and ”… on Apple Vision Pro” — both are ON by default. The app is iPhone/iPad only (TARGETED_DEVICE_FAMILY "1,2") and is never tested on those platforms; a broken Mac/visionOS run is a 2.1 rejection on a surface we never shipped.
  • ☐ App Review Information: contact name, phone, email; review notes pasted; no sign-in.
  • ☐ Screenshots retaken from the RC (ios/AppStore/README.md checklist). Mandatory display sizes today: iPhone 6.9” and — because the app supports iPad — iPad 13”; 6.5” iPhone is optional. Confirm the live requirement in the upload UI on the day (Apple changes it).
  • ☐ An update: App Store Connect will not create version X.Y.Z while the previous version is still “Pending Developer Release” — release (or cancel) that one first. Paste What’s New from the sheet; the field exists from the first update on.
  • ☐ Version release: manual. Phased release: on for every update; Apple offers it only for updates, so a first version goes to everyone at once when you press Release. make asc-diff reads both back (Version · What's New, Version · Phased release).
  • ☐ After approval the version sits at “Pending Developer Release” — it is NOT live. Open the version page and press Release (for an update this starts the 7-day phased rollout); record the timestamp.

3. Re-check on submission day

make asc-diff again (someone may have edited the console since), then: Apple changes these without notice — read https://developer.apple.com/news/upcoming-requirements/: minimum Xcode/SDK (and raise MIN_XCODE_MAJOR in ios/tools/testflight_archive.sh + MIN_SDK_MAJOR in ios/tools/appstore_preflight.py together), the age-rating questions, whether accessibility labels have become mandatory.

4. If App Review rejects

Read the cited guideline, reply in Resolution Center with the specific evidence (the review notes already explain offline use, location, notifications and the Live Activity). A metadata-only rejection is fixed in App Store Connect and in docs/ios/app-store-listing.md in the same sitting — the doc and the listing must never diverge. A binary rejection that needs no source change (signing, a processing fault) is rebuilt from the same tag with the next build number. One that needs a source change cannot reuse the version: the tag vX.Y.Z is immutable and an App Store build must be at it, so the fix ships as X.Y.(Z+1) on the platform and here (ios-hotfix.md).

5. After release — halt criteria (decided in advance)

For an update, pause the phased release (App Store Connect → the version → Pause Phased Release) if any of the criteria below holds. A first version has no phased release: its levers are the timing of the manual Release (hold it while anything is in doubt) and, once live, a fixed build with an expedited review (ios-hotfix step 2). The criteria:

  • an integrity-gate false positive in the field (“Scripture integrity check failed” on a clean install);
  • a wrong-text report confirmed against the printed Bir by a person;
  • a crash or hang reported by more than one user on the same screen, or an Organizer crash rate that is clearly above the previous build’s. Then follow ios-hotfix. Weekly during the rollout: Xcode Organizer crashes and hangs → symbolicate with the dSYM UUIDs recorded in the candidate JSON → issue → decide.