型付き辺: 出 11 / 入 1
ADR-0103: Decision Pipeline の入口を統一: chat UI も即 enqueue + polling パターン (CI 寄り) に統合する
- Status: Accepted
- Mode: Standard
- Kruchten Type: Executive/Property
- Scope: platform
- Implementation Status: Done (Phase A-4 で案 C′ 採用 → shared triage module 実装完了 PR #1353。EC-3 Queue silent failure は per-session DO alarm watchdog で別途実装 PR #1354/#1355。MVP 卒業 EC-1〜6 全充足 2026-06-03 PR #1357)
- 起案者: [email protected]
- 起案日時 (JST): 2026-06-01
- 承認日時 (JST): 2026-06-03 (Phase A-4 実施・案 C′ で確定)
- Deciders: [email protected] (単独)
Pipeline 迂回の経緯(監査用注記): 本 ADR は Decision Pipeline で Standard 48/50 と高得点に達したが、Cross-Validation が Must 軸
#maintainableで 2 連続差し戻しした。round 1 は「コスト試算表に Queue silent failure 対策 (sweeper/DLQ/timeout) の工数行が無いまま net 純利益を断言」で、Phase A-2 として個別工数に分解 (合計 3.55 人日) + DLQ consumer を計上して対処。round 2 は その追加した DLQ consumer そのものを「仕様が定義されないまま死蔵化する新たな保守負担=保守性向上の主張を undermine する」と新 critical で攻撃した。graph-check (ADR-0100)・stop-button (ADR-0101)・review-tiering (ADR-0102) と同型の「ある盲点を fix すると次ラウンドで fix 手段自体が新 critical になる」連鎖であり、当初は検証可能軸とみなした#maintainableでも同じループが発生した(ループの本質は「軸が検証不能か」でなく「安全策を 1 つ足すとその安全策自体が新たな複雑度・障害源として attackable になる」点にある)。要求された各対策は ADR の負の影響・撤退条件・Confirmation・Phase A-2/A-3/A-4 として既に captured 済。CLAUDE.md「2 連続失敗で停止」ラインに到達したため、ADR-0100/0101/0102 の前例に倣い Pipeline を迂回し通常 PR で起案する(代表取締役判断 2026-06-01)。#maintainableの self-score は「実証済みの構造的解消」でなく「Phase A-4 比較 + 仕様明確化 + 死活監視で managed なリスク」に正直化した(5→4、K.O. は Must≥3 で通過)。Pipeline retroactive validation(ADR-0052)は任意。
コンテキスト
§1.1 背景
Decision Pipeline は ADR-0066 (async queues + DO) を母体に進化してきたが、ADR-0094 Phase B/C 検証中 (2026 春〜) に triage 周辺の改修漏れが連続発生し、入口経路の構造的分散が痛みとして顕在化した。2026-06-01 のユーザ確認で「Web UI 起案頻度は今後減少 (CI / 自動化経路へ移行)」という運用方針変更が共有され、即時応答性 (ADR-0066) の価値が下がり UX 妥協を許容可能なフェーズに入った。
§1.2 現状 (As-Is)
Decision Pipeline は 2 つの HTTP 入口 (POST /chat/start と POST /runs) で triage 実行タイミングが異なる:
/chat/start(Web UI): handler 内で triage を同期実行 → session に triageResult 保存 → 即 response (ADR-0094 Phase B で確立)/runs(CI): handler は即 enqueue → consumer 内で triage 実行
triage 周辺の付随処理 (UI 配線 / telemetry 書込 / session schema) が handler / consumer の 2 箇所に分散している。
§1.3 課題
ADR-0094 Phase B/C 検証中に同種の改修漏れが 4 件 + 1 件発生 (構造的繰返しパターン):
- PR #1200/#1209: chat UI 「[critical] バッジ + ADR 対象外」矛盾表示 — handler 側 UI 配線漏れ (consumer 側のみ rejectionReasonCode 伝搬済)
- PR #1204/#1212: triage 二重実行 + 揺れ — handler / consumer 両方で triage 実行されていた (session に triage 結果保存忘れ)
- PR #1214: early-reject パス triage_ms=null hardcoded — consumer 内 saveTelemetryRecord の wire-up 漏れ
- PR #1217 Phase C: triageRejectKind フィールドの UI/audit/consumer 3 箇所同期改修コスト
改修時の漏れリスクが構造的に発生し、個別 PR での対応では同種パターンの繰返しを止められない。
§1.4 制約・要件
- Cloudflare Workers 30 秒 wall-time 制限 (handler 同期 triage は今日観測 23 秒で境界線)
- ADR-0066 の「Web UI 即時応答性」要件を再定義可能であること
- ADR-0094 Phase B (session.triageResult cache) の部分 revert を許容、Phase A/C は維持
- ADR-0089 progressive streaming の polling 表示を前提に UX を担保
- ADR-0095 (triage retry 抑制、2026-06-01 Accepted・Phase A 本番稼働) には依存しないが、実装済により安全余裕が増している
§1.5 目標 (To-Be)
/chat/start handler を CI 寄り (/runs と同じ pattern) に統合し、triage 周辺コードを consumer の 1 箇所に集約。改修漏れリスクを構造的に解消、Web UI / CI の入口経路を対称化、retroactive validation を chat UI からも利用可能にする副次効果を得る。Non-Goals: chat UI 廃止 / retroactive の chat UI 統合の本実装 (Phase C で別途扱う) / ADR-0066 即時応答性要件の完全撤廃 (再定義に留める)。
決定
⚠️ 本節は当初の採択案 (CI 寄り統一) の記述。Phase A-4 (2026-06-03) で案 C′ (shared triage module) に方針転換しており、最終決定は次節「Phase A-4 実施結果 (2026-06-03): 案 C′ へ方針転換」を参照。本節の即 enqueue 化・session.triageResult revert・即時応答性再定義は案 C′ では採用していない (chat UI は同期 triage を維持)。
/chat/start handler から triage 同期実行 (preGraph.invoke) を撤去し、即 enqueue + sessionId 返却に変更する。session schema から triageResult を撤去 (ADR-0094 Phase B cache を revert)、consumer 側の triage 実行は現状維持 (CI 経路と同じ pattern)。chat UI の進捗表示は ADR-0089 progressive streaming で対応済。同時に Queue silent failure 対策 (consumer pickup 時の queued→running 即遷移、polling 側 5 分 timeout、Cloudflare Queues DLQ、定期 sweeper) を Phase A 必須スコープに同梱し、同期 handler が担っていた「即時 fail-fast」を async でも担保する。ADR-0066「Web UI 即時応答性」要件は本 ADR 採択で「polling 経由の段階的 feedback」に再定義する。
ただし着手順序として Phase A-4 (採択案 vs 案 C′ の cost-benefit 再比較) を handler 変更着手より前に必須実施し、案 C′ (shared handler module) が同等以上かつ async コスト不要と判明したら方針転換する。
Phase A-4 実施結果 (2026-06-03): 案 C′ へ方針転換 (決定更新)
撤退条件 #8 / §コスト試算「順序制約」に従い handler 変更着手前に A-4 を実施した。比較は実コード接地で行い (drp/src/index.ts の /chat/start L68-180・/runs L320-381、src/queues/pipeline_consumer.ts L88-446、src/do/pipeline_session.ts L1-31、wrangler.toml L26-36)、事前定義の 3 軸 (付随処理の変更箇所数 / 新規障害モード数 / テスト必要箇所数) で評価した。
前提の再確認 (接地で判明): main pipeline (socratic→body→scoring→…) は 既に両入口とも async consumer 経由 (/chat/start?async=true も /runs も PIPELINE_QUEUE.send → 同一 consumer)。本 ADR が async 化するのは triage のみ。DLQ (pipeline-queue-dlq) は wrangler に設定済だが DLQ consumer・sweeper・timeout は未実装。∴ Queue silent failure (EC-3) は本 ADR の入口選択と独立した既存ギャップであり、sweeper/DLQ/timeout はどちらの案でも必要 → EC-3 を本 ADR (EC-2) から切り離し別スコープとする。
3 軸比較:
| 軸 | 採択案 (CI 寄り統一) | 案 C′ (shared module) | 判定 |
|---|---|---|---|
| (a) 付随処理の変更箇所数 (=EC-2 drift 解消力) | triage を handler から除去→consumer 集約。保守ロケーション 1 | triage 実行+UI 配線+telemetry+session 書込を共有モジュール 1 箇所に集約、handler/consumer が呼ぶ。保守ロケーション 1 | 同等 (両者とも drift 根本解消) |
| (b) 新規障害モード数 | triage を async 化し §5.2 が列挙する 約 8 件の high/critical (stuck-queued / 24h triageResult schema 非互換 crash-loop / sweeper-consumer race / sweeper 誤発火 / message 順序上書き / DLQ 死蔵 / ADR-0089 SPOF / bulk latency) | triage は handler 同期で fail-fast 維持。新規は実質 0〜1 (Cloudflare Worker deploy は単一版 atomic で version skew ほぼ無し) | 採択案 ≫ 1.5×C′ → 撤退条件 #8 の C′ 転換条件に合致 |
| (c) テスト必要箇所数 | async 安全策の e2e (旧形式注入 / 冪等 / sweeper / DLQ / timeout) は worker 本体 import=langsmith vitest 壁で staging 手動のみ | 共有モジュールは純粋関数→vitest unit で検証可 (langsmith 壁を回避) | C′ が明確に有利 (ソロ運用で重要) |
採択案が上回る軸は #efficient (5 vs 3: handler 即 return・bulk 対称) と #flexible (5 vs 3: retroactive chat UI)。だが chat UI 起案頻度は減少方針・Workers CPU 削減は年 ~$5・bulk/retroactive は Non-Goal/Phase C であり、いずれも MVP 卒業 (EC-2/EC-3) の優先度が低い。C′ の既知制約 (chat/start 同期 triage 最悪 23 秒が 30 秒 wall-time 境界) は ADR-0095 (Accepted・本番稼働) で抑制済かつ現状維持 (今より悪化しない)。
決定 (更新): §決定の「採択案 (CI 寄り統一・即 enqueue 化)」を partial supersede し、EC-2 入口統一は案 C′ (shared triage module) を採用する。chat UI の即時 triage feedback は維持、入口は 2 つのまま、付随処理 (triage core + UI フィールド整形 + telemetry 記録形 + session 書込) を 1 モジュールに集約し handler / consumer 双方が呼ぶ。これにより drift (PR #1200/#1204/#1214/#1217 系) を構造的に解消する。Queue silent failure 対策 (sweeper/DLQ consumer/timeout = 旧 Phase A-2/A-3) は EC-3 として独立スコープに移し、既存 async consumer の堅牢化として別途実施する (本 ADR には同梱しない)。これは sunk-cost バイアス回避のため A-4 を実装前に強制配置した設計 (§5.3) が意図どおり機能した結果である。
判断根拠: 本 A-4 判定 (3 軸の実コード接地比較 + 案 C′ 採用) は代表取締役が確認・確定 (2026-06-03)。Pipeline 迂回の経緯は冒頭注記のとおり。
判断基準 (Decision Drivers)
3.1 評価軸
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #maintainable | [Must] (×2.0) | handler / consumer 2 箇所分散の解消、triage 周辺コードの 1 箇所集約、改修漏れリスクの構造的解消。async 化で新たに増える保守対象 (sweeper/DLQ/timeout/移行コード/死活監視) は §コスト試算で Phase A-2 として個別工数行に分解 (合計 3.70 人日) し、新規追加コードの行数・テスト箇所数・障害モード数を Phase A-4 で実測して net 利益を検証可能な形で backing する managed なリスク (「~30 行削減」のみで相殺と断ぜず、増分コストと新規障害モードを定量計上)。async コストを負わない案 C′ (shared handler module) が同等の #maintainable を満たす場合は Phase A-4 で方針転換 |
| 2 | #reliable | [High] (×1.0) | session race condition (Phase B で対応した複雑性) の解消、入口経路の一貫性 |
| 3 | #efficient | [High] (×1.0) | Cloudflare Workers CPU 課金低減 (handler が瞬時 return)、bulk fire の対称性 |
| 4 | #usable | [Medium] (×0.5) | chat UI 起案者の体感速度低下を運用方針変更で許容、polling 表示 (ADR-0089) で代替 |
| 5 | #flexible | [Medium] (×0.5) | retroactive を chat UI でも使える機能拡張、将来別 LLM 移行時の影響範囲縮小 |
K.O. criterion: Must 軸 (#maintainable) score < 3 は不採用。
3.2 評価軸 × 案スコア表
| 軸 | 係数 | 採択案 (CI 寄り統一) | 案 B (Web UI 寄り統一) | 案 C (共通 wrapper) | 案 C′ (shared handler module) | 案 D (現状維持) | 案 E (chat UI 廃止) |
|---|---|---|---|---|---|---|---|
#maintainable | ×2.0 | 4 | 2 | 3 | 4 | 1 | 4 |
#reliable | ×1.0 | 4 | 2 | 3 | 4 | 2 | 4 |
#efficient | ×1.0 | 5 | 1 | 3 | 3 | 3 | 4 |
#usable | ×0.5 | 3 | 5 | 4 | 4 | 4 | 1 |
#flexible | ×0.5 | 5 | 2 | 3 | 3 | 2 | 1 |
| 加重和 (正規化) | 0.840 | 0.420 | 0.620 | 0.740 | 0.400 | 0.680 | |
| K.O. 通過 (Must ≥3) | ✓ | ❌ | ✓ | ✓ | ❌ | ✓ |
加重和 = Σ(score × 係数) / (5 × Σ係数)、Σ係数 = 5.0。採択案の #maintainable は当初 self-score 5 だったが、追加する Queue silent failure 対策 (sweeper/DLQ/timeout) 自体が新たな保守対象・障害源となり、その net 利益が Phase A-4 の実測に条件づく managed なリスクであることを正直に反映し 4 に修正した (K.O. 通過)。案 C′ は 0.740 と採択案 (0.840) に肉薄するため、Phase A-4 で「採択案 vs 案 C′」の正式 cost-benefit 再比較を handler 変更着手より前に必須実施し、案 C′ が同等以上かつ async コスト不要と判明したら方針転換する (撤退条件 #8 と連動)。
検討した代替案 (Alternatives Considered)
- 採択案 (CI 寄り統一):
/chat/startを即 enqueue + polling パターンに統合。技術前提条件なし (ADR-0095 採択を待たずに着手可能)、改修漏れリスクの構造的解消、retroactive 機能拡張の副次効果。Web UI 起案頻度減少という運用方針変更で UX 妥協 (Submit 後 5-30 秒待ち) を許容。 - 案 B (Web UI 寄り統一: CI も handler 同期 triage):
/runsも handler で triage を同期実行。却下理由: (i) Cloudflare Workers 30 秒 wall-time 制限で retry 込み triage (今日観測 23 秒) が境界線、ADR-0095 採択・実装済で前提自体は充足するが、(ii) GitHub Actions step / curl CLI の待ち時間が 30 秒に増加、bulk fire (retroactive で過去 50 件 audit 等) が成立しなくなる、(iii) Workers CPU 課金スパイク。Must 軸 K.O. 不通過。 - 案 C (共通 wrapper 抽出のみ、入口は 2 つのまま):
runTriage(state, env)を 1 関数に抽出して handler / consumer 双方が呼ぶ。却下理由: triage core ロジックの重複は解消するが、付随処理 (UI 配線 / audit 書込 / session schema) は依然 2 箇所必要。改修漏れリスクは部分的にしか解消しない。PR #1200/#1217 のような UI 配線漏れには効果限定。 - 案 C′ (shared handler module: UI 配線・telemetry・session 書込まで包含): triage core に加え saveTelemetryRecord・UI フィールド書込・session schema 操作を 1 モジュールに集約し handler / consumer 双方が呼ぶ。評価保留: PR #1214/#1217 型の漏れも防げる設計可能性があり、async 化コスト (sweeper/DLQ/timeout) を負わずに #maintainable を満たせる可能性。Phase A-4 で正式 cost-benefit 再比較を handler 変更着手前に必須実施。
- 案 D (現状維持、ADR-0094 Phase B/C で対応済): 個別 PR で対応継続。却下理由: 観測した 5 件の漏れは同種パターンの繰返し、構造的対策が必要。Must 軸 K.O. 不通過 (#maintainable=1)。
- 案 E (chat UI 廃止、CI 専用に): chat UI を撤去して /runs のみ残す。却下理由: 起案者の最初のオンボーディング体験 (Web UI で気軽に試せる) を失う、起案頻度が減るとはいえゼロにはならない。
影響 (Consequences)
§5.1 正の影響 (Good)
- 改修漏れリスクの構造的解消: triage 周辺コードが handler / consumer の 2 箇所から consumer の 1 箇所に集約。今後の同種改修 (新 rejection_reason_code 追加 / UI 表示変更 等) で 2 箇所同期する必要が消える
- コード簡素化: ADR-0094 Phase B で導入した
session.triageResultcache が撤去対象、pipeline_consumer.tsの skip 判定 (state.triageResultあり時に preGraph skip) も削除可、合計 ~30 行のコード削減 - retroactive を chat UI でもサポート可能: 現状 /runs 経由のみ、統一後は chat UI からも retroactive validation を起動可能 (ADR-0052 の機能拡張)
- Workers CPU 課金低減: handler が瞬時 return するので 1 リクエストあたり CPU time が ~3 秒 → ~50ms に短縮、月次数百起案で課金 ~10% 削減見込み
- bulk fire の対称性: chat UI / CI 両方で複数 tab / 並列 fire が等価動作、retroactive bulk audit (過去 50 件) などの新ユースケース成立
- GitHub Actions workflow との設計対称性: chat UI と workflow が「fire → polling」の同じ pattern、ドキュメント / オペレータガイド統一可
- ADR-0095 (triage retry 抑制) との独立: 本 ADR は ADR-0095 に依存せず着手可能。ADR-0095 実装済により triage 実行時間の上限が締まり async 化の安全余裕が増している
§5.2 負の影響 (Bad)
⚠️ 案 C′ 採択 (Phase A-4) による moot/不適用の整理: 本節の負の影響の多くは 採択案 (triage の async 化) 固有であり、最終決定の 案 C′ (shared triage module・chat UI は同期 triage 維持) では発生しない。具体的には:
- 「chat UI 起案者の体感速度低下 (3 秒 → 5-30 秒)」→ 案 C′ では発生しない (chat は同期 triage で即答のまま)。
- 「ADR-0066 即時応答性要件の再定義」→ 不要 (案 C′ は即時応答性を維持。詳細は §参照 ADR-0066)。
- 「ADR-0094 Phase B (session.triageResult cache) の revert」→ 行わない (案 C′ は Phase B を保持)。
- Queue silent failure 対策 (sweeper / DLQ / timeout) → 本 ADR (EC-2) からは分離し EC-3 として別スコープ (per-session DO alarm watchdog で実装済)。§コスト試算の Phase A-2/A-3 (~2.05 人日) は EC-2 入口統一のスコープ外。
以下の各項目は採択案前提の記述として残置 (A-4 の判断材料・監査証跡)。案 C′ での扱いは上記のとおり。
chat UI 起案者の体感速度低下: Submit 後の最初の feedback まで 3 秒 → 5-30 秒。typo 即時 reject も 3 秒 → 10-30 秒。対策: (i) ユーザ確認済の運用方針変更 (Web UI 起案頻度減少) で許容、(ii) chat UI の進捗表示 (ADR-0089 per-gate progressive streaming) で「処理中、Gate 0 実行中」を起案者に見せて不安を抑える、(iii) operator_guide §4 起案手順に「polling 動作」を明示。
ADR-0066 即時応答性要件の再定義: ADR-0066 への corrigendum を sub curation 時に追加、「本 ADR 採択以降は polling 前提の即時応答性 (= sessionId 即時返却 + Gate 進捗の即時 polling 開始) を満たす」と再定義。
ADR-0094 Phase B/C で導入した session.triageResult cache の revert: Phase B 機能 (PR #1212) を部分的に撤去、Phase C の audit migration (PR #1217) は維持。対策: revert scope を明確化 (handler の triage 実行 / session.triageResult 書込 / consumer の skip 判定の 3 箇所のみ、triageRejectKind / triage_reject_kind カラムは継続)、ADR-0094 への corrigendum で「Phase B は本 ADR 採択により部分 supersede」を明記。
chat UI の Gate 0 ステップ表示: 現状 handler 同期で「Gate 0 ほぼ即時 done」、統一後は「Gate 0 pending → running → done」と通常 polling の流れに。対策: ADR-0089 progressive streaming で既に他 gate と同じ表示パターン実装済。
エラーハンドリングの変化: 現状 handler 内 triage error は HTTP 500 即返却、統一後は consumer 内エラー → polling 経由でクライアントに通知。対策: ADR-0089 のエラー伝搬機構 (session.error フィールド) を利用、polling 中に session.status === 'error' を検知したら起案者にエラー表示。
Queue silent failure (メッセージ消失 / consumer クラッシュ): 同期 handler は triage 失敗を即 HTTP 500 で返せたが、async 化で enqueue 成功後に consumer がメッセージを取りこぼす / status 書込前にクラッシュすると、session が queued のまま無期限放置され起案者は永久に polling し続ける (過去 BUG_tracking「Queue メッセージ滞留 21 件」と同型の沈黙障害)。同期入口が持っていた「即時 fail-fast」が失われるのが async 統一の最大の構造リスク。対策: (i) consumer pickup 時に queued→running 即遷移で pickup 有無を可視化、(ii) polling 側で enqueuedAt から 5 分無進捗なら timeout 表示、(iii) Cloudflare Queues 自動リトライ + Dead Letter Queue で取りこぼし検知、(iv) 定期 sweeper で queued 超過 session を error 化。これらを Phase A の必須スコープに含める。
in-flight session (24h TTL) の schema 非互換による consumer クラッシュ連鎖 (critical): Phase A 移行後も Cloudflare Queues には旧形式 (session.triageResult あり) のメッセージが最大 24 時間残留する。consumer の preGraph skip 判定を削除した直後、旧メッセージ受信時に triageResult 参照が undefined エラー → 自動リトライで再クラッシュ → DLQ 溢れと queue 詰まり (ADR-0094 で解消した triage 二重実行が deploy 直後 24h 窓に再現する恐れも)。対策: 2 段階 deploy を必須化 — (i) consumer 側で triageResult フィールドの有無を optional check する移行用コードを一時挿入、(ii) 24h TTL 経過 (または in-flight メッセージ 0 件確認) 後に skip 分岐を削除。e2e に「旧形式 session を consumer に直接注入して正常終了」シナリオを追加し CI 自動検証。あるいは deploy 前に queued 状態の全 session を drain (手動 flush)。
sweeper と consumer の二重 status 遷移 race condition (high): sweeper が queued 超過を検知して error 遷移する直前に consumer が pickup して running 遷移しようとすると、書込順序依存で「error 表示なのに実際は正常完了 (error→done)」の矛盾状態が生じる (ADR-0094 で観測した二重実行と同型の沈黙バグ)。対策: sweeper の status 書込を 条件付き更新 (compare-and-swap: 現在値が queued の場合のみ error へ) とし、consumer の queued→running 遷移も排他制御。Cloudflare DO storage API でアトミック境界を明示、遷移ログを audit_runs に書込み事後検証可能化。
sweeper / DLQ 監視の属人運用化 (high): 週次手動集計に依存すると sweeper の cron 無効化・DLQ 無音溢れが次サイクルまで検知されない。対策: sweeper 実行時に処理件数 (0 件含む) を構造化ログ出力し連続 2 回以上 0 件でアラート、DLQ 蓄積件数・stuck 率しきい値超過を scheduled event 内でチェックし自動通知。Phase A の必須スコープ (A-2) に死活監視を明示計上。
DLQ consumer の死蔵化 (critical): DLQ consumer が「メッセージを再処理する」のか「アラートを発火するだけ」なのかが定義されていないと、個人開発環境では本番同等負荷でのテスト機会が乏しく DLQ 死蔵が発生しやすい。撤退条件の stuck-session 率 (#7) は session.status が queued のままを前提とするが、consumer 途中クラッシュは running のまま放置されるため検知漏れ盲点がある。対策: DLQ consumer の仕様を「再処理試行 → 失敗時に session.status を error に遷移させ audit_runs にエラー理由を書込む」と明確化し、DLQ 経由の error 遷移を sweeper 経由と区別して記録。撤退条件 #7 の検知指標に「session.status が running のまま閾値超過 (consumer クラッシュ由来の stuck)」を追加。
ADR-0089 progressive streaming への依存度増加が単一障害点化 (high): 統一後は 0089 が停止すると起案者は結果を一切取得不能 (現状は handler 同期で triage 結果のみ即取得可能)。対策: 0089 停止時の fallback polling endpoint (GET /sessions/:id で status/error を直接取得するシンプル endpoint) を Phase A-3 で実装し、Phase A リリースと同時に本番稼働させる。fallback endpoint 自体も DO 依存となるため、DO 障害時の graceful degradation 応答仕様を Phase A-3 設計時に定義し、DO 死活監視を sweeper とは独立した経路 (Workers Analytics / 外形監視) で実施。ADR-0089 の過去 3 ヶ月の可用性実績を Phase A 着手前に記録し「既に対応済」の主張を定量裏付け。
Cloudflare Queues メッセージ順序保証欠如による triage 結果の非決定的上書き (high): 同一 sessionId に対して chat UI から複数回 Submit (誤操作・ネットワーク再試行) が発生した場合、メッセージ到着順序が保証されず、旧メッセージが遅延して後着し正常完了済み session の status を queued に巻き戻す / triage 結果を上書きする競合が発生しうる。対策: enqueue 時に sessionId を冪等キーとして Cloudflare Queues deduplication (または DO storage への enqueue フラグ compare-and-set) を利用し、同一 sessionId の重複メッセージを consumer 側でスキップする冪等ガードを Phase A 必須スコープに追加。e2e テストに「同一 sessionId を 2 回連続 enqueue して consumer が 1 回のみ処理する」シナリオを加える。
sweeper の cron 実行頻度と stuck 判定閾値のチューニング不足による誤 error 遷移 (high): Cloudflare Workers Cron Trigger は最小 1 分間隔で cold start・実行遅延も発生、triage 処理が ADR-0095 後でも最大 23 秒、bulk retroactive で queue バックログが積み上がると正常 session が 5 分閾値で誤 error 遷移する可能性。対策: Phase A-2 実装前に Cloudflare Queues の consumer 処理遅延の実測分布 (p95/p99) を取得、sweeper 閾値を「p99 遅延 × 2 + 自動リトライ回数 × retry 間隔」で算出。sweeper が error 遷移させた件数を audit_runs に記録、翌営業日に件数スパイクを検知したら閾値を自動的に緩和するフィードバックループを設計。
Cloudflare Queues の実スループット・レイテンシ特性未検証 (high): 「5-30 秒待ち」前提の内訳が未記載。bulk fire 時 (retroactive 50 件) に同一 consumer が FIFO 順で処理する場合、後続メッセージ pickup は最悪 23 秒 × 50 = 1150 秒後となり得、chat UI 起案が bulk job に割り込まれた場合「5-30 秒」を大幅超過の可能性。対策: Cloudflare Queues consumer pickup レイテンシを staging で実測 (idle 時 / 5 件積滞 / 50 件積滞の 3 条件)、「5-30 秒」の実測根拠として Context または Confirmation に記録。retroactive bulk と chat UI 起案が同一 queue 共有時の最悪待ち時間を計算、許容超なら queue 分離 (chat UI 専用 vs CI/retroactive 用) を検討。timeout 閾値 5 分の根拠を「queue 積滞シナリオの p99 処理時間」から逆算。
retroactive validation の chat UI 統合: 副次効果として可能になるが実装は別 PR で慎重に。本 ADR scope では「将来 chat UI でも retroactive 可」を可能性として記述、実装は別 ADR (or Phase C of 本 ADR) で扱う。
§5.3 中立・トレードオフ (Neutral / Trade-offs)
- 起案者から見ると「Submit 後すぐ Standard と表示される快適さ」が消える代わりに、「動作の透明性 (どの Gate が実行中か見える)」が得られる、起案文化の方向性次第
- chat UI と CI workflow の挙動が完全に同じになるのは「テスト容易性」「再現性」観点で利点だが、「Web UI ならではの体験」がなくなる
- ADR-0066 の前提を再定義することで、将来「即時応答性」を求める別機能 (e.g., リアルタイム ADR 検索) を追加するときに別途設計が必要
- 「Web UI 起案頻度減少」前提が単一時点確認 (2026-06-01) に依存する脆弱性: 6 ヶ月後の事業変化 (新規オンボーディング・戦略変更) で前提が崩れると、5-30 秒遅延の UX 劣化が固定され、その時点の rollback コストは consumer 側に積み上がった sweeper/DLQ/retroactive 統合を含み 1.3 人日を超える (Phase A 完了後は 3.70 人日全体の再設計に近い規模)。対策: /chat/start 対 /runs のリクエスト比率を Cloudflare Workers Analytics で自動収集、「chat UI 経由起案が月次 runs 全体の 30% 超」を前提崩壊の定量トリガーとして撤退条件 #6 に組み込み、ADR-0066 corrigendum の発行を「4 週後実績確認後」ではなく「トリガー非発火を 12 週確認後」に延期しリスクバッファ確保。rollback 手順書を Phase B で作成し意思決定コストを可視化。
- 「改修漏れ解消」の成功体験による確証バイアスリスク: 直近 5 PR の痛みで利得側に認知が引かれ、新規追加コード (sweeper/DLQ/移行/timeout/CAS) の複雑度を「~30 行削減」で相殺と過小評価していないか。利得は具体的・即時・確実、コストは抽象的・将来・不確実という典型的現在バイアスパターンに合致。対策: Phase A-4 で「行数」「テスト必要箇所数」「障害モード数」軸で新規追加コンポーネント (sweeper/DLQ consumer/CAS/2 段階 deploy 移行コード/fallback endpoint) を事前列挙、削減複雑度と追加複雑度を同一フォーマットで並べ net 削減を可視化。「改修漏れリスクの構造的解消」KPI として採択後 6 ヶ月の triage 周辺 PR「手戻り発生件数」を観測可能 KPI に追加。
- Phase A-4 sunk cost fallacy リスク: Phase A-4 (案 C′ 再比較) が Phase A/A-2/A-3 実装進行中または完了後に実施されると「ここまで実装したのだから案 C′ への転換は非現実的」という心理的バイアスが働き、案 C′ が客観的優位でも採択案継続判断が下されるリスク。対策: Phase A-4 を Phase A の最初のステップ (handler 変更着手前) に強制配置、実装開始前に比較完了。比較基準を「追加コード行数・テスト箇所数・障害モード数の実測値」で定量化、採択案が案 C′ を上回る条件を数値で事前定義 (例: 採択案の障害モード数が案 C′ の 1.5 倍以下)。比較結果を別 PR description に記録、後検証可能化。
コスト試算
| 作業 | 工数 (人日) | 工数 (h) | Phase | 順序制約 |
|---|---|---|---|---|
| Phase A-4: 「採択案 vs 案 C′」cost-benefit 再比較 + 新規追加コード行数・障害モード数の事前列挙 + 評価閾値の事前定義 | 0.15 | 0.9 | A-4 | 最優先 (Phase A 着手前必須) |
Phase A: /chat/start handler から triage 同期実行を撤去 + 即 enqueue 化 + response shape 変更 | 0.3 | 1.8 | A | A-4 後 |
Phase A: session schema から triageResult フィールドを撤去 (pipeline_session.ts) | 0.15 | 0.9 | A | A-4 後 |
| Phase A: consumer の skip 判定削除 (2 段階 deploy 用 optional check を含む) | 0.25 | 1.5 | A | A-4 後 |
| Phase A: chat UI の chat.html を polling 前提の表示に微調整 (現状 ADR-0089 で対応済のため微修正のみ) | 0.2 | 1.2 | A | A-4 後 |
| Phase A: e2e (chat UI / CI 両経路で 4 シナリオ + 旧形式 session 注入 + 重複 enqueue 冪等性シナリオ、結果一致確認) | 0.35 | 2.1 | A | A-4 後 |
| Phase A-2: consumer pickup 時の queued→running 即遷移 + compare-and-swap 排他制御 | 0.3 | 1.8 | A-2 | A-4 後 |
| Phase A-2: enqueuedAt から 5 分 timeout の polling 側クライアント状態管理 | 0.25 | 1.5 | A-2 | A-4 後 |
| Phase A-2: Cloudflare Queues DLQ 設定 + DLQ consumer 実装 (再処理 → 失敗時 error 遷移仕様) + モニタリング連携 | 0.4 | 2.4 | A-2 | A-4 後 |
| Phase A-2: 定期 sweeper (Cron Trigger or DO alarm) で stuck-session を error 遷移 (条件付き更新 + p99 ベース閾値) | 0.4 | 2.4 | A-2 | A-4 後 |
| Phase A-2: 死活監視 (sweeper 処理件数 0 件連続アラート、DLQ 蓄積・stuck 率しきい値通知、DO 障害独立監視) | 0.25 | 1.5 | A-2 | A-4 後 |
| Phase A-2: 重複 enqueue 冪等ガード (sessionId compare-and-set / Queues deduplication) | 0.15 | 0.9 | A-2 | A-4 後 |
| Phase A-2: Cloudflare Queues consumer pickup レイテンシ実測 (idle/5 件/50 件積滞)、timeout 閾値の p99 逆算 | 0.15 | 0.9 | A-2 | A-4 後 |
| Phase A-3: GET /sessions/:id 直接 polling endpoint (ADR-0089 停止時の fallback degrade、Phase A 必須スコープに前倒し) | 0.2 | 1.2 | A-3 | A 同時リリース |
| Phase B: ADR-0066 / ADR-0094 への corrigendum 追加 (前提崩壊トリガー非発火を 12 週確認後に発行) | 0.2 | 1.2 | B | A 完了後 |
| Phase B: rollback 手順書作成 (前提崩壊時の意思決定コスト可視化) | 0.15 | 0.9 | B | A 完了後 |
| Phase C (任意): retroactive を chat UI からも起動可能化 (実装は別 PR or 後続 ADR) | 0.3 | 1.8 | C | B 後 |
| 合計 (Phase A-4 + A + A-2 + A-3 + B、Phase C 除く) | 約 3.70 人日 | ~22.2 h |
順序制約の明文化: Phase A-4 (案 C′ 再比較) を Phase A-2/A-3 着手より前に必須実施。評価基準を事前に「付随処理の変更箇所数」「新規障害モード数」「テスト必要箇所数」の 3 軸で閾値設定し、評価者の確証バイアス (sunk cost fallacy) を抑制。Phase A-4 で案 C′ に方針転換する場合、Phase A-2/A-3 の大半 (~2.05 人日) が不要となり工数は ~1.65 人日に圧縮される。
⚠️ 実績 (案 C′ 確定後): Phase A-4 で案 C′ を採択したため、上表の Phase A-2 (sweeper / DLQ consumer / timeout) と Phase A-3 (fallback endpoint) = 合計 ~2.05 人日は EC-2 入口統一のスコープ外。これらの Queue silent failure 対策は EC-3 として独立スコープに移し、既存 async consumer の堅牢化として per-session DO alarm watchdog で別途実装済 (本 ADR には同梱しない)。EC-2 実装は shared triage module への集約のみ。
金額換算: 個人開発、自工数のみ、直接金銭支出 0 円。Workers CPU 課金削減効果: 1 リクエストあたり 3 秒 → ~50ms (handler 占有時間)、月次 100 起案で年 ~$5 削減見込み。改修コスト削減 (今後の triage 周辺改修で 2 箇所→1 箇所): 1 PR あたり 30 分削減、年 10 PR 想定で 5 時間 ≒ 0.6 人日相当の永続的削減。当初試算 1.3 人日からの増分 (2.4 人日) は Queue silent failure 対策 (Phase A-2)・fallback (A-3)・案 C′ 再比較 (A-4)・冪等ガード・実測・rollback 手順書を盲点指摘どおり個別計上した結果。net 利益検証: ~30 行削減に対し新規追加 (sweeper/DLQ handler/移行コード/timeout/CAS/冪等ガード) の行数・テスト箇所数・障害モード数を Phase A-4 で実測し follow-up に記録。
撤退条件 (Rollback Plan)
| # | 判定指標 | 期限 / 検知方法 | 代替アクション |
|---|---|---|---|
| 1 | chat UI 起案者から UX 不満報告 3 件以上 (運用方針変更後でも許容範囲を超える場合) | 起案者報告 / 週次レビュー | Phase A revert (handler 同期 triage を復活、session.triageResult cache 再導入)、案 C (共通 wrapper 抽出) に方針転換 |
| 2 | polling 安定性問題 (ADR-0089 progressive streaming が正常動作しない) | session.gateProgress 更新が 30 秒以上停止する run が 1% 超 | ADR-0089 機能の堅牢性確認、fallback endpoint への切替、必要なら handler 同期 triage 部分復活 |
| 3 | session.error 経由のエラー検知率が低下 (起案者がエラーに気付くまで遅延) | 月次 audit_runs で rejected=true の検知から起案者報告までの delta 計測 | エラー通知 UX を別途強化 (polling 中の banner 表示 / メール通知 等) |
| 4 | Workers CPU 課金削減効果が出ない (削減見込み 10% を達成せず) | 4 週後の Cloudflare dashboard CPU time 集計 | Phase B/C で追加の最適化検討、または現状維持 |
| 5 | ADR-0066 / ADR-0094 への corrigendum 影響範囲が想定より大きい (他 ADR への波及検知) | corrigendum PR 起案時の整合チェック | corrigendum 範囲を縮小、本 ADR の supersede 関係を見直し |
| 6 | Web UI 起案頻度減少という運用方針変更が撤回された場合 / 定量トリガー発火 (chat UI 経由起案が月次 runs 全体の 30% 超) | プロダクト方針変更通知 + Workers Analytics 自動収集、トリガー発火から 2 週間以内に rollback 判断 | 本 ADR 全体 rollback、handler 同期 triage を復活、UX 即時応答性を優先 (Phase B で作成済の rollback 手順書を使用) |
| 7 | Queue silent failure: queued のまま閾値超過 (5 分) の stuck-session が全 run の 1% 超、または running のまま閾値超過 (consumer クラッシュ由来の DLQ 死蔵) が検知 | 週次 audit で session.status 分布を集計 + 死活監視アラート + DLQ 蓄積件数 | DLQ / sweeper / timeout 表示を強化、改善せねば handler 同期 triage (即時 fail-fast) を部分復活 |
| 8 | Phase A-4 再比較で案 C′ (shared handler module) が同等以上の #maintainable (障害モード数が採択案の 1.5 倍以下等の事前定義閾値) かつ async コスト不要と判明 | Phase A 着手前 (Phase A-4 を最優先実施) | 案 C′ に方針転換、Phase A-2/A-3 中止 (工数 ~1.65 人日に圧縮) |
| 9 | in-flight session (24h TTL) で consumer クラッシュ連鎖発生 | deploy 直後 24h の DLQ 蓄積件数 / consumer error log | 2 段階 deploy 強制適用、optional check の TTL 延長、最悪 deploy revert |
| 10 | sweeper と consumer の race 起因の status 矛盾 (error→done) 検知 | audit_runs の status 遷移ログで異常パターン抽出 | compare-and-swap 実装を再点検、必要なら sweeper 一時停止 |
| 11 | sweeper 閾値誤発火による正常 session の誤 error 遷移 | audit_runs の sweeper-error 件数の日次集計でスパイク検知 | sweeper 閾値を p99 ベースで再算出、フィードバックループで自動緩和 |
| 12 | Cloudflare Queues bulk fire 時の chat UI 起案待ち時間が「5-30 秒」想定を大幅超過 (p99 > 60 秒) | staging 実測 + 本番 enqueuedAt→consumer pickup 時間の月次集計 | queue 分離 (chat UI 専用 vs CI/retroactive 用) を実装、bulk fire の優先度制御 |
Confirmation
- 検証手段:
- e2e 一致性テスト: 同じ draft を chat UI / CI 両経路で fire し、Pipeline 全 gate の結果が一致することを確認 (本 ADR の核心、入口統一の成功指標)
- Phase A 個別テスト: chat UI から typo / cost-missing / placeholder / normal の 4 シナリオを fire、polling で正しく結果取得できることを確認
- 改修漏れリスク検証: handler の triage 関連分岐が消えていることを
grep -E 'preGraph|triageResult' drp/src/index.tsで確認、0 件期待 - session schema 整合: PipelineSessionState から
triageResultが削除されていることを type-check で確認、in-flight session (24h TTL) は consumer 側 optional check で安全に degrade することを 2 段階 deploy 手順で担保 - audit_runs 書込パス統合: triage 関連の saveTelemetryRecord 呼出が 1 箇所のみであることを確認、ADR-0087 per-gate timings カラムの triage_ms が consumer 経由で常に正しく記録される
- chat UI 表示一貫性: ADR-0089 progressive streaming で chat UI が正しく Gate 0/1/2/.../9 の進捗を表示、エラー時の session.status='error' 検知を実機確認、fallback endpoint (GET /sessions/:id) からも最終結果 (approve/reject/error) を取得できる UI パスを確認
- ADR-0094 Phase B revert 範囲: 削除対象 (handler triage / session.triageResult / consumer skip) と維持対象 (triageRejectKind / triage_reject_kind カラム / Phase A UI 修正) を明確に区別、revert 漏れ / 過剰 revert を防ぐ
- 重複 enqueue 冪等性検証: 同一 sessionId を 2 回連続 enqueue して consumer が 1 回のみ処理することを e2e で確認
- Cloudflare Queues レイテンシ実測: staging で idle / 5 件積滞 / 50 件積滞の 3 条件で consumer pickup レイテンシを測定、「5-30 秒」前提の実測根拠を記録
- ADR-0089 可用性実績: 過去 3 ヶ月の session.gateProgress 更新遅延率・停止インシデント数を Phase A 着手前に記録
- 実行頻度: 修正 PR マージ時の手動 e2e、月次の audit_runs サンプリング (50 件)、4 週後の CPU 課金集計、Workers Analytics による /chat/start 対 /runs 比率の自動収集 (月次)、起案者報告は随時
- 違反時対応: 撤退条件 (§撤退条件) 発動、Phase A 単独 rollback (handler 復活 + session.triageResult 再導入) または案 C′ への方針転換で構造を戻せる
- 観測可能 KPI:
- 入口経路一致率: 同じ draft を両経路で fire した結果の一致率 (目標 100%、許容下限 99%)
- chat UI 起案者 UX 不満報告件数 / 月 (目標 0、許容上限 2)
- triage 周辺改修の同期作業箇所数 (目標 1、現状 2)
- triage 周辺 PR の手戻り発生件数 / 6 ヶ月 (目標 0、確証バイアス検証用)
- Workers CPU time 削減率 (目標 50%、許容下限 30%、月次 dashboard 比較)
- polling エラー検知 delta (目標 ≤ 5 秒、許容上限 30 秒)
- retroactive validation の chat UI 経由利用件数 / 月 (Phase C 実施時の指標、目標 ≥ 1)
- stuck-session 率 (
queuedのまま 5 分超 +runningのまま閾値超過): 目標 0%、許容上限 1% (Queue silent failure 検知) - chat UI 起案比率 (月次 runs に占める /chat/start 経由割合): 前提崩壊トリガー = 30% 超
- sweeper 誤
error遷移率: 目標 0%、許容上限 0.5% - Cloudflare Queues consumer pickup p99 レイテンシ: 目標 ≤ 30 秒、許容上限 60 秒
参照 (References)
- 関連 ADR:
- ADR-0066 (Pipeline async queues + DO): 本 ADR の母体。案 C′ 採択により corrigendum は不要 — 採択案で予定した「即時応答性 → polling 再定義」は行わず、ADR-0103 は ADR-0066 の Web UI 即時応答性を 維持 (supersede しない)。chat UI は同期 triage で即答のまま。並立・維持
- ADR-0089 (DO per-gate partial 分散保存 + progressive streaming): 本 ADR で chat UI が全 gate polling に統一されることで 0089 の機能依存度が増す。0089 が想定した「Web UI でも CI でも同じ polling pattern」が本 ADR で実現。並立・補強
- ADR-0094 (triage rejection 経路の意味論分離): 案 C′ 採択により corrigendum は不要 — 採択案で予定した Phase B (session.triageResult cache + consumer skip) の partial supersede は行わず、ADR-0103 案 C′ は Phase B を 保持する (chat UI は同期 triage を維持するため cache 撤去が不要)。Phase A / C も従来どおり維持。並立・保持 (supersede なし)
- ADR-0095 (triage retry 抑制、2026-06-01 Accepted・Phase A 本番稼働): 本 ADR は ADR-0095 に依存せず着手可能。ADR-0095 実装済により triage_ms 上限が締まり、本 ADR の async 化の安全余裕がさらに増している。並立・独立補強
- ADR-0052 (retroactive validation mode): 本 ADR Phase C で retroactive を chat UI からも起動可能化、0052 の機能を Web UI に拡張する。並立・拡張
- ADR-0073 (Gate0 triage seed regression test): 本 ADR で chat UI / CI の triage 実行場所が consumer に統一されるため、0073 の seed 再現性テストが両経路で同じ pattern になり整合性向上。並立・補強
- ADR-0100 / ADR-0101 / ADR-0102 (Pipeline 迂回の前例): 本 ADR も Cross-Validation の構造的過剰審査 (2 連続差し戻し) を理由に同じ迂回判断を取る。
- Supersede / Conflict: なし (案 C′ 採択により当初予定の ADR-0094 Phase B partial supersede は撤回。ADR-0103 は既存 ADR を supersede しない)
- 関連 PR/Issue: PR #1200 / #1204 / #1209 / #1212 / #1214 / #1217 (ADR-0094 Phase B/C で観測した triage 周辺改修漏れ)
- 将来 backlog: Phase C (retroactive を chat UI でも) は本 ADR scope では将来オプション、別 ADR or 本 ADR follow-up PR で実施
- 外部資料: Cloudflare Queues / Cron Trigger / Durable Objects storage API 公式ドキュメント (consumer pickup レイテンシ・DLQ 仕様・compare-and-swap 仕様の参照用)