Google Play の completed は「公開済み」ではなかった — update.json は商品ページから更新する
アプリ内の更新案内に使う update.json が、ストアより先に新しい版を案内していた。 公開後の手更新をなくそうとすると、次に問題になるのが「いつ公開済みと判断するか」だった。審査を見る仕組みは残し、更新案内だけを Apple Lookup API と Google Play の商品ページに同期する形へ分けた。
update.json だけが先に 2.0.0 になっていた
対象の勤怠アプリは、アプリ本体とは別に GitHub Pages で update.json を配信している。アプリはこのJSONを読み、自分より新しい版があればストアへの導線を出す。
リリース作業中、そのJSONは iOS と Android の両方で 2.0.0 を指していた。しかし一般ユーザーが見る公開面に出ていたのは、App Store が 1.2.0、Google Play が 1.1.0 だった。更新案内だけが将来の状態へ進み、押してもその版を取得できない状態になっていた。
自動同期を最初に動かすと、JSONは公開面に合わせてこの2つの版へ戻った。これは新しい版を誤って巻き戻したのではない。更新案内が約束すべき 「いまストアから取得できる版」へ戻した結果だった。
completed は公開完了ではなく配信設定だった
Google Play Developer API の production track には、release の状態として completed が返る。名前だけを見ると公開処理まで完了したように読めるが、この値が表すのは全ユーザー向けに配信する設定である。 審査と公開面への反映はそのあとに残る。
ここで捨てたのは Google Play Developer API そのものではない。production track の変化を追う用途には引き続き使える。ただし、アプリ内で新版を案内してよいかの判定には使わない。管理画面上の操作が完了したことと、利用者が商品ページから取得できることは別の事実だからだ。
control plane は「どう配信するよう設定したか」を示す。delivery plane は「利用者に何が届いているか」を示す。更新案内が約束するのは後者である。
審査監視と更新案内で、見る場所を分けた
審査監視はこれまでどおり管理APIを見る。App Store Connect API と Google Play Developer API の状態が変われば通知し、審査の進み具合を運用側が把握する。一方、更新案内の同期は一般ユーザーと同じ公開面を見るようにした。
iOS は Apple Lookup API から公開中のバージョンとリリースノートを取得する。Android は Google Play の商品ページから公開バージョンと「新機能」を読む。取得した値はOSごとに既存JSONへ重ねるため、片方のストアだけ先に公開されても、もう片方の値を巻き込まない。
Google Play のHTML解析は、管理APIより壊れやすい。それでも公開判定の根拠としてはこちらが正しい。バージョンや更新内容を解析できない場合は更新せず、失敗を通知するようにした。誤った案内を出すくらいなら、既存の案内を維持する。
const [{ manifest: current, sha }, published] = await Promise.all([
readRepositoryManifest(token),
readPublishedStores(),
]);
const next = mergePublishedUpdates(current, published);
const changed = formatManifest(current) !== formatManifest(next);
if (!changed) return;
await updateRepositoryManifest(token, sha, next);
await waitForPublicManifest(next);この処理を nr cron へ登録した。差分があるときだけ GitHub Contents API で対象ファイルを更新するので、サイト側の別作業やローカルの作業ツリーには触れない。更新内容もストアの文面から正規化し、長すぎる案内や空の案内をそのまま通さない。
GitHub Pages の配信URLまで確認する
GitHub Contents API の更新成功も、まだ control plane の成功にすぎない。アプリが読むのはリポジトリのファイルではなく、GitHub Pages から配信されるURLだ。そのため更新後はキャッシュを避けて公開URLを読み直し、期待したJSONと一致するまで待つ。
最初は待機を30秒にしていたが、実際の Pages build は 41.333秒かかり、正常な更新を失敗として扱った。待機上限を90秒へ延ばし、リポジトリ更新ではなく公開URLの一致を成功条件にした。外部配信を伴う処理では、書き込みAPIの成功を完了地点にしないことが再発防止になった。
まとめ
管理APIと公開面のどちらが正しいか、という二者択一ではなかった。審査や配信設定を監視するなら管理API、利用者に更新を案内するなら公開面を見る。目的ごとに正とする観測点が違う。
さらに、同期先にも同じ境界がある。GitHub上のファイルが更新されたことと、GitHub Pages のURLからその内容が取得できることは別だ。利用者向けの自動化は、control plane を操作した時点ではなく、delivery plane で結果を観測できた時点で完了にする。