yoi/tickets/hook-interceptor-separation.review.md
2026-04-18 17:19:59 +09:00

73 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# レビュー: Hook と Interceptor の責務分離
対象差分: `crates/pod/src/{hook,pod,lib,pod_interceptor}.rs`、削除: `compact_interceptor.rs` / `hook_interceptor.rs`staged、未コミット
## 要件達成状況
| 要件 | 状態 |
|---|---|
| Hook の Input が read-only サマリ型になる | ✅ `PromptSubmitInfo` / `PreRequestInfo` / `ToolCallSummary` / `ToolResultSummary` / `TurnEndInfo` / `AbortInfo` を新設。`Hook::call(&self, input: &E::Input)` で `&mut` が消えた |
| Hook から context / history の直接操作パスが型レベルで存在しない | ✅ `Vec<Item>` / `ToolCallInfo` / `ToolResultInfo` が Hook の Input から消え、サマリ型に置換 |
| Interceptor は context / history の直接操作が可能なまま | ✅ `PodInterceptor``Interceptor` を実装し `&mut Vec<Item>` / `&mut ToolCallInfo` 等を受け取る |
| Interceptor → Hook の実行順序(内部が先、公開が後) | ✅ `PodInterceptor` の各メソッドが: 内部ロジックcompaction check 等)→ サマリ構築 → Hook 呼び出し の順 |
| HookInterceptor ブリッジの削除 | ✅ `hook_interceptor.rs` deleted |
| CompactInterceptor の統合 | ✅ `compact_interceptor.rs` deleted。compaction check は `PodInterceptor::pre_llm_request` に統合 |
| 既存の compaction 動作が壊れない | ✅ `compact_state``PodInterceptor` に渡し、`exceeds_turn()` で Yield する同じロジック |
| 単体テストで Hook が context を操作できないことが検証される | ✅ テストの Hook が `&PreRequestInfo`read-onlyを受け取る形で書かれている。`&mut` パスは型レベルで不可能 |
## アーキテクチャ
### 良い点
**`PodInterceptor` が single composite interceptor として全責務を統合**:
- compaction check (内部) → サマリ構築 → Hook 呼び出しを1つの `pre_llm_request` 内で制御
- Worker 側は単一の `set_interceptor` で完結(`Vec<Box<dyn Interceptor>>` 不要)
- `compact_interceptor.rs` の decorator パターンinner HookInterceptor をラップ)が消え、フラットな構造に
**Hook の `call` シグネチャが `&self, input: &E::Input` に**:
- `&mut E::Input``&E::Input` への変更で、Hook 実装者が context を書き換える経路が型レベルで消えた
- 将来スクリプトに公開するとき、`&T` だけ渡せば良いsandbox の粒度が明確)
**サマリ型の設計**:
- `ToolCallSummary::arguments``serde_json::Value` clone — 構造的アクセスが可能で permission 判断に使える
- `ToolResultSummary::output``ToolOutput` clone — summary + content 両方にアクセス可能
- `PreRequestInfo` が item_count / estimated_tokens / turn_index / tool_calls_this_turn を集約 — compaction 判断に必要十分
- `TurnEndInfo::final_text_preview` が 512 byte 制限 + UTF-8 boundary — Pod orchestration で spawned Pod の結果要約に使える
**ターン追跡の統合**:
- `next_turn_index` / `tool_calls_this_turn``PodInterceptor` 内の `AtomicUsize` で管理
- `on_prompt_submit` でリセット、`pre_tool_call` でインクリメント
- Hook が受け取るサマリにこの情報が反映される
### テスト
4 ケース:
- `pre_llm_request_yields_and_skips_hooks_when_compact_threshold_exceeded` — 内部機構compactionが Hook より先に short-circuit することを検証
- `pre_llm_request_runs_hooks_when_under_threshold` — 通常時は Hook が走ることを検証
- `pre_llm_request_runs_hooks_when_no_compact_state` — compaction 無効時も Hook が走ることを検証
- `pre_llm_request_short_circuits_on_first_non_continue` — 複数 Hook の短絡評価を検証
特に 1 つ目が**設計の核心Interceptor が先、Hook が後。内部が short-circuit したら Hook は走らない)**を lock-in している。
## 指摘事項
### 1. 🟢 `pod.rs` の `ensure_interceptor_installed` が少しすっきりした
旧: `CompactInterceptor::new(hook_interceptor, state)` で decorator をネスト → 新: `PodInterceptor::new(registry, compact_state)` でフラットに構築。`compact_state` の有無で分岐していた `set_interceptor` 呼び出しが1箇所に統合。
### 2. 🟢 `HookEventKind::Input` に `Send + Sync` バウンドが追加
```rust
type Input: Send + Sync;
```
`&E::Input` を async メソッドで受け取るために必要。正しい追加。
### 3. 🟢 `extract_message_text` / `preview` がユーティリティとして分離
`PodInterceptor` 内の private function として切り出し。`preview` は UTF-8 boundary を考慮した切断で、`truncate_content`tool output truncationと同じパターン。
## 結論
**無条件で受け入れ可**。チケットの要件を完全に達成。特に「型レベルで Hook から context 操作が不可能」という核心的な保証が `&E::Input` (not `&mut`) + サマリ型で実現されている。テストも設計意図を正確にカバー。