先日、クラウド上でビルドまで通ったアプリを Rork Max の提出ボタンで送ったところ、画面上は「送信済み」と表示されたのに、数分後に App Store Connect から差し戻しのメールが届きました。Rork の画面にはそのエラーは出ていません。メールの本文にあったのは ITMS-90683 という記号ひとつでした。
このとき起きていたのは、「2クリック」という言葉が畳んでいた工程のうち、いちばん奥の層で弾かれた、ということでした。ボタンの数と、裏で走る工程の数は一致しません。個人でアプリを出していると、この距離が見えないまま止まる瞬間が必ず来ます。この記事は、その距離に地図を引くためのものです。
「1クリック」「2クリック」は4つの層を畳んでいる
Rork Max が掲げる「実機インストールは1クリック、App Store 提出は2クリック」という体験は、本来 Xcode と Apple Developer のダッシュボードを行き来して手作業でやっていた工程を、ボタンの裏に押し込んだものです。押し込まれた工程はおおよそ4つの層に分けられます。ボタンが少ないのは工程が少ないからではなく、うまくいっている限り工程が見えないからです。
層 裏で担っていること 漏れたときに出る典型的な症状
第1層 署名 配布証明書とプロビジョニングプロファイルの照合 実機に入らない、信頼されていない開発元
第2層 Info.plist バンドルID・バージョン・用途説明文の宣言 ITMS-90683、ITMS-91053 などの記号
第3層 アップロード アーカイブを IPA にまとめ App Store Connect へ転送 処理中のまま進まない、ビルドが現れない
第4層 審査前チェック 輸出コンプライアンス・プライバシー宣言の検証 提出直後の自動差し戻しメール
エラーが赤く表示されたとき、まずこの表のどこにいるかを言い当てられるだけで、対処の速度は大きく変わります。以下、層ごとに「何を担っているか」と「漏れたときにどう読むか」を順に見ていきます。
第1層 署名は「誰が・どのデバイスで」を保証している
iOS のアプリは、必ず配布証明書で署名され、プロビジョニングプロファイルによって「このバンドルIDのアプリを、この権限で、このデバイス群で動かしてよい」という許可が結びつけられます。Rork Max の1クリック実機インストールは、この証明書とプロファイルの発行・紐付けを裏で済ませています。
漏れが起きるのは、証明書やプロファイルの有効期限、あるいはデバイス登録の上限に触れたときです。無料のプロビジョニングで実機に入れている場合、「信頼されていない開発元」という表示に突き当たることがあります。これは提出そのものの失敗ではなく、手元の実機テスト側の壁です。実機テストの上限とタイミングについては、Companion での無料デバイステストの天井と有料登録の見極め で扱っています。
第1層で覚えておきたいのは、署名の問題は「App Store Connect に届く前」に起きるということです。つまり、提出後のメールで弾かれたのなら、それは第1層ではありません。切り分けの最初の分岐がここにあります。
第2層 Info.plist が申請の言語になる
第2層は、アプリが自分自身について Apple に申告する書類です。バンドルID、バージョン番号とビルド番号、必要な権限の用途説明文、暗号化の使用宣言。これらはすべて Info.plist というキー・バリューの集合に書き込まれ、審査システムはこの書類を機械的に読みます。
冒頭で私が突き当たった ITMS-90683 は、この層の代表例でした。カメラや写真ライブラリなど、プライバシーに関わる機能を使うのに、その用途を説明する文字列(NSCameraUsageDescription など)が plist に無い、という差し戻しです。生成されたアプリがある機能を使い始めたのに、対応する説明文が付いてこなかった、という食い違いで起こります。
<!-- Info.plist の一部。プライバシーに触れる機能ごとに用途説明が要る -->
< key >NSCameraUsageDescription</ key >
< string >プロフィール写真の撮影に使用します</ string >
< key >NSPhotoLibraryUsageDescription</ key >
< string >保存済みの画像を読み込むために使用します</ string >
<!-- 暗号化の宣言。標準的な HTTPS のみなら false でよいことが多い -->
< key >ITSAppUsesNonExemptEncryption</ key >
< false />
この層の良いところは、提出する前に手元で構文検査ができることです。macOS があれば plutil で plist が壊れていないかを一発で確かめられます。
# plist が壊れていないか(XML構文レベル)を検査する
plutil -lint Info.plist
# 期待する出力: Info.plist: OK
# 特定のキーが入っているかを確認する
plutil -extract NSCameraUsageDescription raw Info.plist
構文が壊れていれば第3層のアップロードで弾かれ、キーが足りなければ第4層で弾かれます。同じ plist でも、詰まる層が違うのです。ITMS-90683 そのものの直し方は用途説明文が無いときの Info.plist 修正 に、プライバシーマニフェスト由来の ITMS-91053 はプライバシーマニフェストの提出前監査 にまとめてあります。この記事が置いているのは個別の直し方ではなく、「その記号がどの層から来たか」を読む力のほうです。
第3層 アーカイブとアップロードは API を叩いている
第3層では、署名済みのビルドが .ipa という配布用の箱にまとめられ、App Store Connect へ転送されます。Xcode を使う世界では Transporter や xcrun altool が担っていた部分で、Rork Max のクラウドコンパイルはこれをサーバー側で実行しています。Mac を持たずに提出まで届くのは、この転送がローカルではなくクラウドで走るからです。クラウドコンパイルの位置づけはMac 不要のネイティブビルドの仕組み で整理しています。
この層の症状は、赤いエラーというより「無言の停滞」として現れます。アップロードは成功したのに App Store Connect の TestFlight にビルドが現れず、あるいは「処理中」のまま何時間も進まないことがあります。これは審査の差し戻しとは別物で、Apple 側の処理キューか、ビルドのメタデータ検証で止まっている状態です。
自分の側で確認できるのは、ビルドが実際に届いているかどうかです。App Store Connect API のトークンがあれば、送ったビルドが一覧に現れているかを機械的に問い合わせられます。
# App Store Connect API でアプリのビルド一覧を取得する(JWT は事前生成)
# 「送ったはずのビルドが Apple 側に届いているか」を確認する用途
curl -s -H "Authorization: Bearer ${ ASC_JWT }" \
"https://api.appstoreconnect.apple.com/v1/builds?filter[app]=${ APP_ID }&limit=5" \
| python3 -c "import sys,json; d=json.load(sys.stdin); [print(b['attributes']['version'], b['attributes']['processingState']) for b in d['data']]"
# 出力例: 42 PROCESSING / 41 VALID
ビルドが PROCESSING から VALID に変われば第3層は抜けています。ここに現れないなら、まだ Apple に届いていないので、待つべきか送り直すべきかの判断に使えます。停滞を「エラー」と誤読して何度も再提出すると、同じビルド番号の重複でかえって詰まります。
第4層 審査前チェックは提出直後に自動で走る
ビルドが VALID になり、いざ提出すると、審査官が見る前に自動チェックが走ります。冒頭のメールが来たのはこの層です。輸出コンプライアンスの宣言、プライバシーマニフェスト、必須の権限説明。これらが揃っていないと、人間の審査に回る前に機械が差し戻します。
個人開発でいちばん多く踏むのは、輸出コンプライアンスの宣言漏れだと感じています。標準的な HTTPS 通信しか使っていなくても、暗号化を使うかどうかの宣言(ITSAppUsesNonExemptEncryption)を明示しないと、提出のたびに確認を求められます。plist に一度書いてしまえば毎回の質問が消えます。この宣言の考え方は輸出コンプライアンス宣言の恒久対応 に詳しくまとめました。
第4層で大事なのは、ここで弾かれたエラーは「アプリの中身」ではなく「アプリの申告」の問題だ、ということです。コードを直しても解決しません。plist と App Store Connect の設定を直す層です。中身の修正に走る前に、まずこの区別を思い出してください。
提出前に自分の手元で回す3つの確認
差し戻しの往復は、1回あたり数分から、Apple の処理待ちを含めれば数十分を溶かします。個人開発では、この往復を減らすことがそのまま制作時間の確保につながります。提出ボタンを押す前に、私は次の3つを手元で回すようにしています。順番にも意味があります。
plist の構文が壊れていないかを検査する(第3層のアップロード停滞を防ぐ)
使う機能の用途説明が揃っているかを確認する(第2層・第4層の差し戻しを防ぐ)
暗号化の宣言が入っているかを確認する(第4層の輸出コンプライアンスを防ぐ)
確認 方法 防げる層
plist の構文が壊れていない plutil -lint Info.plist第3層のアップロード停滞
使う機能の用途説明が揃っている プライバシー系キーの有無を plutil -extract で確認 第2層・第4層の差し戻し
暗号化宣言が入っている ITSAppUsesNonExemptEncryption の存在確認第4層の輸出コンプライアンス
この3つは、いずれも Apple にビルドを送る前に、手元だけで完結します。3分の確認が、数十分の往復を消すことが何度もありました。提出フロー全体の抜け漏れチェックは提出前チェックリスト にまとめてあるので、初めての提出前に一度眺めておくと安心です。
どこまで自分で直し、どこから Rork に委ねるか
ここまで層を分けてきましたが、目的は「全部を自分で管理する」ことではありません。むしろ逆です。Rork Max の価値は、うまくいっている限り、この4層を意識せずに済むところにあります。抽象が漏れたときだけ、どの層かを言い当てて手を入れられれば十分です。
私自身の線引きはこうです。第2層と第4層、つまり plist と申告に関わる差し戻しは、自分で直します。テキストの宣言を足すだけで、コードには触れないからです。第1層の署名まわりと第3層のクラウド処理は、Rork の仕組みに委ねることを推奨します。ここを手で触ろうとすると、抽象の利点そのものを捨てることになります。
道具を使う側が、道具の内側を少しだけ知っておく。全部を知る必要はなく、詰まったときにどの扉を開ければいいかが分かる程度で足ります。個人でアプリを続けていて、この「ちょうどいい距離」が、いちばん時間を守ってくれると感じています。
まとめ
次にあなたの提出が赤く止まったら、コードを開く前に、エラーが4つの層のどこから来たかをひとつ言い当ててみてください。署名なら提出前、記号付きの差し戻しなら plist、無言の停滞ならアップロード、提出直後のメールなら申告。その一言が、往復の回数を確実に減らします。
私自身もまだ、新しい記号を見るたびに調べ直しています。共に手を動かしながら、詰まる場所の地図を少しずつ細かくしていけたら嬉しいです。お読みいただきありがとうございました。