# 機能設計書 51-権限ルールセット

## 概要

本ドキュメントは、OpenCodeの権限ルールセット機能について、その設計仕様を記述するものである。

### 本機能の処理概要

権限ルールセット機能は、AIエージェントによるツール実行（ファイル編集、Bashコマンド実行など）の許可・拒否・確認をパターンマッチングベースで制御する機能である。

**業務上の目的・背景**：AIエージェントが自動的にコードを編集したり、シェルコマンドを実行する際に、意図しない操作や危険な操作を防止する必要がある。開発者は、プロジェクトやファイルパターンごとに、どの操作を許可するか、どの操作について確認を求めるかを細かく制御できる。これにより、セキュリティと利便性のバランスを取りながら、安全な開発環境を維持できる。

**機能の利用シーン**：
- セッション中にAIエージェントがファイルを編集しようとする際の許可確認
- 外部ディレクトリへのアクセスを制限する場合
- 特定のパターンに一致するファイルへの書き込みを自動許可する場合
- Bashコマンド実行前の確認表示

**主要な処理内容**：
1. 設定ファイル（opencode.json/opencode.jsonc）からルールセットを読み込み、内部形式に変換
2. ツール実行時に、パーミッション名とパターンに基づいてルールを評価
3. 評価結果（allow/deny/ask）に基づき、即時実行・拒否・ユーザー確認を実行
4. ユーザーからの応答（once/always/reject）を処理し、必要に応じてルールを永続化
5. 複数の保留中パーミッションリクエストをセッション単位で管理

**関連システム・外部連携**：設定ファイル（opencode.json/opencode.jsonc）からの読み込み、プロジェクト固有データベース（SQLite）への永続化

**権限による制御**：パーミッションルールはグローバル設定・プロジェクト設定・ランタイムで承認されたルールの3層でマージされ、後から適用されたルールが優先される

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | ディレクトリレイアウト | 補助機能 | 権限応答処理（onPermissionRespond） |
| 26 | セッション | 補助機能 | PermissionPromptによる権限確認表示 |

## 機能種別

バリデーション / 制御処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| permission | string | Yes | パーミッション種別（edit, bash, read等） | 空文字不可 |
| pattern | string | Yes | 対象パターン（ファイルパス、コマンド等） | 空文字不可 |
| action | "allow" \| "deny" \| "ask" | Yes | ルールのアクション | 3値のいずれか |
| sessionID | string | Yes | セッション識別子 | Identifier形式 |
| patterns | string[] | Yes | 評価対象パターンの配列 | 空配列可 |
| metadata | Record<string, any> | No | 追加メタデータ | - |
| always | string[] | No | "always"応答時に永続化するパターン | - |

### 入力データソース

- 設定ファイル（opencode.json/opencode.jsonc）の`permission`セクション
- ツール実行時のランタイムパラメータ
- ユーザーからのUI応答

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| action | "allow" \| "deny" \| "ask" | 評価結果のアクション |
| permission | string | 評価対象のパーミッション種別 |
| pattern | string | マッチしたパターン |

### 出力先

- ツール実行制御（許可/拒否/ユーザー確認待ち）
- データベース（セッション承認済みルールの永続化）
- イベントバス（permission.asked / permission.replied）

## 処理フロー

### 処理シーケンス

```
1. ルールセット構築
   └─ Config.Permissionから内部Ruleset形式に変換、パターン展開（~/を$HOMEに展開）

2. パーミッション評価リクエスト受信
   └─ PermissionNext.ask()で評価開始

3. 各パターンについてルール評価
   └─ evaluate()でルールセットを逆順検索、最後にマッチしたルールを適用

4. 評価結果に応じた分岐
   ├─ allow: そのまま処理継続
   ├─ deny: DeniedErrorをスロー、処理中断
   └─ ask: ユーザーに確認を求め、Promise待機

5. ユーザー応答の処理
   ├─ once: 今回のみ許可、Promise解決
   ├─ always: パターンを承認済みリストに追加、同一セッションの他の保留リクエストも自動解決
   └─ reject: RejectedError/CorrectedErrorをスロー、同一セッションの他の保留リクエストも拒否
```

### フローチャート

```mermaid
flowchart TD
    A[ツール実行リクエスト] --> B[PermissionNext.ask呼び出し]
    B --> C{各パターンを評価}
    C --> D[evaluate関数でルールマッチング]
    D --> E{マッチしたルールのアクション}
    E -->|allow| F[次のパターンへ]
    E -->|deny| G[DeniedErrorスロー]
    E -->|ask| H[ユーザーに確認リクエスト]
    F --> I{全パターン評価完了?}
    I -->|Yes| J[ツール実行許可]
    I -->|No| C
    H --> K[permission.askedイベント発行]
    K --> L{ユーザー応答待ち}
    L -->|once| M[今回のみ許可]
    L -->|always| N[ルール追加・他の保留も解決]
    L -->|reject| O[RejectedError/CorrectedErrorスロー]
    M --> J
    N --> J
    G --> P[処理中断]
    O --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ルール優先順序 | 後から定義されたルールが優先される | 常時 |
| BR-02 | パターン展開 | ~/と$HOME/はホームディレクトリに展開 | パターン文字列解析時 |
| BR-03 | ワイルドカードマッチング | *で任意の文字列にマッチ | パターンマッチング時 |
| BR-04 | セッション連動拒否 | reject時は同一セッションの全保留リクエストを拒否 | reject応答時 |
| BR-05 | 編集ツール統合 | edit/write/patch/multieditは"edit"パーミッションとして統合評価 | disabled()評価時 |

### 計算ロジック

- ルール評価: `ruleset.findLast((rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern))`
- マッチしない場合のデフォルト: `{ action: "ask", permission, pattern: "*" }`

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 状態読み込み | permission | SELECT | プロジェクト別の承認済みルール取得 |
| ルール永続化 | permission | INSERT/UPDATE | always応答時のルール保存（現在は無効化） |

### テーブル別操作詳細

#### PermissionTable

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | project_id, data | project_id = Instance.project.id | 初期化時に取得 |
| INSERT/UPDATE | data | 承認済みRuleset配列 | コメントアウト中（UI実装待ち） |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RejectedError | ユーザーがメッセージなしで拒否 | ツール実行を中断 |
| - | CorrectedError | ユーザーがメッセージ付きで拒否 | フィードバックを含めて中断 |
| - | DeniedError | 設定ルールで明示的にdeny | ルール情報を含めてスロー |

### リトライ仕様

リトライは行わない。ユーザー応答待ちはPromiseで管理され、タイムアウトなし。

## トランザクション仕様

データベースへの永続化は単一INSERT/UPDATE操作で行われ、トランザクション管理は不要。

## パフォーマンス要件

- ルール評価: O(n) where n = ルール数
- 通常のルール数（数十件）では即座に完了

## セキュリティ考慮事項

- パターン展開により$HOMEや~がホームディレクトリに解決されるため、意図しないディレクトリへのアクセスを防止
- deny設定されたパスは即座に拒否され、ユーザー確認なしで実行されない
- セッション連動拒否により、一度拒否したセッションでの追加操作も自動拒否

## 備考

- ルールの永続化機能（データベース保存）は実装済みだが、UI実装待ちのためコメントアウト中
- 編集系ツール（edit/write/patch/multiedit）は統一的に"edit"パーミッションとして扱われる

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、権限ルールの基本的なデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | next.ts | `packages/opencode/src/permission/next.ts` | Zodスキーマ定義（Action, Rule, Ruleset, Request, Reply） |

**読解のコツ**:
- **25-28行目**: `Action`スキーマで"allow"/"deny"/"ask"の3値を定義
- **30-44行目**: `Rule`と`Ruleset`の構造を確認。permission + pattern + actionの組み合わせ
- **68-86行目**: `Request`スキーマでパーミッションリクエストの全体像を把握

#### Step 2: エントリーポイントを理解する

権限チェックの開始点となる関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | next.ts | `packages/opencode/src/permission/next.ts` | ask()関数とevaluate()関数 |

**主要処理フロー**:
1. **131-161行目**: `ask`関数 - パーミッションチェックのエントリーポイント。各パターンについてevaluateを呼び出し
2. **236-243行目**: `evaluate`関数 - ルールセットをマージし、findLastでマッチするルールを検索
3. **141-142行目**: deny時はDeniedErrorをスロー
4. **143-157行目**: ask時はPromiseを返し、ユーザー応答を待機

#### Step 3: 設定からの読み込みを理解する

設定ファイルからルールセットへの変換処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | next.ts | `packages/opencode/src/permission/next.ts` | fromConfig()関数とexpand()関数 |

**主要処理フロー**:
- **17-23行目**: `expand`関数 - ~/と$HOME/をホームディレクトリに展開
- **46-62行目**: `fromConfig`関数 - Config.PermissionをRuleset形式に変換

#### Step 4: 応答処理を理解する

ユーザーからの応答（once/always/reject）の処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | next.ts | `packages/opencode/src/permission/next.ts` | reply()関数 |

**主要処理フロー**:
- **163-234行目**: `reply`関数 - ユーザー応答の処理
- **179-194行目**: reject時の処理 - 同一セッションの他の保留リクエストも拒否
- **200-225行目**: always時の処理 - パターンを承認リストに追加、他の保留も自動解決

#### Step 5: APIエンドポイントを理解する

REST APIでの権限操作を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | permission.ts | `packages/opencode/src/server/routes/permission.ts` | POST /:requestID/reply と GET / |

**主要処理フロー**:
- **10-45行目**: POST /:requestID/reply - パーミッションへの応答
- **46-67行目**: GET / - 保留中パーミッション一覧取得

### プログラム呼び出し階層図

```
ツール実行（Tool.execute等）
    │
    └─ PermissionNext.ask()
           │
           ├─ evaluate() ×N（パターン数分）
           │      └─ Wildcard.match() ×2（permission, pattern）
           │
           ├─ [deny] → DeniedError
           │
           └─ [ask] → Bus.publish(Event.Asked)
                         │
                         └─ UI表示 → ユーザー応答
                                        │
                                        └─ POST /permission/:id/reply
                                               │
                                               └─ PermissionNext.reply()
                                                      │
                                                      ├─ [once] → resolve()
                                                      ├─ [always] → 承認追加 → resolve()
                                                      └─ [reject] → RejectedError/CorrectedError
```

### データフロー図

```
[入力]                   [処理]                         [出力]

opencode.json          PermissionNext.fromConfig()
  permission ─────────────────────────────────────────▶ Ruleset[]
     │
     │                   PermissionNext.evaluate()
     │  ───────────────────────────────────────────────▶ Rule (action)
     │
     ▼                   PermissionNext.ask()
ツール実行要求 ────────────────────────────────────────▶ Promise<void> or Error
     │
     │                   PermissionNext.reply()
     │  ───────────────────────────────────────────────▶ approved[] (state)
     ▼
ユーザー応答 ─────────────────────────────────────────▶ イベント発行
                                                        (permission.replied)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| next.ts | `packages/opencode/src/permission/next.ts` | ソース | 権限ルールセットのコアロジック |
| permission.ts | `packages/opencode/src/server/routes/permission.ts` | ソース | REST APIエンドポイント |
| permission.tsx | `packages/app/src/context/permission.tsx` | ソース | フロントエンド権限コンテキスト |
| session.sql.ts | `packages/opencode/src/session/session.sql.ts` | ソース | PermissionTableの定義 |
| config.ts | `packages/opencode/src/config/config.ts` | ソース | 設定ファイルからの読み込み |
| wildcard.ts | `packages/opencode/src/util/wildcard.ts` | ソース | ワイルドカードマッチング |
