COCOA騒動メモ
COCOA が動いていなかったことで大臣が謝罪してひと騒動起きている件について、開発者視点からのメモを残してみます。
なぜこのメモを書いたのか
世間的には不正確な情報で叩ければOKの風潮が強くてしんどいので、正しいと思われる情報を拾い集めたものです。中抜きwww 王子wwwww Xamarin wwwwwwww みたいな人にはあんまり興味ないかと思います。
調べ始めたきっかけはこのツイートと引用されたblog記事ですが、記事の内容が違うことはすぐに指摘されて撤回されていたのですが、実際どうだったのかさらに調べてみました。
接触通知アプリ COCOA とはなんなのか
仕組みとか何かは公式サイトでもみてもらうとして。この件で煽っている人でも一部理解できていない人がいるようなのですが、直接的な効果としては
- 保健所が濃厚接触者追跡をする際の手助けとなるためのアプリ
ということになります。アプリをインストールしていれば感染が防げるわけでもないし、感染者を見つけることもできないですが、接触者追跡での見落としを減らしたり、保健所の人手をより詳細な分析にまわしたりなどの効果によって間接的に感染者を減らす効果が期待されていると考えられます。
どんな不具合だったのか
対象の不具合は github issue https://github.com/cocoa-mhlw/cocoa/issues/14 でzaruudonさんによって指摘されている「COCOA の Android 版で、9月末以降には接触可能性の通知が一切表示されない」という現象であると考えられています。iOS 版は(この件については)問題ないはずです。
不具合原因詳細
接触通知システムの(関連する部分の)動作
接触通知システムについての詳細は説明しきれないので他を当たってもらうこととして、関係あるのは以下のような部分です。
COCOAアプリは毎日1回、陽性者情報をサーバから取得して、端末内の接触履歴情報と照合します。
この時、陽性者情報と接触履歴情報には4つの"RiskLevel"が含まれています。
レベル名 | 内容 | |
---|---|---|
陽性者情報 | transmission risk level | 陽性者の種別 |
接触履歴情報 | duration risk level | 接触時間 |
^ | days risk level | 接触からの日数 |
^ | attenuation risk level | 電波強度(≒距離) |
RiskLevel は分類コードのようなものにあたり、分類コードにもとづいて”RiskValue"のテーブルから係数を取得して、4つのValueを掛け算することによって "TotalRiskScore"を算出し、このスコアが指定した最小値 "MinimumRiskScore"以上の場合に、濃厚接触の疑いとして通知を表示する仕組みになっています。
ポイントとしては、アプリからAPIに渡すパラメータは上記のテーブルの値とMinimumRiskScoreを含んだアプリごとの設定情報(Configuration)で、計算や比較の処理自体はGoogle/Appleが実装(シーケンス図のENAPIの部分)しており、ブラックボックスになっています。
不具合
不具合の根本原因は Google と Apple がそれぞれ実装したブラックボックスの実装が異なっていたことにあります。
陽性者情報に含まれる transmission risk level から、 アプリごとの設定情報に含まれる "RiskValue"のテーブル(8個の数値)を引く際に、Google / Apple の実装でひとつ参照位置がずれています。
level (Google) | 0: unused | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
level (Apple) | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
RiskValue | 1固定 | RiskValue[0] | RiskValue[1] | RiskValue[2] | RiskValue[3] | RiskValue[4] | RiskValue[5] | RiskValue[6] | RiskValue[7] |
仕様書上も transmission risk level の定義域が Google は 0 - 8, Apple は 0 - 7 と異なっています。ですがこれは後出しで整理されたようで、初期のドキュメントには記述が漏れていたようです(後述)。
実装としては配列のインデックスがlevelと一致しているApple側のほうがプログラマとしては自然ですがGoogle側のunusedの値を定義するのも不自然とは言えないのでどっちもどっちというか結局のところすり合わせの問題に見えます。
用語としても、Google のソースでは上記の RiskValue の部分は "RiskScore" と呼んでいるなど若干のずれがあって、仕様のすりあわせと実装の整理がおいつかなかったようです。
2/11 追記:
ここの英文の解釈が伝わっていないぽいので……
Each app defines its own meaning for each of the risk levels (0-7). The values assigned to each risk level should be in the range of 0-8.
Apple の仕様書に従った用語でいうと、transmissionRiskLevel の "risk levels" は 0-7 が定義されていて、0-8 なのは "The values" = transmissionRiskLevelValues の配列値で、上記の説明でいうと RiskValue の値のほうです。level は分類コードで 0-7, RiskValue は 0-8 で掛け算に使う係数で、似ているけど意味が違っていますが、Apple / Google の実装では用語と値の範囲が若干ずれているものの、分類(level)と係数(value/score)は正確に分けて記述されています。
時系列の整理
時系列としてはこのようになっています。
日付 | 内容 | 参考リンク |
---|---|---|
4/10 | Google + Apple 接触者追跡(ContactTracing)計画発表 | |
4/29 | Google API仕様書 v1.2 | |
4/29 | iOS13.5 developer beta, ExposureNotification 機能追加 | |
5/2 | Apple API 仕様書BETA版 | |
5/4 | Google, Apple 1国1アプリなどの計画公表 | |
5/7 | Google API仕様書v1.3, RiskScore の計算式記載 | |
5/21 | Google ExposureNotification API 正式公開 | |
5/22 | Apple API 仕様書(BETAの記述を削除), totalRiskScore計算式記載 | archive |
5/25 | iOS13.5 リリース, ExposureNotification ベータ版公開 | |
6/2 | GooglePlay開発者サービスにExposureNotification機能追加 | |
6/18 | Google API仕様書に transmissionRiskLevel の値の定義を記載(0-8) | archive |
7/12 | Apple API仕様書にtransmissionRiskLevel の値の定義を記載(0-7) | archive |
7/21 | Apple 内部参考実装公開 | |
7/20? | Google 内部参考実装公開 | |
8/26 | Google 内部参考実装に RiskScore計算部分を追加(公開範囲漏れの対応) | 指摘のissue コミット |
11/25 | GitHub cocoa-mhlw プロジェクトに issue 登録 | github |
11/28 | Xamarin RiskLevel が Android/iOSで異なっている issue 登録(未解決) | github |
要点としては、以下のようなことが言えると思います。
- 5/7 のドキュメントの修正で TotalRiskScore の計算式が改定されている
- 5/21 ごろに v1正式版の API が確定, iOS 13.5 もリリースされた
- 実装はおそらく最初から異なっていて、途中で修正されたために動作がおかしくなったという証拠はない
- ただし、この時点ではドキュメント上は仕様は一致していて実装の差異はないと考えても矛盾はなかったと思われる
- (iOS13.5 developer beta では途中で計算式が変更された可能性はある(未検証))
- 7/12 の時点で、ドキュメント上 transmissionRiskLevel の値の定義が異なっていることが公開はされている
- 実装の具体的な差異が判明したのは 8/26 に内部参考実装が公開された時点
- Xamarin 側では差異には気づいていなくて cocoa 側での報告後に issue が作成されている(関連はいまのところ不明)
疑問点
COCOAアプリで設定値と判定ロジックを修正してリリースしたのは 9/24 の v1.1.4 (コミットログ) ですが、以下のような疑問点が出てきます。
- 目的としては接触判定条件とされている"1m, 15分"を改善しようとしたように見えるが、COCOA側(厚労省またはパーソルP&T)からAppleへ質問したのか
- Apple のことだけ github のに書かれているが、Google には確認していないのか
- この時点で Google/Apple が実装の差異に気がついていたか
- Android 版を動作確認していないのは明らかだが、そもそもiOS 版は確認したのか
いずれにしても、修正したけどAndroidでは少なくとも試していない(iOS も不明)のはお粗末ですが、あくまでも明らかな要因は 9/24 の変更を行ったことがきっかけであって、「挙動がコロコロ変わる」みたいなのは事実としてはなかったのではないかと考えています
修正案
もうプロジェクト側では修正完了していると思いますが。自分であれば以下のような対応とします。
- サーバ側で送信する transmission_risk_level を 1 に変更する
おそらくこれだけで通知は表示されるようになるはずです。ついでに、
- Configuration は Android / iOS 別の設定を指定できるようにする
という修正もやっておくほうがよさそうです。共通化することで片方だけ直し忘れるみたいな
リスクをなくすことはできるのですが、それはそれで別にレビューなどで対応したほうが
よさそうに思えます。
2/11追記: transmission_risk_level を 1 にする、のはgithubとかでも指摘されていて独自のアイディアとかそういうものではないです。
未検証点
このメモを書いた時点では、論理的には整理はついたと考えていますが以下の点については未検証です。
- Google開発者サービスのバージョンごとの実際の実装内容
- iOSのバージョンごとの実際の実装内容, 特にiOS13.5のdeveloper betaとリリース版
これらはクローズドソースのため少なくとも第三者的には外部から検証するのは困難だと考えています。それぞれの実機や開発者アカウントがあれば調べる方法はなくはないですね。
COCOAは「難しくない」アプリなのか
まず「難しくない」理由について並べてみます。
- 接触通知の根本の仕組みは Google/Apple が実装していてそのAPIを呼ぶだけだから
- 画面数も少ないし複雑な動作をしないから
- 複雑なアルゴリズムや最新の研究を反映したようなアプリではないから
これらについては間違っているわけではないので表面的には「難しくない」アプリに分類される面もあるとは思います。
難しさの理由
ですが、やっぱりCOCOAには特有の「難しさ」が含まれていると考えています。なので「簡単なアプリ」みたいな話で安易には片付けにくいです。
クローズドな部分での不具合だった
今回の不具合は前述の通り Google/Apple が実装していてクローズドな部分での不具合だったようです。また、正常系の動作だけ観察していても表面的にはわかりづらいので、API内部の動作が想定通りかを調べるような作業が必要だったと考えられます。
クロスプラットフォーム開発の問題
COCOA は Xamarin で実装されていて、Android でも iOS でも共通部分は同じコードで動かそうという思想で作られています。今回の不具合も、Android でも iOS でもパラメータは同じものを使っているために発生しています。
プライバシー保護の問題
COCOA は保健衛生向けのアプリであって、健康状態を取り扱うのでプライバシー保護を重視することが必要です。接触通知API自体もそのような配慮が最優先で設計されていますし、アプリ側もそのような配慮が求められます。
このために、一般のアプリでは広く使われている、例えば GoogleAnalytics などのアプリの動作を統計的に集計するような仕組みを導入できなかったので発覚が遅れたとは言えます。
API 制限の問題
接触通知API については、Google/Apple ともに、一般の開発者のアプリで自由に呼び出せるようにはなっておらず、開発者アカウントでしか実際に呼び出して確かめることはできません。このため、一般には情報が出回らないし限られた数の開発者だけで問題解決しなければならないことになります。
運用保守の問題
COCOA は当初開発していたメンバーと運用保守で修正対応しているメンバーが違っています。非公式に裏で繋がっていたりしたらもう少しマシな状況になっていたかもしれませんが、引き継ぎはあったとしてもほぼ無関係に作業していると考えています。
一般的に引き継ぎで渡された場合の難しさとして、だいたい動いているプログラムは正しく思えるため疑う場所を探すのが難しいということがありますし、動かすまでの試行錯誤を経ていないので、ある程度の経験がないとどういう方法で何を試すかといった視点も見つけることが難しい場合が多くあります。
ユーザ数の問題・スマートフォンアプリの問題……
スマートフォンアプリという時点で厚労省には経験がないですしそもそもユーザ数がめちゃくちゃ多いアプリというのはそれだけでも困難があります。
どう対処するべきだったか
開発者サイドの視点
大前提として一度も動かしていないのか?という点があります。単なる手抜きとも思えますがそうとも限らなくて、
- デバッグビルドのアプリを動かすための Google/Apple の開発者アカウントなどは用意されていたか(大前提)
- 開発用に使えるサーバは用意されていて自由に変更できるような環境にあったか
- アプリ開発者とサーバ開発者は密に意思疎通できるような状況にあったか
といった点は検証する必要があると考えています。
最悪の場合、アプリ開発者はこれらの環境が用意されていなかったということもありうるのかもしれませんが、そうだとしても、パラメータ変更依頼に対して変更しました完了です!というのはさすがに無責任なので、上記のような環境を依頼するとか変更後の検証手順を変更依頼者側に確認するとかは最低限必要だったのではと思います。
運用者サイドの視点
アプリをリリースした場合、一番まずいことは正しく動作しなかったために誰もアプリを使わなくなること(アンインストールされること)なので、初動の監視・継続性の監視は特に重要です。
今回 Analytics に頼ることはできなかったとは言っても、通知が出ていなかったというのを誰も気づけなかったというのは非常にまずいので、
- 通知を表示した場合、サーバにその件数などを送信する(システム的な解決)
- COCOA 経由で保健所へ連絡したユーザについてはきちんと数がわかるように集計する(例えばHER-SYSに入力する)
といったことが必要です。後者については保健所の手間が増えるので嫌がられるという面はありますが……でもアプリを導入した以上は効果を検証しない選択肢はないです。
また、継続的にアプリを運用する場合に、継続的にテスト環境を用意していなかったのか、また、本番環境も動作していることを確認できる仕組みを用意するべきだったと思います。今回で言えば、たとえば動作確認用の端末に毎週でも毎日でもいいですが通知が表示されるような環境を用意して本番環境が正常動作していることがわかるようにしておくべきでした。
2/11 追記: 書き忘れましたけどクローズドベータ公開みたいな仕組みも検討するべきだった気がします。初回リリース時からできるのがベストですがそれはスケジュール的に無理だったとしても、不具合が見つかってアップデートというのを数回やっていても全部の問題が解決したわけではない状況だったわけなので、募集すればそれこそボランティアでもひとは集まると思うのです。不具合があることを隠そうという意識があるとなかなか難しいですが、不具合を直そうとしている意識があればユーザ側も協力することもできるのに、そういう雰囲気づくりはなされなかったようには感じます。
また、クローズドベータを実施しようと思えば自然に動作確認環境をどう整備するかとかを設計する必然性が出てくるので、実際に人が集まる集まらないとは関係なく実施できる用意をするのは意味がありそうです。
マネージャレベルの視点
そもそも保健所の手助けをするためのアプリですし効果検証できるようにデータを取ることを考えていないことがおかしいので、仕様定義の段階でどういう効果を目的としているのか、どういう方法で検証するのかを考えていなかったのではないかという疑問が出てきます。
プロダクトオーナーなりプロジェクト管理者なりが目的を理解していない、あるいは検証しようとしない丸投げパターンなのは非常によくない。インストール件数や陽性登録数はグラフにして発表したりしていたので、何もしていなかったわけではないと言いたいかもしれませんけど、例えば陽性登録1件に対してCOCOA経由で保健所に連絡したユーザ数がわからない、というのは全体の基本的な設計として問題があるように思えます。
不具合への体制としても疑問があり、ユーザ数が多かったら窓口を用意して待っていれば不具合報告も多くくるだろう、というのはおそらく安易な考えです。実際には広く使われているものほど報告の割合は減ると考えられます。たとえばAmazonとかでも売れているものにたくさんレビューがつくのかというとそういうわけじゃないことでもわかります。広く使われるアプリほど、ユーザからの意見などを正確に偏らず集めるだけでも計画性が必要です。
結局不正確な情報とはどの点か
- (5/6以前は別として考えれば)Google / Apple が「途中で」仕様変更したとか実装が変わったという事実はなさそう
- 動いていたものがいつのまにか動かなくなった、というタイプの問題ではなく、変更した時点で動作確認していればわかる問題
- issue についているコメント(github)は、結論はともかく経緯の細部には誤認がある
- 初期開発者にはこの件については問題はなさそう(引き継ぎとかはどの程度行われたかは謎)
- 「開発者の能力が低すぎて手抜きが行われた」という理由なのかは要検証(プロジェクト管理側の問題が大きい)
- 「命に関わるアプリ」みたいなのはそもそもアプリの目的をわかっていない
他に不具合はないのか
あります(略)
さらなる改善へ
(めんどくなってきたしすでに多くの人が指摘しているので。。)
その他
- カネの話として3億円とか1200万円とか出てくるのは全体像を見てなさすぎる(3億円はHER-SYSの改修を含んでいるし、少なくとも第2次補正予算で19億円がHER-SYS(とCOCOA)に追加投入されている)
- 「中抜き」は誤用(手遅れぽい)
補足とか反応への返事とか
誰も読まない気がして途中で投げ出していてすみません。出典が書けていないものがあると思うので随時追記するかもしれません。基本的には自分が不具合を調べたというよりは不具合を見つけた方(件のissueのzaruudonさんとか)がえらいので、裏付けをぐぐったりWebArchiveとかで掘り起こしただけです。
ExposureNotification API は元の名前も Contact Tracing で接触者情報を通知します、という機能的に考えても日本で言えば保健所がやる濃厚接触者分析を省力化するために設計されたという建てつけであるのは明らかだと思っています。
日本でCOCOAの当初の導入時や宣伝時には「保健所のために」ということは言われていなかったし通知が来た後検査を受けられるかどうかなどでも混乱があったので、最初から目的がなんだったのかが曖昧になっていたのではという感じはします。
もちろん目的をごまかそうとしたのだとかそういう陰謀論も構築できるとは思いますがそういうのはひとまず考えないことに。
これについてはその通りで、単純化すると「Google が勝手に動作変えたりするからいつのまにかおかしくなった」みたいな話なのかどうかを調べた結果そうじゃなかったらしい、という話です。
これは当初開発者がどこまで確認してどういう引継ぎがあったかということにつながりますが
- 通知が表示されるようなパラメータやロジックの試行錯誤
- すでに通知が表示されるパラメータやロジックがわかっているときの動作確認(リグレッションテスト)
でわけて考えると、ブラックボックスなので試行錯誤がたいへんだった、という話なんじゃないんですかね……?(元発言まださがしていないですが)。リグレッションテストについては端末2台を並べて置いて、「通知が来るはずなのに来ない」ということはないんじゃないかと思いますが実際やってみたわけじゃないんでわかりませんね (やってみる方法の話はzaruudonさんのgist記事があるそうです。説明)
level は 0 始まりの分類コードで、その分類コードにしたがって掛け算に使う値 (RiskValue) をテーブルから取得して、最後に掛け算しています(ちょっと表現を直しました)。
これ説明不足なのはわかっていたんですがすみません。
- 単純にマイナー環境(マイナー端末とかマイナー通信環境とかマイナー設定とかetc..)のユーザが増える
- ユーザ数が多いことがわかっている場合には不具合や要望を正式ルートで上げないひとや黙ってアンインストールするだけのひとの割合が増える
- 不具合報告を調べる場合にも正確な報告とそうでもない報告が入り混じるし、重要度と報告数が連動するとも限らないので分析に手間がかかる
といったあたりの原因がありますがたぶんわたしのぽんこつ経験とかより大手のゲーム運営さんとかのほうがより実態を知っているはず。
いまのプラポリの範囲には入っていないのでシステム的に自動ではできないことになりますが、それも最初のサービス設計としてプラポリに入れるか、同意を取ったユーザだけでも送信できるようにするとか、あるいは検査の段階で COCOA の通知との紐づけするとか、いろんな手段がありますが、なにも調べないというのは選択肢としてはありえないと考えています。
これについてはちょっと言葉足らずでしたのですみません。
言いたかったのは「アプリが正常に動いているかどうかと、アプリのユーザが感染するかどうかは直結はしていない」という意図で、アプリが正常に動いていないことで感染者数が増加してしまうと感染する可能性が増えるという点においては、間接的にですが命にかかわるアプリであるとは思っています。
今回の不具合とは別の話というだけで、これ自体は異論はないです。次こそうまくいくといいですね。
誰がこのメモを書いたのか
このメモを書いたのは主にAndroid/iOSネイティブアプリの受託開発とかをやっているプログラマです。小さいプロジェクトのリーダーくらいは経験がありますしサーバでも組み込みでも普通(とは)程度には対応できますが巨大案件のマネージメントとかやっていたわけではないのでその辺は下から見た視点になります。
(zenn の練習おわり)
Discussion
よろしければご教授ください。
ここの細部の誤認ですが、例えば以下の部分のrisk levelに関する記載などでしょうか。
(Google でもrisk level 0 はUnusedとして定義されている)
ここの部分は、許容する=設定値の配列から使用する、許容しない=設定値の配列を使用しないという意味かと思いますので誤認ではないかもですね…。
すいません。細部の誤認については読み取れませんでした。
そのコメントについて誤認されていると考えているのは大きくまとめると2点で、詳細にみると以下のような感じでしょうか (『』部分は issue のコメントからの引用です)
transmission_risk_level の値の範囲 以外 の"仕様"自体が Apple / Google で大きく整合していなかったという前提で記載されている
この記事の本文で追記しなおしたように、"level" が分類コードで "RiskValue" が係数であるという点の認識がない
前者について補足すると、Google の v1.3 の PDF にも transmission_risk_level の値の範囲の記載はなく、Apple の正式版でも transmissionRiskLevelValues のほうに(間接的に)記載されたのは 6/14 の archive がありますがそれより古い archive は見つけることができていないです(これは私の探し方が悪い可能性はあります)。 いずれにしても内部的にはもっと正確なドキュメントが存在していたと考えるほうが普通じゃないかと思いますので、仕様が整合していないという前提とするのは疑問(そもそもAPI仕様書だけでは実装できないので判断できない)かと考えます。
後者については、transmission_risk_level は分類コードであって計算対象になじまない(分類コードを足したり掛けたりしない)ということを認識されていないように思えます。もっというとv1.2 のころの『4つのリスク項目を加算』の仕様はその点で仕様としてイケていなかったので早々に破棄された可能性があるように思いますがこの説明はどこにもされていないと思うので想像にしかすぎませんね。
いや、『4つのリスク項目を加算』の頃は、あくまでも危険度のリニアなレベルとして 8段階を想定していたので掛けたり足したりする仕様だったけど、もっと細かく分類したいということで離散化して分類コード扱いに変更したために、4つの RiskValue を掛け算する仕様に変更されたのかもしれないですね(結局のところ変更理由はApple/Google に聞かないとわかりません)
「以外」が抜けていたので逆になっていたので直しました。。失礼しました
返信ありがとうございます。
Apple/Google 間の仕様の不整合の範囲、分類コードと係数の混同ということですね。
ご返信を踏まえ該当の Issue を読み直すと確かにおっしゃる通りですね。
ざっと読んでどこか違和感は感じましたが、コメント時には気づけませんでした。
コメント後個人的にまとめていたので今なら気づけた可能性もありますが、短絡的にコメントしてお時間を使わせてしまいました...。
仕様の不整合の範囲は、Apple/Google 間で当然協議しているのではと思うので、仕様公開等のタイミングの問題はあっても基本的なところでの不整合はなかったのではと思っています。実際、分類コード(transmission_risk_level)も推奨される report type の説明部分(1-7)は一致しています。
もちろん真実は Apple/Google でなければ分からないので、あくまで想像となりますが。
そして最初のコメントで申し上げるのを忘れておりましたが、わかりやすいまとめありがとうございます。
おかげで開発者以外の視点も踏まえて、開発と運用について考える機会を持つことができました。
詳細な分析記事をありがとうございます。
COCOAの開発とは直接的には関係のない一個人ですが、とても学びになったので、主にサービス設計的な部分でどういった対策ができたのか、ということをまとめて記事にしました。参考まで。 (どう対処するべきだったか、の部分では各担当者の想定で書かれていたので、サービス設計としてどうあるべきだったか、みたいな観点での検討をしました)
わたしもCOCOAの開発には関係ないし今後もおそらく関わることもないのですが。さざんかぬふさんの記事のほうがふつうのアプリの開発でも参考になるまっとうな記事ですね。
一点だけ気になったのは、「そもそも本番のAPIは一般開発者では触ることができない」という点をある意味許容された書き方になっているのですが、開発者が「本番のAPIは触れないから開発者はそんなの知らん」みたいな立ち位置になってしまうのもあまりよくないと考えますので、「マネージメントとして開発者に充分な環境を用意できるようにする」「開発者は不足があるのに黙って済ませない」「(開発者に環境を与えられない場合はとくに)開発者以外でも受け入れ側でリリース前に充分に検証する」が大前提かなとは思います。とはいえ大筋がサービス設計ということなのでこの点は全体からすると脇道かもとは思いますのでこっそりこちらに書いておきますw
コメントありがとうございます。
確かにそうですね、この部分に関しては「開発者が自主的に検証を行えるテスト環境・開発環境の確保」として追記しました。私も普段はどちらかというと、それぞれの立場での自主性とか巻き込みの観点で考えることが多く、内容にはとても同意します。ありがとうございました。