Files
gui-yue 1baf1aca5a Follow up PR #265: refine chapters, diagrams, and add S20 (#283)
* feat: s01-s14 docs quality overhaul — tool pipeline, single-agent, knowledge & resilience

Rewrite code.py and README (zh/en/ja) for s01-s14, each chapter building
incrementally on the previous. Key fixes across chapters:

- s01-s04: agent loop, tool dispatch, permission pipeline, hooks
- s05-s08: todo write, subagent, skill loading, context compact
- s09-s11: memory system, system prompt assembly, error recovery
- s12-s14: task graph, background tasks, cron scheduler

All chapters CC source-verified. Code inherits fixes forward (PROMPT_SECTIONS,
json.dumps cache, real-state context, can_start dep protection, etc.).

* feat: s15-s19 docs quality overhaul — multi-agent platform: teams, protocols, autonomy, worktree, MCP tools

Rewrite code.py and README (zh/en/ja) for s15-s19, the multi-agent platform
chapters. Each chapter inherits all previous fixes and adds one mechanism:

- s15: agent teams (TeamCreate, teammate threads, shared task list)
- s16: team protocols (plan approval, shutdown handshake, consume_inbox)
- s17: autonomous agents (idle polling, auto-claim, consume_lead_inbox)
- s18: worktree isolation (git worktree, bind_task, cwd switching, safety)
- s19: MCP tools (MCPClient, normalize_mcp_name, assemble_tool_pool, no cache)

All appendix source code references verified against CC source. Config priority
corrected: claude.ai < plugin < user < project < local.

* fix: 5 regressions across s05-s19 — glob safety, todo validation, memory extraction, protocol types, dep crash

- s05-s09: glob results now filter with is_relative_to(WORKDIR) (inherited from s02)
- s06-s08: todo_write validates content/status required fields (inherited from s05)
- s09: extract_memories uses pre-compression snapshot instead of compacted messages
- s16: submit_plan docstring clarifies protocol-only (not code-level gate)
- s17-s19: match_response restores type mismatch validation (from s16)
- s17-s19: claim_task deps list handles missing dep files without crashing

* fix: s12 Todo V2 logic reversal, s14/s15 cron range validation, s18/s19 worktree name validation

- s12 README (zh/en/ja): fix Todo V2 direction — interactive defaults to Task,
  non-interactive/SDK defaults to TodoWrite. Fix env var name to
  CLAUDE_CODE_ENABLE_TASKS (not TODO_V2).
- s14/s15: add _validate_cron_field with per-field range checks (minute 0-59,
  hour 0-23, dom 1-31, month 1-12, dow 0-6), step > 0, range lo <= hi.
  Replace old try/except validation that only caught exceptions.
- s18/s19: add validate_worktree_name() to remove_worktree and keep_worktree,
  not just create_worktree.

* fix: align s16-s19 teaching tool consistency

* fix pr265 chapter diagrams

* Add comprehensive s20 harness chapter

* Fix chapter smoke test regressions

* Clarify README tutorial track transition

---------

Co-authored-by: Haoran <bill-billion@outlook.com>
2026-05-20 21:45:38 +08:00

233 lines
12 KiB
Markdown
Raw Permalink 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.
# s03: Permission — 実行前に権限を判断する
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
s01 → s02 → `s03` → [s04](../s04_hooks/) → s05 → ... → s20
> *"ツール実行前に権限を判断"* — 権限パイプラインは、どの操作に承認が必要かを決める。
>
> **Harness レイヤー**: 権限 — ツール実行前に一つのゲートを追加。
---
## 課題
s02 の Agent は 5 つのツールを持つ。file tools は `safe_path` で保護されるが、bash は制限なし。「プロジェクトを掃除して」と頼むと、`rm -rf /` を実行しかねない。
安全性はモデルを信頼することではなく、コードに頼る — ツール実行前に判断を挟む。
---
## ソリューション
![Permission Overview](images/permission-overview.ja.svg)
s02 のループは完全に維持される。唯一の変更は、ツール実行前に `check_permission()` を挿入すること — 各ツール呼び出しは 3 つのゲートを固定順序で通過する:ハード拒否が最優先、次にソフト確認、どちらも一致しなければ許可。
3 つのゲートは 3 つの決定に対応する:
| ゲート | 役割 | 一致時 |
|--------|------|--------|
| 1. 拒否リスト | 常に禁止される操作(`rm -rf /``sudo` | 即座に拒否、実行しない |
| 2. ルールマッチング | コンテキスト依存の操作(作業ディレクトリ外への書き込み、`rm` ファイル) | ゲート 3 へ |
| 3. ユーザー承認 | ゲート 2 が一致した場合、ユーザー確認を待機 | ユーザーが許可または拒否を決定 |
3 つのゲートのどれにも一致しない → 直接実行。日常の操作の大部分はこの経路を通る。
---
## 仕組み
![Permission Pipeline](images/permission-pipeline.ja.svg)
**ゲート 1**:ハード拒否リスト。最初に確認し、一致すればブロックメッセージを返す。(教育デモ:単純な文字列マッチングは信頼できるセキュリティ機構ではない — コマンドの変種やシェル展開で回避される可能性がある。CC のアプローチは付録を参照。)
```python
DENY_LIST = [
"rm -rf /", "sudo", "shutdown", "reboot",
"mkfs", "dd if=", "> /dev/sda",
]
def check_deny_list(command: str) -> str | None:
for pattern in DENY_LIST:
if pattern in command:
return f"Blocked: '{pattern}' is on the deny list"
return None
```
**ゲート 2**:ルールマッチング — 「いつユーザーに聞くべきか」を記述する。各ルールはツールとチェック条件を指定する。
```python
PERMISSION_RULES = [
{
"tools": ["write_file", "edit_file"],
"check": lambda args: not (WORKDIR / args.get("path", "")).resolve().is_relative_to(WORKDIR),
"message": "Writing outside workspace",
},
{
"tools": ["bash"],
"check": lambda args: any(kw in args.get("command", "") for kw in ["rm ", "> /etc/", "chmod 777"]),
"message": "Potentially destructive command",
},
]
def check_rules(tool_name: str, args: dict) -> str | None:
for rule in PERMISSION_RULES:
if tool_name in rule["tools"] and rule["check"](args):
return rule["message"]
return None
```
**ゲート 3**:ルールが一致した後、ユーザー入力を待機。
```python
def ask_user(tool_name: str, args: dict, reason: str) -> str:
print(f"\n{reason}")
print(f" Tool: {tool_name}({args})")
choice = input(" Allow? [y/N] ").strip().lower()
return "allow" if choice in ("y", "yes") else "deny"
```
**3 つのゲートを直列に接続**、ツール実行前に挿入する:
```python
def check_permission(block) -> bool:
# ゲート 1: ハード拒否
if block.name == "bash":
reason = check_deny_list(block.input.get("command", ""))
if reason:
print(f"\n{reason}")
return False
# ゲート 2 + 3: ルールマッチング → ユーザー承認
reason = check_rules(block.name, block.input)
if reason:
decision = ask_user(block.name, block.input, reason)
if decision == "deny":
return False
return True
# agent_loop で — s02 のループに 1 行追加するだけ:
for block in response.content:
if block.type == "tool_use":
if not check_permission(block): # ← 新規
results.append({... "content": "Permission denied."})
continue
output = TOOL_HANDLERS[block.name](**block.input) # s02 既存
results.append(...)
```
---
## s02 からの変更点
| コンポーネント | 変更前 (s02) | 変更後 (s03) |
|---------------|-------------|-------------|
| セキュリティモデル | なし(モデルを信頼) | 3 ゲート権限パイプライン |
| 新規関数 | — | check_deny_list, check_rules, ask_user, check_permission |
| ループ | すべてのツールを直接実行 | 実行前に check_permission() を挿入 |
---
## 試してみよう
```sh
cd learn-claude-code
python s03_permission/code.py
```
以下のプロンプトを試してみよう:
1. `Create a file called test.txt in the current directory`(そのまま通過するはず)
2. `Delete all temporary files in /tmp`bash + rm でゲート 2 が発動)
3. `What files are in the current directory?`(読み取り専用、すべて通過)
4. `Try to write a file to /etc/something`(作業ディレクトリ外への書き込みでゲート 2 が発動)
観察のポイント:どの操作がそのまま通過するか? どれに確認が必要か? どれが即座に拒否されるか?
---
## 次へ
権限チェックは実装された — しかし、毎回ループ内に `check_permission()` をハードコードしている。ツール実行の前後にログを追加したい場合は? 特定の操作後に自動的に git commit をトリガーしたい場合は? このような拡張ロジックがループ内に散らばると、ループはすぐに膨張する。
→ s04 Hooksループにフックを追加する。拡張ロジックはフックにぶら下げ、ループはクリーンに保つ。
<details>
<summary>CC ソースコードを深掘り</summary>
> 以下は CC ソースコード `types/permissions.ts`、`utils/permissions/permissions.ts`、`toolExecution.ts`、`utils/permissions/yoloClassifier.ts`、`tools/AgentTool/forkSubagent.ts` の検証に基づく。
### 一、PermissionResult3 種ではなく、4 種
教育版の 3 つのゲートdeny → ask → allowは CC と完全には対応しない。CC の `PermissionResult` には 4 つの behavior がある(`types/permissions.ts:241-266`
| behavior | 意味 | 教育版の対応 |
|----------|------|-------------|
| `allow` | 直接許可 | ゲート 3 通過 |
| `deny` | 直接拒否 | ゲート 1 一致 |
| `ask` | ユーザーにダイアログを表示 | ゲート 2 一致 |
| `passthrough` | ツールが意見を表明せず、汎用パイプラインに委ねる | 教育版にはなし |
### 二、本番環境の検証段階
CC のツール呼び出しは 3 つのゲートを通るのではなく、`checkPermissionsAndCallTool()``toolExecution.ts:599-1745`、hooks、`hasPermissionsToUseToolInner()``utils/permissions/permissions.ts:1158-1310`、classifier ロジックに分散する複数の段階を経る:
1. **Zod schema 検証**`toolExecution.ts:614-680`)— パラメータの型チェック
2. **validateInput()**`toolExecution.ts:682-733`)— ツールレベルの意味的検証
3. **backfillObservableInput()**`toolExecution.ts:784`)— レガシーフィールドの補完
4. **PreToolUse hooks**`toolExecution.ts:800-862`)— フックが allow/deny/ask を返す
5. **resolveHookPermissionDecision()**`toolExecution.ts:921-931`)— フック + パイプラインの決定を調整
6. **hasPermissionsToUseToolInner()**`permissions.ts:1158-1310`)— 多層ルールチェック:
- ツール全体が deny rule で無効 → `deny`
- ツール全体が ask rule でマーク → `ask`
- `tool.checkPermissions()` ツール自身の判断
- ツール自身が deny を返す → `deny`
- `requiresUserInteraction()``ask`
- コンテンツ関連の ask ルール → `ask`(バイパス不可)
- セキュリティチェック違反 → `ask`(バイパス不可)
- bypassPermissions モード → `allow`
- ツール全体が allow rule で許可 → `allow`
- passthrough → `ask` に変換
### 三、拒否リスト1 つのファイルではなく、8 つのソース
CC には単一の deny list はない。権限ルールは 8 つのソースから来る(`types/permissions.ts:54-62`
| ソース | 設定場所 |
|--------|---------|
| `userSettings` | `~/.claude/settings.json` |
| `projectSettings` | `.claude/settings.json` |
| `localSettings` | `settings.local.json` |
| `flagSettings` | フィーチャーフラグ |
| `policySettings` | 企業管理ポリシー |
| `cliArg` | `--allowedTools` / `--deniedTools` |
| `command` | インラインコマンド |
| `session` | セッション内一時承認 |
各ルールの形式:`{ toolName: "Bash", ruleBehavior: "deny", ruleContent: "npm publish:*" }`。複数ソースのルールは統合され、高優先度ソースが低優先度を上書きする低→高user < project < local < flag < policy、さらに cliArg、command、session
### 四、isDestructive() とは
CC では `isDestructive``Tool.ts:405-406`)は**純粋に UI 表示用** — ツール一覧に `[destructive]` ラベルを表示するだけ。権限決定には参加しない。デフォルトではすべてのツールが `false` を返す。ExitWorktreeremove 時)と MCP ツール(`annotations.destructiveHint` に依存)のみがオーバーライドする。
### 五、YoloClassifier自動承認
CC の auto モードでは、毎回ダイアログを表示するわけではない。`classifyYoloAction``utils/permissions/yoloClassifier.ts:1012`)はツール呼び出し + 会話コンテキストを分類器 LLM に送って安全性を判断する。まず acceptEdits モードのシミュレーションを試み(`permissions.ts:620-656`、acceptEdits が許可すれば → 自動承認)、次にセーフツールホワイトリストを確認し(`permissions.ts:658-686`)、最後に分類器を呼び出す。分類器が連続して拒否しすぎた場合 → 手動承認にフォールバック。
### 六、権限バブリング
サブ AgentAgentTool 経由でフォークされたもの)の `permissionMode``'bubble'` に設定される(`forkSubagent.ts:50`)。これは権限ダイアログが**親 Agent のターミナルにバブルアップ**することを意味する。サブ Agent で黙って拒否されるのではない。Bash 分類器はこの過程で引き続き実行され — 権限ダイアログを表示しつつ、バックグラウンドで自動承認可能か判断する。
### 教育版の単純化は意図的
- 多段階パイプライン → 3 ゲート:理解のハードルが大幅に下がる
- 8 ルールソース → 1 つのローカル DENY_LIST概念量を制御可能
- isDestructive → 省略(教育版には UI レイヤーがなく、CC でも権限決定には参加しない)
- YoloClassifier → 省略(追加の LLM 呼び出しとテレメトリに依存)
- 権限バブリング → 省略s15 でマルチ Agent を扱う)
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->