型付き辺: 出 1 / 入 0
細分化する: ADR-0142全体は INDEX →
ADR-0234: 逐語照合の正規化処理を共通化 (全 gate で契約統一)
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.tsはnormalizeForGroundingを共用しており、数値換算経路の 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::normalizeForQuoteMatchとtools/pregate-eval/decide.tsの byte 一致契約 (ADR-0142 §amend note) は SSoT 経由で維持する。tools/pregate-eval/decide.tsはquote_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_normalizeflag の入力仕様・変換規則・境界ケースが未定義の状態を許容しない。定義が揃うまでは該当 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}とし、TypeScriptRequired<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.0 | 5 | 3 | 4 |
| 保守性 | ×2.0 | 5 | 2 | 3 |
| 適合性 | ×2.0 | 5 | 4 | 2 |
| 運用性 | ×1.0 | 4 | 4 | 2 |
| 柔軟性 | ×1.0 | 5 | 3 | 3 |
| 加重和 (正規化) | 0.960 | 0.640 | 0.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_normalizeflag の仕様未定義状態を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. 撤退条件
- Shipping 後 1 week window で abc_decide の telemetry (axis_verified_count 相当) が shipping 前 2 week のベースラインの標準偏差の 2σ を超える下振れを示したら rollback (SSoT 経路の bug 疑い · 母数小の場合の通常変動を除外するため統計的基準を採用 · 盲点 #5 対策)。
pnpm --filter drp run testで回帰 (883 tests のうち 5 件以上 fail) を検知したら rollback。- tools/pregate-eval/decide.ts の SSoT 直接 import が破れているか、07-18 効果測定時に byte 一致が破れているなら rollback (SSoT 経由の同期漏れ疑い)。
- ESLint rule の false-positive で 3 件以上の既存 file が違反 warning を出すなら rule ロジックを見直し (rollback ではなく exempt 追加)。
9. Confirmation
- 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 判定。 - 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 判定。 - ESLint rule の CI 通過率 100%: 全 PR で
pnpm --filter drp run lintが pass し、直接.normalize('NFKC')呼び出しの新規追加が block されている · 実行頻度 = PR 毎 · 違反時 = PR merge block。 - SSoT 直接 import 契約の保持:
tools/pregate-eval/decide.tsがdrp/src/lib/quote_normalize.tsを構造的に import している状態を PR 毎の CI ステップで検証 (grep -q "from.*quote_normalize" tools/pregate-eval/decide.ts相当) · 実行頻度 = PR 毎 (月次 diff から昇格) · 違反時 = §8 条件 3 で rollback 判定 (盲点 #1 対策)。 - ESLint exempt 挙動の独立スモークテスト:
no-direct-normalize-nfkcrule 追加 PR でpnpm --filter drp run lint -- --rule-tester相当のスモークテストを実行し、drp/src/lib/quote_normalize.ts自身が lint error を出さないことを検証 · 実行頻度 = rule 追加 PR 単独 · 違反時 = rule 追加 PR merge block (盲点 #3 対策)。 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 外部資料
- Unicode NFKC 正規化仕様: https://unicode.org/reports/tr15/
- ESLint custom rule 実装ガイド: https://eslint.org/docs/latest/extend/custom-rules
11. 実装配線
- lint 配線:
.eslintrc.cjs(oreslint.config.js= ESLint flat config 形式の設定 file) にno-direct-normalize-nfkccustom rule を追加 ·drp/src/lib/quote_normalize.ts(= normalize SSoT の pure module) を exempt · CIpnpm --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は最重要安全則に該当しないため更新なし · memoryquote-normalize-ssot-drp-lib(= SSoT 経路と ESLint rule の存在を pointer 化) を session close 時に auto-memory へ追記。