yoi/tickets/permission-extension-point.md

106 lines
5.5 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.

# パーミッション: パターンベースのツール実行制御
## 背景
現状の `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>` として利用可能