8分

四回とも、テストは緑だった

ここまで書いた四本のディープダイブには共通点があります。あると信じていたものが実際には無く、それを教えてくれたのは毎回テストではなく本番でした。

  • 振り返り
  • テスト
  • 可観測性
  • 運用
実行者、操作種別、時刻を表示したOpenMakeの監査ログ画面
実際に何が起きたかは、結局は記録が決めます。四回とも、見に行くまで誰も知りませんでした。

SHIPPED / EVIDENCE

この記事で扱うこと

大容量アップロード、MCPの分離、実行の計測、推論バックエンドの移行。四件を並べると失敗の形は違いますが構造は同じでした。コードは書いたとおりに動き、テストも通り、あると信じていた保証だけがその場に無かったのです。

振り返った事例
4件
発見時のテスト
すべて通過
分離・計測の実測値
0
発見の経路
すべて本番

01

同じ失敗が四つの形で出た

四本を書き終えてようやく見えたことがあります。四件とも、バグを直した話ではありませんでした。コードはおおむね書かれたとおりに動き、ユニットテストも回帰テストも通っている状態でした。

問題は別の層にありました。このコードで何かを保証したと信じていたのに、その保証が実際には成り立っていなかったのです。そして四回とも、それを教えてくれたのはテストではなく、実際に動かしてみた結果でした。

四つがそれぞれ違う種類の隙間だったので、一つずつ並べ直すと、テストが何を証明し何を証明しないのかが少しはっきりします。

02

一つ目、自分たちが決めた上限は本当の上限ではなかった

Agent Taskのファイル添付にはアプリケーションが決めたサイズ上限があり、その上限はよく検証されていました。ところが公開のリクエストはアプリケーションに届く前にプロキシを通ります。エッジで100MBに引っかかって切られると、こちらの検証も進捗表示も始まる前に失敗します。

テストは自分たちのコードの内側しか見ません。リクエストがコードへ届く前に死ぬ場合は、テストの視界の外です。これはコードが間違っていたのではなく、システムの境界をコードの境界と同じだと勘違いしていたということでした。

現れ方もコードの外でした。実サービスのアドレスへ130MBのファイルを送ってみて、保存された結果のハッシュが元と同じかを確認して初めて、経路の全体が証明されました。

03

二つ目、フォールバックが静かに通常経路になっていた

外部MCPサーバーをOSレベルで分離する機能を作る際、道具が使えない環境では元のコマンドをそのまま実行して警告だけ残すようにしました。分離できないからといってサービスを落とさない、という判断自体は妥当です。

その判断を覆したのが、選んだ道具がLinux専用で本番ホストがmacOSだという事実でした。例外時のためのフォールバックが、すべてのリクエストで作動していたのです。コードもあり、テストも通り、実際の分離は0でした。

テストが検証したのは、ゲートが条件に応じて開閉するという事実でした。本番環境でそのゲートが常に閉じていることは検証の対象ではありませんでした。これはバグより厄介です。設計どおりに動いた結果なので、誰も違和感を持たないからです。

04

三つ目、計測が誰も通らない道に付いていた

ツール結果がどれだけ切り詰められるかを、直す前にまず測ろうと計測を入れました。その計測自体は正確に動いていました。ただ、サンドボックス作業のツールは、計測を付けた実行経路を通りません。

だから数字は0でした。ツール結果のステップは2件あるのに、記録の試行は一度もありません。0は何も起きなかったとも、何も見えていなかったとも読めるのに、その二つを見分ける手段がダッシュボードにはありませんでした。

これは前の二つとは少し性質が違います。ここで間違っていたのは保証ではなく観測でした。観測が間違っていると、その上で下すすべての判断も一緒に間違います。

05

四つ目、機械的な置換がパターンの外を残した

推論バックエンドを移すとき、呼び出し元100か所超をcodemodで一度に移行しました。設定オブジェクト経由で読む形はすべて拾えました。環境変数を直接読む箇所は形が違い、そのまま残りました。

残ったコードはコンパイルも通りテストも通ります。旧バックエンド専用の経路を呼び、その応答形式を前提にするコードが静かに生きていても、その経路を実際に通るまでは何の合図もありません。

一括置換は強力ですが、何を取りこぼしたかは教えてくれません。その日の夕方に残りを自分で探しにいったことが、この作業で最も重要な段階でした。

06

四回とも同じやり方で表に出た

見つかった経路を並べると、パターンがはっきりします。130MBのファイルを実サービスへ送ってみた、本番ホストがどのOSかを確認した、ブラウザーでチャットから作業まで一度通してみた、codemodを回した後に残った参照を自分で探した。

どれも低コストです。何日もかかる調査ではなく、一度実際に動かしてみることでした。それでも四件とも、その一度をやるまで誰も知りませんでした。

テストが無意味だという話ではありません。四件ともテストは自分の仕事をしました。回帰を防ぎ、リファクタリングを安全にし、規則が壊れれば教えてくれました。ただテストが証明するのは、こちらが検証すると決めたことだけであり、毎回間違っていたのはその定義そのものでした。

07

その結果として変えたこと

この四回を通じて三つの習慣が定着しました。

一つ目、静かなフォールバックをやめました。いまは分離が効いたサーバーがログに並び、効いていないサーバーは設定にそう書かれています。見えない状態は、間違っていても気づけません。

二つ目、分からないことをデータとして残します。実行ステップを計画ノードへ紐づけるとき、分からないものは推測せず空のままにします。値を作ってしまえば、その指標を根拠に使った瞬間に判断が汚染されます。

三つ目、残すものには理由と期限を書きます。データベースの制約に縛られて変えられなかった識別子も、リスクを承知で先送りしたバージョン固定も、コミットにそう書いてあります。だから一つは十三日後に片づき、もう一つはまだ残っていると今も分かるのです。

08

この振り返りが捕まえられないもの

上の三つの習慣も事後対応です。四回とも問題が起きたあとに学んだことであり、次の隙間がこの三つのどれかの形をしている保証はありません。

いまも開いているものがあります。アップロードのプロトコルは完了段階でファイルのチェックサムを受け取らないため、バイト同一性をプロトコル自体では証明できません。MCPサーバー2つはホストに入れたバイナリに依存して非分離で動いており、パッケージのバージョン固定は一斉に壊れる恐れから先送りのままです。

これらを書き残すのは、解決したと言わないためです。四回の共通点がそれでした。問題は自分たちが知らないと分かっている場所ではなく、知っていると信じていた場所にありました。

根拠資料

根拠資料

Engineering Logへ戻る