Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/adr/adr-030-deterministic-post-merge-feedback.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,68 @@ PR #78 dogfood で発覚した **Windows の `child.kill()` が takt の descend
- takt が timeout で kill されたが、orphan が後から report を書き終えた
- takt が exit=non-zero を返したが、aggregate-feedback は完了していた

#### Abrupt 終了の多層 recovery (Bundle c-1 で追加)

PR #109 マージ直後の post-merge-feedback workflow が SIGPIPE で silent 中断され `.failed` marker 未生成という failure mode が実証された。原因は `feedback::run` が `Result::Err` を返した場合のみ `write_failed_marker` を呼ぶ設計で、Rust default の SIGPIPE 動作 (`SIG_DFL` = unwind せず process 終了) では `Result::Err` 経路にも Drop 経路にも到達しないため。本節は ADR-030 "失敗マーカーによる recovery" の決定論性を **abrupt 終了系 (SIGPIPE / SIGTERM / kill -9 / SIGKILL / power loss / OOM Killer / panic) でも担保するための多層構造** を spec として明記する。

##### L1: in-process recovery

`cli-merge-pipeline::feedback::run` 内で **pre-emptive `.failed` marker** + **RAII Drop guard** の 2 機構で marker 存在を保証する:

| 機構 | 動作 | カバー範囲 |
|---|---|---|
| pre-emptive marker | `feedback::run` の `check_concurrent_run_guard` 直後に `write_pending_marker` で `.failed` marker を先制書込み。正常完了時のみ `cleanup_failed_marker` で削除 | **SIGPIPE / SIGTERM / kill -9 / SIGKILL** など unwind せず即時 process 終了する経路 (Rust default では Drop は走らない) |
| RAII Drop guard (`FailedMarkerGuard`) | `armed = true` で生成。Drop 時に marker 存在を idempotent check し、欠落していれば backup marker を書込み。`disarm()` 呼出で no-op 化 | **panic / 早期 return** など Drop が走る経路。caller が detailed marker を書いた後でも idempotent (既存 marker は overwrite しない) |

正常 path:
1. `write_pending_marker` で marker 書込み + `FailedMarkerGuard::new(armed=true)`
2. 全 step 成功
3. `cleanup_failed_marker` で marker 削除
4. `marker_guard.disarm()` → armed=false
5. scope 終了、Drop は no-op

abnormal path (panic / 早期 return):
1. `write_pending_marker` で marker 書込み (armed=true)
2. 途中で panic or `?` で早期 return
3. scope 巻き戻し、Drop が `marker.exists() = true` を確認 → no-op (pre-emptive marker が既に在る)

abrupt path (SIGPIPE / SIGKILL 等):
1. `write_pending_marker` で marker 書込み (armed=true)
2. process が **unwind せず即時終了**
3. Drop は走らない → しかし pre-emptive marker は既にディスクに残存

##### L2: out-of-process recovery (orphan run reaper)

L1 の pre-emptive marker 書込み **直前** に process が死んだ場合 (例: `feedback::run` を呼び出す直前で OOM Killer 発火、power loss、`std::fs::write` 自体が完了する前の kill -9) は L1 の救済対象外。この極致 case 用に `hooks-session-start` が SessionStart hook で **out-of-process reaper** を走らせる:

- **scan 対象**: `.takt/runs/*/meta.json` の `status: "running"` AND `task` が `"post-merge-feedback for #"` で始まる run
- **orphan 判定閾値**: `ORPHAN_THRESHOLD_SECS = TAKT_TIMEOUT_SECS + 300 (= 1500s)`。`TAKT_TIMEOUT_SECS` 経過後も `running` のまま放置されている run は abrupt termination で死んだとみなす
- **reap 動作**: `.claude/feedback-reports/<pr>.md.failed` marker を生成 + `meta.json` の `status` を `"failed"` に更新 (`reaped_by: "hooks-session-start"` field も追加)
- **冪等性 / false-positive 抑止**: 以下のいずれかに該当する run は reap を skip する:
1. 既存の `.failed` marker がある (L1 もしくは前回 reaper pass で処理済み)
2. **`.claude/feedback-reports/<pr>.md` 成功レポートが存在する** — 上記 Reconciliation 節で記述した「takt parent kill 後に descendants が report 完成」path では meta.json が `status: "running"` のまま残るが、実際には成功している。reap せず stale meta.json を放置する方が、false-positive の `.failed` marker で `hooks-user-prompt-feedback-recovery` が毎 prompt nag するより害が少ない
- **nudge**: 検出時は SessionStart の `additionalContext` に `[POST_MERGE_FEEDBACK_REAPER]` tag 付きで通知

##### 責務分離

| 層 | 場所 | 救済対象 |
|---|---|---|
| **L1 floor** (in-process pre-emptive marker) | `cli-merge-pipeline::feedback::run` | SIGPIPE / SIGTERM / kill -9 / SIGKILL / panic / `Result::Err` (= 大半の経路) |
| **L1 backstop** (in-process Drop guard) | 同上 (`FailedMarkerGuard`) | panic / 早期 return での marker 消失防止 (idempotent backup) |
| **L2 reaper** (out-of-process) | `hooks-session-start::compute_reaper_nudge` | pre-emptive write 完了前の OOM Killer / power loss / kill -9。Drop guard で救済不可な致命系の backstop |
| **L2 recovery** (UserPromptSubmit hook) | `hooks-user-prompt-feedback-recovery` | 上記いずれかで生成された `.failed` marker を Claude に通知 |

L1 と L2 は **重複動作しない**: L1 が marker を書いていれば L2 reaper は `marker.exists()` で skip。L2 が走るのは L1 が完全に効かなかった致命系のみ。

##### SLA (post-merge-feedback の完了/失敗保証)

「post-merge-feedback はマージ後、次のいずれかの状態に **必ず** 遷移する」をステートメントとして規定:

- **完了 (`.claude/feedback-reports/<pr>.md` 生成)**: `pnpm merge-pr` 同期実行内、`TAKT_TIMEOUT_SECS` 以内
- **失敗 marker 化 (`.failed` marker 残存)**: L1 経路は `feedback::run` の return 時点で確定。L2 経路は **次回 Claude Code SessionStart 時** で確定 (orphan が `ORPHAN_THRESHOLD_SECS` 経過後)

つまり、L1 のみであれば「マージ後 `TAKT_TIMEOUT_SECS` 以内に完了 or marker 化」が保証される。L2 (致命系の backstop) を含めても「次回 SessionStart 時には必ず marker 化」が保証される。実数値は `cli-merge-pipeline::feedback::TAKT_TIMEOUT_SECS` / `ORPHAN_THRESHOLD_SECS` を参照のこと (本 ADR で数値固定するとコード変更時に drift する)。

#### 並行起動 guard (Phase B post-fix で追加)

cross-invocation context overwrite race の予防として、`feedback::run` の冒頭で `.takt/post-merge-feedback-context.json` の経過時間を確認:
Expand Down
2 changes: 1 addition & 1 deletion docs/local-llm-offload-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ cargo test -p cli-finding-classifier --test lint_screen_evals -- \
| 🛠️ D 前提整備 (順位 109) | ✅ 完了 | #144 (2026-05-11) | `.takt/lint-screen-report.md` に `## Diagnostic` section、real pipeline 経由で warn log が visible |
| 🔄 D Round 1 (D-1〜D-3) | ✅ 完遂 | #145/#146/#147/#148 (2026-05-12) | env var override (順位 115) 解消、D-3 で初の real lint_screen 観測 = 1 data point |
| 🔄 D Round 2 (D-4〜D-6) | ✅ 完遂 | #150/#151/#152 (2026-05-13) | size ramp-up + verdict variance 観測、累積 6 data points / 4 PR |
| 🔄 D Round 2 (D-7) | 未着手 | (Bundle c-1) | cli-merge-pipeline Drop guard / orphan reaper / ADR-030 spec |
| 🔄 D Round 2 (D-7) | 🚧 進行中 | (Bundle c-1) | cli-merge-pipeline pre-emptive marker + Drop guard / orphan reaper / ADR-030 spec |

**Phase E 着手前提条件 = 3-5 PR 累積 dogfood は D-6 完遂時点で既に充足** (4 PR / 6 data points)。D-7 完遂後に Phase E (採否判定) 移行可能。Round 1/2 で観測した **false positive 5 件中 5 件が file-type / scope 混同型** (mistral:7b の context window 内 hook source 周辺 hallucinate) → Bundle k 順位 123 (MD 除外フィルター) の構造的解消対象。詳細 metrics・観測の意義・各 PR の dogfood outcome は [phase-d-outcomes.md](local-llm-offload-phase-d-outcomes.md) 参照。

Expand Down
8 changes: 4 additions & 4 deletions docs/todo-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,8 @@
| 57 | 🔧 Tier 2 | **Aggregation cap integration test (PR #105 T2-1 採用)** | todo7.md | S | なし (`collect_all_violations` の MAX_VIOLATIONS contract を test 化、将来の lint 追加時に `truncate(MAX)` 削除 regression を防止する explicit 安全網) |
| 60 | 💎 Tier 3 | **analyze-session の transcript filter 絞り込み (旧 #A-3)** | todo7.md | M | なし (旧 docs/pipeline-token-efficiency.md #A-3、ADR-036/037 化に伴い計画書削除、本 task のみ todo に移管。analyze-session の input range を PR 作成 commit〜merge に限定して input token 30-50% 削減見込み、dogfood で実測必要) |
| 61 | 🔧 Tier 2 | **post-PR 検証フローに CR review.body 手動スキャン step 追加 (PR #108 T2-1 採用)** | todo7.md | XS | なし (PR #108 で analyze-coderabbit が review body の outside diff range comment を検出漏れし line 371/378 の修正が後追い、blind spot の暫定緩和策として手動 checklist を整備) |
| 63 | 🚀 Tier 1 | **cli-merge-pipeline に Drop guard / signal handler 追加 (PR #109 T1-1 採用) ★ Bundle c** | todo7.md | M | なし (PR #109 SIGPIPE 事故で ADR-030「失敗マーカーによる recovery」仕様の構造的違反が実証、Pre-emptive marker + signal trap で abrupt 経路を多層防御) |
| 64 | 🚀 Tier 1 | **orphan run reaper (`meta.json status=running` 5-15 分放置検出 + 自動再起動) (PR #109 T1-2 採用) ★ Bundle c** | todo7.md | M | なし (順位 63 で救済不可の致命系 = kill -9 / SIGKILL / power loss / OOM の backstop、SessionStart hook または cli-pr-monitor 経路で実装) |
| 65 | 🚀 Tier 1 | **exe + `--help` を PreToolUse でブロックして src/ Read に誘導 (PR #109 T1-3 採用) ★ Bundle c** | todo7.md | S | なし (PR #109 SIGPIPE の直接トリガ = AI が `cli-merge-pipeline.exe --help` 実行 → exe は --help 未対応で merge 本体実行を構造的に防止、今後追加 exe にも自動適用) |
| 66 | 💎 Tier 3 | **長時間 subprocess の pipe truncate 禁止ルールをグローバル明文化 (PR #109 T3-1 採用) ★ Bundle c** | todo7.md | XS | なし (順位 65 = 決定論層、本ルール = 判断ガイド層、`~/.claude/rules/common/development-workflow.md` 等に追加) |
| 67 | 💎 Tier 3 | **ADR-030 に abrupt 終了時の振る舞いを spec として明記 (PR #109 T3-2 採用) ★ Bundle c** | todo7.md | XS | 順位 63 / 64 と同 PR (実装と仕様の整合性確保、L1 in-process Drop guard + L2 out-of-process reaper の責務分離 + SLA 化) |
| 69 | 💎 Tier 3 | **`no-ephemeral-todo-reference` の `yaml`/`yml` extensions 追加理由をコメントで明記 (PR #110 T3-1 採用) ★ Bundle d** | todo5.md | XS | なし (rule⑥ コメント欄に 1-2 行追記、設計 doc と実装の経緯保存、git blame 不要化) |
| 78 | 💎 Tier 3 | **ADR-038 (Rust timestamp arithmetic safety) + CLAUDE.md security 拡充 (PR #115 T3-1 採用) ★ Bb-3 follow-up** | todo5.md | S | なし (config が user-editable system boundary のとき `sanitize()` 値域検証を必須化し dependent arithmetic に `// SAFETY: <sanitize-fn> により上限保証` コメントを要求するパターンを ADR + CLAUDE.md に codify、Rust 固有の checked_add + MAX_SAFE capping + time-dependent test の 3 層を明文化) |
| 79 | 💎 Tier 3 | **`docs-governance.md` § Retirement Workflow に「残タスクの lifecycle 整合」要件明記 (PR #117 T3-1 採用)** | todo5.md | XS | なし (PR #117 で順位 15 を Bb-3 で吸収済として削除した際、現 Step 2「残タスクを priority table に登録」が priority table から除外するケース = 完了/deprioritize/defer を未定義だった実証。除外時の commit/PR で 3 値のいずれかを明示する要件を追加して将来の同型 ambiguity を構造的に防ぐ) |
Expand Down Expand Up @@ -80,8 +77,9 @@
| 123 | 🚀 Tier 1 | **lint-screen の Markdown ファイル除外フィルター追加 (PR #151 T1-#2 採用、PR #152 で再観測) ★ Bundle k** | todo8.md | M | なし (D-3/D-4/D-5/**D-6** の 4 PR で一貫観測した `.md` への `unused-import` hallucinate FP を構造的に解消。Phase D dogfood 観測から導かれた最も価値ある決定論的防止策、拡張子ベース mechanical filter で実装可能、Frequency High + Adoption Risk None) |
| 124 | 🚀 Tier 1 | **`no-ephemeral-todo-reference` rule の TOML positive test 追加 (PR #151 T1-#1 採用、PR #152 で再観測)** | todo8.md | S | なし (extensions 拡張が複数 PR にわたり反復する pattern (yaml/yml = PR #110、toml 等)、test gap 累積を構造的に防ぐ。Frequency Medium で採用基準を満たす) |
| 125 | 🔧 Tier 2 | **UTF-8 マルチバイト boundary test を他の string-processing hooks に横展開 (PR #151 T2-#1 採用)** | todo8.md | M | なし (PR #151 で `byte_offset_to_line` char-boundary panic bug を test 拡充で発見、同型関数を持つ他 hooks に systemic 防御を確保。test 拡充が production fault detection に直結する事例の横展開) |
| 126 | 💎 Tier 3 | **ADR-038 に mistral:7b 「diff 外 context hallucinate」failure mode を追記 (PR #151 T3-#1 採用、順位 123 と同 PR 推奨、PR #152 で再観測)** | todo8.md | XS | なし (**4 PR 観測** = High freq の failure mode を ADR codify、Phase b' fixture では再現しない pattern のため永続記録の価値あり、順位 123 と同 PR で実装と仕様の整合性確保) |
| 126 | 💎 Tier 3 | **ADR-038 に mistral:7b 「diff 外 context hallucinate」failure mode を追記 (PR #151 T3-#1 採用、順位 123 と同 PR 推奨、PR #152 / PR #153 で再観測 = 5 PR 連続)** | todo8.md | XS | なし (**5 PR 連続観測** = High freq の failure mode を ADR codify、Phase b' fixture では再現しない pattern のため永続記録の価値あり、順位 123 と同 PR で実装と仕様の整合性確保。PR #153 T3-#1 では root cause + structural fix の両方を明記する要件追加) |
| 127 | 💎 Tier 3 | **extensions 拡張時の test 追加 pattern をコード comment で明文化 (PR #151 T3-#2 採用、順位 124 と同 PR 推奨、PR #152 で再観測)** | todo8.md | XS | なし (`feedback_no_unenforced_rules.md` 例外 = 既存実践の明文化 + 機械強制ではなく guide 効果。順位 124 と同 PR で test location を正確に参照、順位 122 と同じロジック) |
| 128 | 💎 Tier 3 | **CLAUDE.md § Cross-File Reference Lifecycle に多ファイル同時削除 retirement condition checklist を追加 (PR #153 T3-#2 採用)** | todo8.md | XS | なし (PR #133 (todo.md 分割) + PR #153 (analysis.md 分割) の successful pattern を明文化、`feedback_no_unenforced_rules.md` 例外 = 既存実践の明文化 + guide 効果、順位 122 / 127 と同じロジック、`~/.claude/` global 配下で派生プロジェクトに自動波及) |

**戦略**: Tier 1 を 2〜3 セッションで片付け → Tier 2 で ADR-032 の前提 + rate-limit + convergence cost 削減を進める → Tier 3 で ADR-032 を land + ドキュメント整備。Tier 4-5 は cleanup / 外部展開で daily efficiency への直接効果は小さい。

Expand Down Expand Up @@ -126,3 +124,5 @@
**Bundle k (PR #151 post-merge-feedback、Phase D dogfood 観測由来の lint-screen FP 対策、2026-05-13)**: PR #151 (Phase D D-5 = comment-lint test 拡充 + MAX cap test) merge 後の post-merge-feedback で 11 findings 中 **5 件採用** (Tier 1 #1, #2 / Tier 2 #1 / Tier 3 #1, #2) を 5 entries (順位 123-127) で登録。**コア発見**: D-3 (PR #148) / D-4 CR fix (PR #150) / D-5 ×2 (PR #151) の 3 PR・4 push events で「mistral:7b が docs-only diff や `.md` ファイルに対して Rust の `unused-import` を hallucinate する」FP pattern が一貫して観測 = Phase b' fixture では再現しない failure mode。**順位 123 (lint-screen MD 除外フィルター、Tier 1 / M / High freq)** が最重要 = 拡張子ベース mechanical filter で構造的に解消可能、Phase D dogfood 観測から導かれた最も価値ある決定論的防止策。**Sub-PR 推奨**: k-1 (順位 123 + 126、実装 + ADR-038 codify、Effort M+XS、コア層) / k-2 (順位 124 + 127、TOML test + extensions code comment、Effort S+XS、test gap 補強層) / k-3 (順位 125、UTF-8 boundary 横展開、Effort M、独立) で 3 PR 分割推奨。**却下** (4 件): UTF-8 lint rule (FP リスク、AST 必須) / `byte_offset_to_line` 強化 (PR #151 で既対応) / UTF-8 guideline + extensions checklist (`feedback_no_unenforced_rules.md` 適用)。**様子見** (3 件): T2 #2 (lint-screen dogfood CI step、L effort + takt test infra 調査依存) / T3 #3 (test 拡充→bug 発見 pattern を ADR-007 記録、1 PR 観測のみ) / T3 #4 (multi-rule scenario fixture pattern を test comment 明文化、Low × Low)。**本 PR 含意**: Phase D dogfood 観測 (analysis.md L334-340) が直接 actionable な決定論的防止層 (順位 123) に結実、Phase E 採否判定前に systemic FP root cause が解消される構造的進展。

**Bundle k 補強 (PR #152 post-merge-feedback、D-6 docs-only PR、2026-05-13)**: PR #152 (Phase D D-6 = fix.md instruction-level review-diff refresh + Bundle k 順位 123-127 entry 登録) merge 後の post-merge-feedback で 8 findings 中 4 件採用 (Tier 1 #1 / Tier 2 #1 / Tier 3 #1, #2)。**全 4 件が Bundle k 既存エントリ (順位 123/124/126/127) と完全重複** = post-merge-feedback analyzer 自身が「Bundle k 優先度 X で既に roadmap 済」と明記。新規順位を追加せず、**既存 4 entries (順位 123/124/126/127) に PR #152 を追加観測として追記** (frequency 観測: 3 PR → **4 PR** に更新、Bundle k の優先度 / Sub-PR 分割推奨は不変)。**含意**: PR #152 (docs-only) でも `.md` への `unused-import` FP が同根 root cause で再現したことが「lint_screen FP は diff 内容ではなく hook source 周辺 context を見て hallucinate している」仮説を 4th observation として裏付け = 順位 123 拡張子フィルター実装の confidence 向上。**様子見** (2 件): PostToolUse hook 自動化 (案 D、Frequency Low) / fix.md 自己参照 ambiguity (1 PR 観測のみ、次回 fix.md 編集機会に opportunistic 適用)。**却下** (2 件): 機械検知不可な `~/.claude/rules/*` 追加 (memory `feedback_no_unenforced_rules.md` 適用)。

**Bundle k 補強 (PR #153 post-merge-feedback、analysis.md 軽量化 PR、2026-05-13)**: PR #153 (D-6 post-merge follow-up + analysis.md 49KB→26KB split) merge 後の post-merge-feedback で 6 findings 中 **2 件採用** (Tier 3 #1, #2)。**T3 #1 = 順位 126 (ADR-038 hallucinate codify) と完全重複** → 順位 126 entry を「**5 PR 連続観測** (#148/#150/#151/#152/#153)」+ root cause / structural fix の明示記載要件追加で更新。**T3 #2 = 新規採用** → **順位 128 (CLAUDE.md § Cross-File Reference Lifecycle に多ファイル同時削除 retirement condition checklist 追加)** として登録、PR #133 (todo.md 分割) + PR #153 (analysis.md 分割) の successful pattern を明文化。**様子見** (2 件): CLAUDE.md → docs-governance.md cross-link / docs-only PR template の Retired sections list (どちらも Frequency Low)。**却下** (2 件): cross-reference lifecycle 自動 lint rule (NLP 必要) / file role scope exceptions guidance (1 観測のみ、過剰一般化リスク)。**含意**: docs-only PR でも mistral:7b の FP が 5 観測目として再現、Bundle k 順位 126 の優先度を High freq として確定。多ファイル分割の retirement workflow を順位 128 で global rule 化することで、今後の docs/* 50KB 分割 (history.md 等) で同 pattern を mechanical に reproducible 化。
Loading