綴Photo の勉強ノートに戻る
開発記録2026.07.28

束ねて寝かせたら、片方は二度と起きなかった — 永久に鳴らないトリガーに縛っていた話

設計macOS認証開発プロセス綴Photo

AIレビューが出した「新規の P1」を追いかけたら、1ヶ月前の自分のメモに同じことが書いてあった、という記録です。直す対象がコードより先に「寝かせ方」だったので、そっちを書きます。記事を書いた後で被害の見積もりが間違っていたと分かったので、その訂正も最後に入れました。

ここで言う「web」は、綴ったものを web でも見られるように開発中のアプリ(紡Book)のことです。

Codex に全体レビューを一周させたら、P1(リリースを止める級)が4件返ってきた。そのうちの1件がこれ。

macOS Release に loopback OAuth 用の server entitlement がない

読んで、見覚えがあった。1ヶ月前の自分の設計書に載っている。

まず何が抜けていたか

ログインの仕組みはこうなっています。アプリは Google を直接触らず、自分の web のログインページをブラウザで開いて、認証が終わったら結果を受け取る。その「受け取る」ために、アプリが一瞬だけ自分でサーバになる。

dart
// lib/core/auth/oauth/redirect_capture.dart
// ブラウザからの戻り(?code=...)を受けるため、自分自身が待ち受けポートを開く。
// ポート番号 0 = OS に空いてるやつを1個くれ、の意味。
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);

問題はここ。macOS のサンドボックスでは、外へ繋ぎに行くのと待ち受けるのが別の権限になっています。

許可はディスク上の2ファイル。待ち受け自体はメモリで、保存しない。DebugProfile.entitlementsmacos/Runner/ ・開発ビルドが読むnetwork.client 外へ繋ぐnetwork.server 待ち受ける閉じてもファイルに残るRelease.entitlementsmacos/Runner/ ・配布ビルドが読むnetwork.client 外へ繋ぐnetwork.server 無い閉じてもファイルに残るloopback 待ち受け(メモリの HttpServer)ブラウザからの戻りを受ける。server が要る。ログインが終わったら捨てる。権限は上のファイル。

2つのファイルを並べるとこうなっていた。

xml
<!-- DebugProfile.entitlements(開発用)— これはある -->
<key>com.apple.security.network.server</key> <!-- 待ち受ける(自分がサーバになる)許可 -->
<true/>
<key>com.apple.security.network.client</key> <!-- 外へ繋ぎに行く許可 -->
<true/>
 
<!-- Release.entitlements(配布用)— server が無い -->
<key>com.apple.security.network.client</key> <!-- 外へ出るぶんだけ。待ち受け側が無い -->
<true/>

ここで一つ言っておきたいのは、雑にやった結果ではないということ。Release 側のファイルは、他のキーには「なぜ要るのか」のコメントが付いている。フォントを実行時に取るから client が要る、ピッカーで選んだファイルを読むから user-selected が要る、再起動後も読めるように bookmark が要る。Debug 側には「Release.entitlements と揃えて」と2箇所も書いてある。

2つのファイルを揃える意識はあった。揃え損ねた1個は、他とは違うタイミングで、違う理由で足されたやつだった。だから「次からちゃんと見る」は対策になりません。ちゃんと見た結果がこれなので。

なお、署名済みの配布ビルドで実際に待ち受けが失敗するところは、まだ見ていない。Mac が手元にないと確かめられない。サンドボックスの仕様からはそうなるはず、という段階の話として読んでほしい。

「新規の P1」は1ヶ月前の自分のメモに載っていた

6月末に一度、同じように全体レビューをかけて実装設計書を作っている。その中の未消化リストにこう書いてあった。

残=OAuth 戻り口(mobile)と macOS entitlement は
「App Links 移行」で一体化し着手保留

自分で見つけて、自分で「あとでやる」に入れて、そのまま1ヶ月置いた。忘れたのではなく、置いた場所が悪かった。

束ねた2つは、別のトリガーで動くものだった

「一体化」した2つを並べてみる。

  • OAuth 戻り口(App Links) — Android と iOS の話。ブラウザから https://... のリンクでアプリを開き直す仕組みです。実装するには Android の署名鍵を作るか、iOS を TestFlight に上げるか、サーバに本番設定を置くかが要ります。
  • macOS entitlement — デスクトップの話。上に書いた通り、待ち受けの権限を1行足すだけです。

書き出すと明らかに別物なのですが、当時は「どっちもログインの戻り道の話だから」で1つのブロックにした。それらしく見えたし、実際どちらも認証の周辺ではあります。

「あとでやる」に入れるとき、再開の条件(トリガー)も一緒に書いてあった。3つ。それぞれが macOS entitlement に掛かるかを、いま1個ずつ照合するとこうなります。

この束は設計書ブロックDの未消化リスト。残タスクの数には入らない。DB にもメモリにも無い。保留棚 「App Links 移行」として1束OAuth 戻り口Android / iOS の App Linkshttps でアプリを開き直すmacOS entitlement待ち受け権限を1行足すデスクトップ。戻り道は loopbackAndroid の署名鍵 → 触るiOS の TestFlight → 触るサーバの本番デプロイ → 触るトリガーは1本も届かない鳴っても起きない残タスク(数える側)には載らない寝かせた瞬間、レビューの対象から消える。ファイルに書いた。DB には無い。

引っかかるのは、この照合を当時やらなかったこと自体ではない。照合するという発想が無かったことのほうだ。トリガーは束に対して1回書くもので、束の中身1つずつに対して書くものだとは思っていなかった。

保留に入れた瞬間、それは在庫から消える

もっと効いたのはこっち。

自分は「未対応の残タスク」を数えて、溜まりすぎたら実装をやめて検証に回す、というルールで動いています。だから1ヶ月、誰も——レビューも、自分の目も——ここを見なかった。7月も自分で触ってはいたが、触っていたのは Windows 版なので踏みません。

記事を書いた後で、被害の見積もりが間違っていたと分かった

最初この記事には「出荷済みの Mac 版が1ヶ月ログインできなかった」と書いていた。タイトルもそれだった。

書き上げた後、実装を進めているときに「本当にそうなの?」と聞かれて、日付を突き合わせた。

  • Mac の dmg を配ったのが 6月11日
  • ログインの実装が入ったのが 6月24日
日付は作業ログと git。誤った見積もりは markdown 5ファイル。DB には無い。6月11日Mac dmg を出荷ログインボタンが無い今の利用者は踏まない6月24日ログインを実装出荷の13日後まだ配っていない次に出す Mac戻ってこないボタンが載る壊れているのは次の版出荷済みではない「出荷済みが1ヶ月ログインできない」を5ファイルへコピー設計書進捗メモタスク表作業ログこの記事どれもディスク上の markdown。閉じても残る。DB には無い。

引っかかったのは、同じレビューの中で Windows について全く同じ訂正を自分でしていたこと。「今ストアに出ている版は起動不能」と書きかけて、公開日とガードを足した日を比べて「壊れるのは次に出す版」と直した。その数時間後に、macOS で同じことをやった。片方は確かめて、片方は確かめなかった。

どちらも再検査の対象から外れる操作だった点で、この記事が扱っている「保留に入れたものは誰も見ない」と形が同じだった。

抜けていた権限も、束ね方の事故も、事実は何も変わりません。変わったのは深刻さの区分だけです。ただ、その区分は「何から先に直すか」を決めるものなので、盛ると直す順番が歪みます。

束ねたら、トリガーを項目ごとに聞き直す

割ってさえいれば macOS 側はその日のうちに終わっています。権限を1行足すだけなので5分もかかりません。

判定の軸も置き直した。「もう誰かが使っているか」ではなく「次に配るものに載るか」。載るなら「あとで」ではなく、数えられる側に戻します。

で、これをどこに置くか

上のルールは、書いた時点ではただの反省文で、次に寝かせるときに思い出す保証がどこにもありません。1ヶ月前の自分も、たぶん同じくらい真剣にやっていた。

なので置き場所として出てきたのが CI だった。Debug にあって Release に無い認証まわりの権限があったら、そこで落ちます。寝かせ方をまた間違えても、次のプッシュで引っかかります。

同じ移し方は7月11日に一度やっていて、そのときは「既存の違反は責めず、新しい違反だけ止める — アーキテクチャ検査を一方向ラチェットにする」に書いた通り、既存の34箇所は allowlist に記録して見逃したまま、新しく増えた違反だけを止める形にした。今回の権限の検査はまだ入れていないので、ここは置き場所が同じだったという話にとどまる。

人が覚えて守るルールを、全員が必ず通る出口に移します。この日に見えていたのはここまでで、何をどこまで検査させるかはまだ決めていない。

見積もりを5箇所にコピーした話も同じで、気づけたのは注意力が戻ったからではない。実装中に「本当にそうなの?」と聞かれて、日付を突き合わせたからだった。

この記事は2026年7月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。