Skip to content
C.W.K.
Stream
Lesson 01 of 07 · published

Delivery Is TestFlight, and It Is Four Facts

~15 min · testflight, delivery, app-store-connect, pipeline, reporting

Level 0Bundle Opener
0 XP0/81 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"Four facts, and only the last is delivery."

A Build Check Is Not a Delivery

There are two ways to get your app onto an iPhone, and they answer different questions. Running from Xcode or installing with devicectl signs the app with a development profile that lists registered devices, and it needs that device paired, in Developer Mode and unlocked, one device at a time. It answers "does this build run on hardware?" It is a build check. TestFlight signs the build the way the App Store will, installs it over the air on every tester's device, and can update it there automatically when the next build lands. It answers "do the people who use this app have it?"

The limits are Apple's and worth knowing by number. A TestFlight build can be tested for 90 days. Internal testers are up to 100 App Store Connect users on your team, and their builds need no review. External testers can number up to 10,000, and a build for them may need a beta review. Every family app ships to an internal group, and every build, the first as much as the hundredth, goes that way.

The Failure This Rule Exists For

A coding session finished the terminal app's first iOS milestone, installed it on the owner's iPhone by cable, and logged TestFlight as a future step. The coding engine's app had shipped a one-command TestFlight pipeline twelve days earlier, and it was the declared reference every later app was told to copy. The owner had to point at it. Nothing about the cable install was broken. It simply was not delivery, and calling it done cost a full cycle. The pull toward the cable is strong because it is what most tutorials show and what gives a result in a minute, which is why the rule is written down rather than left to judgment.

Four Facts, Reported Separately

Even a successful upload is not delivery. What happens after it is four separate facts, and a report names the one that is true:

  1. Uploaded. The pipeline printed its _UPLOAD_OK line, behind an export gate that can fail.
  2. Processed. App Store Connect finished processing the build. This can take minutes, and it is also where some refusals arrive, by email, after a green run.
  3. Assigned. The internal group's build list includes it.
  4. Installed. The device shows the new build, or the tester row says Installed. Only this one is delivery.

The family's pipeline is one shared program vendored into every iOS repo, with each app's facts in a small config file beside it. Its last line repeats the rule, so the next reader of the log cannot mistake fact one for fact four.

Code

One pipeline, three modes, and the line it prints·bash
scripts/mobile-testflight.sh --plan     # check every fact, build nothing
scripts/mobile-testflight.sh            # guards, tests, build number, archive, .ipa
scripts/mobile-testflight.sh --upload   # ...and hand the build to App Store Connect

# The last line of a successful upload says exactly one thing:
# SPARK_MOBILE_UPLOAD_OK build 42 source 3f2c9e1 — uploaded is fact 1 of 4: Apple processing,
#   the internal group and the INSTALLED build are still separate facts
An app's facts, kept beside the shared pipeline·bash
# scripts/mobile-testflight.config.zsh: the app's facts. The pipeline itself is shared.
mtf_app=SparkMobile
mtf_marker=SPARK_MOBILE
mtf_mobile=clients/apple/SparkMobile
mtf_project=SparkMobile.xcodeproj
mtf_scheme=SparkMobile
mtf_bundle_id=com.example.spark.mobile
mtf_icon=clients/apple/SparkMobile/Assets.xcassets/AppIcon.appiconset/AppIcon.png
mtf_simulator_env=SPARK_SIMULATOR_ID
mtf_inputs=(clients/apple/SparkMobile Package.swift Sources)
mtf_build_number=project-yml
mtf_watch=no
mtf_commit_subject=spark-mobile

External links

Exercise

Write the config file for SparkMobile and a --plan run that prints every fact it resolved without building. Then write a short status note for each of the four facts as a template your future self will fill in ("Build 42: uploaded; not yet processed"), and add one rule to Spark's README that forbids reporting a cable install as delivered.
Hint
A --plan mode is mostly the guards run early: the config exists and sets every required name, the simulator variable is set, the project has a numeric build number, and the test entry point is executable. Print one line per fact so a failed plan names the missing one.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.