diff --git a/docs/adr/adr-054-prompt-injection-trust-boundary-defense.md b/docs/adr/adr-054-prompt-injection-trust-boundary-defense.md index 4b065201..860f8514 100644 --- a/docs/adr/adr-054-prompt-injection-trust-boundary-defense.md +++ b/docs/adr/adr-054-prompt-injection-trust-boundary-defense.md @@ -87,6 +87,12 @@ whole-tree security facet ([.takt/facets/instructions/review-security-whole.md]( decision trigger は `pr-monitor-config.toml` の `[fix.scope_guard]` section コメントと `scope_guard.rs` の module doc に永続記録する (ephemeral 計画書のみへの記載は retire 時に dead pointer 化するため不可)。 +**dogfood 記録**: + +- observe 期間 (2026-07-12 有効化〜2026-08-01): post-pr-review 実行 30 回、うち fix step 実行 5 回。violation (OBSERVE ログ) の観測 0 件 = 誤検知ゼロ。 +- 2026-08-01: `mode = "enforce"` へ昇格。以降が decision trigger (enforce で 3-5 PR) の計測期間。fix step の発生頻度が低下しているため (直近 2 週間は 0 回)、判定材料の蓄積は fix 発生ベースで待つ。 +- 観測ギャップ: violation 記録は monitor の stderr のみで永続化されない (ADR-055 telemetry は cli-pr-monitor 未計装)。採否判定を機械化する必要が生じた場合は lib-telemetry 計装が候補。 + ## ADR-043 との線引き - **決定論層 (層 3)**: ゲート = fail-closed。判定不能はすべて block 側 (ADR-043 準拠)。ただし enabled = false のとき (未有効化) は「何もしない = push 続行」であり、これは「ゲートが存在しない」状態であって fail-open ではない。 diff --git a/docs/harness-improvement-plan.md b/docs/harness-improvement-plan.md index b52dca8a..4dd9c487 100644 --- a/docs/harness-improvement-plan.md +++ b/docs/harness-improvement-plan.md @@ -70,7 +70,7 @@ Anthropic 公式のハーネスエンジニアリング指針(決定論的基 | WP-08 | 1-B | incident→eval 回帰スイート | S | なし | 完了([ADR-049](adr/adr-049-incident-eval-regression-suite.md)) | | WP-09 | 1-C | PR 監視の GitHub Actions 化 Phase A | M | なし | 完了([ADR-022](adr/adr-022-automation-responsibility-separation.md) 原則 6。無人分析コメント + wakeup 取りこぼしゼロを観測済) | | WP-10 | 1-C | 自律境界ポリシー ADR | S | なし | 完了([ADR-052](adr/adr-052-autonomy-execution-boundary-classes.md)。Rust 分類関数は WP-17/18 着手時に実装 = ADR-052 記載) | -| WP-11 | 2 | prompt injection 信頼境界の 3 層防御 | M-L | WP-08 | 観測中([ADR-054](adr/adr-054-prompt-injection-trust-boundary-defense.md)。scope_guard observe 運用中 → § 残作業) | +| WP-11 | 2 | prompt injection 信頼境界の 3 層防御 | M-L | WP-08 | 観測中([ADR-054](adr/adr-054-prompt-injection-trust-boundary-defense.md)。2026-08-01 enforce 昇格済 → § 残作業) | | WP-12 | 2 | 発火テレメトリ + ハーネス ROI 棚卸し | M | なし | 完了([ADR-055](adr/adr-055-firing-telemetry-collection.md) + [ADR-062](adr/adr-062-monthly-harness-roi-review.md)。初回月次レビュー〔2026-08-12 以降〕は ADR-062 の機構が管理) | | WP-13 | 3 | EXE_SUFFIX 抽象化 | M | なし | 完了([ADR-005](adr/adr-005-hooks-path-resolution-with-template.md) amendment。launcher 経路の実走確認済) | | WP-14 | 3 | PowerShell 3 本の Rust 化 | S-M ×2 | なし | 完了(新規 ADR 不要判断 = 決定は各 crate doc + commit message に記録。実走確認済) | @@ -82,10 +82,10 @@ Anthropic 公式のハーネスエンジニアリング指針(決定論的基 ## 5. 残作業(観測継続) -### WP-11 残: scope_guard の enforce 昇格判定 +### WP-11 残: scope_guard の本採用判定 -- 現状: `pr-monitor-config.toml` の `[fix.scope_guard]` を `mode = "observe"` で dogfood 中(ADR-054 の決定論層)。 -- 残作業: 誤検知ゼロを確認したら `mode = "enforce"` へ昇格し、3〜5 PR で採否判定(bounded lifetime)。判定基準・kill-switch は ADR-054 を参照。 +- 現状: observe 期間(2026-07-12〜08-01、fix step 実行 5 回)で誤検知ゼロを確認し、2026-08-01 に `mode = "enforce"` へ昇格済(ADR-054 の dogfood 記録参照)。 +- 残作業: enforce で 3〜5 PR(fix step 発生ベース)誤検知ゼロを確認したら本採用(ADR-054 の status 更新)。判定基準・kill-switch は ADR-054 を参照。 ### WP-15 追補残: レート制限 park の実観測 diff --git a/docs/todo-summary2.md b/docs/todo-summary2.md index b3244204..0a26a2a9 100644 --- a/docs/todo-summary2.md +++ b/docs/todo-summary2.md @@ -40,7 +40,7 @@ | 255 | 💎 Tier 3 | **ADR-040 の実測値を新 GPU (RTX PRO 5000 48GB) で再 calibration (ADR-046 WP-01 スパイクで陳腐化を観測)** | todo15.md | S | なし (ADR-038/040 が前提とする RTX 3070 8GB は RTX PRO 5000 Blackwell 48GB に更新済み。27-31B Q4 モデルが 100% GPU で動き VRAM が制約でなくなったため、ADR-040 の VRAM/latency trade-off 表と「VRAM scarcity → model swap 制約」framing が陳腐化。ADR-046 で mistral:7b / gemma4 / qwen3-coder の VRAM・latency を実測済 → ADR-040 amendment に反映、num_ctx 選定 flow の memory 軸を latency 軸へ再重み付け) | | 256 | ⏳ Tier 5 | **classifier FP 検出強化プロンプトで格上げ候補を再評価 (WP-04 見送りの follow-up、ADR-038 amendment 由来)** | todo15.md | M | なし (WP-04 実測で全候補が FP 検出未達 = 能力限界か `classify.txt` の mistral 向け tune 不適合かが未分離。FP 検出強化プロンプト版で qwen3-coder:30b 等を再測し、能力限界と確認できれば恒久見送り、プロンプト不適合なら該当モデル + 専用プロンプトで格上げ。eval 手法・gold セットは scratchpad WP-04 資産を再利用。materially better な新モデル出現時も再評価トリガー) | | 257 | ⏳ Tier 5 | **push pipeline の `cargo test` を cargo-nextest 化 (WP-05 で Stop hook には無効と判明、push 側 follow-up)** | todo15.md | S-M | なし (WP-05 実測: Stop hook は cargo test 不在で nextest 非適用、真因は逐次実行→並列化で解決済。ただし push pipeline (cli-push-runner quality_gate) の `cargo test -- --ignored` は実測 ~80s で nextest 高速化の余地あり。ツール依存追加 = ADR-017 pinning + 派生プロジェクト配布のコスト、push が Stop より低頻度な点を踏まえた費用対効果を評価。doctest は nextest 非実行のため `cargo test --doc` 併走が必要) | -| 272 | 🚀 Tier 1 | **cli-docs-lint に ADR 重複採番 + CLAUDE.md 索引整合チェック追加 (PR #261 post-merge-feedback T1-#2 採用)** | todo15.md | S | なし (PR #261 で ADR-052/053 採番衝突が実発生、既存 cli-docs-lint の check-mode 骨格流用。順位 135 placeholder policy は todo entry 側の「ルール」で本 entry は land 済ファイルの「仕組み」検知、相補で重複ではない) | +| 272 | 🚀 Tier 1 | **cli-docs-lint に ADR 重複採番 + CLAUDE.md 索引整合チェック追加 (PR #261 post-merge-feedback T1-#2 採用)** | todo15.md | S | なし (PR #261 で ADR-052/053 採番衝突が実発生、既存 cli-docs-lint の check-mode 骨格流用。順位 135 placeholder policy は todo entry 側の「ルール」で本 entry は land 済ファイルの「仕組み」検知、相補で重複ではない。責務は採番重複/索引存在/番号一致のみでステータスタグ整合は順位 357、実装は同一 module 同居可) | | 275 | 🔧 Tier 2 | **層別テストテンプレート (StubOllama パターン・integration 独立性) の共有化 (PR #265 post-merge-feedback T2-1 採用)** | todo15.md | M | なし (WP-11/ADR-054 の多層防御実装で「空 StubOllama による LLM 未呼び出し証明」「tempdir+jj init+CwdRestore の integration 独立性」を都度設計。WP-17 の classifier/scope guard 拡張で同種判断が再発見込み。shared crate 化の境界は ADR-044 で判定、WP-17 着手前の実施が効果的) | | 276 | 💎 Tier 3 | **ADR-007 に「コメント配置の意思決定フロー」を追加 (PR #265 post-merge-feedback T3-2 採用)** | todo15.md | S | なし (PR #265 で非 doc コメントの Bundle Z block が 2 回発生 = doc コメント/識別子名/マーカー付き Why の配置判断が未文書化。linter 自動化は NLP 必要で却下済み、既存 Q1-Q3 形式で人間/AI の判断補助を doc 化。バッチ PR で消化可) | | 277 | 💎 Tier 3 | **PR body 配置タイミング規約を dev-conventions に明記 (PR #265 post-merge-feedback T3-3 採用)** | todo15.md | XS | なし (push パイプライン実行中の working copy に `__pr-body.md` を作成し snapshot 混入をかろうじて回避したヒヤリハット実発生。「push 完了後に scratchpad で準備し --body-file に絶対パス」を規約化。バッチ PR 消化可、並列安全化 PR docs への相乗りも可) | @@ -112,6 +112,8 @@ | 354 | 💎 Tier 3 | **todo ファイル削除・更新時のチェックリストを dev-conventions.md に追加 (#332 post-merge feedback T3-8 採用)** | todo14.md | XS | なし (PR #332 で todo16.md 複数セクション削除時に lint:md を 3 回以上再実行。段階削除+都度 lint:md+順位番号本文混入注意の checklist 化、専用スクリプト化 (Tier2 様子見) と独立の即応策。順位334 と相補) | | 355 | 💎 Tier 3 | **新規スキル作成チェックリストを dev-conventions.md に追加 (#332 post-merge feedback T3-9 採用)** | todo14.md | XS | なし (PR #332 で monthly-review skill 作成時に weekly-review を都度参照する手戻り。SKILL.md/evals.json/trigger_eval.json の 3 点セット + Phase 構成 + deploy 前 sync check を checklist 化) | | 356 | 🔧 Tier 2 | **weekly/monthly staleness 判定の共通 fixture parametrized test を追加 (#331 post-merge feedback T2-1 採用)** | todo14.md | S | なし (monthly_review.rs の staleness 判定が weekly_review.rs と逐語重複、片方修正で挙動乖離するリスク。同一 fixture〔threshold 境界/Missing/Stale/Unreadable/未来値/main-root canonical〕で両流路を検証、inline test module に配置) | +| 357 | 🔧 Tier 2 | **CLAUDE.md の ADR index ステータスタグと ADR 本体の整合チェックを追加 (#340 post-merge feedback T1-1 採用)** | todo14.md | M | なし (ADR-047 の index タグが `試験運用` のまま本体の `却下` と乖離した実害が残存。ADR-007 の 2 層は単一ファイル起点のため独立 doc-consistency チェックとして実装。責務はステータスタグ整合のみで採番/索引存在/番号一致は順位 272、実装は同一 module 同居可。着手時に ADR-047 タグ即修正を含む) | +| 358 | 💎 Tier 3 | **Cross-File Reference Lifecycle (ephemeral→permanent 移行手順) を dev-conventions.md に明文化 (#340 post-merge feedback T3-1 採用)** | todo14.md | S | なし (PR #340 で移管先未記載 3 件を CodeRabbit が指摘。順位 261 の見送り限定 convention を完了/委譲ケースへ拡張し、退役条件を含む 3 段階プロトコルを checklist 化) | **戦略**: Tier 1 を 2〜3 セッションで片付け → Tier 2 で ADR-032 の前提 + rate-limit + convergence cost 削減を進める → Tier 3 で ADR-032 を land + ドキュメント整備。Tier 4-5 は cleanup / 外部展開で daily efficiency への直接効果は小さい。 diff --git a/docs/todo14.md b/docs/todo14.md index 36907de2..39ab4d4e 100644 --- a/docs/todo14.md +++ b/docs/todo14.md @@ -625,3 +625,47 @@ #### 完了基準 - weekly/monthly の staleness 判定が同一 fixture で検証され、片方のロジック変更による挙動乖離がテストで検知されること。 + +--- + +### CLAUDE.md の ADR index ステータスタグと ADR 本体ステータスの整合チェックを追加 + +> **動機**: PR #340 で CLAUDE.md の ADR-047 index タグが `*(試験運用)*` のまま、ADR-047 本体のステータス「却下 (2026-07-19 確定)」と乖離して残存していることを、pre-push simplicity review と post-merge 分析が独立に指摘した (実害継続を Read で確認済み)。index タグと本体ステータスの整合は手動更新に依存しており、ステータス遷移 (試験運用 → 採用/却下) のたびに再発しうる。#340 post-merge feedback Tier1 #1 で採用。 +> +> **対処案**: CLAUDE.md の ADR index 行のステータスタグと、対応 ADR ファイル本体の「ステータス」見出しの一致を検証する doc-consistency チェックを pre-push 経路に追加する。[ADR-007](adr/adr-007-custom-linter-layer-boundary.md) の正規表現層/AST 層はいずれも単一ファイル起点設計のため、`custom-lint-rules.toml` への追加ではなく独立チェック (cli-docs-lint 拡張 or 専用スクリプト/test) として実装する。**責務分界 (PR #341 CodeRabbit 指摘で明文化)**: 本 entry はステータスタグ整合のみを扱い、採番重複/索引存在/番号一致は順位 272 の責務。実装は同一 validator module への同居が可能で相補。 +> +> **参照**: `.claude/feedback-reports/340.md` Tier1 #1、[ADR-007](adr/adr-007-custom-linter-layer-boundary.md)、[ADR-047](adr/adr-047-prepush-refute-facet.md)、順位 272 (同居実装候補)。 +> +> **実行優先度**: 🔧 Tier 2 — Severity Medium / Frequency Medium / Effort M / Adoption Risk None。 + +#### 作業計画 + +- [ ] CLAUDE.md の ADR-047 タグを `*(試験運用)*` → `*(却下)*` に修正 (実残存の不整合解消、着手時の即修正) +- [ ] ADR index タグと ADR 本体「ステータス」見出しの整合チェックを実装 (cli-docs-lint 拡張 or 独立スクリプト、順位 272 と同居検討) +- [ ] pre-push 経路 (lint:docs) への組込みと、不整合 fixture での検知確認 +- [ ] 本エントリ削除 + todo-summary2.md 行削除 + +#### 完了基準 + +- CLAUDE.md の ADR index タグと ADR 本体ステータスの乖離が pre-push で機械検知されること (採番/索引存在/番号一致の検知は順位 272 の完了基準で扱い、本 entry の対象外)。 + +--- + +### Cross-File Reference Lifecycle (ephemeral→permanent 移行手順) を dev-conventions.md に明文化 + +> **動機**: PR #340 の計画書スリム化で、CodeRabbit から「WP-14 の永続移管先未記載」「外部 SaaS 事実の移管方針」「WP-02 の todo 移管先未記録」の 3 件が指摘された。ephemeral 計画文書から permanent 成果物への知識移行の手順は「見送り」ケース限定の順位 261 convention にしか存在せず、完了/委譲ケースの移管先明記が規約の空白だったことが構造要因。#340 post-merge feedback Tier3 #1 で採用。 +> +> **対処案**: `docs/dev-conventions.md` の順位 261 convention (spike 見送り 3 点セット) を拡張し、Cross-File Reference Lifecycle として明文化する: (1) permanent 成果物を先に作成・validate、(2) permanent→ephemeral 方向の参照を除去し、移管先 (ADR / todo 順位 / crate doc 等) を ephemeral 側の状態列に明記 (完了/委譲/見送りの全ケース対象)、(3) 計画文書の退役条件 (全状態確定 + 永続成果物からの参照ゼロ + 残タスクの lifecycle 整合) を含める。 +> +> **参照**: `.claude/feedback-reports/340.md` Tier3 #1、`docs/dev-conventions.md` (順位 261 convention)、`docs/harness-improvement-plan.md` (退役手順の実例)。 +> +> **実行優先度**: 💎 Tier 3 — Severity Medium / Frequency Medium / Effort S / Adoption Risk None。 + +#### 作業計画 + +- [ ] `docs/dev-conventions.md` に Cross-File Reference Lifecycle の checklist を追加 (順位 261 convention の拡張として整理) +- [ ] 本エントリ削除 + todo-summary2.md 行削除 + +#### 完了基準 + +- ephemeral 計画文書の完了/委譲/見送りいずれのケースでも、永続移管先の明記と参照方向の規律が checklist で確認できること。 diff --git a/docs/todo15.md b/docs/todo15.md index 11926481..c76c85c5 100644 --- a/docs/todo15.md +++ b/docs/todo15.md @@ -226,7 +226,7 @@ > > **参照**: `.claude/feedback-reports/261.md` Tier 1 #2、`src/cli-docs-lint/src/main.rs` (CheckMode 拡張、preamble / cross-ref / priority-inversion の既存 check-mode dispatch と kill-switch 骨格を流用)、ADR-007 (層の線引き)、ADR-039。 > -> **関連 (重複ではない)**: 順位 135 (todo8.md、ADR-NNN placeholder policy) は todo entry 側の採番 hardcode を防ぐ「ルール」であり、本 entry は land 済みファイル群の衝突を検知する「仕組み」(ADR-042 の役割分担で相補)。feedback report Tier 2 #2 (ADR sanity テスト新設) は本 entry と目的重複のため却下済み。 +> **関連 (重複ではない)**: 順位 135 (todo8.md、ADR-NNN placeholder policy) は todo entry 側の採番 hardcode を防ぐ「ルール」であり、本 entry は land 済みファイル群の衝突を検知する「仕組み」(ADR-042 の役割分担で相補)。feedback report Tier 2 #2 (ADR sanity テスト新設) は本 entry と目的重複のため却下済み。順位 357 (todo14.md、index ステータスタグ ⇔ ADR 本体ステータスの整合) はチェック対象が異なる別 entry — 本 entry の責務は a/b/c (採番重複/索引存在/番号一致) のみでステータスタグは扱わない。実装は同一 validator module への同居が可能 (PR #341 CodeRabbit 指摘で責務分界を明文化)。 > > **実行優先度**: 🚀 **Tier 1** — Effort S。既存 cli-docs-lint 骨格の流用で新規 module 1 つ + fixture テスト。 @@ -240,7 +240,7 @@ #### 完了基準 -- ADR 採番衝突・索引不整合・ファイル名/タイトル番号不一致が push 前に決定論的に検出され、PR #261 型の rebase 時大量置換が再発しない構造になっていること。 +- ADR 採番衝突・索引不整合・ファイル名/タイトル番号不一致が push 前に決定論的に検出され、PR #261 型の rebase 時大量置換が再発しない構造になっていること (ステータスタグ整合は順位 357 の完了基準で扱い、本 entry の対象外)。 --- diff --git a/pr-monitor-config.toml b/pr-monitor-config.toml index 5ced0aaa..409f64f9 100644 --- a/pr-monitor-config.toml +++ b/pr-monitor-config.toml @@ -53,12 +53,13 @@ group = "rust-lint-test" # templates 側のコメントアウトで default OFF が継承される)。 # mode: "observe" = violation を記録するが push は続行 (誤検知率の計測用)。 # "enforce" = violation で auto-push を中止し action_required に倒す (fail-closed)。 -# bounded lifetime (ADR-054): dogfood 初期は observe で誤検知ゼロを確認し、その後 enforce に -# 上げる。enforce で 3-5 PR 誤検知ゼロなら本採用、誤検知頻発なら enabled = false で kill。 +# bounded lifetime (ADR-054): observe 期間 (2026-07-12〜08-01、fix step 実行 5 回) で誤検知 +# ゼロを確認し、2026-08-01 に enforce へ昇格。enforce で 3-5 PR 誤検知ゼロなら本採用、 +# 誤検知頻発なら enabled = false で kill。 # 緊急バイパス (kill-switch): 環境変数 PR_MONITOR_SCOPE_GUARD_DISABLE=1 [fix.scope_guard] enabled = true -mode = "observe" +mode = "enforce" # --------------------------------------------------------------------------- # [classifier] — ADR-038 (採用、2026-05-15) 経由のローカル LLM finding classifier。 diff --git a/src/cli-pr-monitor/src/stages/auto_push.rs b/src/cli-pr-monitor/src/stages/auto_push.rs new file mode 100644 index 00000000..787d7eaa --- /dev/null +++ b/src/cli-pr-monitor/src/stages/auto_push.rs @@ -0,0 +1,223 @@ +//! auto-push 実行層: takt 修正の自動 re-push を 2 段 gate (scope guard / 品質 gate) で +//! 検証してから実行する。`repush.rs` (re-push 要否の判定・dispatch) から分割した module で、 +//! 「push してよいか」の最終防衛線と push 実行そのものを責務に持つ。 +//! +//! block → push 不実行の配線は `decide_auto_push` の返り値で machine-enforce する +//! (PR #341 CodeRabbit finding: enforce 昇格時の回帰ケース固定)。 + +use lib_report_formatter::Finding; + +use crate::log::log_info; +use crate::stages::push::run_push; + +/// auto-push 直前の最終判定。`Proceed` 以外は `run_push` に到達しない (`BlockedViolation` → +/// push 不実行の配線をこの返り値で machine-enforce する)。gate は遅延評価で受け取り、 +/// scope guard が block した場合は評価しない (短絡順序の固定。`decide_repush` と同型の DI)。 +#[derive(Debug, PartialEq)] +enum AutoPushDecision { + /// scope guard BLOCK → push せず action_required (fail-closed、ADR-054) + AbortScopeViolation { reason: String }, + /// 品質 gate FAIL → push せず action_required (fail-closed、ADR-043) + AbortGateFailure { reason: String }, + /// 両 gate 通過 (skip 含む) → push 実行 + Proceed, +} + +fn decide_auto_push( + scope_outcome: crate::stages::scope_guard::ScopeGuardOutcome, + evaluate_gate: impl FnOnce() -> crate::stages::gate::GateOutcome, +) -> AutoPushDecision { + if let crate::stages::scope_guard::ScopeGuardOutcome::BlockedViolation { reason } = + scope_outcome + { + return AutoPushDecision::AbortScopeViolation { reason }; + } + if let crate::stages::gate::GateOutcome::Failed { reason } = evaluate_gate() { + return AutoPushDecision::AbortGateFailure { reason }; + } + AutoPushDecision::Proceed +} + +/// auto-push の実行。push 前に 2 段の gate を評価する: +/// 1. scope guard (`stages/scope_guard.rs`、WP-11 / ADR-054): fix diff が finding 対象外の +/// ファイルを変更していれば block (prompt injection 防御、default OFF opt-in)。 +/// 2. 品質 gate (`stages/gate.rs`、PR #224 対策 B1): テスト等の FAIL で block。 +/// +/// どちらの block も push せず `action_required` に倒す (fail-closed、ADR-043)。 +/// 判定は `decide_auto_push` に分離し、block → push 不実行の配線をテストで固定する。 +pub(crate) fn run_auto_push( + config: &crate::config::FixConfig, + findings: &[Finding], + pr_label: &str, + pre_cid: Option<&str>, +) { + let scope_outcome = + crate::stages::scope_guard::evaluate_scope_guard(&config.scope_guard, pre_cid, findings); + log_info(&format!("[decision] scope_guard: {:?}", scope_outcome)); + let decision = decide_auto_push(scope_outcome, || { + let gate_outcome = crate::stages::gate::evaluate_gate(&config.gate, pre_cid); + log_info(&format!("[decision] gate: {:?}", gate_outcome)); + gate_outcome + }); + match decision { + AutoPushDecision::AbortScopeViolation { reason } => { + log_info(&format!( + "[action] auto_push 中止 (scope guard BLOCK、fail-closed): {}", + reason + )); + mark_scope_guard_action_required(pr_label, &reason); + } + AutoPushDecision::AbortGateFailure { reason } => { + log_info(&format!( + "[action] auto_push 中止 (gate FAIL、fail-closed): {}", + reason + )); + mark_gate_failure_action_required(pr_label, &reason); + } + AutoPushDecision::Proceed => { + log_info(&format!( + "[action] auto_push: {} の takt 修正を自動 re-push", + pr_label + )); + let push_ok = run_push(config); + if push_ok { + log_info("[action] auto_push: 成功"); + } else { + log_info("[action] auto_push: 失敗 (手動対応が必要)"); + } + + if crate::stages::review_trigger::should_trigger_review_after_push( + push_ok, + config.trigger_review_after_push, + ) { + crate::stages::review_trigger::trigger_coderabbit_review(); + } + } + } +} + +/// auto-push を中止した理由を state の `action_required` として永続化する。 +/// state 読み書き失敗は log のみ残して続行する (push は既に中止済みで安全側)。 +fn mark_action_required(summary: String) { + use crate::state::{read_state_from, state_file_path, write_state_to}; + + let path = state_file_path(); + let Some(mut state) = read_state_from(&path) else { + log_info("[action_required] state 読み込み不可のため永続化を skip (log のみ)"); + return; + }; + state.action = "action_required".into(); + state.summary = summary; + if let Err(e) = write_state_to(&path, &state) { + log_info(&format!("[action_required] state 書き込み失敗 (継続): {}", e)); + } +} + +/// gate FAIL を state に反映する。 +fn mark_gate_failure_action_required(pr_label: &str, reason: &str) { + mark_action_required(format!( + "{}: auto-push gate FAIL — push は実行されていません。修正後に pnpm push してください。原因: {}", + pr_label, reason + )); +} + +/// scope guard BLOCK (finding 対象外ファイルへの変更 = injection の疑い) を state に反映する。 +fn mark_scope_guard_action_required(pr_label: &str, reason: &str) { + mark_action_required(format!( + "{}: auto-push scope guard BLOCK — finding 対象外ファイルへの変更を検知したため push を中止しました (prompt injection の疑い、ADR-054)。fix commit を確認してください。原因: {}", + pr_label, reason + )); +} + +/// auto_push_severity 設定値から自動 push するか否かを返す。 +/// "critical" / "major" => true、"none" => false、未知値 => false (警告ログあり) +pub(crate) fn should_auto_push(setting: &str) -> bool { + match setting { + "none" => false, + "critical" | "major" => true, + other => { + log_info(&format!( + "auto_push_severity に未知の値 '{}' が指定されています。'none' として扱い自動 push をスキップします", + other + )); + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// enforce の BlockedViolation では gate 評価に進まず push にも到達しない + /// (PR #341 finding: block → push 不実行の配線と短絡順序を machine-enforce)。 + #[test] + fn decide_auto_push_blocked_violation_aborts_without_gate_eval() { + let out = decide_auto_push( + crate::stages::scope_guard::ScopeGuardOutcome::BlockedViolation { + reason: "x".into(), + }, + || panic!("gate は scope BLOCK 時に評価されてはならない (短絡)"), + ); + assert_eq!( + out, + AutoPushDecision::AbortScopeViolation { reason: "x".into() } + ); + } + + /// observe の violation は push を止めない (gate 通過なら Proceed)。 + #[test] + fn decide_auto_push_observed_violation_proceeds_when_gate_passes() { + let out = decide_auto_push( + crate::stages::scope_guard::ScopeGuardOutcome::ObservedViolation { + out_of_scope: vec!["a.rs".into()], + }, + || crate::stages::gate::GateOutcome::Passed, + ); + assert_eq!(out, AutoPushDecision::Proceed); + } + + /// scope 通過でも gate FAIL なら push しない。 + #[test] + fn decide_auto_push_gate_failure_aborts() { + let out = decide_auto_push(crate::stages::scope_guard::ScopeGuardOutcome::Passed, || { + crate::stages::gate::GateOutcome::Failed { reason: "g".into() } + }); + assert_eq!( + out, + AutoPushDecision::AbortGateFailure { reason: "g".into() } + ); + } + + /// kill-switch / config off (SkippedDisabled) は gate 判定に委ね、通過なら Proceed。 + #[test] + fn decide_auto_push_skipped_disabled_proceeds_when_gate_passes() { + let out = decide_auto_push( + crate::stages::scope_guard::ScopeGuardOutcome::SkippedDisabled, + || crate::stages::gate::GateOutcome::Passed, + ); + assert_eq!(out, AutoPushDecision::Proceed); + } + + #[test] + fn should_auto_push_none_returns_false() { + assert!(!should_auto_push("none")); + } + + #[test] + fn should_auto_push_critical_returns_true() { + assert!(should_auto_push("critical")); + } + + #[test] + fn should_auto_push_major_returns_true() { + assert!(should_auto_push("major")); + } + + #[test] + fn should_auto_push_unknown_value_returns_false() { + assert!(!should_auto_push("non")); + assert!(!should_auto_push("Critical")); + assert!(!should_auto_push("")); + } +} diff --git a/src/cli-pr-monitor/src/stages/mod.rs b/src/cli-pr-monitor/src/stages/mod.rs index c01c8982..9c4e6d3d 100644 --- a/src/cli-pr-monitor/src/stages/mod.rs +++ b/src/cli-pr-monitor/src/stages/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod auto_push; pub(crate) mod collect; mod create_pr; pub(crate) mod gate; diff --git a/src/cli-pr-monitor/src/stages/repush.rs b/src/cli-pr-monitor/src/stages/repush.rs index f71557cf..e241ba2e 100644 --- a/src/cli-pr-monitor/src/stages/repush.rs +++ b/src/cli-pr-monitor/src/stages/repush.rs @@ -2,7 +2,6 @@ use lib_report_formatter::Finding; use crate::fix_commit::FixCommitState; use crate::log::log_info; -use crate::stages::push::run_push; // ─── re-push 判定 (pure function、副作用を注入可能) ─── @@ -83,113 +82,6 @@ pub(crate) fn decide_repush( } } -// ─── re-push フロー ─── - -/// auto-push の実行。push 前に 2 段の gate を評価する: -/// 1. scope guard (`stages/scope_guard.rs`、WP-11 / ADR-054): fix diff が finding 対象外の -/// ファイルを変更していれば block (prompt injection 防御、default OFF opt-in)。 -/// 2. 品質 gate (`stages/gate.rs`、PR #224 対策 B1): テスト等の FAIL で block。 -/// -/// どちらの block も push せず `action_required` に倒す (fail-closed、ADR-043)。 -fn run_auto_push( - config: &crate::config::FixConfig, - findings: &[Finding], - pr_label: &str, - pre_cid: Option<&str>, -) { - let scope_outcome = - crate::stages::scope_guard::evaluate_scope_guard(&config.scope_guard, pre_cid, findings); - log_info(&format!("[decision] scope_guard: {:?}", scope_outcome)); - if let crate::stages::scope_guard::ScopeGuardOutcome::BlockedViolation { reason } = - &scope_outcome - { - log_info(&format!( - "[action] auto_push 中止 (scope guard BLOCK、fail-closed): {}", - reason - )); - mark_scope_guard_action_required(pr_label, reason); - return; - } - - let gate_outcome = crate::stages::gate::evaluate_gate(&config.gate, pre_cid); - log_info(&format!("[decision] gate: {:?}", gate_outcome)); - if let crate::stages::gate::GateOutcome::Failed { reason } = &gate_outcome { - log_info(&format!( - "[action] auto_push 中止 (gate FAIL、fail-closed): {}", - reason - )); - mark_gate_failure_action_required(pr_label, reason); - return; - } - - log_info(&format!( - "[action] auto_push: {} の takt 修正を自動 re-push", - pr_label - )); - let push_ok = run_push(config); - if push_ok { - log_info("[action] auto_push: 成功"); - } else { - log_info("[action] auto_push: 失敗 (手動対応が必要)"); - } - - if crate::stages::review_trigger::should_trigger_review_after_push( - push_ok, - config.trigger_review_after_push, - ) { - crate::stages::review_trigger::trigger_coderabbit_review(); - } -} - -/// auto-push を中止した理由を state の `action_required` として永続化する。 -/// state 読み書き失敗は log のみ残して続行する (push は既に中止済みで安全側)。 -fn mark_action_required(summary: String) { - use crate::state::{read_state_from, state_file_path, write_state_to}; - - let path = state_file_path(); - let Some(mut state) = read_state_from(&path) else { - log_info("[action_required] state 読み込み不可のため永続化を skip (log のみ)"); - return; - }; - state.action = "action_required".into(); - state.summary = summary; - if let Err(e) = write_state_to(&path, &state) { - log_info(&format!("[action_required] state 書き込み失敗 (継続): {}", e)); - } -} - -/// gate FAIL を state に反映する。 -fn mark_gate_failure_action_required(pr_label: &str, reason: &str) { - mark_action_required(format!( - "{}: auto-push gate FAIL — push は実行されていません。修正後に pnpm push してください。原因: {}", - pr_label, reason - )); -} - -/// scope guard BLOCK (finding 対象外ファイルへの変更 = injection の疑い) を state に反映する。 -fn mark_scope_guard_action_required(pr_label: &str, reason: &str) { - mark_action_required(format!( - "{}: auto-push scope guard BLOCK — finding 対象外ファイルへの変更を検知したため push を中止しました (prompt injection の疑い、ADR-054)。fix commit を確認してください。原因: {}", - pr_label, reason - )); -} - -/// auto_push_severity 設定値から自動 push するか否かを返す。 -/// "critical" / "major" => true、"none" => false、未知値 => false (警告ログあり) -pub(crate) fn should_auto_push(setting: &str) -> bool { - match setting { - "none" => false, - "critical" | "major" => true, - other => { - log_info(&format!( - "auto_push_severity に未知の値 '{}' が指定されています。'none' として扱い自動 push をスキップします", - other - )); - false - } - } -} - fn execute_repush_action( fix_config: &crate::config::FixConfig, findings: &[Finding], @@ -198,7 +90,9 @@ fn execute_repush_action( action: RepushAction, ) { match action { - RepushAction::AutoPush => run_auto_push(fix_config, findings, pr_label, pre_cid), + RepushAction::AutoPush => { + crate::stages::auto_push::run_auto_push(fix_config, findings, pr_label, pre_cid) + } RepushAction::UserConfirmWithSeparatedFix { commit_id } => { log_info(&format!( "[action] auto_push スキップ: ユーザー確認待ち (fix commit 分離済み: {})", @@ -243,7 +137,7 @@ pub(crate) fn execute_repush_flow( let decision = decide_repush(pre_cid, post_cid.as_deref(), crate::runner::diff_is_empty); log_info(&format!("[decision] repush: {:?}", decision)); - let allow_auto = should_auto_push(&fix_config.auto_push_severity); + let allow_auto = crate::stages::auto_push::should_auto_push(&fix_config.auto_push_severity); log_info(&format!( "[state] auto_push_setting: {} (allow_auto: {}), fix_state_created: {}", fix_config.auto_push_severity, @@ -266,31 +160,6 @@ mod tests { use super::*; use crate::runner::{capture_commit_id, diff_is_empty}; - #[test] - fn should_auto_push_none_returns_false() { - assert!(!should_auto_push("none")); - } - - #[test] - fn should_auto_push_critical_returns_true() { - assert!(should_auto_push("critical")); - } - - #[test] - fn should_auto_push_major_returns_true() { - assert!(should_auto_push("major")); - } - - #[test] - fn should_auto_push_unknown_value_returns_false() { - // タイポや未知値は fail-closed: 自動 push しない - assert!(!should_auto_push("non")); - assert!(!should_auto_push("Critical")); - assert!(!should_auto_push("")); - } - - // ─── decide_repush: commit id + diff の二段構え判定 ─── - #[test] fn decide_repush_same_commit_id_returns_no_change() { // pre == post: ID 変化なし → diff 確認せずに NoChange