付録
本付録は、本文で語った事件を再説明する場ではありません。再利用可能な設計パターン、章とADR・コミットの対応関係、用語の定義、参考文献を、検索して参照するための資料としてまとめます。
全体像: 最終的なレイヤー構成と、そこへ至る流れ
各章は一つの事件を追いますが、事件ごとの詳細に入る前に、最終的にawaseがどんな形に 落ち着いたかを俯瞰しておきます。以下の図は主経路(入力から出力まで)・観測と信念の層・ 世代照合・アプリごとの適応層という4つのまとまりと、それぞれが互いにどこで作用を 差し止めているかを示しています。

矢印のラベルが、それぞれの層の役目を表しています。「アプリごとの適応層」は物理キーを 渡すかどうかとTSF側の準備確認を、「観測・信念層」と「世代照合」は4層reducerを通じて 「作用してよいか」の最終判断を、それぞれ主経路に対して差し込みます。主経路自体は 判断(Decision)と実行(Effect)を分離しただけの単純な形のままで、複雑さのほとんどは この3つの周辺層に押し出されています。出力の先には、メモ帳・Word(Win32/UWP)、 Chrome・Edge、Windows Terminal・VS Codeという具体的なアプリ名を置き、その先で実際に かな漢字変換を行うGJI(Google 日本語入力)・MS-IMEという2つの競合IMEへつながる様子も 示しています。フックからIME確定文字までの間、WH_KEYBOARD_LL・IMM32/TSF・ GetProcessIoCountersといったWindows APIのどこに触れているかも矢印に添えました。
最終形はいきなりこの4つのまとまりを持っていたわけではありません。以下は、同じ図法で 描いた3つの中間段階です。

最初の開発日には、これだけが動いていました。判定はengine一つで完結し、周辺の層は まだ存在しません。

n-gramによる際どい判定の補正と、Decision/Effectモデル、アプリ種別ごとの出力切り替えが 加わりますが、依然として一本の経路の中だけで完結しています。

ここで初めて、主経路の外側に別の層が新設されます。「観測は信念を直接上書きしない」 というbeliefの発想(第5〜6章)と、「物理キーを渡すか渡さないか」を制御する適応層 (第7〜9章)です。主経路そのものは変わらず、複雑さが周辺へ押し出され始めているのが 分かります。
第4段階が、冒頭で見た最終形です。第10章で観測・信念層と適応層をあらためて4層reducerに 統合し、第11章で世代照合という型を追加することで、「作用してよいか」を最後に一箇所で 判断する経路が完成します。第12章はこの構造にも残る限界を認め、終章はGo2 Runtimeという 別の系で同じ空白を確認します。以降のパターンカタログ・ADR対応表・用語集は、この大きな 流れの中の個々の事件を検索するための詳細です。
パターンカタログ
本文全体から抽出した9個の設計パターンです。各パターンは「パターン名・文脈・問題・働く力・解決・結果・限界・関連する章とADR」の8項目で統一しています。
1. Observer never overrides desired(観測は信念を上書きしない)
- 文脈: 非同期のprobe・pollが到着し、既存の信念(belief)を更新しようとする場面です。
- 問題: 観測をそのまま状態へ反映すると、ユーザーの意図(intent)を、後から届いた古い観測が消してしまいます。
- 働く力: 観測の遅延・順序入れ替えは避けられません。一方でintentは、観測より優先して守られるべき情報です。
- 解決: Intent・Observation・Transition・Barrierの4層に分離し、Observationはbeliefを直接書き換えず、常にIntentとの照合を経由してから反映します。
- 結果: observationがintentを上書きして起きていた症状が解消しました。
- 限界: 4層分離自体は「いつ照合するか」を決めません。照合のタイミングそのものが、別の急所(パターン2)になり得ます。
- 関連する章・ADR: 第10章、ADR-032
2. Generation/epoch fencing(世代照合による足止め)
- 文脈: 非同期に完了するAPI呼び出し(IME apply等)の結果が、発行順とは異なる順序で返ってくる場面です。
- 問題: 古い呼び出しの結果が、新しい状態を上書きしてしまいます(stale write)。
- 働く力: 呼び出しの完了順序は制御できませんが、「どの世代に対する呼び出しか」は発行時に分かります。
- 解決: 単調増加する世代カウンタ(FocusEpoch)を発行時に刻み、適用時に現在の世代と照合してから適用します。
- 結果: observation起因のstale writeを構造的に防止しました。
- 限界: admit()通過後から実際の適用までの間に世代が変わるケース(TOCTOU、確認した瞬間と使う瞬間のずれ)は、世代照合だけでは防げません。
- 関連する章・ADR: 第11章、第12章、ADR-077
3. Own the channel, don't filter the noise(チャネルを断つ、ノイズは濾さない)
- 文脈: 合成イベントと物理イベントが区別できない入力経路です。
- 問題: フィルタを重ねても、新しいエッジケースが次々見つかり収束しません。
- 働く力: 合成と物理の判別は原理的に不可能な場合がありますが、経路そのものへのアクセスは制御できます。
- 解決: 判別を諦め、対象アプリに物理キーそのものを見せません(チャネルレベルでConsumeします)。
- 結果: 複数段階のフィルタ強化で解決しなかった問題が、チャネル遮断によって再発しなくなりました。
- 限界: チャネルを断てない構成(相手プロセスの内部ウィジェット構造など)では、別の観測手段が必要です。
- 関連する章・ADR: 第8章、ADR未確認(要補完)
4. Side-channel observability(副次観測)
- 文脈: 競合プロセスの内部状態を知る公式APIが存在しない場面です。
- 問題: DLLフック・COM傍受・IPC盗聴のいずれも失敗し、状態を直接観測できません。
- 働く力: OS標準のプロセスメトリクス(I/Oカウンタ等)は、公式APIとして公開されています。
- 解決: 必要な操作自体をプローブとして転用し、
GetProcessIoCounters()の差分から相手の処理内容を推測します。 - 結果: 競合IMEの活性化状態を、専用プローブを送らずに検出できるようになりました。
- 限界: この技法自体の一般的な限界は、2013年のアンチデバッグ手法の文脈でのみ間接的に確認されています。
- 関連する章・ADR: 第7章、ADR-048、ADR-062
5. Classify at the edge(端で分類する)
- 文脈: プラットフォーム層(TSF・IMM32。いずれもWindowsがIMEとのやり取りを仲介する仕組みで、IMM32が古くからのAPI、TSFがその後継)から届く生イベントは、種類も信頼度もまちまちです。
- 問題: コアロジックが生イベントを直接扱うと、分類ロジックがコア全体に散らばります。
- 働く力: 分類に必要な情報(由来・種別)は、プラットフォーム層でこそ最も豊富に手に入ります。
- 解決: プラットフォーム層で分類を完了させてから、コアロジックへ渡します(hourglassアーキテクチャ)。
- 結果: コア側は分類済みの型だけを扱えばよくなり、判定ロジックの重複が減りました。
- 限界: 分類基準を後から変える場合、プラットフォーム層と型定義の両方を変更する必要があります。
- 関連する章・ADR: 第10章
6. Confidence-tiered sensor fusion(確信度による観測の融合)
- 文脈: 複数のprobeが、同じ対象について矛盾する情報を返す場面です。
- 問題: 単純に「最後に届いた値を正とする」と、信頼度の低い観測が高い観測を覆してしまいます。
- 働く力: 各observationの由来(evidence)によって、信頼度は異なります。
- 解決: 観測源ごとに信頼度を重み付けし、投票的に1つのbeliefへ統合します。
- 結果: 確信度型の導入により、矛盾する観測がそのまま反映される事態を防ぎました。
- 限界: 重み付けの基準自体は経験則であり、新しい観測源が増えるたびに見直しが必要です。
- 関連する章・ADR: 第6章、ADR-043〜045
7. Snapshot at capture time, not at drain time(捕捉時にスナップショットする)
- 文脈: イベントが非同期キューに溜まり、ドレイン(取り出し)処理でまとめて処理される場面です。
- 問題: ドレイン時点の状態を基準にすると、イベント発生時と処理時の時間差が誤判定を生みます。
- 働く力: イベントの捕捉時点でこそ、正しい文脈(state)が確定しています。
- 解決: イベント捕捉時に状態をスナップショットし、ドレイン処理はそのスナップショットだけを見ます。
- 結果: 非同期ドレインに起因する時間差起因の誤判定を防ぎました。
- 限界: スナップショット自体が古くなる問題(admit後・適用前)は、このパターン単体では解決しません。
- 関連する章・ADR: 第10章、第11章
8. Shadow-mode migration(並走移行)
- 文脈: 新しい実装(shadow_model等)を、既存の実装と置き換える場面です。
- 問題: いきなり切り替えると、新実装の未知の欠陥が本番へ影響します。
- 働く力: 新旧を同時に動かして差分だけを見れば、切り替え前に欠陥を発見できます。
- 解決: 新旧実装を並走させ、差分ログで検証してから段階的に昇格させます(ADR-040)。
- 結果: shadow_modelをSSOT(Single Source of Truth、単一の真実源)へ昇格させる過程を、安全に行えました。
- 限界: 並走期間中は、二重のメンテナンスコストがかかります。
- 関連する章・ADR: 第10章、ADR-040
9. SSOTモチーフの反復
- 文脈: IME belief・GJI(Google Japanese Input、Google 日本語入力)warmth・ observation admissionという、一見別々の3つの問題領域です。
- 問題: それぞれ個別に「状態を一箇所に集約すれば直る」という仮説から出発し、一度は失敗しました。
- 働く力: 集約先を増やしても、陳腐化した観測が状態を汚染する問題そのものは消えません。
- 解決: 3領域とも最終的に「観測の受理条件を明示する」という同型の設計に収束しました。
- 結果: 独立に発見された同型パターンとして、他の領域でも再現し得る指針になりました。
- 限界: この反復は「集約すれば直る」という初期仮説が誤りだったことの傍証であり、正しい設計を自動的に導くものではありません。
- 関連する章・ADR: 第10章、第11章
10. Typed proof is not live proof(型は過去の証明であって現在の証明ではない)
- 文脈:
Go2Cmd<Checked>のようなphantom typestateが、値に「安全性チェックを通過した」という印を付与する場面です。 - 問題: 型が示すのは「過去のある時点でチェックを通過した」という事実であり、「いまも条件が真である」ことの証明ではありません。
- 働く力: 型システムは構築時点の条件しか強制できませんが、実行はその後の時間差を経て行われます。
- 解決: dispatch直前に、型が示す証明とは別に、現在の世代・鮮度を再検証する境界を設けます(パターン2と組み合わせて使います)。
- 結果: Go2 Runtime実地検証によって、この間隙が成熟した実プロジェクトでも構造的に残っていることを確認しました。
- 限界: 本書執筆時点では、この境界そのものを実装として持ち込む検証は完了していません。
- 関連する章・ADR: 終章
ADR・コミット対応表
章・事件・ADR番号・主要コミットの対応です。本文の再説明はせず、参照先として使う表です。
| 章 | 事件名 | 症状 | ADR番号 | 主要コミット | 不変条件 | 破れる条件 |
|---|---|---|---|---|---|---|
| 第10章 | IME belief/SSOTモデルの変遷 | observationがintentを上書き | ADR-032, 077 | d2e183f, f8dd8d4, 6baabf9, a4db93e, 604cf99 | 観測は意図を直接書き換えない/generation一致でのみ状態変更 | admit後・適用前のepoch遷移(TOCTOU) |
| 第4章 | Windows Terminal/TSFコールドスタートliteral化 | ローマ字キーストロークのリテラル漏れ | ADR-049 | 83d5707, 4249846, f426297, 9c80975, a3cce29 | 固定待機でなく副作用からreadiness推論 | 344ms超の長時間アイドル再初期化 |
| 第8章 | ImmCross/LINE偽VK_F3/F4 echo | 物理KANJIキー押下が偽トグル生成 | 要確認(未特定) | d99e3f1, 77ccf34, e890a26, f84c74b, 08b8661, 0e364ea | 対象アプリに物理IMEキーを見せない | ImmCrossProbe自体の信頼性問題(Qt子ウィジェット) |
| 第8章(補) | GJIキーバインド3世代交代 | F13/F14衝突 | ADR-034/057/067 | 7f8291f, 81df62c, cfbbd20, b271aee, 098c663 | idempotentな絶対セットキーを使う | MS-IMEはVK_KANJIが必要(ADR-063) |
| 第10章(補) | VK/ScanCode混同検知dylint | newtype導入後も取り違え継続 | ADR-012 | 2dd43f4 | vk.rs外でmagic hexを直接書かない | 未確認(現状lintで防御) |
| 第7章 | 捨て打ち機能(旧サクリファイシャル・ウォームアップ) | Chrome cold-start中のリテラル化 | ADR-048, 062 | 26bc0fe, d02ec44, 6c1732d, 22c3905 | 必要操作自体をプローブに転用、Chrome+GJIのみ適用 | vim等ターミナルアプリとのVK_A衝突(他アプリはVK_IME_OFF→ON方式へ離脱済み) |
| 第9章 | StepCoro統一 | 増殖したFSM群(5種類)の複雑化 | ADR-053 | e548e63, ccd4711, f01d401, b077c3d, 2c756dd, d1d6d17 | 一回限りの手続きはStepCoro、長期生存・外部問い合わせ対象はFSMとして残す | ウォームパスのLiteralDetectFsmはコルーチン化されず残存(意図的) |
注記です。第8章のコミットハッシュとADR番号の一部は、既存調査からの転記であり、リポジトリ側での再確認は未実施です。
拡張用語集
src/toc.mdの用語集をもとに、Go2 Runtime実地検証で見つかった対応語を追記しています。
| 用語 | 定義 | Go2 Runtime対応語 |
|---|---|---|
| awase (awase) | 本書で描くWindows用日本語入力エンジン(NICOLA親指シフト方式) | — |
| NICOLA / 親指シフト | 親指キーとの同時打鍵タイミングで仮名を確定する日本語入力方式 | — |
| shadow state | OSが正直に教えてくれないIME状態を、自前で推測・保持する仕組み | WorldState(immutableな世界モデル) |
| belief(信念) | 観測から導出した「いまのIME状態はこうだろう」という推定値 | BeliefView<T>(旧称BeliefSnapshot<S,B>) |
| Observation(観測) | probe・pollから届く生の情報。beliefを直接上書きしない | StateUpdate(S層が受け取る生観測) |
| FocusEpoch | フォーカス変更等を境に単調増加するカウンタ。観測の鮮度を判定する基準 | TickId(tick単調カウンタ。ただしepoch照合には未使用) |
| AcceptedObservation | 世代(epoch)照合(admit)を通らないと構築できない、型で保証された「新鮮な観測」 | 現状Go2に相当機構なし(Go2Cmd<Checked>は過去の証明のみ) |
| TOCTOU急所 | admitした時点では新鮮でも、実際に適用する時点では陳腐化している間隙 | Executor::execute直前のガードがwatchdogのみである間隙(ADR-R009) |
| 捨て打ち機能 | 必要な操作自体をプローブとして転用し、I/Oカウンタ差分で副次観測する技法 | 直接対応なし(SimIo::injectは素朴な注入のみ) |
| Go2 Runtime | 著者が別に設計しているロボット(Unitree Go2)向けアプリケーションランタイム | — |
| Observed<T> | Go2 Runtimeの観測品質型。Fresh・Stale・Unknown・Brokenの4値を持つ | awaseのshadow state・beliefに相当 |
| BeliefQuality | 観測の信頼度(confidence)と裏付け種別(evidence)を保持する型。鮮度は持たない(INV-OB1) | awaseの確信度型に相当 |
| ControlLease / fencing_token | 複数オペレータ間で、古い命令が新しい命令を誤って上書きしないためのリーダー選出の仕組み(ADR-P005) | awaseのFocusEpochとは別問題(対比事例) |
| Field<T> | beliefを介さない素の観測値(odom等)にも、鮮度・品質を必須で添付するラッパ型 | shadow stateの「何でもbelief化する」思想と相似 |
参考文献リスト
| 文献 | 書誌情報 | 備考 |
|---|---|---|
| Kleppmann, フェンシングトークン論 | Martin Kleppmann, "How to do distributed locking", 2016-02-08, https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html | fencing tokenの標準的な説明・Redlock批判で知られるブログ記事です。 |
| Chubby (Burrows, OSDI 2006) | Mike Burrows, "The Chubby lock service for loosely-coupled distributed systems", 7th USENIX OSDI '06, 2006-11. https://www.usenix.org/conference/osdi-06/presentation/chubby-lock-service-loosely-coupled-distributed-systems | PDF直リンクは要検索です。書誌情報としては確認済みです。 |
| Firefox OS/Gaia bug 1110030 | Bugzilla@Mozilla, Bug 1110030, "Routing hardware key events to keyboard app when an input field is focusing", https://bugzilla.mozilla.org/show_bug.cgi?id=1110030 | IsSynthesizedByTIPフラグへの言及箇所は要検索です(バグ番号・タイトルのみ確認済み)。 |
| NIOSH Hierarchy of Controls | CDC/NIOSH, "Hierarchy of Controls", https://www.cdc.gov/niosh/hierarchy-of-controls/about/ | elimination・substitution・engineering・administrative・PPEの5段階を示す公式頁です。 |
| Yaron Minsky, "Make illegal states unrepresentable" | 原典記事URLは要検索です(OCamlコミュニティで広まった言い回し)。 | 要検索 |
| "Typestate via Revocable Capabilities" (arXiv 2510.08889) | arXiv番号のみ確認済みで、URLは要検索です。 | 要検索 |
Kubernetes resourceVersion | Kubernetes公式ドキュメント, "API Concepts: Efficient detection of changes", https://kubernetes.io/docs/reference/using-api/api-concepts/ | オブジェクトの世代を表す文字列で、楽観的排他制御に使われます。 |
| Raftコンセンサスアルゴリズムのterm番号 | Diego Ongaro, John Ousterhout, "In Search of an Understandable Consensus Algorithm", USENIX ATC 2014 | 第11章で触れた「任期」番号の出典です。 |
| Gaffer On Games | Glenn Fiedler, "Gaffer On Games"(ネットコード分野で広く参照される個人技術ブログ), https://gafferongames.com/ | URLはトップページのみ確認、個別記事の直リンクは要検索です。 |
| GGPO | GGPO開発チーム, "GGPO Rollback Networking SDK", https://github.com/pond3r/ggpo | ロールバック方式ネットコードの代表的な実装です。 |
Bevy Tick | Bevy公式ドキュメント, bevy_ecs::component::Tick, https://docs.rs/bevy_ecs/latest/bevy_ecs/component/struct.Tick.html | Rust製ゲームエンジンBevyのECSが持つ、フレームごとに増える世代カウンタです。 |
crossbeam-epoch | crossbeam-rsプロジェクト, https://docs.rs/crossbeam-epoch/ | 「epoch」という語は共通ですが、解いているのはメモリ回収問題であり、第11章の観測の陳腐化とは別問題です(false friend)。 |
slotmap / generational-arena | それぞれのcrates.ioページ、https://docs.rs/slotmap/ 、https://docs.rs/generational-arena/ | 世代付きインデックスを持ちますが、値の構築自体を型で制限してはいません。 |
GhostCell / generativity | Joshua Yanovski et al., "GhostCell: Separating Permissions from Data in Rust"(ICFP 2021)、generativity crateドキュメント | ブランド型・phantomライフタイムで不正な構築を防ぐ技法ですが、世代カウンタとの組み合わせ例は確認できませんでした。 |
| Peter Ferrie, アンチデバッグ手法集 | Peter Ferrie, "The Ultimate Anti-Debugging Reference", 2011年初出・以降改訂 | I/Oカウンタ差分によるプロセス挙動の推測が、2013年前後の解説記事に同様の手法として登場します。原典URLは要検索です。 |
| F5/NGINXの受動的ヘルスチェック | F5公式ドキュメント「Passive Health Monitoring」、NGINX公式ドキュメント「passive health checks」 | 専用の確認要求を送らず、既存の通信に相乗りして相手の状態を推測する手法です。 |
| チョード式キーボードの特許(US4,680,572) | United States Patent 4,680,572 (1987年登録) | 複数キーの組み合わせで文字を確定させる入力方式の古典的な特許です。 |
| QMKファームウェアのtap-hold機能 | QMK公式ドキュメント, "Tap-Hold Configuration Options", https://docs.qmk.fm/tap_hold | キーを押す長さで異なる動作を割り当てる、自作キーボード向けファームウェアの機能です。 |
| Rodney Brooks, Subsumption Architecture | Rodney A. Brooks, "A Robust Layered Control System for a Mobile Robot", IEEE Journal of Robotics and Automation, 1986 | 知覚・計画・行動という順序そのものに異議を唱えたロボティクスの古典的論文です。 |
| Joint Detection and Estimation理論 | 信号処理分野における、検出(detection)と推定(estimation)を同時に扱う理論の総称。個別の代表論文は要検索です。 | 逐次処理(先に検出、後で推定)の劣位性を形式的に扱う理論分野です。 |
| Gary Bernhardt, "Functional Core, Imperative Shell" | Gary Bernhardt, "Boundaries"(講演、2012年、Destroy All Software) | 判断を行う純粋な核と、副作用を実行する薄い外殻を分ける設計思想の呼び名として広まりました。講演URLは要検索です。 |
| Android ANR(Application Not Responding) | Android公式ドキュメント, "Keep your app responsive", https://developer.android.com/topic/performance/vitals/anr | メインスレッドが一定時間応答しないアプリを検知する仕組みです。URLは要検索です。 |
| Kubernetes readiness/livenessプローブ | Kubernetes公式ドキュメント, "Configure Liveness, Readiness and Startup Probes", https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ | 固定時間ではなく実際の応答確認によって、通信を回してよいかを判断する仕組みです。 |
| TCP再送タイマーとKarnのアルゴリズム | Phil Karn, Craig Partridge, "Improving Round-Trip Time Estimates in Reliable Transport Protocols", ACM SIGCOMM 1987 | 実測した往復時間をもとに再送までの待ち時間を更新し続ける手法です。 |
| Redux | Redux公式ドキュメント, https://redux.js.org/ | 一回限りの処理と、アプリ全体が持ち続ける状態を意図的に分離するフロントエンド向け状態管理ライブラリです。 |
| JavaScript/Python/Rustのasync/await | 各言語公式ドキュメント(ECMAScript async functions、PEP 492、Rust async book) | コンパイラが一直線に書かれた手続きを、中断・再開可能な状態機械へ変換する仕組みです。個別URLは要検索です。 |
| フライバイワイヤとフライトエンベロープ保護 | 例: Airbus公式資料「Fly-by-wire」解説等 | 操縦桿の意図・センサーの観測・保護則の最終判断を分離する航空機の設計です。個別の一次資料URLは要検索です。 |
| CWE-367 (Time-of-check Time-of-use Race Condition) | MITRE, "CWE-367", https://cwe.mitre.org/data/definitions/367.html | 確認時点と使用時点のずれによる脆弱性の分類項目です。 |
未確認・要確認事項
- 第8章の一部コミットハッシュは、リポジトリでの再確認が未実施です。
- 第8章(ImmCross/LINE偽イベント)に対応するADR番号は未特定です。
- 参考文献のうち「要検索」と記した項目は、出版前に原典を直接確認してください。
- 第11章・第7章・第8章・第3章に追加した関連研究(フェンシングトークン、ゲームnetcode、 Rustの世代付き型、アンチデバッグ手法、受動的ヘルスチェック、労働安全の階層モデル、 チョードキーボード特許、Subsumption Architecture等)の書誌情報は、本セッションで Web検索を伴わずに記憶とmaster-research-logの記述から記載したものです。URL・年号・ 巻号等は出版前に一つずつ裏取りしてください。
- 第1・2・4・9・10・12章に追加した関連研究(ブラウザのイベントループ/Android ANR、 RAIIの他言語比較、Kubernetes readiness/livenessプローブ、TCP再送のKarnのアルゴリズム、 Redux、async/await、フライバイワイヤのフライトエンベロープ保護、CWE-367/TOCTOU等)も 同様にWeb検索を伴わず記憶から記載したものです。出版前に一つずつ裏取りしてください。