今日は、Omoikaneのコードを書き進めるだけではなく、AIたちが作った変更をどう検証し、どこまで終わったら取り込むかを考えていた。実装案は増える。レビューの報告も増える。それでも、mainに入る変更が同じ速さで増えるとは限らない。
朝に状況を確認した時点では、未マージのPRが27件あった。そのうち18件がDraftだった。保存用のPRも含まれるので、27件全部を急いでマージすればいいわけではない。しかし、個々の担当が仕事を進めているのに、統合までの流れが細くなっているのは気になった。
最終的には、CIのフォント準備の改善と正規表現の修正がマージされ、拒否されたWorkerの無駄な初期化を防ぐ変更もDraft PRになった。長く追っているiframe保持テストについては、ようやくbootstrapの内訳を一度測れた。運用側では、ブラウザから判断を返す承認UIを作った。
この記事は2026年11月5日18時06分ごろまでのroomの報告と、執筆時に確認したGitHubの状態を基にしている。参加端末のハンドルネームは伏せ、実装担当、検証担当、調整担当という役割で書く。前日の記事がGCの改善候補と解析ツールの話だったので、今日はそこから先の、検証と判断を回す仕組みの話になる。
作業が進んでいることと統合が進んでいること
cc-sakuraのroomを見ると、各担当はかなり細かく動いている。修正案の準備、テストの実行、実行ログの保存、別担当による確認。そのどれも必要な仕事だが、投稿数だけで進捗を判断すると、実際より順調に見えることがある。
一つのPRについて、対象テストは通っていても全体テストは終わっていない場合がある。全体テストが通っていても、CIの特定の環境では失敗することがある。あるいは、その失敗自体は以前から知られていても、確認に必要な結果ファイルが保存されず、残りの条件を確かめられないことがある。
今日も、MacのGate5で起きる既知の時間制限が複数のPRに影響していた。一方で、すべてをその問題だけで説明できるわけではない。ローカルに別の失敗を残しているPRや、前提となるPRがまだ取り込まれていない変更もあった。表面上はどれも赤いチェックだが、止まり方は違う。
私が知りたかったのは、誰が忙しいかということより、どの条件が満たされれば次へ進むのかだった。そこが曖昧なままだと、担当者は調査を続けられるし、新しい課題もいくらでも見つかる。けれども、古いPRの完了は後回しになる。
そこで、古いCI改善や正規表現のPRを完了させる流れを優先し、新しい案は別の作業領域で準備するところまでにする場面があった。手が空いているから新しいPRを増やす、というだけでは解消しない詰まり方だった。
重い検証が一台に集まっていた
重い全体検証は、メモリとストレージに余裕があるLinuxのx86環境へ集まっていた。小さい環境で全体テストを走らせると、プロセスがSIGKILLで終わり、最後の集計まで到達しないことがあったためだ。
大きい環境へ委譲する判断そのものは合理的だった。ただ、その結果として、複数の案件が同じ列に並ぶ。全体テストを実行し、その後に通常ビルドを確認する。それが終わるまで次の案件は始めない。依存のビルドからやり直す場合には、待ち時間も長い。
別の環境にはCPUの余裕があったので、もう少し評価を引き受けられるのではないかと聞いた。調べてもらうと、CPUが空いていることと、検証を始められることは別だった。その環境はLinuxのARMで、メモリは約8GiB、swapはなく、ビルド用領域の空きは約8GiBしかなかった。
一方、ソースを置く共有領域には空きがあった。しかし、そちらはVirtioFS経由で、ビルド用のguardが実行権限の問題を理由に拒否する構成だった。空いているディレクトリを指定するだけでは解決しなかった。
CPU、メモリ、ディスクの空き、ファイルシステムの性質は、別々に確認しないといけない。さらに、ハンドル名から実行OSやCPUを推測するのも危ない。今回も、Mac側の環境と物理的な資源を共有していても、実際に検証を回す中身はLinuxのARMだった。
キャッシュを掃除して第二の評価環境を作る
容量を調べると、完了済みの作業に使った約40GiBのビルドキャッシュが見つかった。ただし、ビルド成果物には、後から失敗を調べ直すためのテストバイナリが含まれている。全部を一括で消すと、結果の根拠まで失う可能性がある。
そこで、対象のキャッシュを別のファイルシステムへ圧縮退避し、元の内容と照合してから整理する流れになった。ソースのworktreeやブランチ、ログは残し、削除対象を一つのtargetに限定する。空き容量も、丸めた表示だけでなく実際のbytesで確かめた。
回収後は約48GiBまで空きが戻ったが、それでも開始条件のぎりぎりだった。表示が「48G」だから大丈夫とは限らず、ほかの担当が少し書き込くだけで下回る。途中では数MiB足りずに開始を見送る場面もあった。
最終的に、この環境は正規表現修正のengine側の評価を引き受けた。関係するengine群で2,044件、正規表現の対象で11件が成功し、合計2,055成功、失敗0、除外6件だった。新しく追加したcanonicalizationの回帰テストも成功した。全体テストは別のx86環境に残したまま、切り分けられる評価を並行して進められたことになる。
これはマシンを一台追加した話ではない。既存の資源を整理して、CPUが空いている環境を実際の評価に使える状態へ戻した話だ。ただし、全体検証を同じように任せられるほどメモリが増えたわけではない。その環境に合う範囲を割り当てる必要は残る。
手元のMacも掃除した
room側の整理とは別に、手元のMacにも使い終わった生成物がたまっていた。ディスクの空きは約53GiBで、古い検証用targetやブログのプレビューが大きかった。
こちらでは、比較用バイナリやログを残し、増分ビルドキャッシュ、再生成できるブログの出力、解析ツールの検証用ビルドを整理した。空きは約58GiBになり、実際に増えたのは約5GiBだった。ディレクトリの表示サイズと、ファイルシステム全体で回復した容量は同じ数字にならないので、回収量は整理後の空きで見るようにした。
外付けSSDをDockerへ渡す案も考えた。しかし、起動済みコンテナのマウント構成を変えるには通常はコンテナの再作成が必要になる。Mac側のディレクトリを共有するだけでは、先ほどの共有ファイルシステムの問題が残る可能性もある。
今は再起動できないので、そこは先に進めなかった。追加のストレージが欲しいという話と、今の作業を止めずに使えるという話は分けて考える必要があった。今日できる範囲では、保持すべきものを明確にしてキャッシュを整理する方が現実的だった。
bootstrapのどこに時間がかかっているのか
iframeを80世代保持するテストの課題は、前日から続いている。GCの改善候補は得られていたが、それだけでMacの時間制限が解消したとは言えなかった。
今日の静的監査では、大量に生成される関数やアクセサーの構造が詳しく分かってきた。しかし、生成される数が多いからといって、そのまま共有できるわけではない。公開される関数の同一性や、Realmごとの状態、HomeObjectなどの保持関係がある。
小さいhelperをinline化する候補も見つかったが、それも安全な削減として確定したわけではない。引数の式にgetterや文字列変換が含まれれば、展開した結果、評価回数を増やすだけで挙動が変わり得る。候補の数から改善率を出すのは早かった。
そこで、まず時間を分けて測る方針を選んだ。parse、ASTのclone、scope解析、link、bytecode生成、evaluateといった区間を記録し、どこを調べるべきか決める。大きな再設計に入る前に、一度内訳を見たかった。
親のbootstrapが約276ms、iframe側が約291msだった。内訳は次のとおりだった。
| 区間 | 親 | iframe |
|---|---|---|
| bootstrap全体 | 276.2ms | 290.6ms |
| parse | 137.7ms | 123.9ms |
| AST clone | 19.8ms | 20.0ms |
| scope解析 | 21.2ms | 21.4ms |
| link | 55.3ms | 63.7ms |
| うちbytecode生成 | 49.8ms | 58.0ms |
| evaluate | 40.4ms | 60.5ms |
bytecode生成はlinkに含まれるので、両方を足してはいけない。ほかの区間も包含関係を確認し、兄弟の区間が重ならないことを確かめたうえで集計している。
条件も大事で、これはLinux上のtest profileで一回実行した値だ。GCの計測と時間記録そのものの負荷を含む。release buildやMacの絶対値ではなく、改善率を予測できる数字でもない。それでも、この実行ではparseが親の約50%、iframe側の約43%を占めていたことが分かった。
キャッシュが両方missだった
今回観測したのは、親のbootstrap一回とiframeのbootstrap一回の、合わせて二つだ。どちらもsource cacheとparsed cacheがmissだった。二つのbootstrapでは、束縛名の組が異なっていた。
ここを雑に読むと、「iframeを二回作ってもキャッシュが効かない」と言ってしまいそうになるが、その測定はしていない。warm状態の繰り返しを測った結果ではない。分かっているのは、今回の親とiframeの二つでmissしたということまでだ。
キャッシュに当たらない理由が、必要な分離によるものなのか、共有できる部分があるのかは、別の調査になる。時間を測る前からimmutableなtemplateを再利用する案はあったが、compileの時間が大きいから即採用、というわけにはいかない。再利用後に構築し直す状態のコストもある。
次は、キーの作られ方、束縛名の差の由来、キャッシュの所有範囲や寿命をコードから読む静的調査を承認した。追加の大きな計測や製品変更まで一括で許可したわけではない。まず、このmissが何を意味するのかを知りたい。
また、iframeのscenario全体にはbootstrap外の約238msも残っていた。そちらを細かく分ける選択肢もあるが、最初に調べる対象はparse側にした。今日の計測は原因を一つに断定するものではなく、次の質問を具体的にするためのものだった。
計測用コードも普通に失敗する
この計測は一発では動かなかった。最初のビルドは、テストモジュールのjson!のimport漏れで失敗した。計測本体はまだ実行されていない段階だった。
別に、Cargoが返すartifactの種類を選ぶスクリプトにも誤りがあった。対象crateはrlibとcdylibを指定しているのに、選択条件が単純なlibを期待していた。実際のCargo JSONを照合して分かった問題だ。
複数のAIがソースを読み、レビューを通した後でも、こうした不備は残る。二人が確認したという人数だけでは、独立した確認になっているとは限らない。同じ前提を共有し、同じ間違いを見逃すことがある。
修正は、importの一行追加と、artifactの種類を実manifestに合わせる変更を中心にした。失敗したrunを上書きせず、新しい出力先を使い、ビルドキャッシュは同じworktreeに結び付いたものを再利用した。最終的には、最初のcompile失敗一回、計測本体はその後の一回で成功、という経緯になった。
ここから、次の新しいRust変更では、実行と同じfeatureやtoolchainを使うcargo checkを試す方針になった。ただし、checkの成功はlinkやテスト成功の保証にはならないし、追加の時間と容量も使う。変更範囲に応じて対象を選び、費用も測る。
Python側のartifact選択は別の問題なので、実際のCargo JSONを正例にし、種類違い、別のmanifest、余計なfeatureなどを拒否する対照も用意することになった。Rustのコンパイル確認と、実行スクリプトの契約は、別々に確かめる必要がある。
拒否されたWorkerを作りかけない
Dedicated Workerの修正PRでは、ポリシーで起動を拒否した場合にも、子のJavaScript runtimeを作っていた経路を扱った。拒否できる条件が既に分かっているなら、重いbootstrapを始める前に止めたい。
最初の対象十テストでは、修正前の二成功・八失敗から、修正後は十件成功になった。しかし、そのあと追加したCDPテストが全体実行で失敗した。エラーは同一オリジンの制約で、テストが確認したかったmarkerのassertまで到達していなかった。
この失敗を、Worker修正そのものの欠陥とも、単なるflakyとも、その場では断定しなかった。テストの準備方法を修正し、狙った処理まで到達する形に変えた。
さらに、掃除処理を意図的に外した変異版を用意し、そのときに期待するassertで落ちることも確認した。準備の失敗や時間制限で落ちただけでは、修正を検出するテストになったとは言えない。どのassertに到達して、何を比較して失敗したかが重要だった。
最終的に、修正版は全体6,323成功・失敗0・除外24件、追加した十四テストもそれぞれ成功し、通常ビルドまで終わった。同じ検証済みtreeでDraft PRを作成した。Shared Workerの問題まで解決したわけではなく、元の課題もそのまま残る。実装範囲を広げず、Dedicated側の変更として区切った。
古いPRを完了へ持っていく
フォント準備のCI改善PRは今日マージされた。Linuxで必要なフォントを準備する手順を有界にし、基盤側で失敗した場合の記録を残す変更だ。関連するフォント準備のIssueも閉じた。
このPRでもMacのチェックは問題を残していた。だが、変更対象のLinuxフォント準備はMacでは実行されず、Linux二環境の結果は通っていた。変更の範囲と失敗の範囲を見て判断したのであって、赤いチェックを全部無視する方針にしたわけではない。
正規表現のPRもマージ済みになった。engine側とroot側を分担して検証し、通常CIとReleaseは成功した。一方、ARMのGUI Pointer Lockには失敗を残していた。これも個別の残件として扱う必要があり、全ワークフローが緑だったという説明にはしない。
Gate4のintegration分割PRは、夕方の停止前に全十shardの原結果を確認し、実CIの集約がgoになった。6,399件の成功を確認し、partition間の重複や欠落も照合した。ただし、その時点ではGate5のsuiteがまだ実行中で、PRは未マージだった。
こういう記録は細かいが、「通った」の対象がどこなのかを残さないと、再開時に同じことを調べ直す。完了した部分を完了として保存し、残りだけを待てるようにするのも、今日の作業の一つだった。
nayutaにseenの条件付き説明が入った
調査道具のnayutaはv0.5.0になった。前日に話題にしていた、訪問済み集合を使うworklistの重複排除に、限定された構文を認識する説明が加わった。
例えば、ローカルなVecとHashSetを作り、popした頂点についてseen.insertが成功した場合だけ隣接リストを走査する形だ。対象のテンプレートなら、到達可能な頂点をV、隣接リストの要素数をEとして、条件付きの期待O(V+E)を表示する。
開始点ごとにseenを作り直す呼び出し元では、探索同士をまとめて一回分にしない。各呼び出しの到達範囲の仕事を合算する。集合のclearや未知の呼び出し、追加処理などがある場合は、その説明を付けない保守的な実装だ。
元のO(N⁴)などの構文上の候補は消さず、追加の説明として表示する。警告が消えないから対応していない、という読み方も、説明が出たから任意のグラフ探索を証明できた、という読み方も違う。
全38テストとCLIの出力を確認して、作業者へ配布した。不具合があれば、バージョン、最小再現、実出力、期待結果を添えてnayutaへIssueを出してもらう。道具を渡したところで終わりではなく、実際の開発で使った反応を戻す形にしている。
承認を会話の中に埋めない
今日もう一つ作ったのが、cc-sakuraのHTML UIで判断依頼をためる機能だ。roomで「これを進めてよいか」と聞かれても、通常の進捗投稿と同じ列に流れるので、あとから未回答だけ拾うのが大変だった。
特に、私は実装担当ではなくシステムオーナーなので、必要なのはすべてのレビューの細部に参加することではない。どの判断が必要で、その選択によって何が動くのかを把握したい。担当者間で解ける相談と、管理者が決める依頼が同じ表示だと、そこが分かりにくい。
新しいUIでは、作業者がapproval_requestという型の投稿を出す。タイトル、許可してほしい範囲の説明、選択肢を持たせる。未回答の依頼を一覧にし、選んだ回答をapproval_decisionとして元投稿への返信にする。
{
"type": "approval_request",
"title": "キャッシュのキーを静的調査する",
"description": "コード読解のみ。追加計測と製品変更は含まない。",
"options": ["承認", "保留", "却下"]
}
自由文をAIに読ませて、勝手に承認待ちへ変換する方式にはしていない。判断の単位と選択肢を、依頼する側が明示する。承認される対象が一つの投稿で分かることを優先した。
この形式は、特別な新しいserver APIを必要としない。既存のhistoryとsendを使うので、旧clientでも普通のJSON投稿として送受信できる。新しいHTML clientだけが、投稿を承認待ちの一覧として解釈する。
再起動しても判断が残るようにする
承認待ちの状態をブラウザの一時的なメモリにだけ置くと、再起動した時に消えてしまう。そこで、room履歴を正とし、依頼と回答から状態を復元するようにした。初回は履歴を読み、新着は差分だけ取得する。
同じUIプロセスでは判断の投稿を直列化し、送信前にも回答済みか確認する。二重クリックがあっても同じ回答を重ねて送らないようにした。ただし、別々のUIプロセスから同時に送る場合まで厳密な一回性を保証するものではない。
投稿は明示的に--allow-approvalsを付けた場合だけ有効にする。従来どおりの起動なら閲覧のみだ。有効なUIのURLを知る人は、その設定された参加者の名義で判断を返せるので、閲覧リンクの扱いも変わる。
また、roomに判断が投稿されることと、CodexやClaude自身のsandbox権限が変わることは別だ。AI側には、既知のownerのsenderとhandle、元の依頼ID、選択肢の一致を確認するよう周知した。payloadのsourceという文字列だけを本人確認に使うわけではない。
承認UIのPRを作り、起動とHTTP接続ログのPRとは分けてマージした。UIを更新し、承認用APIまで応答することも確認した。既存の自由文依頼は自動では一覧に入らないため、各AIに新しい形式を通知するところまで行った。
待機列を増やす前に環境を確かめる
第2のx86環境で検証を分担する案も承認した。ただし、これは有料のマシンを調達してよいという意味ではない。まず既存の参加環境を確認し、どの作業を移せるかを具体化する方針だ。
候補の一つは空き容量が不足していた。保管中の約33GBのtargetには、過去のPRや検証の証跡が関係していて、単なる不要キャッシュとして解放できないとの報告だった。必要条件から見ると、約68GiBの追加確保が必要になる。
一方、ARM環境でengine側を分担できたように、全部の検証を同じ環境で回す必要もない。x86で確認すべきこと、ARMでも進められること、ソースを読むだけで進められることを分ける。その区別をしないで「余っているCPUへ投げる」だけだと、メモリか容量でまた止まる。
新しい環境を増やす案には魅力があるが、まず今の待ち時間がどこから来ているかを見たい。依存の再ビルドなのか、全体テストの時間なのか、結果のレビューなのか。今日の記録では、その三つがどれも無視できなかった。