ADR-0086: telemetry に cross_validation_verdicts 構造化カラムを追加
- Status: Accepted
- Mode: Standard
- Kruchten Type: Existence/Property
- Scope: platform
- Implementation Status: Done (PR #1135)
- 起案者: [email protected]
- 起案日時 (JST): 2026-05-29 18:06
- 承認日時 (JST): 2026-05-29
- Deciders: [email protected] (単独)
コンテキスト
1.1 背景
ADR-0076 で導入した Cross-Validation Gate は、各 (blind-spot finding × evaluation axis) ペアに対する verdict (undermines: true/false, reasoning) を生成し、pipeline state の crossValidationVerdicts 配列に格納する。しかし TelemetryRecord (D1 schema) には cross_validation_detail TEXT (markdown サマリ) しか保存されておらず、構造化された verdict データは DB に残らない。
1.2 現状
- pipeline_consumer.ts:
cross_validation_detail: (r.crossValidationDetail as string) ?? nullのみ。r.crossValidationVerdictsは破棄 crossValidationVerdictsの型:Array<{findingTitle, findingSeverity, mustAxis, mustScore, isMustAxis, undermines, reasoning}>- PR #1112 で extended (全 finding × 全 axis) になり、1 run あたり 10-40 verdict が生成される
- markdown サマリは人間用、機械的な集計には使えない
1.3 課題
(1) 「過去 1 ヶ月で最も毀損が多い Must 軸はどれか」「critical 盲点で実際に reject に至ったケース統計」のような SQL 集計が不可能 (markdown を grep するしかない)。 (2) 将来の改善施策 (Cross-Validation 精度改善、軸別の閾値調整等) のための分析データが失われる。 (3) /audit/runs API は markdown を返すだけで、UI 側で構造化データとして再構築できない。
1.4 制約・要件
- D1 スキーマ変更は ALTER TABLE ADD COLUMN (破壊なし)
- 既存 v2 レコードの後方互換: 新カラムは NULL
- verdict 配列のサイズ: 典型 10-40 件、最大 100 件 (cap 不要、JSON 文字列で 5-20KB、ただし reasoning 長文化時は超過リスクあり → §5.2 / §6 で対策)
- /audit/runs API レスポンスで verdict 配列を JSON parse して返却 (parse 失敗時のフォールバック必須、§6.5 Confirmation 参照)
1.5 目標
crossValidationVerdicts を JSON 文字列として telemetry に保存し、SQL で軸別/severity 別の集計を可能にする。Non-Goals: 別テーブルへの normalize、Analytics Engine 等の外部分析基盤連携 (本 ADR スコープ外、必要時に別 ADR で検討)。
1.6 D1 json_each() 動作実測 (2026-05-29 検証)
Cloudflare D1 (SQLite-based) で json_each() が利用可能であることを実測:
SELECT key, value FROM telemetry_records, json_each(gate_timings)
WHERE session_id = '...' LIMIT 5;
結果: {"key": "triage", "value": 44}, {"key": "socratic", "value": 107964}, ... のように JSON フィールド展開が正常に動作。本番 telemetry-records テーブルで実行成功 (rows_read: 約 20, sql_duration_ms: 数 ms)。SQLite 3.38+ の json1 拡張が D1 にバンドル済みであり、機能・性能ともに集計用途で問題なし。
検証範囲の限界: 本実測は session_id 指定の単一行に限定。全期間・全セッションを対象とした集計クエリ (例: 過去 1 ヶ月で最も毀損が多い Must 軸) では未検証。数万行 × 40 verdict の json_each() 展開で rows_read が数百万に達し得る (Gate 1 盲点 #1 指摘)。Review After 評価時に 10,000 件規模ダミーデータでの実測を必須とする (§6 撤退条件で再評価)。
1.7 ステークホルダー
- 運用者: Cross-Validation 精度評価のための過去データ蓄積
- 将来 Jr: 軸ごとの cross-validation 統計を分析可能
1.8 過去 ADR との関係
| ADR | 関係 |
|---|---|
| ADR-0076 (Cross-Validation Gate) | Extends — verdict をテレメトリ保存 |
| ADR-0082 (Telemetry v2) | Extends — schema 拡張パターンを踏襲 |
| PR #1112 (全 finding × 全 axis) | Extends — 拡張されたデータ量を保存対象に |
Review After: 2026-08-29 (3 ヶ月後) — §6 撤退条件の定量基準で評価
決定
telemetry_records テーブルに cross_validation_verdicts TEXT カラム (JSON 配列) を ALTER TABLE ADD COLUMN で追加し、pipeline_consumer.ts で JSON.stringify(r.crossValidationVerdicts) を保存する。/audit/runs API では try-catch 付き JSON.parse でフォールバック (null) を提供する。reasoning 長文化対策として書き込み前に 50KB のバイト長閾値チェックを実装し、超過時は reasoning を truncate する。
判断基準 (Decision Drivers)
3.1 評価軸
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #suitable | Must (×2.0) | Cross-Validation 精度改善のための構造化分析データを永続化できるか |
| 2 | #maintainable | High (×1.0) | スキーマ変更コスト、後方互換、影響モジュール数 |
| 3 | #reliable | High (×1.0) | JSON parse 失敗・書き込み失敗・マイグレーション失敗時の堅牢性 |
| 4 | #efficient | Medium (×0.5) | D1 rows_read コスト、集計クエリのスケーラビリティ |
| 5 | #secure | Medium (×0.5) | reasoning に含まれうる機密情報の保存・露出リスク |
K.O. criterion: Must 軸 (#suitable) score < 3 は不採用。
3.2 評価軸 × 案スコア表
| 軸 | 係数 | 案 A (採択: JSON カラム) | 案 B (別テーブル) | 案 C (現状維持) |
|---|---|---|---|---|
#suitable | ×2.0 | 4 | 5 | 0 |
#maintainable | ×1.0 | 4 | 2 | 5 |
#reliable | ×1.0 | 3 | 4 | 5 |
#efficient | ×0.5 | 3 | 4 | 5 |
#secure | ×0.5 | 2 | 2 | 5 |
| 加重和 (正規化) | 0.690 | 0.700 | 0.600 | |
| K.O. 通過 (Must ≥3) | ✓ | ✓ | ❌ |
案 A と案 B は加重和が拮抗するが、verdict 数 10-40 の中規模データに対し別テーブル + JOIN のオーバーヘッドは過剰投資であり、#maintainable で案 A を選好する (タイブレーク)。
検討した代替案 (Alternatives Considered)
- 案 A【採用】: JSON カラム追加 — telemetry_records に
cross_validation_verdicts TEXT(JSON 配列) カラムを追加。pipeline_consumer.ts でJSON.stringify(r.crossValidationVerdicts)を保存。/audit/runs API で JSON parse して返却。SQL 集計は json_each() でアクセス (§1.6 実測検証済)。影響: schema.sql, migrate-v3.sql, audit/types.ts, audit/persistence.ts, queues/pipeline_consumer.ts, index.ts。工数 ~1.5h。 - 案 B: 別テーブル normalize —
cross_validation_verdicts (session_id, finding_title, must_axis, must_score, is_must_axis, undermines, reasoning)を新設。不採用: verdict 数が 10-40 と中程度、JSON で十分。テーブル追加 + JOIN のオーバーヘッドに見合わない。#maintainable劣後。 - 案 C: 現状維持 (markdown のみ) — 変更コストゼロ。不採用: 構造化データの永続的損失。将来の分析施策が不可能。
#suitableK.O. 不通過。
影響 (Consequences)
5.1 正の影響 (Good)
- SQL 集計可能化: 軸別 undermines 率、severity 別統計などが json_each() で取得可能 (§1.6 実測)
- /audit/runs API が構造化 verdict を返却し、UI 側で再構築可能
- Cross-Validation Gate の精度改善 (ADR-0076) のための過去データ蓄積開始
- スキーマ変更は ALTER TABLE ADD COLUMN のみで破壊なし、既存 v2 レコードは NULL で後方互換
5.2 負の影響 (Bad)
- 全件集計クエリでの json_each() フルスキャンによる rows_read 超過リスク (Gate 1 #1): 数万行 × 40 verdict の展開で rows_read が数百万に達し、D1 課金上限超過でクエリブロックの可能性。§1.6 の実測は単一行のみで全件未検証 → §6 で実測を撤退条件化
- reasoning 長文化による JSON カラムサイズ肥大化 (Gate 1 #2): 100 件 × 平均 300 文字で約 30KB となり「5-20KB」見積もりを超過。LLM 出力長文化で更に悪化 → §6.5 Confirmation で 50KB 閾値の truncate フォールバックを必須実装
- migrate-v3.sql 適用失敗の無音検知漏れ (Gate 1 #3): ALTER TABLE ADD COLUMN は冪等でなく、部分失敗時にカラム不在で INSERT が失敗、queue consumer が drop 設定だと telemetry が無音損失 → §6.5 で smoke test と DLQ アラート必須化
- /audit/runs API での JSON.parse 失敗による 500 エラー (Gate 1 #4): NULL (v2 レコード)・空文字列・破損 JSON で例外発生時、try-catch 漏れがあれば 1 件で全レスポンスが落ちる → §6.5 で try-catch + null フォールバック + ユニットテスト必須
- reasoning に含まれる機密情報・個人情報の無期限保存リスク (Gate 1 #6): LLM 生成 reasoning が評価対象ドキュメントの個人情報・営業秘密を間接含有しうる。telemetry_records に TTL なし、/audit/runs で外部露出。GDPR 「忘れられる権利」対応困難 → §6 で TTL ポリシーと PIA 要否を Review After 評価項目化
5.3 中立・トレードオフ (Neutral / Trade-offs)
- デプロイ混在期間リスク (Gate 1 #5): Cloudflare Workers の Blue-Green 非保証により v2 コード × v3 スキーマの混在時間帯が存在。既存 INSERT が named-column 形式 (新カラム省略可) であることを実装時に確認し、デプロイ手順を「スキーマ適用 → コードデプロイ」順序で固定化する
- undermines 件数の KPI 化によるメトリクスゲーム化リスク (Gate 1 #8): 集計可能化により「数値改善のための閾値調整」インセンティブが生じ、ADR-0076 の genuine 品質保証が歪曲されうる → Review After 評価に定性的事例レビューを必須化
- 案 B (別テーブル) への将来移行は SQL レベルで可能 (json_each + INSERT INTO) であり、現時点で案 A 採用しても後戻り可能
撤退条件 (Rollback Plan)
6.1 定量的撤退条件 (Review After: 2026-08-29)
以下のいずれかを満たした場合、案 B (別テーブル) または案 C (削除) への移行を検討:
- rows_read 超過: 10,000 件規模ダミーデータでの軸別集計クエリで rows_read > 100 万 または sql_duration_ms > 5,000ms
- 保存失敗率: cross_validation_verdicts カラムの NULL 率 (v3 レコード対象) > 1%
- JSON サイズ超過率: 50KB 閾値を超えて truncate が発生したレコード比率 > 5%
- 集計クエリ未実施: 3 ヶ月間で軸別 undermines 率の集計クエリが 1 度も実行されなかった (= 価値未実証)
- 意思決定への寄与なし: 集計結果が閾値調整等の意思決定に 1 件も使われなかった
- GDPR インシデント: reasoning 経由の個人情報露出が 1 件でも発生
6.2 ロールバック手順
| 対象 | 具体手順 | 影響範囲 |
|---|---|---|
| pipeline_consumer.ts | cross_validation_verdicts 書き込み行を削除 → 再デプロイ | 新規 telemetry は NULL になるが既存データは保持 |
| /audit/runs API | JSON.parse 部分を削除し markdown のみ返却 | UI 互換 (構造化データは消える) |
| D1 schema | D1 が ALTER TABLE DROP COLUMN をサポートするか事前確認。未サポートなら新テーブル作成 + データ移行 + RENAME | 過去データの構造化部分は喪失 |
6.3 Review After 評価チェックリスト
- 10,000 件ダミーデータで全件集計クエリの rows_read / sql_duration_ms 実測実施済か
- 軸別 undermines 率の集計クエリが少なくとも 1 回実行されたか
- その結果が閾値調整等の意思決定に使われたか
- cross_validation_verdicts カラムの NULL 率 (v3 レコード対象) が 1% 未満か
- JSON サイズ超過 (truncate 発生) 率が 5% 未満か
- 実際の reject 事例の質的レビューを実施したか (メトリクスゲーム化防止)
- reasoning の機密情報含有について PIA (プライバシー影響評価) 要否を判断したか
- telemetry_records の TTL / 定期削除ポリシー策定要否を判断したか
6.4 Confirmation (準拠確認 / Fitness Function)
| # | 検証手段 | 実行頻度 | 違反時の対応 |
|---|---|---|---|
| 1 | CI smoke test: migrate-v3.sql 適用後に PRAGMA table_info(telemetry_records) で cross_validation_verdicts カラム存在確認 | デプロイ毎 (CI) | デプロイブロック |
| 2 | ユニットテスト: audit/persistence.ts に NULL (v2)・正常 JSON (v3)・破損 JSON の 3 ケースを追加し JSON.parse フォールバックを検証 | PR 毎 (CI) | PR マージブロック |
| 3 | 書き込み前バイト長チェック: pipeline_consumer.ts で JSON.stringify 結果が 50KB 超過時に reasoning を truncate するロジックをユニットテストで検証 | PR 毎 (CI) | PR マージブロック |
| 4 | DLQ 監視: pipeline_consumer.ts の D1 書き込みエラーを明示的に catch し Dead Letter Queue または専用アラートに接続。NULL 率を週次でメトリクス化 | 週次 (運用ダッシュボード) | NULL 率 > 1% でアラート、原因調査 |
| 5 | Review After 評価: §6.3 チェックリストを 2026-08-29 に起案者または後任が実施 | 1 回 (2026-08-29) | 撤退条件 6.1 該当時は案 B / 案 C への移行 ADR を起案 |
参照 (References)
- 関連 ADR: ADR-0076 (Cross-Validation Gate), ADR-0082 (Telemetry v2)
- 関連 PR/Issue: PR #1112 (全 finding × 全 axis 拡張)
- 外部資料: Cloudflare D1 json1 拡張ドキュメント (SQLite 3.38+) (要追記: 正式 URL)