05. Cross-Validation Node (盲点 × Must 軸 整合検証)
TL;DR(このノードは何をする・専門語ゼロ): 採点で高得点が付いた下書きでも、「中身が本当に裏付けされているか」までは点数に出ない。このノードは、前段が見つけた見落としの一覧と、本文が「これは絶対外せない」と位置づけた評価項目を一つずつ突き合わせ、その見落としが高評価の土台を崩していないかを照合する。土台を崩す重大な組み合わせが見つかった時だけ差し戻し、それ以外は記録だけ残して次へ進める。
実装:
drp/src/nodes/cross_validation.tsプロンプト: KV IDcross-validation(ADR-0085 で KV lifecycle 管理対象。ノード内FALLBACK_PROMPTをフォールバック) LLM パラメータ: KV IDgate2-consistencyの params を流用 (temperature 0.2 / seed 42) 位置づけ: Gate 4 Scoring の後段、Gate 2 Consistency の前段 (graph.ts:scoring → cross_validation → consistency) テスト: なし (差戻し semantics は ADR-0076 / ADR-0092 の retroactive validation で検証)
1. 役割と位置づけ
Gate 4 Scoring は ADR 文面の品質 (10 項目 × 5 点) を評価するが、「記述内容が検証済みか」は問わない。 文面が巧みでも技術的前提が未検証なら高得点が付き得る。本ノードはこのギャップを埋める。
- 解決する課題: Gate 1 (盲点検出エンジン / ADR-0071) が挙げた blind-spot findings〔blind-spot=盲点。採点の土台を崩しうる未検証の前提や見落とし〕と、本文 §3.1/§3.2 の Must 評価軸〔Must 軸=この決定で絶対に外せない必須評価軸〕スコアを突き合わせ、「ある盲点が、ある Must 軸の高スコアを正当化する前提を毀損 (undermines) していないか」を検証する。
- 設計思想: Scoring (文面品質) と Cross-Validation〔Cross-Validation=交差検証。盲点が採点の前提を崩さないかの照合〕 (内容の検証済み度) の役割分離。
「文面は高品質だが技術的前提が薄い」ドラフトを的確に弾く (
lessons_learned.md参照)。 - 由来: ADR-0076 (Gate 4 後の新ゲートとして新設) / ADR-0092 (採点安定性の可視化と整合)。
Gate 4 と本ノードは相補的。本系列で初めて両方を一度に通過した適用例は ADR-0088 (Gate4 41/50 + Cross-Validation INFO)。
2. フロー図
flowchart LR
SC[scoring] -->|rejected=false| CV[cross_validation]
CV -->|critical×Must×undermines| E[END 差戻し]
CV -->|それ以外| CO[consistency]
3. トリガー条件
| 条件 | 挙動 |
|---|---|
scoring を通過 (rejected=false) して遷移 | 実行 |
blindSpotFindings が 0 件 | スキップ (検証対象なし、crossValidationVerdicts: [] で通過) |
| 本文から評価軸 (§3.1/§3.2) を抽出できない | スキップ (cross_validation_skip ログ、通過) |
差戻しなしで通過した場合も crossValidationDetail / crossValidationVerdicts は生成され、PR 本文・telemetry に残る。
4. 入力 (State)
| State フィールド | 用途 |
|---|---|
blindSpotFindings | Gate 1 が出力した盲点 findings (title / severity / evidence)。検証対象の一方 |
adrBody | 本文。§3.1 から評価軸名と [Must] 重要度、§3.2 スコア表から各軸スコアを抽出 |
評価軸の抽出ロジック (extractEvaluationAxes):
- §3.1:
#tagを含む行から軸名を収集し、同一行に[Must]があれば Must 軸とマーク。 - §3.2「評価軸 × 案 スコア」表から各軸のスコア (整数) を取得。
全 finding × 全 axis の直積ペアを生成 (拡張版: 全 severity / 全 importance を対象、ADR-0086 で telemetry 構造化)。
5. 処理ロジック
1. findings = state.blindSpotFindings (0 件なら即スキップ通過)
2. axes = extractEvaluationAxes(state.adrBody) (0 件なら即スキップ通過)
3. pairs = findings × axes の直積 (各ペア: finding_title/severity/evidence × must_axis/must_score/is_must_axis)
4. params = loadLlmParams('gate2-consistency', { temperature: 0.2, seed: 42 })
5. llm = createLlm(MODELS.consistency='claude-opus', temperature, seed)
6. prompt = loadPrompt('cross-validation', FALLBACK_PROMPT)
7. user msg: pairs (JSON) + ADR Body (context)
8. invoke → JSON 抽出 → verdicts[] (finding × axis ごとに undermines: boolean + reasoning)
9. 差戻し判定 = verdicts.filter(severity==='critical' && isMustAxis && undermines)
- 1 件以上 → rejected=true + buildRejectionMessage
- 0 件 → 通過 (detail のみ生成)
判定の保守性 (FALLBACK_PROMPT より): 「undermines」= 盲点が、その軸スコアを正当化するのに必要な エビデンスを直接否定する未検証前提・未テスト前提・未緩和リスクを指す。軸に関連するだけで スコア正当化を無効化しない finding は undermines としない。LLM には全 input ペアを出力させる (undermines=false 含む全マトリクスを表示・telemetry に残すため)。
6. LLM 設定
| 項目 | 値 | 根拠 |
|---|---|---|
| モデル | claude-opus (MODELS.consistency) | 盲点と評価軸前提の文脈理解・矛盾検出。Gate 2 と同モデルプール |
| temperature | 0.2 (KV gate2-consistency params) | 判定の揺らぎ最小化 |
| seed | 42 | 再現性確保 (ADR-0056 構造化 audit log) |
| プロンプト | KV cross-validation (ADR-0085) | 本文 lifecycle 管理対象。フォールバックはノード内 FALLBACK_PROMPT |
| コスト | finding 数 × axis 数のペアを 1 回の呼出で処理 (cross_validation_start ログで pairs 数を可視化) |
7. 副作用
なし。LLM 呼出のみ。cross_validation_start / cross_validation_complete / cross_validation_skip /
cross_validation_error の構造化ログを出力。
8. 出力 (State)
// 通過
{ crossValidationDetail: string, crossValidationVerdicts: CrossValidationVerdict[] }
// 差戻し (critical × Must × undermines が 1 件以上)
{ rejected: true, rejectionMsg: string, crossValidationDetail, crossValidationVerdicts }
crossValidationDetail: Must 軸を上に置いた評価軸別マトリクス Markdown (毀損は ⚠️、OK は ✅)。PR 本文に転記。crossValidationVerdicts: 全ペアの判定配列。telemetry のcross_validation_verdicts構造化カラムに格納 (ADR-0086 / ADR-0087、schema v3、PR #1134 / #1135)。rejectionMsg:critical × Must × underminesのみを Must 軸別にグルーピングした差戻しメッセージ。
9. 分岐 (次ノード)
.addConditionalEdges('cross_validation', (s) => s?.rejected ? END : 'consistency')
critical × Must × undermines が 1 件以上 → rejected=true で END (差戻し)。それ以外は consistency へ。
10. エラー時の挙動
- LLM 失敗 / JSON parse 失敗: try-catch で fail-open〔fail-open=失敗時に止めず通過させる方針〕 (
cross_validation_errorログ →crossValidationVerdicts: []で通過)。- ⚠️ 他の reject ゲート (triage の ADR-0091 は fail-closed、policy_alignment は throw) と異なり、本ノードは
検証失敗時に通過させる 設計。「検証できなかった = 差し戻し根拠なし」と解釈する。監査上は
cross_validation_errorログの有無で「検証スキップされた起案」を識別できる。
- ⚠️ 他の reject ゲート (triage の ADR-0091 は fail-closed、policy_alignment は throw) と異なり、本ノードは
検証失敗時に通過させる 設計。「検証できなかった = 差し戻し根拠なし」と解釈する。監査上は
- 盲点 0 件 / 評価軸 0 件: スキップして通過 (差戻し根拠が構成できないため)。
11. 既知の弱点・運用注意
- 差戻しは critical × Must のみ: high/medium/low severity の盲点や非 Must 軸の毀損は 情報提供のみ (PR 本文には載るが pass/fail に不使用)。保守的に偽陽性を抑える設計。
- fail-open のトレードオフ: LLM エラー時に素通りするため、
cross_validation_errorが頻発する場合は実質的にゲートが無効化される。telemetry でエラー率を監視。 - 評価軸抽出の脆さ: §3.1 の
#tag+[Must]マーカーと §3.2 スコア表の構造に依存。本文フォーマットが崩れると軸 0 件でスキップされる (Light モードは §3 省略可のため本ノードは実質スキップされやすい)。 - コスト累積: finding 数 × axis 数のペアを claude-opus で処理。盲点が多い起案ではペア数が増える (
cross_validation_startログのpairsで把握)。 - K-fold 安定性〔K-fold=データを K 分割して交互に検証し採点のばらつきを測る手法〕: ADR-0092 で採点安定性の可視化と整合。交差検証系列の拡張余地。
12. テストケース
なし。差戻し semantics の検証は実起案の retroactive validation で実施。
- 適用例: ADR-0093 (v5 で Cross-Validation 差戻し Score 42/50
#reliable毀損 → v6 で前提補強し通過)、 ADR-0095 (v2 強化版が Cross-Validation reject 48/50 → v1 シンプル版採用)。
13. 過去の設計判断ログ
| 日時 | 変更 | 経緯 |
|---|---|---|
| ADR-0076 | Gate 4 後の新ゲートとして新設 | 文面品質 (Scoring) と内容の検証済み度を分離。盲点 × Must 軸の整合を機械的に当てる |
| ADR-0086 / 0087 | telemetry に cross_validation_verdicts 構造化カラム追加 (schema v3) | 全 finding × axis verdict を audit に残す。PR #1134 / #1135 |
| ADR-0092 | 採点安定性の可視化と整合 | K-fold 系列との整合、軸別毀損トレンドのダッシュボード化計画 |
| (拡張版) | 全 severity / 全 importance のペアを LLM に渡し、差戻しは critical × Must のみに限定 | 表示用マトリクスは網羅、差戻しは保守的に |
14. 関連リンク
- 前ノード: 04_scoring.md
- 次ノード: 06_consistency.md
- 関連 ADR: