1. Executive Summary

  • 文脈: 当社の意思決定パイプラインは、起案本文と AI 出力の逐語引用照合を複数のゲートで行い、normalize (NFKC + markdown pair 剥がし + 空白畳み) を各 gate が個別実装している。
  • 課題: 以前の ADR で一部のゲートにのみ追加された処理が原因で、複数のゲート間で異なる判定が生じ、一部の品質指標が悪化しています。
  • 対策: 正規化処理を共通モジュールとして新設し、単一の情報源として複数のゲートで利用するように統一します。
  • 効果: normalize 契約分裂に起因する silent 精度劣化を根絶し、ADR-0142 Phase 1 の効果を全 gate に均等展開する。
  • 代償: pure module 1 個 + 4 file の import 置換 + tools/pregate-eval/decide.ts の byte 一致同期で 2-3 時間、ESLint rule 1 個追加。

2. 何を解決するか

2.1 直面していること

保守性:

  • 複数の既存正規化関数は、それぞれ異なる処理を持っています。
  • abc_decide.ts::normalizeForAbcSpanMatch (L117-120) は NFKC + 空白畳みのみで markdown pair 剥がしを持たない。
  • recall_pre_gate_pure.ts::normalizeForCitationMatch (L14-20) も NFKC + 空白畳みのみで markdown pair 剥がしを持たない。
  • body_fact_check_pure.ts::normalizeForGrounding は NFKC + 空白除去 + 万円/割/年月日換算を持つが markdown pair 剥がしを持たない。
  • upstream_citation_pure.tsnormalizeForGrounding を共用しており、数値換算経路の normalize policy が装飾剥がし系と分裂している。

信頼性:

  • 同じ verbatim quote が gate ごとに異なる判定を返し、pregate で通ったものが Gate 4.5 で mismatch になる silent 精度劣化が起きている。

2.2 放置すると起こること

信頼性:

  • LLM は - **X**: Y 型 bullet の先頭 bold pair を quote 時に脱落させる pattern を持ち、30 日 window で 55/116 INVALID の主因となっている。
  • abc_decide (Gate 0b) は evidence_spans[].text の逐語照合失敗により axis 未検証 → confidence 降格が起き、rejection 経路でないため観測 SQL からも見えにくい。
  • recall_pre_gate (R9 Recaller) は relevance_reason 内の bold bullet quote が silent drop し recall_high_count が下振れ、Gate 2 consistency の候補集合が痩せて誤検知や false-pass を招く。

2.3 潜在リスク (実害未発生 · 発生確率不明)

保守性:

  • 新規 gate 追加のたびに normalize を個別実装する pattern が続き、Phase 1 相当の応急対応が今後も局所化する可能性がある。
  • ADR-0142 Phase 3 (verbatim 廃止 · semantic verify) 保留中は Phase 1 の応急対応が主戦線になるため、SSoT 化しないと契約分裂が累積する。

3. 採用したい方針

3.1 対策方針

drp の逐語照合の正規化方針を drp/src/lib/quote_normalize.ts の pure module に集約し、4-5 gate は import 経由で 1 経路に統一する。ADR-0142 §amend Phase 1 で pregate のみに追加された markdown pair 剥がしを全 gate に均等展開し、Phase 1 効果が gate 局所化した状態を解消する。数値換算系 (body_fact_check / upstream_citation) と装飾剥がし系 (pregate / abc_decide / recall_pre_gate) の両用途を flag 合成でカバーしつつ、gate 種別ごとに named preset 関数を提供することで任意 flag 組み合わせの直接渡しを discourage し、flag 誤指定による silent regression を設計段階で封じる。

  • 保守性: drp/src/lib/quote_normalize.ts を pure module (fetch/env/langchain 非依存) として新設し、normalize policy の SSoT にする。
  • 柔軟性: flag 合成 API normalizeForVerbatimQuote(text, {nfkc, markdown_strip, whitespace, numeric_normalize, jp_amount_normalize}) で、装飾剥がし系と数値換算系の両用途を 1 経路でカバーする。
  • 保守性: gate 種別ごとの named preset 関数 (normalizeForDecoration() / normalizeForNumeric()) を SSoT に定義し、任意 flag 組み合わせの直接渡しを discourage する (盲点 #2 対策)。
  • 信頼性: 4-5 gate は用途に応じた preset 関数を指定して import 経由で normalize する (pregate/abc_decide/recall_pre_gate は装飾剥がし系 preset · body_fact_check/upstream_citation は数値換算 preset)。
  • 保守性: ESLint custom rule で drp/src/ 配下の直接 .normalize('NFKC') 呼び出しを禁止し、SSoT 経由を機械強制する。

3.2 守るべき設計制約

  • 保守性: problem_space_decide.ts::normalizeForQuoteMatchtools/pregate-eval/decide.ts の byte 一致契約 (ADR-0142 §amend note) は SSoT 経由で維持する。tools/pregate-eval/decide.tsquote_normalize.ts から直接 import する構造的保証に切り替え、月次手動 diff を廃止する (盲点 #1 対策)。
  • 保守性: 本 SSoT は ADR-0142 Phase 1 応急対応の全 gate 均等展開であり、Phase 3 (verbatim 廃止 · semantic verify) 着手時に quote_normalize.ts を retire できる形で維持する (Phase 3 の意味論変更を阻害しない後方互換)。
  • 信頼性: pure module 性を維持し fetch/env 非依存とすることで vitest pool-workers 全 pool で安全に動作させる。
  • 信頼性: 既存 gate の挙動を悪化させない (Phase 1 対応は pregate 側で確定済 · 他 gate は精度改善方向のため rollout 安全)。
  • 保守性: numeric_normalize / jp_amount_normalize flag の入力仕様・変換規則・境界ケースが未定義の状態を許容しない。定義が揃うまでは該当 flag を never 型で封印してコンパイルエラーにする (盲点 #6 対策)。

3.3 仕様

  • 保守性: ESLint custom rule no-direct-normalize-nfkc を追加し drp/src/ 配下で .normalize('NFKC') の直接呼び出しを error にする (exempt: drp/src/lib/quote_normalize.ts 自身)。
  • 保守性: 既存 problem_space_decide.ts の regex (/\*\*([^*]+?)\*\*/g · /`([^`]+?)`/g · /[\s\u3000]+/g) を SSoT に移送し、他 gate は preset 関数経由で normalize する。
  • 柔軟性: flag default は {nfkc: true, markdown_strip: true, whitespace: true, numeric_normalize: false, jp_amount_normalize: false} とし、TypeScript Required<NormalizeFlags> 型で flag 省略をコンパイルエラーにする (盲点 #2 対策)。
  • 信頼性: normalizeForGrounding の万円、割引、年月日の換算 regex の適用順序は NFKC → markdown_strip → 万円、割引、年月日の換算 → 空白除去 を固定順序として SSoT に移送し、順序不変を保証する golden test を追加する (盲点 #4 対策)。
  • 保守性: numeric_normalize / jp_amount_normalize の入力仕様・変換規則・境界ケース (万円→数字の変換規則 · 年月日の正規化対象範囲 · 「1億2000万円」等の複合表現の扱い) を SSoT の JSDoc に明記し、未定義のまま flag だけ存在する状態を禁止する (盲点 #6 対策)。

4. 判断基準

4.1 評価軸

#軸 (Q42)日本語軸名重要度 (係数)案件特有の解釈
1#reliable信頼性Must (×2.0)Phase 1 効果の全 gate 均等展開 · 3 例 sample で pregate と abc_decide の同 quote 判定が一致する
2#maintainable保守性Must (×2.0)tools/pregate-eval との byte 一致契約維持と契約分裂の再発防止 · 新規 gate の直接 normalize 呼び出しを機械 lint できる
3#suitable適合性Must (×2.0)pure module 維持 · fetch/env/langchain 非依存で vitest 全 pool で動作
4#operable運用性High (×1.0)全 drp test pass · shipping 前後で INVALID 率が悪化しない (既存 gate の挙動不変)
5#flexible柔軟性High (×1.0)ADR-0142 Phase 3 着手時に SSoT を retire できる後方互換

K.O. criterion: Must 軸 score < 3 は不採用。

4.2 評価軸 × 案スコア表

係数採択案 (SSoT 化)案 A (各 gate 個別追加)案 B (Phase 3 先行)
信頼性×2.0534
保守性×2.0523
適合性×2.0542
運用性×1.0442
柔軟性×1.0533
加重和 (正規化)0.9600.6400.560
K.O. 通過 (Must ≥3)❌ (保守性=2)❌ (適合性=2)

5. 検討した代替案

5.1 案 1 (採用): drp/src/lib/quote_normalize.ts SSoT 化

採用案。詳細は §3.1 対策方針 参照。

5.2 案 2 (不採用): 各 gate に markdown pair 剥がしを個別追加 (P1b batch 相当)

  • 保守性: Phase 1 と同じ regex を 4 file に copy-paste する短期解で、SSoT がないため契約分裂の再発防止機構を持てない。
  • 保守性: normalize policy が variance すると (数値換算 vs 装飾剥がし)「どの gate はどの policy か」の暗黙契約が manual 同期になり drift が累積する。
  • 運用性: 実装コスト差は SSoT 化と比べ 30-60 分程度で、恒久解を先送りする理由がない。

5.3 案 3 (不採用): ADR-0142 Phase 3 (verbatim 廃止) を先行

  • 適合性: Semantic verify への設計変更で normalize 依存自体を消す抜本策だが、Pipeline verify の意味論変更で影響 gate 数が多い (Cross-Val / IBIS decode 全体)。
  • 運用性: 効果測定は Phase 1 の 07-18 実測後判断のため、Phase 3 着手の判断材料が現時点で揃っていない。
  • 適合性: Phase 3 着手判断は ADR-0142 §Phase 計画が所有し、07-18 の Phase 1 効果測定結果を受けて同 ADR 側で行う (本 ADR のスコープ外)。
  • 柔軟性: 本 ADR は Phase 1 応急対応の全 gate 均等展開として Phase 3 と独立に進められ、Phase 3 着手時には quote_normalize.ts を retire する経路で整合する (SSoT 化が Phase 3 の抜本策と競合しない)。

6. 影響

6.1 正の影響

  • 信頼性: abc_decide の axis 未検証 (silent confidence 降格) が 5-10% 削減される見込み。
  • 信頼性: recall_pre_gate の citation silent drop が減り recall_high_count 下振れが回復、Gate 2 candidate set の痩せを抑止する。
  • 保守性: 契約分裂の機械 lint により、新規 gate 追加時の normalize policy drift を防止する。
  • 保守性: tools/pregate-eval/decide.ts の SSoT 直接 import 化により、月次手動 diff の運用負荷が消える (盲点 #1 対策の副次効果)。

6.2 負の影響 / リスク

  • 保守性: 既存 grep 検索 pattern (.normalize('NFKC')) が hit しなくなり、SSoT 経由 API 名での再検索に切り替える必要がある。
  • 運用性: テストの regex fixture を bold-pair pattern に追加し、abc_decide / recall_pre_gate / body_fact_check_pure / upstream_citation の 4 test file の fixture を更新する必要がある。
  • 保守性: SSoT module が存在するという事実が「数値換算も解決済み」という完了錯覚を生むリスクがあるため、numeric_normalize / jp_amount_normalize flag の仕様未定義状態を never 型で封印して技術的に阻止する (盲点 #6 対策)。

7. コスト試算

7.1 前提条件

項目根拠
実装 file 数5 (pure module 1 + import 置換 4)§2.1 の gate 一覧
tools 同期 file 数1 (tools/pregate-eval/decide.ts)ADR-0142 §amend byte 一致契約 (SSoT 直接 import 化)
ESLint rule 追加1 (no-direct-normalize-nfkc)§3.3
test fixture 更新 file 数4§6.2
golden test 追加1 (normalizeForGrounding 順序不変)§3.3 (盲点 #4)

7.2 月コスト試算

  • 実装工数 (通常ケース): 3 時間 (pure module 1 + import 置換 5 file + tools 同期 1 + ESLint rule 1 + test fixture 4)。
  • 実装工数 (最悪ケース): 5-6 時間 (通常 3h + normalizeForGrounding 換算 regex の SSoT 移送範囲確定の仕様議論 1-2h + 数値換算系 gate の regression fixture 充足 1h) — 盲点 #7 対策として最悪ケース見積もりを併記。
  • ランタイム影響: pure module 呼び出しは JS 関数呼び出し 1 段追加で gate あたり <0.1ms 想定、Workers per-step CPU 30s 制約 (KPI-9) に無影響。
  • Cloudflare 課金: なし (worker runtime のみ · 追加 R2/KV/D1 なし)。

8. 撤退条件

  1. Shipping 後 1 week window で abc_decide の telemetry (axis_verified_count 相当) が shipping 前 2 week のベースラインの標準偏差の 2σ を超える下振れを示したら rollback (SSoT 経路の bug 疑い · 母数小の場合の通常変動を除外するため統計的基準を採用 · 盲点 #5 対策)。
  2. pnpm --filter drp run test で回帰 (883 tests のうち 5 件以上 fail) を検知したら rollback。
  3. tools/pregate-eval/decide.ts の SSoT 直接 import が破れているか、07-18 効果測定時に byte 一致が破れているなら rollback (SSoT 経由の同期漏れ疑い)。
  4. ESLint rule の false-positive で 3 件以上の既存 file が違反 warning を出すなら rule ロジックを見直し (rollback ではなく exempt 追加)。

9. Confirmation

  1. abc_decide axis 未検証率の月次比較 (目標 shipping 前 2 week ベースライン比 5% 以上改善 · 悪化 2σ 以下): SELECT DATE_TRUNC('week', created_at), AVG(CASE WHEN abc_confidence=1 THEN 1 ELSE 0 END) FROM telemetry_records WHERE created_at >= '2026-07-15' GROUP BY 1 で shipping 前後を比較 (abc_confidence=1 は axis 未検証時の confidence 降格を捕捉する proxy · クエリコメントに明記) · 実行頻度 = 週次 · 違反時 = §8 条件 1 で rollback 判定。
  2. recall_pre_gate citation silent drop の月次比較 (目標 5% 以上改善): SELECT DATE_TRUNC('week', created_at), AVG(recall_high_count) FROM telemetry_records WHERE created_at >= '2026-07-15' GROUP BY 1 で recall_high_count 平均値の shipping 前後比較 · 実行頻度 = 週次 · 違反時 = §8 条件 1 と併せて rollback 判定。
  3. ESLint rule の CI 通過率 100%: 全 PR で pnpm --filter drp run lint が pass し、直接 .normalize('NFKC') 呼び出しの新規追加が block されている · 実行頻度 = PR 毎 · 違反時 = PR merge block。
  4. SSoT 直接 import 契約の保持: tools/pregate-eval/decide.tsdrp/src/lib/quote_normalize.ts を構造的に import している状態を PR 毎の CI ステップで検証 (grep -q "from.*quote_normalize" tools/pregate-eval/decide.ts 相当) · 実行頻度 = PR 毎 (月次 diff から昇格) · 違反時 = §8 条件 3 で rollback 判定 (盲点 #1 対策)。
  5. ESLint exempt 挙動の独立スモークテスト: no-direct-normalize-nfkc rule 追加 PR で pnpm --filter drp run lint -- --rule-tester 相当のスモークテストを実行し、drp/src/lib/quote_normalize.ts 自身が lint error を出さないことを検証 · 実行頻度 = rule 追加 PR 単独 · 違反時 = rule 追加 PR merge block (盲点 #3 対策)。
  6. normalizeForGrounding 順序不変の golden test: 「1億2000万円」等の複合数値表現に対する変換結果を snapshot 化し、regex 適用順序変更を検知 · 実行頻度 = PR 毎 · 違反時 = PR merge block (盲点 #4 対策)。

10. 参照

10.1 関連 ADR

  • Refines: ADR-0142 (受付プリゲート) — Phase 1 応急対応 (pregate のみ markdown pair 剥がし) の全 gate 均等展開として本 ADR を追加する。Phase 3 (verbatim 廃止 · semantic verify) 着手時には quote_normalize.ts を retire できる後方互換を保ち、Phase 3 の意味論変更を阻害しない。
  • ADR-0157 (ABC 守りの価値スクリーン) — 並立。abc_decide の normalize 経路 (normalizeForAbcSpanMatch) を SSoT 化対象に含み、axis 未検証の silent 精度劣化 5-10% を Phase 1 効果の均等展開で解消する。

10.2 関連 PR/Issue

  • PR #5608 (2026-07-11 · ADR-0142 §amend Phase 1 · pregate normalize 強化) — 本 ADR の起点
  • PR #5656 (2026-07-12 · P0 fail-closed batch) — 同 session の関連 shipping

10.3 外部資料

11. 実装配線

  • lint 配線: .eslintrc.cjs (or eslint.config.js = ESLint flat config 形式の設定 file) に no-direct-normalize-nfkc custom rule を追加 · drp/src/lib/quote_normalize.ts (= normalize SSoT の pure module) を exempt · CI pnpm --filter drp run lint で必須検証 · rule 追加 PR 単独で pnpm --filter drp run lint -- --rule-tester 相当のスモークテストを実行し exempt 挙動を検証 (盲点 #3 対策) · normalizeForGrounding 順序不変の golden test を drp/test/lib/quote_normalize.test.ts に新設 (盲点 #4 対策) · tools/pregate-eval/decide.ts の SSoT 直接 import を CI grep で検証 (盲点 #1 対策)。
  • 起案フロー配線: 本 ADR は Pipeline 通常経路 (Standard mode 想定) で pregate → triage → socratic → body_generation → cross_validation → consistency の reviewer 判定を経て shipping = 即本番デプロイ (deploy-worker.yml = Cloudflare Workers 反映 workflow) に載せる。
  • docs SSoT 同期: drp/CLAUDE.md の「変更時テスト」表は pure module のため既存 test カバー範囲内で追記なし · .claude/rules/drp.md は最重要安全則に該当しないため更新なし · memory quote-normalize-ssot-drp-lib (= SSoT 経路と ESLint rule の存在を pointer 化) を session close 時に auto-memory へ追記。