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 main for version 0.1.0;
  • requires an ad-hoc authorization followed by the existing protected testflight signing-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 ID com.chdafni.frogcityfeast, the Apple Distribution certificate, and the three protected iPad UDIDs;
  • archives and exports with Xcode’s release-testing method;
  • 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:

  1. resolves exactly one iOS or Universal bundle ID matching the protected product identifier and never modifies it;
  2. compares the protected .p12 certificate fingerprint and serial against the sufficiently unexpired DISTRIBUTION API certificate. Apple’s activated field is not used as a Distribution-certificate validity signal;
  3. 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;
  4. reuses only one exact active, sufficiently unexpired IOS_APP_ADHOC profile with exact one-bundle, one-certificate, and three-device relationships; or
  5. 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:

  1. Run iOS Ad Hoc registered-device validation from main.
  2. Enter version 0.1.0 and enable confirm_build.
  3. For validation only, leave publish_private_ota disabled and confirm_private_ota empty.
  4. For an explicitly authorized private installation build, enable publish_private_ota and enter UPLOAD_PRIVATE_OTA.
  5. Approve the ad-hoc authorization job.
  6. Separately approve the protected testflight credential job.
  7. 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=Ignore on 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-upload permits the protected workflow to create, validate, and roll back its own blobs; and
  • device-install is 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-services installation link; and
  • the read-only device-install policy.

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:

  1. 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.
  2. Scan the QR with the iPad Camera app and open the result. If prompted, choose Safari.
  3. Tap Install in the iOS confirmation dialog and wait for the Frog City Feast icon to finish installing.
  4. 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.