Skip to content

fix(monitor): 不完全な信号から success を導く fail-open 2 件を修正 (rate-limit 検知 / 判定文) - #309

Closed
aloekun wants to merge 4 commits into
masterfrom
fix/monitor-fail-open-signals
Closed

fix(monitor): 不完全な信号から success を導く fail-open 2 件を修正 (rate-limit 検知 / 判定文)#309
aloekun wants to merge 4 commits into
masterfrom
fix/monitor-fail-open-signals

Conversation

@aloekun

@aloekun aloekun commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • CodeRabbit の rate-limit 検知が「制限中と判っているのに待ち時間を読めない」だけで制限なし扱いになっていた fail-open を、構造ごと修正 (marker 一致なら既定値付きで必ず rate-limit として報告)
  • CR が 3 度目に変更した書式 **Next review available in:** **15 minutes** の regex を追加
  • 監視レポートの判定文が unresolved_threads を無視し、未解決スレッドが残っていても「問題は見つかりませんでした」と結論していた fail-open を修正
  • WP-15 の 完了 条件 (1) 達成 (release 実生成 + 実 artifact での検証) を plan に記録

Context

いずれも本日の運用中に実発火した不具合。 2 件のコード修正は「不完全な信号から安心させる結論を出す」という同じ family で、人間がレポート末尾だけ読むと見落とす経路になっていた。

① rate-limit 検知 (PR #307 で実発火): CodeRabbit がレート制限でレビューを開始できなかったのに、監視は stop_monitoring_success / 「CodeRabbit 指摘なし」と報告した。検知は 2 段構えで、marker 判定 (rate limited by coderabbit.ai) は一致していたが extract_wait_time が既知 2 書式のいずれにも一致せず None を返し、parse_rate_limitextract_wait_time(body)? で早期 return していた。CR は書式を 3 度変えている:

ADR-034 が「CR は format を時間経過で変更するため multi-variant 配列で対応する (PR #182/#184 で silent regression を実体観測)」と警告していた事象の再発であり、regex を足すだけでは 4 回目で同じことが起きる。そこで marker 一致時は既定値 15 分で必ず park し、wait_time_parsed: false を下流へ伝えて summary / ログに「書式が未知」と明示する構造に変えた。既定値が短すぎても self-correcting (wakeup 後に再 poll し、まだ制限中なら再 park)。

② 判定文 (PR #307 の recheck で実発火): 同じレポート内で「未解決スレッド2件」「action: action_required」と表示した直後に「判定: 問題は見つかりませんでした」と結論していた。compute_verdictfindings の件数だけで分岐しており、再チェックでは新規コメントが無く findings が空になる一方で未解決スレッドは残り続けるため。action は正しく action_required になっていたので、判定文だけが不完全だった。post-merge feedback の analyzer も独立に Tier 1 High として検出している。

スコープ: master を最短で緑に戻す必要があった E2E の競合修正 (PR #308) は先行させ、本 PR は監視系 fail-open 2 件 + 記録に限定した。

Validation

  • Windows: cargo test --workspace 全 pass / clippy --workspace --all-targets --all-features -- -D warnings clean
  • Linux (WSL Ubuntu 24.04): 39 スイート全 pass / clippy clean
  • cli-pr-monitor 257 → 262 (判定文の test 4 件 + helper 追加)、check-ci-coderabbit 97 pass
  • incident 由来 fixture は PR feat: Linux バイナリビルド + クラウド setup script (WP-15) #307 に実投稿された comment 本文を使用 (ADR-049 の流儀)
  • 既存 test rate_limit_no_match_when_no_wait_time旧来の誤挙動そのものを固定していたため、新契約に合わせて改名・書き換え (理由を doc に明記)
  • pnpm lint:docs / lint:md pass、pre-push review verdict=APPROVE

References

aloekun and others added 3 commits July 20, 2026 21:01
PR #307 で実発火した誤報告の修正。CodeRabbit がレート制限でレビューを開始
できなかったにもかかわらず、監視が「CodeRabbit 指摘なし」= stop_monitoring_success
と報告した (人間が PR コメントを直接読むまで誰も気付けない状態だった)。

原因は 2 段構えの検知のうち後段だけが失敗し、それが fail-open だったこと:
- is_rate_limit_comment ("rate limited by coderabbit.ai") は一致していた
- extract_wait_time が既知 2 書式のいずれにも一致せず None
- parse_rate_limit は `extract_wait_time(body)?` で早期 return するため、
  **レート制限と判っているのに「制限なし」を返していた**

CR は書式を 3 度目の変更をしていた:
  旧:  Please wait **N minutes and M seconds**
  新:  More reviews will be available in N minutes and M seconds
  現行: **Next review available in:** **15 minutes**   ← 未対応だった

修正は 2 層:
1. **構造 (本質)**: marker が一致したら待ち時間が読めなくても必ず rate-limit として
   報告する。既定値 15 分で park し、`wait_time_parsed: false` を下流へ伝えて
   summary / ログに「書式が未知」と明示する。これにより 4 回目の書式変更が起きても
   silent success ではなく「park + 書式追加が必要という可視の警告」に着地する。
   短すぎる既定値は self-correcting (wakeup 後に再 poll し、まだ制限中なら再 park)。
2. **書式追加**: 現行書式の regex を追加。markdown 強調 (`**`) を `\**` と `\s*` で吸収。

既存 test `rate_limit_no_match_when_no_wait_time` は**旧来の誤挙動そのもの**を
固定していたため、新契約に合わせて書き換えた (改名 + 理由を doc に明記)。

ADR-034 が「CR は format を時間経過で変更するため multi-variant 配列で対応する
(PR #182/#184 で silent regression を実体観測)」と警告していた事象の再発であり、
regex を足すだけでは同じことが繰り返される。構造側を直したのはそのため。

実測: Windows で cargo test --workspace 全 pass / clippy clean。Linux (WSL) でも
check-ci-coderabbit rate_limit 28/28・cli-pr-monitor state 23/23 pass。
incident 由来 fixture は PR #307 に実投稿された comment 本文を使用 (ADR-049)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #307 の recheck で実観測: 同じレポート内で「未解決スレッド2件」
「action: action_required」と表示した直後に「**判定**: 問題は見つかりませんでした」
と結論していた。人間はレポート末尾の判定行を読んで判断するため、未解決の
CodeRabbit 指摘を見落とす経路になっていた。

原因は compute_verdict が result.findings の件数だけで分岐していたこと。
再チェックでは新規コメントが無いため findings は空になるが、未解決スレッドは
残り続ける。action は正しく action_required になっていたので、**判定文だけが
不完全な信号から安心させる結論を出していた**。

findings が空かつ未解決スレッドがある場合の分岐を追加した。findings がある場合は
従来どおり severity ベースの判定を優先し、未解決スレッドの文言で severity 情報を
潰さない。unresolved_threads が None (不明) の場合は 0 と同じ扱いで従来挙動を維持。

これは同日に修正した rate-limit 検知の silent regression と同じ family
(不完全な信号から success を導出する fail-open) で、post-merge feedback の
analyzer も独立に Tier 1 High として検出していた。

test は ADR-049 の流儀で incident 再現 (bad) + 退行防止 (good) を対で追加:
- findings 空 + 未解決 2 件 → 「問題なし」と結論しないこと (incident 再現)
- 未解決 0 件 → 従来どおり「問題なし」
- unresolved_threads = None → 0 と同じ扱い
- findings あり + 未解決あり → severity ベース判定を優先

実測: Windows で cli-pr-monitor 262 pass (257→262、本 test 4 件 + helper 追加)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #307 / #308 マージ後に release-binaries.yml が master で実走し `nightly`
prerelease を生成した (tarball 9.72 MB + sha256、commit 541adde)。

公開された実物の artifact で end-to-end 検証した結果を記録する:
- 認証なしの素の curl で取得 (= 「public release は gh 認証不要」の設計判断を実証)
- checksum 一致 / 17 ファイル展開 (バイナリ 16 + BUILD_INFO)
- file が ELF 64-bit LSB pie executable, x86-64 ... stripped を確認
- **release バイナリそのもので Linux 上の hooks が実発火** (PreToolUse が危険
  コマンドを exit 2 でブロックし通常コマンドは exit 0、SessionStart が
  additionalContext JSON、tree-sitter の comment-lint が違反検出)

あわせて初回 run が赤だった経緯 (PR #308 で修正) も記録した。WSL では修正前でも
60/60 pass して再現できず ubuntu-22.04 の CI が初めて捕捉したため、
**「WSL 検証はスケジューリング依存の race に対しては CI の代替にならない」**という
限定的な教訓として書いた。WP-16 (CI matrix) の必要性を裏づける実例になる。

過度な一般化 (「WSL は CI の代替にならない」) は避けた。post-merge feedback の
analyzer が「既存メモと矛盾しうる」と指摘したとおり、WSL 検証自体は cargo test /
clippy / hooks 発火の確認には有効であり、限界は race に限られる。

残る `完了` 条件 (2) は実クラウドセッションでの cloud-setup.sh 実走のみ。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aloekun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40157f98-8753-4186-a5db-ce841caea47f

📥 Commits

Reviewing files that changed from the base of the PR and between 541adde and 908f6a9.

📒 Files selected for processing (9)
  • docs/harness-improvement-plan.md
  • src/check-ci-coderabbit/src/models.rs
  • src/check-ci-coderabbit/src/rate_limit.rs
  • src/cli-pr-monitor/src/stages/monitor.rs
  • src/cli-pr-monitor/src/stages/poll/iteration.rs
  • src/cli-pr-monitor/src/stages/poll/mod.rs
  • src/cli-pr-monitor/src/stages/poll/rate_limit.rs
  • src/cli-pr-monitor/src/stages/poll/rate_limit_signal.rs
  • src/cli-pr-monitor/src/state.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/monitor-fail-open-signals

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: CodeRabbit check = pass (Review completed 表示だが、実体は下記の通りレート制限で本文レビュー未実施)
  • レビュー状況:
    • CodeRabbit: レート制限中 (Review limit reached, 次回レビュー可能まで約23分後、2026-07-20T12:10:47Z 時点)。指摘コメント・PR レビューはまだ 0 件
    • 人間レビュアー: レビュー未提出 (reviewDecision 空、pulls/309/reviews 空配列)
  • Verdict: approved (適用可能な指摘が現時点で 0 件のため。ただし CodeRabbit 本レビュー未着のため暫定)

Applicable Findings (Critical / High / Major)

該当なし (指摘 0 件)

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

Diff 概要 (軽量サマリー)

  • 変更ファイル 8件 / 差分 約569行、fix/monitor-fail-open-signals ブランチ
  • 内容: PR feat: Linux バイナリビルド + クラウド setup script (WP-15) #307 で実観測した「不完全な信号から success を導いてしまう」fail-open を 2 件修正するバグ修正 PR
    1. check-ci-coderabbit: CodeRabbit rate-limit comment の書式が未知の場合に None(=制限なし) を返していた挙動を修正。marker 一致時は wait_time_parsed: false を伴うフォールバック既定値 (15分) で必ず Some を返す契約に変更。新書式 (Next review available in: **N minutes**) の抽出関数も追加
    2. cli-pr-monitor: compute_verdictfindings のみを見て判定文を出しており、unresolved_threads > 0 でも「問題は見つかりませんでした」と結論する矛盾があったのを修正
    3. state.rs / poll/*.rs: 新フィールド wait_time_parsed の伝播・後方互換 (#[serde(default)]) 対応
    4. docs/harness-improvement-plan.md: WP-15 完了条件(1)の達成記録更新 (ドキュメントのみ)
  • 変更はインシデント再現 (badケース) と回帰防止 (good ケース) の両方のテストを伴っており、ADR-049 (incident→eval regression suite) の流儀に沿っている

次のアクション

  • CodeRabbit のレート制限解除後 (約23分後 or @coderabbitai review 手動トリガー) に本レビューが走るのを待ち、その指摘内容を次回サイクルで再評価する
  • 現時点で CI・人間レビューによるブロッカーはなし。マージ判断は CodeRabbit 本レビュー到着後が望ましい

本 PR の先行 2 コミットだけでは**観測される症状が変わらなかった**ため追加する。
rate-limit の検出は直ったが、検出結果が action 判定で捨てられていた。

3 層構造だった:
  層1: 待ち時間パース失敗 → 「制限なし」扱い      (先行コミットで修正済み)
  層2: cr_ok が「コメント0件」を「レビューclean」と解釈   ← 本コミット
  層3: action 確定後に terminal 短絡し rate-limit 分岐に到達しない ← 本コミット

層2 が本質。CodeRabbit がレート制限でレビューを**開始できなかった**場合もコメントは
0 件になるため、`new_comments == 0 && unresolved_threads == 0` だけを見ると
**レビュー未実施と clean レビューが区別できない**。そこに ci_ok が重なり
stop_monitoring_success に倒れていた。

層3 は iteration.rs の `action != "continue_monitoring"` で即 terminal を返す短絡。
層2 で success が確定すると、せっかく検出した state.rate_limit を使う
handle_rate_limit_branch に一度も到達せず、park も `@coderabbitai review` の
再トリガーも起きなかった。

pending 判定を `checks_still_outstanding` として切り出し、rate_limit を
「まだ結論を出せない」条件に加えた。これで continue_monitoring に倒れ、既存の
park + 再トリガー経路へ流れる。skip_coderabbit 指定時は rate-limit も判断材料から
外す (skip の意味と整合)。

**実環境での検証**: 同じ PR #309・同じレート制限状態で修正前後を実測。
  修正前: action=stop_monitoring_success / 「問題は見つかりませんでした」
  修正後: action=parked_review_recheck  / 「review 完了待ちのため wakeup を予約」
checker 単体では既に rate_limit を正しく検出できていた (wait_minutes=23,
wait_time_parsed=true) ことも確認済みで、捨てていたのが監視側だと特定できている。

test は ADR-049 の流儀で incident 再現 (bad) + 退行防止 (good) を対で追加:
- rate-limit 中は「コメント0件」でも continue_monitoring にすること (incident 再現)
- rate-limit 無しなら従来どおり stop_monitoring_success に到達すること
- skip_coderabbit = true なら rate-limit も判断材料から外すこと

実測: Windows で cli-pr-monitor 265 pass (262→265) / clippy clean。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aloekun

aloekun commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: CodeRabbit check = pending (Review in progress)。他の必須 check なし。mergeStateStatus: UNSTABLE はこの pending check に起因
  • レビュー状況:
    • CodeRabbit: 前回サイクル (12:13 JST 分析時点) はレート制限中で本文レビュー未実施だったが、12:36:23Z に PR 作者 (aloekun) が @coderabbitai review を手動投稿し、12:36:27Z に CodeRabbit が "Review triggered" と受理応答。ただし本稿作成時点でも指摘コメント・PR レビューは引き続き 0 件 (pulls/309/reviews = [], pulls/309/comments = [])
    • 人間レビュアー: レビュー未提出 (reviewDecision 空)
  • Verdict: approved (適用可能な指摘が現時点で 0 件のため。CodeRabbit 本レビューは再トリガー直後で未着のため暫定)

Applicable Findings (Critical / High / Major)

該当なし (指摘 0 件)

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

Diff 概要 (軽量サマリー)

  • 変更ファイル 8件 / 差分 約569行、fix/monitor-fail-open-signals ブランチ (前回分析時点から diff・コミットに変化なし)
  • 内容: PR feat: Linux バイナリビルド + クラウド setup script (WP-15) #307 で実観測した「不完全な信号から success を導く」fail-open を2件修正するバグ修正PR (rate-limit 検知の書式未対応フォールバック / compute_verdict の unresolved_threads 見落とし)、および wait_time_parsed フィールド伝播とドキュメント更新

次のアクション

  • 手動再トリガーされた CodeRabbit レビューの完了を待ち、指摘が投稿され次第、次サイクルで内容を評価する
  • 現時点で CI・人間レビューによるブロッカーはなし。マージ判断は CodeRabbit 本レビュー到着後が望ましい

@aloekun

aloekun commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

本 PR は 再利用せず全破棄 します(マージしません)。

理由

4 コミット目の初版が「本番 config では一度も実行されない誤修正 + 実エントリポイントを迂回して pass するテスト」であり、pre-push review の High REJECT → fix step の自動書き直しを経た合成物になっていました。コミットメッセージには無効と判明した検証主張が残存し、実装も症状側への多層パッチ(monitor 側 2 箇所に分散)です。

中途半端な状態を引き継ぐと、それ自体がより良い実装を制約するため、健全に見えるコミットも含めて一切引き継がずにゼロから再構築します。

後継

同じ要件(R1〜R5)を docs/harness-improvement-plan.md の「WP-15 追補: 監視 fail-open 修正のゼロ再構築」に整理済みで、新規 PR で実装します。根本原因は decide() に rate_limit が渡っていないこと(症状側ではなく判定ロジック側)と特定済みで、新実装はそこを直します。

なお本 PR に残る CodeRabbit の rate-limit コメント(2026-07-20T12:10:47Z 投稿、第 3 世代書式 **Next review available in:** **57 minutes**)は、新実装の incident fixture / 実データ再現に使用します。

@aloekun aloekun closed this Jul 20, 2026
@aloekun
aloekun deleted the fix/monitor-fail-open-signals branch July 20, 2026 14:49
aloekun added a commit that referenced this pull request Jul 20, 2026
## R5: WP-15 `完了` 条件 (1) の達成記録

PR #307 マージ時の release-binaries.yml run は build job が失敗しており
(master が赤で #308 の E2E 修正が必要だった)、成功したのは #308 マージ後の
run (commit 541adde)。この経緯も含めて記録した。

生成物は本セッションで再実測している: WSL Ubuntu 24.04 から素の curl で
tarball (9,721,643 bytes) + .sha256 を**認証なしで**取得 → sha256sum -c 一致
→ 展開して 16 バイナリ + BUILD_INFO 確認 → release バイナリそのもので hooks
実発火 (pre-tool-validate が破壊的削除コマンドを exit 2 でブロックし無害な
echo を exit 0 で通す / session-start が additionalContext JSON を出力)。

これで WP-15 ④ の「public リポジトリの Release asset は素の HTTPS で取得
できるため gh CLI 認証は不要」という設計判断が実 URL・実 asset で裏づけられた。
旧 PR #309 の docs コミットの主張を引き写すのではなく、自分で再実測した結果を
記録している。

## 追補の実装状況

R1〜R4 の実装内容、破棄の実施結果、検証の実測値を反映。E2E カバレッジは
正直に申告した: 担保できたのは全 gate のユニット検証 / checker 実エントリ
ポイントの実データ実走 / 修正前後の差分実測の 3 点で、cli-pr-monitor 側の
統合経路 (park → PARK signal) と wakeup → 再 trigger 経路は、CR レート制限が
本セッション中に自然発生しなかったため未実測である旨を明記した。

なお本ファイルは master 時点で既に 59,798 bytes と file_size_check の 50KB
閾値を超過しており (non-blocking 警告)、本変更で 73,781 bytes になった。
分割は § 9 の退役手順で本ファイルごと削除する前提のため見送る。
aloekun added a commit that referenced this pull request Jul 21, 2026
…#311)

* docs(plan): WP-15 追補 — 監視 fail-open 修正のゼロ再構築方針 (旧 PR #309 全破棄)

旧 PR #309 (fix/monitor-fail-open-signals) は、4 コミット目の初版が本番 config で
一度も実行されない誤修正 + 実エントリポイントを迂回して pass するテストであり、
takt の High REJECT → fix step の自動書き直しを経た合成物となった。続修より
ゼロ再構築が速いとのユーザー判断 (2026-07-20) に基づき、再利用なしの全破棄を
決定。健全に見えるコミットも含めて引き継がない (中途半端な状態の引き継ぎと、
それによる実装の制約を排除するため)。

追補には、新実装が単独セッションで着手できるよう以下を自己完結で記録した:
- 破棄対象と手順 (PR close / branch 削除 / local abandon — 本コミット時点で未実施)
- 要件 R1-R5 (What のみ。実装方式は新実装の裁量、旧コードは参照しない)
- コード実読で検証済みの根本原因チェーン 6 段 (再調査不要)
- 検証要件 (両 OS 全スイート / incident 実データでの checker 単体実測 /
  E2E カバレッジの正直な申告 / 経路同一性を確認してから実測を主張する規律)
- 旧作業の教訓 (本番経路での発火をテストで固定・実エントリポイント・実データ fixture)

本コミットはドキュメント更新のみ。#309 の close・破棄・新実装には着手していない。
lint:docs / lint:md pass。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(check-ci): CR rate-limit 書式の第3世代対応と未知書式 fallback (WP-15 追補 R2)

CodeRabbit が rate-limit comment の待機時間書式を 3 度目に変更 (PR #309、
2026-07-20 実観測) し、`parse_rate_limit` が None を返して rate-limit 検知が
沈黙した。ADR-034 が予告していた再発事案 (PR #182/#184 に次ぐ 2 度目の
書式変更起因 regression)。

- 第 3 世代書式 `**Next review available in:** **57 minutes**` の抽出を追加。
  ラベルと数値の間に markdown 強調と `:` が挟まるため区切りを `[:*\s]*` で
  吸収し、CR が強調記法を変えても壊れないようにした。
- **書式追随を前提にしない fail-closed 化**: marker (`rate limited by
  coderabbit.ai`) が一致したのに待機時間をどの既知書式でも読めない場合、
  従来は None = 「rate-limit ではない」に倒れていた。marker 一致を制限の
  根拠として採用し、待機時間だけを既定 30 分で埋める方式に変更。既定値が
  実 reset より短ければ wakeup 後に再検出されて再 park されるだけで、
  retry は max_retries で有界。
- 既定値適用時は checker が stderr に警告 (cli-pr-monitor がログ転送) し、
  「30 分」を CR の申告値と誤読させない + 書式再変更の検知シグナルを兼ねる。

fixture は PR #309 の実 comment body を出典付きで埋め込み (ADR-049)。この
comment は walkthrough header marker を同一 body に併せ持つため、clean
walkthrough と誤認しない排他も併せて固定した。

ADR-034 の既知 format 一覧に第 3 世代行と fallback 方針を追記し、更新手順の
stale なファイル参照 (main.rs → markers.rs / rate_limit.rs) を修正。

* fix(check-ci): decide() に rate_limit を渡し silent success を排除 (WP-15 追補 R1/R4)

CodeRabbit がレート制限でレビューを開始できないまま、監視が「レビュー済み・
指摘なし」と報告する silent success を、判定ロジック側で塞ぐ。

## 根本原因 (2026-07-20 コード実読 + PR #307/#309 実観測)

CR はレート制限中も commit check を pass にする (外部 SaaS 挙動)。checker は
これを review_state に採用する一方、parse_rate_limit の結果は出力 JSON に
添付されるだけで decide() には渡っていなかった。結果、decide() は
「review_state = success かつ指摘ゼロ」= 完了と読み、stop_monitoring_success
を返していた。monitor 側の terminal 短絡は rate-limit branch より先に発火する
ため、park / 再 trigger 機構は一度も呼ばれない。

症状は monitor 側に出るが、原因は action の算出そのものにある。旧 PR #309 は
症状側 (monitor 2 箇所) への多層パッチで High REJECT を受けたため、本実装では
算出点である decide() に一本化した。

## 変更

- decide() / build_summary() に rate_limit を渡す。
- R1: rate-limit 検出中かつ「レビュー実施の陽性証拠」が無ければ
  continue_monitoring を返し、判断を monitor の rate-limit branch (park /
  再 trigger、既存・有界) に委ねる。has_actionable 分岐より前に置くことで、
  過去サイクル由来の未解決スレッドで action_required に抜ける穴も塞ぐ。
- R4: rate-limit を検出できなかった場合の backstop として、陽性証拠が無い限り
  stop_monitoring_success を出さない。CR が marker 文言自体を変えても silent
  success には戻らず、最悪 max_duration までの監視継続 (timed_out) に倒れる。
- build_summary は rate-limit 中に「CodeRabbit指摘なし」と断定せず
  「レート制限中 (レビュー未実施)」を出す。

## 陽性証拠の定義

review_state (commit status) は制限中でも pass になるため証拠に使わない。
push_time で絞られた「今サイクルの CR 出力そのもの」= walkthrough_clean /
actionable_comments が読めた (Some(0) 含む) / new_comments > 0 のみを採用する。
unresolved_threads は push_time で絞られず過去サイクルの残骸を含み得るので
除外した。

## 残存リスク

陽性証拠を一切残さない clean レビュー経路が CR 側に存在した場合、監視が
max_duration まで走って timed_out 報告になる。silent success より安全側だが
遅くなる。既存 fixture の範囲では walkthrough_clean か actionable_comments の
いずれかが必ず立つことを確認済み。

既存 decide/summary テスト 100 件は無改修で pass (新 gate が確立済み挙動を
乱していないことの確認)。incident 再現テストは PR #309 の実観測値から構成。

* fix(pr-monitor): 判定文が未確定要素を無視して断定しないよう修正 (WP-15 追補 R3)

監視レポートの人間向け判定文が、findings が空というだけで「問題は見つかり
ませんでした」と断定していた。PR #307/#309 では「未解決スレッド2件」を表示
しながら同一レポート内で「問題は見つかりませんでした」と結論する矛盾が実観測
されている。

findings が空であることは「見るべきものが無かった」の十分条件ではない。
レート制限でレビューが走っていない場合も、未解決スレッドが残っている場合も
空になり得る。

- rate-limit 検出中は保留判定文を出す (R1 で checker から rate_limit が
  届くようになったため、monitor 側で判別可能になった)。
- 未解決スレッドが残っている間は「問題なし」「重大な問題なし」のいずれも
  出さず、件数を添えて保留する。重大な指摘がある場合は「修正が必要」を優先。
- 判定順を「未確定 → 重大 → 未解決 → 軽微 → 問題なし」に整理し、未確定要素を
  findings の有無より先に評価する。断定文へ落ちる経路を構造的に塞ぐ形。

compute_verdict は未確定判定 (verdict_for_unsettled_review) と findings 判定
(verdict_for_findings) に分割し、「断定文はどの guard を通過して初めて出せる
のか」を関数境界で表現した (50 行ガイドラインにも整合)。

既存の verdict テスト 13 件は無改修で pass。

* docs(plan): WP-15 完了条件 (1) 達成の記録と追補の実装状況を反映 (R5)

## R5: WP-15 `完了` 条件 (1) の達成記録

PR #307 マージ時の release-binaries.yml run は build job が失敗しており
(master が赤で #308 の E2E 修正が必要だった)、成功したのは #308 マージ後の
run (commit 541adde)。この経緯も含めて記録した。

生成物は本セッションで再実測している: WSL Ubuntu 24.04 から素の curl で
tarball (9,721,643 bytes) + .sha256 を**認証なしで**取得 → sha256sum -c 一致
→ 展開して 16 バイナリ + BUILD_INFO 確認 → release バイナリそのもので hooks
実発火 (pre-tool-validate が破壊的削除コマンドを exit 2 でブロックし無害な
echo を exit 0 で通す / session-start が additionalContext JSON を出力)。

これで WP-15 ④ の「public リポジトリの Release asset は素の HTTPS で取得
できるため gh CLI 認証は不要」という設計判断が実 URL・実 asset で裏づけられた。
旧 PR #309 の docs コミットの主張を引き写すのではなく、自分で再実測した結果を
記録している。

## 追補の実装状況

R1〜R4 の実装内容、破棄の実施結果、検証の実測値を反映。E2E カバレッジは
正直に申告した: 担保できたのは全 gate のユニット検証 / checker 実エントリ
ポイントの実データ実走 / 修正前後の差分実測の 3 点で、cli-pr-monitor 側の
統合経路 (park → PARK signal) と wakeup → 再 trigger 経路は、CR レート制限が
本セッション中に自然発生しなかったため未実測である旨を明記した。

なお本ファイルは master 時点で既に 59,798 bytes と file_size_check の 50KB
閾値を超過しており (non-blocking 警告)、本変更で 73,781 bytes になった。
分割は § 9 の退役手順で本ファイルごと削除する前提のため見送る。

* docs(check-ci): wait_time_parsed の doc が実装と食い違う記述を修正

セルフレビュー (pre-push-review simplicity facet) の指摘。

`wait_time_parsed` の doc は「監視側はこれを見て『実測』と『既定値』を区別
して報告する」と書いていたが、実装では monitor 側への配線を見送っており
(cli-pr-monitor の RateLimitState は本 field を持たない)、記述と実装が
食い違っていた。既定値適用を運用者に伝える経路は実際には checker の stderr
警告 (monitor がログ転送) が担っている。

doc を実態に合わせ、あわせて「なぜ typed 化を見送ったか」(全 struct literal の
更新コストに対し得られるのが park summary の文言精度という副次的利得)と
「値がどこから参照できるか」(monitor が保持する checker の生 JSON) を明記した。

なお本指摘は、PR 全体 (master..@) を対象にセルフレビューを再実行して初めて
検出された。push 時のパイプラインは [diff] stage が tip コミットのみを
レビュアーに渡すため (push-runner-config.toml の `command = "jj diff -r @"`)、
models.rs を含む祖先コミットがレビュー対象外だった。この構造的欠陥は
docs/todo-summary2.md 順位 288 (Tier 1、Severity High で 3 連続再発) として
既知で、本 PR で 4 回目の再発となった。修正は独立 PR で行う。

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 3, 2026
CodeRabbit 指摘「2c の workflow に一致する chain 宣言に更新してください」への対応。

## 経緯 — takt fix が真の記述を偽に書き換えた

指摘を受けた post-pr-review の fix step は、宣言の「2c の実体で step 名・パス・引数を
照合済み」を「実装済みの実体と照合済みではない」へ書き換えて auto-push した。これは
**事実に反する**。2c の実装はローカルの未 land コミットとして存在し、step 名
`Gate fix push (deterministic, 4-axis AND)`・exe パス・4 引数はいずれも実体と照合済み
である (照合は 2b 着手時に実施)。

一方 CodeRabbit の懸念自体は正当だった: 本 PR の diff だけを見るレビュアーには、その
照合の主張を検証する手段がない。fix 後の記述は「偽だが検証可能」、修正前の記述は
「真だが検証不能」で、どちらも宣言として不十分だった。

## 修正内容

主張を検証可能性で 2 つに分ける:

- **diff 内で照合できるもの**: 引数 4 種と exe 名 (`main.rs` の `parse_args` / `USAGE`)。
  ADR-069 § 決定 1 の名前一致要件は、この部分で本 diff 内に閉じて満たされる。
- **diff 外の主張**: step 名と exe パス。2c の未 land 実装と照合済みであることを述べつつ、
  **本 PR の diff だけでは検証できない主張である**と明示し、名前一致の最終確認は 2c の
  diff レビューで行うと書く。

真である事実を落とさず、レビュアーが何を確認できて何を確認できないかを判別できる形に
した。宣言の強度は落ちない — ADR-069 が要求する名前一致は diff 内で閉じている。

なお本件は「fix step の出力を実測検証する」(#309 / ADR-068) が docs 領域でも必要である
ことの実例になった。WP-17 完了後の feedback 採否で扱う。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 3, 2026
* refactor(lib-scope-guard): ADR-054 scope 判定コアを lib へ抽出 (WP-17 PR 2)

Phase B (CI 側の無人 fix push) が同じ scope 検証を必要とするため、cli-pr-monitor に
閉じていた判定コアを lib 化する。挙動は不変。

抽出根拠は ADR-044 層 1 の「3+ crate 重複」ではなく **判定の同一性**。同一 ADR
(ADR-054) の防御がローカル経路と CI 経路に分岐すると、片方だけ緩んだ時点で injection
防御が無効化される。重複数ではなくこの drift リスクを根拠に、2 呼び手の時点で抽出する
(lib-docs-policy が ADR-035 の path 基準を単一実装へ集約したのと同じ理由)。

lib に置いたもの (純粋な文字列処理のみ):
- normalize_path / allowlist_from_paths / parse_changed_files / find_out_of_scope
- ALWAYS_ALLOWED (.takt/review-diff.txt)

呼び出し側に残したもの: diff の取得 (jj / git)、mode 判定 (enforce/observe)、
kill-switch、ログ出力。lib は「変更ファイル集合が許可集合に収まるか」だけを答える。

allowlist の入力を Finding 型ではなくパス文字列の iterator にしたのは、findings の
表現が経路ごとに異なるため (ローカル = lib_report_formatter::Finding、CI = JSON)。
これにより lib は依存ゼロを保てる。

テストも移設し、lib 側に 11 件 (境界値の網羅: 空 summary / 空白行 / tab 区切り /
空白を含むパス / 空 allowlist)。cli-pr-monitor 側には Finding→パスの glue と
mode/統合テストのみ残す。tab 区切り拒否のテストを新規追加した — git diff --name-status
は tab 区切りで、CI 経路が正規化を怠ると fail-closed に倒れることを固定するため。

検証: cargo clippy -D warnings 緑、cli-pr-monitor 260 件 + lib-scope-guard 11 件 pass。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(lib-autonomy-policy): kill-switch 判定コアを lib へ抽出 (WP-17 PR 2a)

後続 PR (WP-17 PR 2b) の fix push 直前ゲート `cli-fix-push-gate` が kill-switch を
含む全軸を 1 回で評価できるようにするため、cli-autonomy-gate 内の decision.rs /
sources.rs を lib へ移す。挙動は不変 (テスト 21 件は PR 1 と同数を維持)。

**本 PR の時点で 2 つ目の呼び手はまだ存在しない。** ADR-044 層 1 は「2 つ目の使用例が
出た時点で extract」と定めるが、後から昇格すると kill-switch 判定が一時的に 2 箇所へ
分岐する期間ができる。その期間を作らないための**明示的な前倒し判断**であり、
層 1 の厳密な充足ではない。この位置づけは各 module doc にも記載した
(pre-push review の simplicity facet が「存在しない 2nd caller を根拠にしている」と
指摘し、未来形へ言い換える fix が入った。指摘は妥当で、4 箇所すべてが修正済み)。

exe 連鎖 (`cli-autonomy-gate && cli-fix-push-gate`) を採らなかった理由:
workflow 側で `&&` を書き忘れると kill-switch を通り越して fix push が走る。
「1 つでも欠けたら停止」の fail-closed 合成が呼び手のミスで壊れる経路を残さないため、
ライブラリ共有にして fix push ゲートが構造的に kill-switch を含む形にする。

cli-autonomy-gate は CLI 面 (引数解析・loud 出力・exit コード・telemetry) だけを持つ
薄い exe になり、汎用の kill-switch 判定 (pnpm autonomy-status / 単体 drill) を担う。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(cli-fix-push-gate): Phase B の fix push 直前ゲートを追加 (WP-17 PR 2)

ADR-052 自動実行可クラスの 4 軸を 1 回の呼び出しで AND 評価する決定論ゲート。
GitHub Actions の Phase B job が push 直前に呼び、非ゼロなら push を止めて
Phase A 相当 (分析コメントのみ) へ degrade する。

4 軸と根拠:
- kill-switch (ADR-066 / ADR-052 原則 5): --config + env AUTONOMY_ENABLED
- target 軸 (ADR-052 原則 2): --branch が claude/ prefix であること
- 内容軸 (ADR-052 原則 2 / ADR-035): fix diff が docs-only であること
- scope (ADR-054): 変更ファイルが findings 由来 allowlist に収まること

各軸の基準は既存の単一実装 (lib-autonomy-policy / lib-docs-policy / lib-scope-guard)
を借り、本 crate 固有のロジックは AND 合成と判定順序だけに留めた。基準の再実装は
ADR-035 / ADR-054 が防ごうとした drift の再生産にあたるため。

判定順は kill-switch → ブランチ → 空 diff → 内容軸 → scope。空 diff を内容軸より先に
見るのは、is_docs_only_summary が空入力へ false を返す仕様で、そのままだと「変更なし」が
「docs-only ではない」と誤報告されるため (drill 6 で実確認)。

findings の出所に関する契約を inputs.rs の doc に明記した: findings は fix を書いた
エージェント自身ではなく、先行する読み取り専用の分析 step の出力でなければならない。
同一エージェントが両方を出すと scope guard が自己申告の追認になり ADR-054 の防御が
成立しない (ローカル経路で review facet と fix step が別エージェントなのと同じ分離)。

loud 出力は deny 理由 1 つに絞らず 4 軸すべての状態を出す。drill 3 (非 claude ブランチ)
では autonomy=allowed branch=not-isolated content=docs-only scope=in-scope と出て、
ブランチだけが原因と 1 行で読める。

引数は 4 つとも必須。省略で軸が無検査になる fail-open を作らないため、どれか 1 つでも
落とすと exit 2 になることをテストで固定した。

テスト 22 件 + 実 exe drill 7 シナリオ (全軸 OK → exit 0、kill-switch / 非 claude
ブランチ / scope violation / code 変更 / 空 diff / rename fail-closed → 全て exit 1)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(wp17): lib module doc を呼び手実在の現実に合わせる + 2b の chain 宣言 (WP-17 2b)

先行 3 コミット (lib-scope-guard / lib-autonomy-policy 抽出、cli-fix-push-gate 追加) は
incident 前に「抽出だけの PR」として書かれており、module doc が呼び手を「計画中・本 diff
の時点では未実装」と説明していた。本 PR は抽出と最初の呼び手を同一 PR に入れるため
(ADR-069 § 決定 3-1)、その前提が成立しない。文言を現実に合わせる。

## 時制修正 (4 ファイル)

- `lib-autonomy-policy/src/lib.rs`: 呼び手 2 件 (`cli-autonomy-gate` / `cli-fix-push-gate`)
  を実在として記載。「前倒しで lib 化した (ADR-044 層 1 の厳密な充足ではない)」という
  但し書きは、2 呼び手が同一 PR に揃った今は不要なので落とし、層 1 充足と書く。
- `lib-scope-guard/src/lib.rs`: 同上。「CI 側の呼び手はまだ実装されていない」を削除。
  lib 化の根拠 (重複数ではなく判定の同一性) は ADR-054 の防御に関わるので残す。
- `cli-autonomy-gate/src/main.rs`: `cli-fix-push-gate` を実在として参照。
- `cli-pr-monitor/src/stages/scope_guard.rs`: 「将来 CI 経路が追加された際に」を現在形へ。

## chain 宣言の精緻化 (ADR-069 初回 dogfood)

計画書の「2b の chain 宣言」を、ADR-069 § 決定 1 の 3 要件に照らして具体化した:

- **未消費は 1 つだけ**であることを明示。`lib-scope-guard` / `lib-autonomy-policy` は
  どちらも呼び手 2 件が本 PR の diff 内に揃っており missing-consumer ではない。
  宣言が要るのは `cli-fix-push-gate` の workflow 呼び手のみ。
- **名前一致要件**を満たすため、2c の実体 (未 land コミット lqxzpvuw) と照合して step 名を
  実名 `Gate fix push (deterministic, 4-axis AND)` に修正した (旧記載 `Gate fix push` は
  前方一致にすぎず、ADR-069 の「矛盾する宣言は降格根拠にならない」に触れうる)。
  exe パスと 4 引数も実体と突き合わせ済み。

宣言付き先頭 PR が missing-consumer で REJECT されないことが ADR-069 試験運用の
decision trigger (a) の初回実測になる。結果は ADR-069 の判断基準へ記帳する。

## 計画書の状態行

2a を完了 (PR #350) に、2b を実施中に更新。この更新自体が計画書を本 PR の diff に載せ、
ADR-069 § 決定 1「置き場所 = diff 内の計画文書のみ」を満たす手段でもある。

検証: cargo test --workspace 1945 件 pass、clippy --workspace --all-targets -D warnings 緑、
pnpm lint:docs / lint:md 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(review): apply CodeRabbit fixes for #351

Resolved findings:
- [Minor] docs/harness-improvement-plan.md:185 2c の workflow に一致する chain 宣言に更新してください。

* docs(wp17): chain 宣言の検証状態を diff 内/外で分けて明記 (CodeRabbit #351)

CodeRabbit 指摘「2c の workflow に一致する chain 宣言に更新してください」への対応。

## 経緯 — takt fix が真の記述を偽に書き換えた

指摘を受けた post-pr-review の fix step は、宣言の「2c の実体で step 名・パス・引数を
照合済み」を「実装済みの実体と照合済みではない」へ書き換えて auto-push した。これは
**事実に反する**。2c の実装はローカルの未 land コミットとして存在し、step 名
`Gate fix push (deterministic, 4-axis AND)`・exe パス・4 引数はいずれも実体と照合済み
である (照合は 2b 着手時に実施)。

一方 CodeRabbit の懸念自体は正当だった: 本 PR の diff だけを見るレビュアーには、その
照合の主張を検証する手段がない。fix 後の記述は「偽だが検証可能」、修正前の記述は
「真だが検証不能」で、どちらも宣言として不十分だった。

## 修正内容

主張を検証可能性で 2 つに分ける:

- **diff 内で照合できるもの**: 引数 4 種と exe 名 (`main.rs` の `parse_args` / `USAGE`)。
  ADR-069 § 決定 1 の名前一致要件は、この部分で本 diff 内に閉じて満たされる。
- **diff 外の主張**: step 名と exe パス。2c の未 land 実装と照合済みであることを述べつつ、
  **本 PR の diff だけでは検証できない主張である**と明示し、名前一致の最終確認は 2c の
  diff レビューで行うと書く。

真である事実を落とさず、レビュアーが何を確認できて何を確認できないかを判別できる形に
した。宣言の強度は落ちない — ADR-069 が要求する名前一致は diff 内で閉じている。

なお本件は「fix step の出力を実測検証する」(#309 / ADR-068) が docs 領域でも必要である
ことの実例になった。WP-17 完了後の feedback 採否で扱う。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 3, 2026
…bit #353)

PR #353 の CodeRabbit 指摘 3 件への対応。うち 2 件は本 PR が持ち込んだ実バグだった。

## 指摘 2 (Minor): terminal 化 3 経路が head_commit を None で上書きする

`finalize_pending_review` / `finalize_waiting_reset` / `finalize_posted_retrigger` が
`state.head_commit = pr_info.head_commit.clone()` と**無条件代入**していた。
`util::get_pr_head_commit` は gh 失敗を None に潰すため、API 障害のたびに保存済み OID が
消える。その状態で次回 `--monitor-only` を実行すると `should_continue_state` が None を
見て継続を拒否し、時刻窓アンカーが「今」へリセットされて間に届いた CR コメントを
取りこぼす — 本 PR が動機に挙げた incident class (#237/#307/#309) と同型。

CodeRabbit が「共通の根本原因」と指摘したとおり 3 経路に同じ欠陥があったため、
`PrMonitorState::record_head_commit(Option<&str>)` を state.rs に置き、Some のときだけ
上書きする単一実装へ集約した。取得失敗は「head が変わった」の証拠ではないので既存値の
保持が正しい (fail-safe)。回帰テスト 2 件 (None で消えない / Some で上書きする) を追加。

## 指摘 3 (Minor): テストが実 gh CLI を呼ぶ

`finalize_waiting_reset` は内部で `fetch_mergeable_status` → `run_gh_quiet` を呼ぶため、
前コミットで追加した回帰テストが実 gh プロセスを起動していた (ネットワーク依存・低速)。

`finalize_waiting_reset_with(..., fetch_mergeable: impl FnOnce(&PrInfo) -> Option<..>)`
へ本体を切り出し、production は `fetch_mergeable_status` を、テストは closure を渡す形に
した (cli-push-runner の `verify_diff_covers_pr_range` と同じ注入の流儀)。

あわせて「注入したものが実際に使われている」ことを確認するテストを追加した — `|_| None`
を渡すだけのテストでは、fetcher を無視する実装でも通ってしまい注入の正しさを判別できない。
Cell フラグで呼び出し自体を観測する。

## 指摘 1 (Minor): 計画書の stale な park 検証残

「WP-15 追補残: レート制限 park の実観測」は park 廃止で (a) が moot になっていた。
見出しを「レート制限時の保留保証 (GitHub Actions 経路)」へ改め、(a) の終了と (b) の
引き継ぎ先を明記した (ADR-018 amendment / ADR-064 ステータス欄と同じ内容を計画書側にも
反映し、3 箇所の記述を揃える)。

検証: cargo test --workspace 1905 件 pass (新規 4 件)、clippy --workspace -D warnings 緑、
pnpm lint:docs / lint:md 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 3, 2026
…d 回帰復元 (CodeRabbit #353)

PR #353 のレビュー指摘への対応。CodeRabbit 3 件 + pre-push simplicity 1 件で、
**4 件中 3 件は本 PR が持ち込んだ実バグ / カバレッジ欠落**だった。

## CodeRabbit 指摘 2 (Minor): terminal 化 3 経路が head_commit を None で上書きする

`finalize_pending_review` / `finalize_waiting_reset` / `finalize_posted_retrigger` が
`state.head_commit = pr_info.head_commit.clone()` と**無条件代入**していた。
`util::get_pr_head_commit` は gh 失敗を None に潰すため、API 障害のたびに保存済み OID が
消える。その状態で次回 `--monitor-only` を実行すると `should_continue_state` が None を
見て継続を拒否し、時刻窓アンカーが「今」へリセットされて間に届いた CR コメントを
取りこぼす — 本 PR が動機に挙げた incident class (#237/#307/#309) と同型。

CodeRabbit が「共通の根本原因」と指摘したとおり 3 経路に同じ欠陥があったため、
`PrMonitorState::record_head_commit(Option<&str>)` を state.rs に置き、Some のときだけ
上書きする単一実装へ集約した。取得失敗は「head が変わった」の証拠ではないので既存値の
保持が正しい (fail-safe)。回帰テスト 2 件 (None で消えない / Some で上書きする) を追加。

## CodeRabbit 指摘 3 (Minor): テストが実 gh CLI を呼ぶ

`finalize_waiting_reset` は内部で `fetch_mergeable_status` → `run_gh_quiet` を呼ぶため、
前コミットで追加した回帰テストが実 gh プロセスを起動していた (ネットワーク依存・低速)。

`finalize_waiting_reset_with(..., fetch_mergeable: impl FnOnce(&PrInfo) -> Option<..>)`
へ本体を切り出し、production は `fetch_mergeable_status` を、テストは closure を渡す形に
した (cli-push-runner の `verify_diff_covers_pr_range` と同じ注入の流儀)。

あわせて「注入したものが実際に使われている」ことを確認するテストを追加した — `|_| None`
を渡すだけのテストでは、fetcher を無視する実装でも通ってしまい注入の正しさを判別できない。
Cell フラグで呼び出し自体を観測する。

## pre-push simplicity 指摘 (SIM-NEW-rate_limit-L122, Medium): fail-closed 回帰の消失

tests.rs のファイル分割時に `finalize_posted_retrigger_action_required_when_write_state_fails`
だけが欠落していた。この関数は 2 つの sibling (`finalize_waiting_reset` /
`finalize_pending_review`) と**逆に fail-closed** で、理由は `@coderabbitai review` 投稿と
いう副作用を伴うため state 永続化の成否で重複投稿を防ぐ必要があるから。fail-open 側 2 件の
回帰テストは移行されていたのに、意図的に非対称な唯一の経路のテストが落ちていた。復元し、
非対称の理由を doc comment に明記した。

## CodeRabbit 指摘 1 (Minor): 計画書の stale な park 検証残

「WP-15 追補残: レート制限 park の実観測」は park 廃止で (a) が moot になっていた。
見出しを「レート制限時の保留保証 (GitHub Actions 経路)」へ改め、(a) の終了と (b) の
引き継ぎ先を明記した (ADR-018 amendment / ADR-064 ステータス欄と同じ内容を計画書側にも
反映し、3 箇所の記述を揃える)。

検証: cargo test --workspace 1906 件 pass (新規 5 件)、clippy --workspace -D warnings 緑、
pnpm lint:docs / lint:md 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 3, 2026
… 3) (#353)

* refactor(pr-monitor): CronCreate park モデルを撤去し single-shot 化 (WP-17 PR 3)

cli-pr-monitor を「1 回 check して必ず terminal 報告で終了する」モデルへ移行する。
旧 Bb-1/Bb-2 の park モデル (未確定なら state に wakeup 時刻を書き、[PR_MONITOR_PARK]
envelope で Claude に CronCreate 予約を依頼し、発火時の再 invoke で継続する) を撤去した。

## なぜ廃止するか (ADR-018 amendment)

- wakeup はローカルセッションの寿命に依存する。PR #237 でセッション終了による
  CronCreate 失効 = 監視の取りこぼしを実観測した。
- WP-17 で GitHub Actions 経路 (pr-monitor workflow の Phase A/B) が常設になった。
  CodeRabbit のレビュー到着・後続コメントがそのままトリガーになるため、ローカルの
  時限 wakeup は冗長になった。

## 挙動の変化

- 未確定 (review 未完) → 旧: park + wakeup 予約 / 新: terminal `pending_review` 報告
  (「後続は GitHub Actions 経路が処理」を明示。ADR-064 の陽性証拠原則どおり
  pending を silent success に見せない)
- rate-limit reset 待ち → 旧: park / 新: terminal `rate_limited` 報告 (ADR-064 (b) の
  保留判定文を維持)。reset 経過後の即時 retrigger 投稿と comment dedup は残す
- fresh push → 旧: checker を呼ばず initial park / 新: 即 1 回 check して報告

## 残したもの (park の付随物ではないため)

- **時刻窓アンカーの継続** (`should_continue_state`): 同一 PR + 同一 head なら
  state.started_at / fix_push_time を維持する。これを落とすと再実行のたびに
  `--push-time` が「今」になり、push 後に届いた CR コメントが新着判定から漏れる。
  旧 `should_resume_wakeup` から wakeup 時刻経過の条件だけを外した形
- rate_limit_retries / rate_limit_last_retriggered_at (dedup と retry 上限は
  invocation 跨ぎで引き続き意味を持つ)
- **順位 141 の mergeable shortcut** (`[RATE_LIMIT_BUT_MERGEABLE]` signal):
  「rate-limit 中でも既に mergeable なら即 merge を選べる」独立機能で、park の
  付随物ではない。初版 diff は rate_limit_signal.rs の削除に巻き込んで silent に
  消しており、pre-push simplicity review が REJECT
  (SIM-NEW-src-cli-pr-monitor-rate_limit-L138: 他の撤去物は全て ADR/計画書に
  列挙されているのに shortcut だけ無言で消えている = 意図の疑い) → fix step が
  rate_limit.rs へテストごと復元した (実測検証済み)。terminal 化した
  `finalize_waiting_reset` から引き続き発火する。signal 文面の選択肢 B は
  single-shot 後の実態 (Actions 経路 / --monitor-only 再実行) に合わせて更新
- **rate-limit 系 terminal での head_commit 保存**: 2 回目の pre-push review が、
  `finalize_waiting_reset` / `finalize_posted_retrigger` で head_commit 保存が
  落ちている回帰を REJECT で検出 (SIM-NEW-rate_limit-L154: 保存が無いと次回
  `--monitor-only` の継続判定が fresh 初期化に倒れ、時刻窓リセットで CR コメントを
  取りこぼす — 本 PR 自身が動機に挙げた incident class の再導入)。fix step が
  両関数への保存 + 回帰テスト 2 件を追加した (実測検証済み)

## 撤去したもの

- state: next_wakeup_at_unix / wakeup_reason / review_recheck_count (旧 state file に
  残っていても unknown field として無視される — 前方互換テストを追加)
- poll: review_recheck.rs / review_recheck_signal.rs (park scheduling と PARK envelope
  整形の全量)。rate_limit_signal.rs はファイルとしては削除したが、上記のとおり
  shortcut 部分は rate_limit.rs へ移設して維持
- config: [review_recheck] セクションと sanitize / overflow 検査 (wakeup 時刻の加算
  演算ごと消滅)、monitor.max_duration_secs (ループ上限)。旧 config は unknown
  field/section として無視される — 前方互換テストを追加

検証: cargo test -p cli-pr-monitor 226 件 pass、workspace 全緑、
clippy --workspace -D warnings 緑。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(hooks-session-start): pr_monitor catch-up nudge を撤去 (WP-17 PR 3)

pr_monitor catch-up nudge (Bb-3) は「park 中にセッションが終了して CronCreate が失効
した場合の救済」だった。park/wakeup モデルの廃止 (同 PR の cli-pr-monitor 変更) に伴い
救済対象の状態が存在しなくなるため、機構ごと撤去する。

- hooks-session-start: pr_monitor.rs module 削除 + main.rs の配線解除
- .claude/hooks-config.toml: telemetry registry から `pr_monitor_catchup` id を除去
  (過去の firings jsonl には残るが現役 id ではない)。あわせて、どのコードも読んで
  いなかった daemon 時代 (ADR-009) の死に設定 [post_pr_monitor] セクションを撤去
  (現行の監視設定はリポジトリルートの pr-monitor-config.toml が正)
- cli-telemetry-report: doc comment の例示 id を現役のものへ差し替え

検証: cargo test --workspace 1893 件 pass、clippy --workspace -D warnings 緑。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(adr): ADR-018 amendment (park モデル廃止) + ADR-064/034 整合 + 計画書 (WP-17 PR 3)

- ADR-018 追記 (2026-08-03): park モデル廃止の決定・根拠・維持したもの (時刻窓アンカー
  の state 継続 / rate-limit dedup) を記録。ADR-064 検証残の移し替え ((a) park 実観測は
  moot / (b) 判定文の保留保証は Actions 経路の検証残へ) を明記
- ADR-064 ステータス欄: 同じ移し替えを検証残の側からも記載 (両方に記録して検証の穴を
  残さない — 計画書の着手前決定 2)
- ADR-034: Bundle b の CronCreate park モデル記述に「廃止済み、歴史記録として読む」
  注記を追加
- 計画書: PR 3 を実施中に更新し、実装で確定した設計判断 (should_continue_state を残す
  理由) と「本 PR の PR がスモーク段 1 の観測対象を兼ねる」ことを記載

検証: pnpm lint:docs / lint:md 0 error。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(pr-monitor): head_commit の None 上書き防止 + テストの gh 依存除去 + fail-closed 回帰復元 (CodeRabbit #353)

PR #353 のレビュー指摘への対応。CodeRabbit 3 件 + pre-push simplicity 1 件で、
**4 件中 3 件は本 PR が持ち込んだ実バグ / カバレッジ欠落**だった。

## CodeRabbit 指摘 2 (Minor): terminal 化 3 経路が head_commit を None で上書きする

`finalize_pending_review` / `finalize_waiting_reset` / `finalize_posted_retrigger` が
`state.head_commit = pr_info.head_commit.clone()` と**無条件代入**していた。
`util::get_pr_head_commit` は gh 失敗を None に潰すため、API 障害のたびに保存済み OID が
消える。その状態で次回 `--monitor-only` を実行すると `should_continue_state` が None を
見て継続を拒否し、時刻窓アンカーが「今」へリセットされて間に届いた CR コメントを
取りこぼす — 本 PR が動機に挙げた incident class (#237/#307/#309) と同型。

CodeRabbit が「共通の根本原因」と指摘したとおり 3 経路に同じ欠陥があったため、
`PrMonitorState::record_head_commit(Option<&str>)` を state.rs に置き、Some のときだけ
上書きする単一実装へ集約した。取得失敗は「head が変わった」の証拠ではないので既存値の
保持が正しい (fail-safe)。回帰テスト 2 件 (None で消えない / Some で上書きする) を追加。

## CodeRabbit 指摘 3 (Minor): テストが実 gh CLI を呼ぶ

`finalize_waiting_reset` は内部で `fetch_mergeable_status` → `run_gh_quiet` を呼ぶため、
前コミットで追加した回帰テストが実 gh プロセスを起動していた (ネットワーク依存・低速)。

`finalize_waiting_reset_with(..., fetch_mergeable: impl FnOnce(&PrInfo) -> Option<..>)`
へ本体を切り出し、production は `fetch_mergeable_status` を、テストは closure を渡す形に
した (cli-push-runner の `verify_diff_covers_pr_range` と同じ注入の流儀)。

あわせて「注入したものが実際に使われている」ことを確認するテストを追加した — `|_| None`
を渡すだけのテストでは、fetcher を無視する実装でも通ってしまい注入の正しさを判別できない。
Cell フラグで呼び出し自体を観測する。

## pre-push simplicity 指摘 (SIM-NEW-rate_limit-L122, Medium): fail-closed 回帰の消失

tests.rs のファイル分割時に `finalize_posted_retrigger_action_required_when_write_state_fails`
だけが欠落していた。この関数は 2 つの sibling (`finalize_waiting_reset` /
`finalize_pending_review`) と**逆に fail-closed** で、理由は `@coderabbitai review` 投稿と
いう副作用を伴うため state 永続化の成否で重複投稿を防ぐ必要があるから。fail-open 側 2 件の
回帰テストは移行されていたのに、意図的に非対称な唯一の経路のテストが落ちていた。復元し、
非対称の理由を doc comment に明記した。

## CodeRabbit 指摘 1 (Minor): 計画書の stale な park 検証残

「WP-15 追補残: レート制限 park の実観測」は park 廃止で (a) が moot になっていた。
見出しを「レート制限時の保留保証 (GitHub Actions 経路)」へ改め、(a) の終了と (b) の
引き継ぎ先を明記した (ADR-018 amendment / ADR-064 ステータス欄と同じ内容を計画書側にも
反映し、3 箇所の記述を揃える)。

検証: cargo test --workspace 1906 件 pass (新規 5 件)、clippy --workspace -D warnings 緑、
pnpm lint:docs / lint:md 0 error。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant