docs(tickets): permission-extension-point完了

This commit is contained in:
Keisuke Hirata 2026-05-09 03:19:30 +09:00
parent 60144c550a
commit 7c5c1609cb
3 changed files with 0 additions and 138 deletions

View File

@ -3,7 +3,6 @@
- 半自動開発運用 Workflow → [tickets/auto-maintain-workflow.md](tickets/auto-maintain-workflow.md)
- Workflow を memory crate から独立させる → [tickets/workflow-crate-extraction.md](tickets/workflow-crate-extraction.md)
- Prompt / Workflow 評価メトリクスと改善 Offer → [tickets/prompt-eval-metrics.md](tickets/prompt-eval-metrics.md)
- パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
- Pod CLI: マニフェスト関連フラグの整理 → [tickets/pod-cli-manifest-flags.md](tickets/pod-cli-manifest-flags.md)
- Pod: 空応答ターン (Submit 後 AI 応答ゼロで Pause/Cancel) を自動巻き戻し → [tickets/pod-empty-turn-rollback.md](tickets/pod-empty-turn-rollback.md)
- Pod: 任意ターンからの Fork複数ターン巻き戻しを汎用化 → [tickets/pod-session-fork.md](tickets/pod-session-fork.md)

View File

@ -1,110 +0,0 @@
# パーミッション: パターンベースのツール実行制御
## 背景
現状の `Scope` はディレクトリ単位の書き込み制約で、静的な境界線。
実際のエージェント運用では、ツール単位・引数パターン単位の動的な権限制御が必要になる。
OpenCode はパターンベースのルールtool × pattern → allow/deny/askを持ち、
`*.env` への書き込み拒否や `rm -rf` の実行拒否を宣言的に設定できる。
## 方針
Permission の評価点は `PreToolCall` Hook とする。マニフェストにルールを宣言し、
insomnia 層が built-in の `PreToolCall` Hook として登録してツール呼び出し時に評価する。
`deny` はターン全体の Cancel/Abort ではない。対象 tool call を実行せず、
permission denied を表す `is_error = true` の synthetic tool result を履歴に追加してターンを継続する。
これにより provider が要求する `tool_use` / `tool_result` の対応を壊さず、LLM は拒否結果を見て別手段の検討やユーザーへの説明に進める。
`ask``deny` の代替ではなく、ユーザー承認待ちを明示する action として扱う。承認されれば元の tool call を実行し、拒否されれば `deny` と同じ synthetic tool result に落とす。
```toml
[permissions]
default_action = "allow" # allow | deny | ask
[[permissions.rule]]
tool = "bash"
pattern = "rm *"
action = "deny"
[[permissions.rule]]
tool = "file_write"
pattern = "*.env"
action = "deny"
```
allowlist 型にしたい場合:
```toml
[permissions]
default_action = "deny"
[[permissions.rule]]
tool = "read"
pattern = "*"
action = "allow"
[[permissions.rule]]
tool = "grep"
pattern = "*"
action = "allow"
```
確認待ちを基本にしたい場合:
```toml
[permissions]
default_action = "ask"
[[permissions.rule]]
tool = "bash"
pattern = "rm *"
action = "deny"
```
評価順序:
1. `[permissions]` が無い場合、Permission 層は無効。従来通り実行する
2. `[permissions]` がある場合、`default_action` は必須
3. `[[permissions.rule]]` は宣言順に評価し、最初に `tool``pattern` が一致した rule の `action` を採用する
4. 一致する rule が無ければ `permissions.default_action` を採用する
## 設計ポイント
- 設計原則3: 新しい trait は作らない。`PreToolCall` Hook として実装
- 設計原則2: マニフェストに宣言した以上、insomnia 層が解決する
- Permission Hook は Pod が自動登録する built-in Hook とし、ユーザー追加 Hook より先に評価する
- `deny``PreToolAction::Abort` / 既存 `Skip` では表現しない。tool call 単位の拒否結果を履歴へ返すため、Worker 側に synthetic tool result を返せる action が必要
- `ask` アクションは Pod Protocol の拡張が必要Event に `PermissionRequest`、Method に `PermissionReply` を追加)
- `ask` を処理できない実行環境では暗黙に待機しない。設定時に validation error とするか、fail closed で `deny` 相当の synthetic tool result に落とす
- `Scope` との関係: Scope は書き込みの物理的境界、Permission はツール実行のポリシー。補完関係
- ルール評価はパターンマッチのみ。コンテキスト依存の判断はしない(シンプルに保つ)
## 段階的実装
1. **拡張ポイントの記録**(今): docs/pod.md の拡張ポイント表に追加
2. **deny/allow の実装**(ツール実装時): `default_action` と rule 評価を manifest に追加し、built-in `PreToolCall` Hook でパターン評価
3. **拒否 tool result の実装**: `deny` が turn Abort ではなく synthetic error tool result として履歴に入るよう Worker の pre-tool action を拡張
4. **ask の実装**Protocol 拡張時): Method/Event に Permission 関連メッセージを追加し、承認後に元 tool call を実行、拒否時は synthetic error tool result を返す
## 受け皿になる外部仕様
### Agent Skills `allowed-tools`
`tickets/agent-skills.md` で ingest した SKILL.md の frontmatter には agent-skills 仕様の experimental field である `allowed-tools` (例: `["Read", "Bash"]`) が含まれる場合がある。`crates/memory/src/skill.rs::parse_skill_md` 時点では `tracing::warn!` で受け流しているだけで、実効化していない。
本チケットの Permission 層が固まった時点で、Skill 由来 Workflow を実行中のみ当該 skill の `allowed-tools` リストに含まれるツールしか走れない形で反映する。スコープは「Workflow 実行中」相当 (Workflow の system message が context に乗っているターン) に限定する想定。skill 単位で local な permission 集合を持つので、グローバルな `[[permissions.rule]]` ルールとは独立に評価する。
実装上の足がかり:
- `WorkflowRecord` の出所は `WorkflowSource::Skill { dir }` で識別済み (`crates/memory/src/workflow.rs`)。`dir` は manifest `[skills] directories` に書かれた skill ルートそのもの
- 受け皿実装時に `SkillFrontmatter::allowed_tools` の保持先を `WorkflowRecord` に伸ばすか、別の SkillRecord registry を持つかは本チケット内で決める
- 現状の `tracing::warn!` は受け皿実装と同時に消す
## 依存チケット
- ~~[remove-hook-module.md](remove-hook-module.md)~~ — 完了。PreToolCall は Pod 層の `hook::Hook<PreToolCall>` として利用可能
## Review
- 状態: Approve with follow-up
- レビュー詳細: [./permission-extension-point.review.md](./permission-extension-point.review.md)
- 日付: 2026-05-05

View File

@ -1,27 +0,0 @@
# Review: パーミッション: パターンベースのツール実行制御
## 前提・要件の確認
- `[permissions]` が無い場合は無効、ある場合は `default_action` 必須: 満たされている。`PodManifestConfig.permissions` は `Option` で、resolve 時に `permissions.default_action` 不在を `MissingField` にしている (`crates/manifest/src/config.rs:39-42`, `crates/manifest/src/config.rs:430-440`)。
- `[[permissions.rule]]` と first-match / fallback 評価: 満たされている。rule は `rename = "rule"` で TOML 形に対応し、`PermissionHook::action_for` が宣言順 `find` + `default_action` fallback を行う (`crates/manifest/src/lib.rs:246-276`, `crates/pod/src/permission.rs:25-35`)。
- built-in `PreToolCall` Hook として、ユーザー Hook より先に評価: 満たされている。Pod 構築直後に `apply_permissions_from_manifest` が呼ばれ、permission hook は通常の user hook 登録前に `HookRegistryBuilder` へ追加される (`crates/pod/src/pod.rs:336-337`, `crates/pod/src/pod.rs:2295-2296`, `crates/pod/src/permission.rs:39-45`)。
- `allow`: 満たされている。permission action `Allow``PreToolAction::Continue` に変換される (`crates/pod/src/permission.rs:49-56`)。
- `deny` は turn Abort ではなく synthetic error tool result: 満たされている。`PreToolAction::SyntheticResult` が追加され、Worker は tool を実行せず result を commit する (`crates/llm-worker/src/interceptor.rs:48-64`, `crates/llm-worker/src/worker.rs:757-769`, `crates/llm-worker/src/worker.rs:1136-1144`)。
- provider-visible `is_error` の保持: 満たされている。`ToolResult` / `Item::ToolResult` / session replay に `is_error` が通り、Anthropic request へ `tool_result.is_error` が投影される (`crates/llm-worker/src/tool.rs:281-310`, `crates/llm-worker/src/llm_client/types.rs:74-89`, `crates/session-store/src/logged_item.rs:34-40`, `crates/llm-worker/src/llm_client/scheme/anthropic/request.rs:327-347`)。
- `ask`: 現時点のスコープとしては満たされている。型として受け付け、承認 protocol 未実装環境では待機せず fail-closed synthetic error result にしている (`crates/pod/src/permission.rs:53-56`, `docs/pod-factory.md:218-235`)。
## アーキテクチャ・スコープ
- `llm-worker` には汎用的な `SyntheticResult` action だけを追加し、manifest policy 本体は Pod 層の `permission.rs` に置かれているため、層の分離は保たれている。
- 新しい trait は追加されておらず、既存の Hook / Interceptor 境界上で実装されている。
- `Scope` の物理境界には手を入れておらず、permission は tool 実行ポリシーとして分離されている。
- `ask` の protocol/UI 実装に踏み込まず fail-closed として明示している点は、今回の段階的実装として妥当。
## 指摘事項
### Non-blocking / Follow-up
- mixed allow/deny 時の tool result 順序が tool call 順とずれる可能性がある — synthetic result は `synthetic_results` に集めて実行済み result の末尾へ `extend` されるため、先頭 call が deny で後続 call が allow の場合、履歴上の result 順が逆転する (`crates/llm-worker/src/worker.rs:745-769`, `crates/llm-worker/src/worker.rs:821-823`)。provider が call id で対応を取れる限り致命的ではないが、会話の時系列としては call 順を保つ方が安全。
- manifest から Pod へ permission が実際に効く統合テストが薄い — manifest resolve と `PermissionHook` 単体、および Worker の synthetic result はカバーされているが、`[permissions]` を含む Pod 構築から tool deny までの結合テストがあると回帰検出しやすい。
## 判断
Approve with follow-up — 要件は満たしており設計上の逸脱もないが、result 順序と Pod 統合テストは後続で固める価値がある。
## 確認コマンド
- `cargo test -p pod permission -- --nocapture`