iOS Ad Hoc Registered-Device Testing
iOS Ad Hoc Registered-Device Testing
This guide prepares an official Apple Ad Hoc build for the three approved iPads without using TestFlight. It does not change the selected normal public App Store distribution route.
TestFlight remains unavailable to the intended under-13 Apple Account. Ad Hoc testing does not alter the account age and does not bypass TestFlight; Apple signs the app for a specific registered device through an Ad Hoc provisioning profile.
Current prepared state
The repository contains a separate manual
iOS Ad Hoc registered-device validation
workflow. It:
- runs only from
mainfor version0.1.0; - requires an
ad-hocauthorization followed by the existing protectedtestflightsigning-credential approval; - uses the pinned Godot 4.7.2, Xcode 26.6, and iOS 26.5 toolchain;
- uses the retained Admin Team API key only to query or register the three protected devices and create or reuse the exact Ad Hoc profile;
- resolves the existing App ID and protected Apple Distribution certificate without creating, modifying, revoking, disabling, or deleting them;
- validates that the profile matches Team ID
CV7JQ487YU, bundle IDcom.chdafni.frogcityfeast, the Apple Distribution certificate, and the three protected iPad UDIDs; - archives and exports with Xcode’s
release-testingmethod; - verifies the signed app, embedded profile, application identifier, and registered-device membership; and
- deletes the IPA, archive, profile, certificate copy, keychain, and export directory after validation.
The GitHub ad-hoc authorization environment is configured for branch main,
requires reviewer CHDAFNI-MSFT, permits no administrator bypass, and contains
no variables or secrets. Signing values remain isolated in the separately
approved historical testflight environment. Three independently masked
device-identifier secrets and the separate provisioning-only Admin key are
configured there; their values are not recorded in the repository. The owner
requested that this Admin key remain retained for future protected
provisioning. It must not replace or rename the App Manager metadata key.
No signed IPA is uploaded as a GitHub artifact or published anonymously. By
default the workflow remains a provisioning and build validator. Private OTA
upload is a separate opt-in mode that requires the exact
UPLOAD_PRIVATE_OTA confirmation and the same two protected approvals.
The first complete protected validation succeeded in workflow run
33703681354
from commit bd2e1d9f9c98c9f27c5da7a25a3a73b57f98b40b. Apple reconciled all
three device records and the exact profile as existing resources. The workflow
validated build 33703681354.1, reported IPA SHA-256
a44bed89626b6b4a6da6db8d3d2a984cde0583ecbc98d08fda2e22ee08cac50d,
then verified removal of the IPA and all temporary signing material.
The first private installation build succeeded in workflow run
33768105238
from commit a3f4ab084e04fa8a1977c4d5cca555c65423e6b7. The workflow validated
build 33768105238.1, reported IPA SHA-256
4ab80672e8cbf22d32754fd1fd4283e277c68224103dac36555e436bb25ede3e,
uploaded the private IPA and manifest, proved both reject anonymous access,
retained no GitHub artifact, and verified runner cleanup.
Protected inputs
The historical testflight environment has these provisioning inputs
configured:
| Secret | Required value | Status |
|---|---|---|
IOS_AD_HOC_DEVICE_UDID_1 |
First approved iPad UDID | Configured |
IOS_AD_HOC_DEVICE_UDID_2 |
Second approved iPad UDID | Configured |
IOS_AD_HOC_DEVICE_UDID_3 |
Third approved iPad UDID | Configured |
APPLE_PROVISIONING_KEY_ID |
Key ID for the retained provisioning-only Admin Team API key | Configured |
APPLE_PROVISIONING_PRIVATE_KEY_BASE64 |
Base64 of that Admin Team API private key | Configured |
APP_STORE_CONNECT_ISSUER_ID |
Existing Team API issuer shared with the metadata key | Configured |
The existing APPLE_CERTIFICATE_BASE64 and
APPLE_CERTIFICATE_PASSWORD secrets provide the certificate identity. The
App Store profile and App Manager key secrets remain separate and are not
overwritten, renamed, or used for device/profile mutation.
Treat every UDID as a protected device identifier. Do not place one in a workflow input, issue, commit, log, or documentation file. Separate GitHub secrets allow each complete identifier to be masked independently.
Device registration behavior
Obtain each approved iPad identifier outside the repository through Apple’s current trusted-device tooling. Confirm only that each value uses Apple’s supported modern or legacy format, then store it directly in its numbered GitHub secret. Do not record the value or a personal device name in Git.
The protected workflow automatically queries each UDID before any write. It
accepts only one exact enabled IOS/IPAD record. A disabled, ambiguous,
wrong-platform, or wrong-device-class match is terminal. When no record exists,
it creates one generic non-personal name such as Frog City Feast Test iPad 1.
It never patches, deletes, disables, or re-enables a device. A conflict or
network-ambiguous create is handled only by bounded exact re-reads; the workflow
never blindly creates the device again.
Device slots are limited by product family and membership year. Disabling or removing a device may not restore the slot until the membership reset window, so the protected values must be verified before the run.
Automatic profile reconciliation
No manual profile download or Ad Hoc profile secret is needed. The workflow:
- resolves exactly one iOS or Universal bundle ID matching the protected product identifier and never modifies it;
- compares the protected
.p12certificate fingerprint and serial against the sufficiently unexpiredDISTRIBUTIONAPI certificate. Apple’sactivatedfield is not used as a Distribution-certificate validity signal; - derives a deterministic profile name from committed product identity, generation token, and hashes of the sorted device set, bundle, and certificate, without embedding a raw UDID;
- reuses only one exact active, sufficiently unexpired
IOS_APP_ADHOCprofile with exact one-bundle, one-certificate, and three-device relationships; or - creates that exact profile once and reconciles conflicts or ambiguous completion through bounded reads without deleting or blindly recreating it.
The downloaded API profileContent exists only under RUNNER_TEMP. Before
signing, the workflow decodes its CMS plist and checks API UUID agreement,
expiration, iOS platform, exact application identifier, disabled debugging,
non-enterprise status, exact three-device membership, and inclusion of the
selected certificate. The signing preparation and exported-IPA checks repeat
the critical validations as defense in depth.
The workflow rejects development, App Store, enterprise, expired, wildcard, wrong-team, wrong-app, wrong-certificate, missing-device, duplicate-device, and extra-device profiles.
Protected workflow sequence
After the protected inputs are confirmed:
- Run
iOS Ad Hoc registered-device validationfrommain. - Enter version
0.1.0and enableconfirm_build. - For validation only, leave
publish_private_otadisabled andconfirm_private_otaempty. - For an explicitly authorized private installation build, enable
publish_private_otaand enterUPLOAD_PRIVATE_OTA. - Approve the
ad-hocauthorization job. - Separately approve the protected
testflightcredential job. - Confirm signing, archive, exported-IPA signature and profile validation, optional private upload, and runner cleanup all succeed.
The validation-only mode intentionally produces no downloadable artifact. A successful validation-only run proves that Apple signing and the registered-device profile work, but it does not install the app.
Protected private OTA delivery
The owner approved a private Azure Blob Storage delivery path retained until manual deletion:
- subscription
ME-MngEnvMCAP328671-chdafni-2; - resource group
rg-mobile-ota-delivery; - storage account
stmobileota2041340e8c; - private container
ios-delivery; and - resource-only governance exclusion
SecurityControl=Ignoreon the storage account. The resource group has no exclusion tag.
The resource exclusion permits Shared Key authorization and public network reachability for this storage account only. Anonymous access remains disabled; HTTPS and TLS 1.2 remain required, and the container has no public access level. Two revocable stored access policies are configured:
workflow-uploadpermits the protected workflow to create, validate, and roll back its own blobs; anddevice-installis read-only for the IPA and manifest installation URLs.
Both policies have a December 31, 2099 safety horizon because Azure rejects a stored-policy SAS when neither the policy nor token defines an expiry. Normal retention remains “until deleted”: delete the blobs or revoke/change the stored policy when access should end rather than relying on that horizon.
Their SAS values are protected testflight environment secrets. The account
and container names are protected environment variables. The workflow masks
both SAS values, never prints or commits an installation URL, verifies the
exported IPA signature and exact embedded device profile before upload, uses
collision-safe blob names, verifies content MD5 and run-owned metadata, checks
that anonymous access is denied, and rolls back incomplete uploads. The IPA
and manifest remain private until their blobs, access policy, storage account,
or resource group is deleted.
Installing without a Mac uses this private HTTPS route:
- the signed Ad Hoc IPA;
- an HTTPS Apple manifest referencing that IPA;
- an
itms-servicesinstallation link; and - the read-only
device-installpolicy.
Do not use public GitHub Pages or a workflow artifact for the IPA. Do not log or commit a signed installation URL.
For each registered iPad:
- Display the locally generated private installation QR on the authorized Windows computer. Treat the QR as a bearer credential and do not share, copy, commit, or publish it.
- Scan the QR with the iPad Camera app and open the result. If prompted, choose Safari.
- Tap Install in the iOS confirmation dialog and wait for the Frog City Feast icon to finish installing.
- Launch the app and complete the physical acceptance checks below.
The QR works only while the private blobs and device-install stored policy
remain available. The Ad Hoc signature limits installation to the exact three
registered devices even if the bearer URL is disclosed. Revoke access by
changing or deleting the stored policy, deleting the blobs, or deleting the
storage account or resource group.
Physical acceptance after installation
Use the installed Ad Hoc build for the release checklist’s A16 iPad acceptance:
- sustained performance and thermal measurements under every stress scenario;
- touch, safe-area, orientation, interruption, and background/foreground tests;
- audio mix, mute behavior, and haptics;
- Reduce Motion, larger text and controls, timing assistance, contrast, and readable status feedback;
- save errors, low storage, reinstall, and local-data deletion; and
- the deferred final icon, screenshot, and in-game visual review.
Ad Hoc acceptance does not submit or release the App Store version. The normal candidate, App Review submission, and manual public release remain separately authorized operations.