# AI生成コードをどうレビューする？テストが緑でも反例と証拠で確かめる

> Canonical: https://vxi.me/ja/posts/vibe-coding-ai-code-review-evidence-gates/
> Locale: ja-jp
> Published: 2026-10-25
> Updated: 2026-10-25
> Markdown: https://vxi.me/ja/posts/vibe-coding-ai-code-review-evidence-gates.md

<figure class="ai-review-evidence-hero not-prose" lang="ja-jp">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.still.ja-jp.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.still.ja-jp.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.mobile.ja-jp.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-evidence-gates.ja-jp.svg" alt="3つの異なるコード版で8/8、10/10、12/12のテストが通過。独立レビューで4種類の欠陥が見つかり、別途4つの攻撃プローブを拒否。オフライン限定。" width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">異なる3つのコード版と独立レビューの結果。テストの緑表示は本番公開の承認ではありません。</figcaption>
</figure>

Vibe Codingでコード生成は速くなりましたが、レビュー負担まで同じように減ったわけではありません。LLMは整ったdiffを素早く作れます。しかし、流暢な説明やテストの全成功だけでは、本番に出してよい実装だとは証明できません。

今回扱うのは、このブログプログラムで使うアニメーション付きターミナルSVG向けのビルド前バリデーターです。限定したXML/CSS規則、manifestのパスとSHA-256、8言語の記事が可視のデモ画像を参照しているかを検証します。固定canonical SVGは正例です。4種類の欠陥はマージ前の隔離テストで見つかり、証拠はこのバリデーターのオフライン範囲に限られます。

最初のテストは **8/8成功** でしたが、独立レビューで誤受理が2件見つかりました。最初の修正後は **10/10成功**、さらに2件が見つかりました。最終固定版は主テスト **12/12成功**。独立レビュアーが追加の攻撃プローブ4件を実行し、すべて拒否されました。JavaScript 3ファイルも node --check を通過しています。

範囲と不変条件を固定し、最小反例を作り、実際の失敗を確認できたテストだけをREDと記録し、正確なcommitで正例・負例を独立再検証し、リスクに応じて判断します。ロジックテストはbuildや対象プラットフォームの検証を代替しません。高リスク変更では範囲、Base/Head commit、不変条件、正例、反例、実際のコマンドと環境、未検証の境界、結論を証拠カードに記録できます。validatorはブログのビルド前コンポーネントであり、独立配布の汎用CLIではありません。レビュー時間の短縮も測定していません。

公開リポジトリには、サニタイズ済みの合成反例と実行可能な教材テストだけを置いています。このブログプログラムの本番用validator、修正コード、内部アーキテクチャは含みません。本文の8/8→10/10→12/12は元の工程で行ったオフライン検証の結果です。公開テストは4種類の欠陥パターンを再現するだけで、本番実装を複製していません。

<a class="gh-repo-pill" href="https://github.com/vxime/ai-code-review-counterexamples/tree/74d9f12331ef4d015bf882a6e09d66e59434368b" target="_blank" rel="noopener noreferrer"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" width="18" height="18"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645.12.54 2.873.12 3.176.765.84 1.23 1.91.81 3.22.61 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606.21 2.896.015 3.286C20.565 22.092 24 17.592 24 12.297"/></svg><span>サニタイズ済みの合成例と再現可能なテスト</span></a>

## なぜ8/8のテストが通っても不正な入力を受け入れたのか

テストが緑でも、現在の入力が現在のassertionを満たしただけです。すべての不正入力が拒否される証明ではありません。

最初の反例では、許可された画像と未許可の別記事リソース参照を同じ行に置きました。旧ロジックは許可された画像タグを一つ見つけると行全体を通しました。独立レビューは別slugに実在するSVGを作り、8言語の記事の正しい画像と同じ行にMarkdown参照を追加しました。存在しないファイルによる偽の失敗を避けるため、参照先も実際に作っています。旧実装は posts_checked=8 で成功しました。

~~~html
<img src="/assets/terminal-demo.svg" alt="Terminal demo" width="356" height="137" /> ![foreign](../foreign/terminal-demo.svg)
~~~

2つ目はファイルシステム競合です。検査したパスと実際に開くファイルが異なる可能性があります。checkout内のパスには誤ったバイト列を置き、外部には期待SHA-256と一致するcanonical SVGを用意しました。親ディレクトリを外部へのsymlinkに置き換えると、旧実装は外部ファイルを読み誤って受け入れました。回避されたのはファイルの出所制約で、ハッシュ検証ではありません。

最終Linux実装は信頼されたcheckoutのルートを開き、開いた親ディレクトリのファイル記述子を基準に各要素を解決します。中間要素はディレクトリで O_NOFOLLOW を使い、最終ファイルもsymlinkを拒否します。型、リンク数、サイズを調べ、読み取り前後のdevice、inode、sizeを比較してからdigestを確認します。/proc/self/fd が使えなければfail closedです。検証範囲は信頼されたcheckoutとprocfsを前提とし、同一UIDやホスト権限の攻撃者は対象外です。[Linux openat2(2)の説明](https://man7.org/linux/man-pages/man2/openat2.2.html)も参照してください。

## 第2ラウンド：ハッシュは正しくても、内容が見えない

最初の修正後、テストは **10/10成功** しました。3つ目はreduced motionの最終フレームです。すべてのフレームが透明から始まり、通常のアニメーションで表示されます。reduced-motion設定でアニメーションだけを止め、最後のフレームを表示しなければ画面が空白になります。

~~~css
.f { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  #f0 { animation: none; opacity: 1; }
  #f3 { animation: none; } /* 最終フレームは透明のまま */
}
~~~

テストはSVGのSHA-256、サイズ、content-addressed filename、canonical manifestを再計算して周辺検証を維持し、「最終結果が見える」という契約を検証しました。[MDNのprefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/prefers-reduced-motion)を参照してください。

4つ目はMarkdownコメントです。旧実装は正規表現で画像タグを数え、HTMLコメントを除外していませんでした。唯一の画像をコメントにすると、ソースにタグが残ってもページには表示されず、旧ロジックは画像1枚と数えます。[CommonMark 0.31.2のHTML block規則](https://spec.commonmark.org/0.31.2/#html-blocks)では、コメント内のタグは可視画像ではありません。

~~~html
<!-- <img src="/assets/demo.svg" alt="Terminal demo" /> -->
~~~

<figure class="ai-review-counterexample not-prose" lang="ja-jp">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.still.ja-jp.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.still.ja-jp.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.mobile.ja-jp.svg" type="image/svg+xml"><img src="/images/posts/vibe-coding-ai-code-review-evidence-gates/ai-review-counterexample.ja-jp.svg" alt="最初の修正版が10/10に合格した後も、HTMLコメント内の画像を可視画像と誤認する問題が見つかった。最終版は12/12に合格し、追加4件を拒否。" width="1200" height="700" loading="lazy" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption style="font-size:0.8rem;line-height:1.5;opacity:.78;margin-top:.35rem">緑のテストでも反例を見逃します。HTMLコメント内の画像タグは可視画像ではありません。</figcaption>
</figure>

## テスト結果を正確なバージョンに結び付ける

| コード版 | 主テスト | 後続の独立レビュー |
| --- | --- | --- |
| 初期版 | 8/8成功 | SEC-01、SEC-02の誤受理 |
| 第1回修正版 | 10/10成功 | SEC-03、SEC-04の誤受理 |
| 最終オフライン検証版 | 12/12トップレベルテスト成功 | 追加4プローブをすべて拒否 |

最終12件にはSEC-01〜04の回帰テストが含まれます。追加の4プローブは12/12に含まれず、16/16と合算してはいけません。JavaScript 3ファイルは node --check に通りましたが、構文検査は動作テストの代わりにはなりません。

後半2件の回帰テストはソース修正より前にコミットされましたが、テストだけのcommitで全テストは実行されていません。したがって完全な10/12 REDの記録はありません。確認できるのは、旧版の完全なvalidatorで誤受理が再現され、最終版の主テストと追加プローブが通過したことです。

動作の証拠が揃ってから複雑度を確認します。helper、分岐、設定、呼び出し元が何の不変条件を守るのか、重複か、実際に使われているかを追います。コードが短いほど単純とは限らず、パス保護を削ると競合が再発する可能性があります。


<!-- vxi-evidence-panel: ai-code-review-counterexamples -->

関連記事：[Mutation Testing](/posts/ai-generated-tests-mutation-testing/)、[OpenQodexのpre-pushレビューのfail-open境界](/posts/openqodex-prepush-review-fail-open/)、[Diffleのレビューコメントアンカー](/posts/diffle-git-diff-comment-anchoring/)。

## テスト通過から本番承認までに残ること

最終独立検証は Node.js v22.16.0、Linux x64のオフラインvalidatorを対象にしました。主テスト12/12成功、追加4攻撃プローブはすべて拒否、canonical SVG・manifest・8言語の正例が成功し、JavaScript 3ファイルも node --check を通過しました。

これは固定ルールと対象環境で4種類の既知の誤受理が修正されたという限定的な結論を支えます。ブログ全体の本番準備完了を証明するものではありません。悪意ある並行書き込みに対するネイティブmacOSの動作は未検証で、サイト全体のbuildと最終ページの実行確認もオフライン試験の範囲外です。

実際のリリース判定はリスクごとに分けます。単体テスト、静的検査、buildと統合テスト、対象プラットフォームと実行時確認は、それぞれ異なる境界を検証します。重要な境界が未検証なら、局所的な成功を全体の本番準備完了と表現してはいけません。

契約と反例で疑うべき動作を見つけ、正確なバージョンのテストと独立証拠で次に深く読む場所を決める。人の判断をなくすのではなく、「問題なさそう」を証拠と範囲が明確なレビュー判断に変えるのです。

## Verification context

- Level: first-party
- Verified: 2026-10-25T00:00:00.000Z
- Environment: Node.js v22.16.0 / Linux x64
- Evidence mode: mixed
- Public repository: https://github.com/vxime/ai-code-review-counterexamples
- Evidence commit: 74d9f12331ef4d015bf882a6e09d66e59434368b
- Evidence receipt: https://vxi.me/evidence/vibe-coding-ai-code-review-evidence-gates.json
