# 通知設計書 31-client/registerCapability

## 概要

本ドキュメントは、serve-d LSPサーバーがクライアントに対して送信する `client/registerCapability` リクエストの設計を記述する。このリクエストは、サーバーが実行時に動的にケーパビリティ（機能）をクライアントへ登録するためのLSP標準リクエストである。

### 本通知の処理概要

本リクエストは、サーバーからクライアントに対して「新しいケーパビリティを動的に登録する」ことを要求するLSP標準プロトコルのリクエストメッセージである。LSPでは、サーバーの機能（ケーパビリティ）の登録方法として、初期化時の静的登録と、実行時の動的登録の2つがある。`client/registerCapability` は後者の動的登録を実現するメカニズムである。

**業務上の目的・背景**：LSPサーバーは初期化時点では利用可能なワークスペース情報やプロジェクト構成が不明な場合がある。動的ケーパビリティ登録により、サーバーは必要なタイミングで適切なケーパビリティをクライアントに登録できる。serve-dでは、具体的にProfileGCログファイル（`**/profilegc.log`）の監視を動的に登録するために使用されている。クライアントが `dynamicRegistration` をサポートしている場合にのみ、このリクエストを送信する。

**通知の送信タイミング**：サーバーのコンポーネント登録完了後（`onRegisteredComponents` イベント発火時）に送信される。具体的には、`extension.d` での初期化処理が完了し、`emitExtensionEvent!onRegisteredComponents` が呼ばれた際に、`profilegc.d` の `setupProfileGCWatchers()` 関数が実行され、クライアントのケーパビリティを確認した上で送信される。

**通知の受信者**：LSPクライアント（エディタ側、例えばVSCode）が受信者である。クライアントはこのリクエストを受け取ると、指定されたケーパビリティを動的に有効化し、対応するイベントの監視を開始する。

**通知内容の概要**：`RegistrationParams` 構造体として送信される。内部には `Registration` の配列が含まれ、各 `Registration` は一意のID、登録対象のメソッド名（例: `workspace/didChangeWatchedFiles`）、およびそのメソッド固有のオプション（例: `DidChangeWatchedFilesRegistrationOptions` でファイルパターンを指定）を持つ。

**期待されるアクション**：クライアントは、登録されたケーパビリティに従って対応する機能を有効化する。例えば `workspace/didChangeWatchedFiles` が登録された場合、クライアントは指定されたglobパターン（`**/profilegc.log`）に一致するファイルの変更を監視し、変更があればサーバーに通知を送信するようになる。

## 通知種別

LSP JSON-RPCリクエスト（サーバー -> クライアント）

本メッセージはLSPプロトコル上「リクエスト」（応答を伴うメッセージ）として分類される。通知（応答不要）とは異なり、クライアントからの応答を待つ。

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（registerCapabilitySync）または非同期（registerCapabilityAsync） |
| 優先度 | 高（サーバー初期化直後に必要なケーパビリティを登録するため） |
| リトライ | なし（同期版ではタイムアウト指定可能、エラー時はログ出力のみ） |

### 送信先決定ロジック

送信先はLSPクライアント（接続中のエディタ）に固定である。クライアントの `capabilities.workspace.didChangeWatchedFiles.dynamicRegistration` が `true` の場合にのみ送信される。

## 通知テンプレート

### JSON-RPCメッセージ形式

| 項目 | 内容 |
|-----|------|
| プロトコル | JSON-RPC 2.0 |
| メソッド名 | `client/registerCapability` |
| パラメータ型 | `RegistrationParams` |

### 本文テンプレート

```json
{
  "jsonrpc": "2.0",
  "id": <request_id>,
  "method": "client/registerCapability",
  "params": {
    "registrations": [
      {
        "id": "<registration_id>",
        "method": "<target_method>",
        "registerOptions": <method_specific_options>
      }
    ]
  }
}
```

### 実際の送信例（ProfileGCファイル監視登録）

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "client/registerCapability",
  "params": {
    "registrations": [
      {
        "id": "profilegc.watchfiles",
        "method": "workspace/didChangeWatchedFiles",
        "registerOptions": {
          "watchers": [
            {
              "globPattern": "**/profilegc.log"
            }
          ]
        }
      }
    ]
  }
}
```

### 添付ファイル

該当なし（JSON-RPCプロトコルであるため添付ファイルの概念はない）

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| id（リクエストID） | JSON-RPCリクエストの一意識別子 | `RequestToken.next()` で自動採番 | Yes |
| registrations[].id | ケーパビリティ登録の一意識別子 | 呼び出し元が指定（例: `"profilegc.watchfiles"`） | Yes |
| registrations[].method | 登録対象のLSPメソッド名 | 呼び出し元が指定（例: `"workspace/didChangeWatchedFiles"`） | Yes |
| registrations[].registerOptions | メソッド固有の登録オプション | 呼び出し元が指定（型はテンプレートパラメータ `T`） | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| サーバー初期化完了 | `onRegisteredComponents` イベント発火 | `capabilities.workspace.didChangeWatchedFiles.dynamicRegistration` が `true` | ProfileGCログファイルの監視を動的に登録するために `setupProfileGCWatchers()` から呼び出される |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| クライアントが動的登録を未サポート | `capabilities.workspace.didChangeWatchedFiles.dynamicRegistration` が `false` または未設定の場合は送信しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[サーバー初期化完了] --> B[onRegisteredComponentsイベント発火]
    B --> C[setupProfileGCWatchers呼び出し]
    C --> D{dynamicRegistration対応?}
    D -->|Yes| E[registerCapabilitySync呼び出し]
    D -->|No| F[スキップ]
    E --> G[registerCapabilityAsync実行]
    G --> H["sendMethod(client/registerCapability)"]
    H --> I[クライアントへJSONRPCリクエスト送信]
    I --> J[awaitResponseで応答待ち]
    J --> K{応答結果}
    K -->|成功| L[trueを返却]
    K -->|エラー| M[エラーログ出力]
    M --> N[falseを返却]
    N --> O["error(Failed to dynamically register GC watchers)"]
    L --> P[終了]
    O --> P
    F --> P
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（LSPプロトコルではデータベースを使用しない）

### 補足

本リクエストで参照されるデータは、クライアントから初期化時に送信された `ClientCapabilities` 構造体のインメモリデータであり、データベースではなくグローバル変数 `capabilities`（`types.d` 354行目）に格納されている。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| クライアント応答エラー | クライアントが `client/registerCapability` リクエストに対してエラーを返した場合 | `registerCapabilitySync` 内で `trace` レベルでエラーログを出力し、`false` を返却。呼び出し元（`setupProfileGCWatchers`）で `error("Failed to dynamically register GC watchers")` を出力 |
| タイムアウト | `registerCapabilitySync` の `timeout` パラメータで指定された時間内にクライアントから応答がない場合 | `awaitResponse` 内で例外がスローされる（デフォルトは `Duration.max` で無期限待ち） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 制限 | なし（サーバー初期化時に1回のみ送信されるため） |

### 配信時間帯

制限なし。サーバー初期化のタイミングに依存する。

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

- 本リクエストはローカルのJSON-RPC接続（stdin/stdout）を通じて送信されるため、ネットワーク経由の攻撃リスクは低い
- 登録するglobパターン（`**/profilegc.log`）は固定値であり、外部入力による操作の余地はない
- 動的登録により、クライアントが予期しないファイル監視を開始する可能性があるが、LSPプロトコルの仕様に準拠した正当な操作である

## 備考

- `registerCapability` はLSP仕様上「リクエスト」（応答を伴う双方向通信）として定義されているが、通知一覧では「LSP標準リクエスト（S->C）」カテゴリに分類されている
- serve-dのコードベースでは、`registerCapabilityAsync`（非同期版・応答を待たない）と `registerCapabilitySync`（同期版・応答を待つ）の2つのラッパーが提供されているが、実際に使用されているのは `registerCapabilitySync` のみ（`profilegc.d` 139行目）
- 旧APIの `registerCapability`（`registerCapabilityAsync` のエイリアス）は `deprecated` として非推奨マークされており、リクエストIDが破棄されてログエラーの原因となるため使用すべきでないとされている
- `registerCapabilityAsync` 内のテンプレート構造体 `RegistrationT` / `RegistrationParamsT` は、型パラメータ `T` をジェネリックに扱うためのもので、`static assert` によりフィールド名がLSP仕様の `Registration` / `RegistrationParams` と一致することを保証している

---

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

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

### 推奨読解順序

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

まず、`client/registerCapability` で送受信されるデータ構造と、動的登録の判断に使われるクライアントケーパビリティの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | protocol.d | `protocol/source/served/lsp/protocol.d` | `Registration` 構造体（2202-2207行目）: id, method, registerOptions の3フィールド |
| 1-2 | protocol.d | `protocol/source/served/lsp/protocol.d` | `RegistrationParams` 構造体（2210-2214行目）: `@allowedMethods("client/registerCapability")` アノテーション付き |
| 1-3 | protocol.d | `protocol/source/served/lsp/protocol.d` | `FileSystemWatcher` 構造体（2344-2348行目）: globPattern と kind |
| 1-4 | protocol.d | `protocol/source/served/lsp/protocol.d` | `DidChangeWatchedFilesRegistrationOptions` 構造体（2374-2377行目）: watchers 配列 |
| 1-5 | protocol.d | `protocol/source/served/lsp/protocol.d` | `DidChangeWatchedFilesClientCapabilities` 構造体（2368-2371行目）: dynamicRegistration フラグ |

**読解のコツ**: D言語の `@serdeFallbackStruct` は mir-ion シリアライゼーションライブラリのアノテーションで、未知のフィールドを無視するJSON逆シリアライズを有効にする。`@allowedMethods` はメソッドとパラメータ型の対応を定義するカスタムUDAである。

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

動的ケーパビリティ登録の起点は `setupProfileGCWatchers()` 関数である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | profilegc.d | `source/served/workers/profilegc.d` | `setupProfileGCWatchers()` 関数（131-150行目）: `@onRegisteredComponents` UDA付き |

**主要処理フロー**:
1. **131行目**: `@onRegisteredComponents` UDAにより、コンポーネント登録完了時に自動呼び出しされる
2. **134-137行目**: `capabilities.workspace.didChangeWatchedFiles.dynamicRegistration` を確認
3. **139-147行目**: `rpc.registerCapabilitySync()` を呼び出し、`"profilegc.watchfiles"` IDで `workspace/didChangeWatchedFiles` の動的登録を要求
4. **148行目**: 失敗時にエラーメッセージを出力

#### Step 3: イベント発火元を理解する

`onRegisteredComponents` イベントがどこで発火されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | types.d | `source/served/types.d` | `onRegisteredComponents` の定義（610-613行目）: コンポーネント登録完了時のイベントUDA |
| 3-2 | extension.d | `source/served/extension.d` | `emitExtensionEvent!onRegisteredComponents` 呼び出し（475行目）: サーバー初期化処理の一環 |

**主要処理フロー**:
- **475行目**: `cast(void)emitExtensionEvent!onRegisteredComponents;` でイベントを発火。これにより `@onRegisteredComponents` UDAが付いた全関数が呼び出される

#### Step 4: RPCインフラ層を理解する

実際のJSON-RPCメッセージ送信を担うインフラ層の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | `registerCapabilityAsync()` メソッド（229-258行目）: テンプレート構造体を構築し `sendMethod` を呼び出す |
| 4-2 | jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | `registerCapabilitySync()` メソッド（261-270行目）: `registerCapabilityAsync` + `awaitResponse` のラッパー |
| 4-3 | jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | `sendMethodRaw()` メソッド（310-324行目）: JSON-RPCリクエストのパケット構築と送信 |
| 4-4 | jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | `awaitResponse()` メソッド（444-448行目）: Fiberのyieldを使った応答待ち |
| 4-5 | jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | `sendRawPacket()` メソッド（145-169行目）: Content-Lengthヘッダ付きのRaw送信 |

**主要処理フロー**:
- **229-258行目**: `registerCapabilityAsync` はテンプレート型 `T` でオプション型を受け取り、`RegistrationT` / `RegistrationParamsT` のローカル構造体を定義。`static assert` でフィールド名の一致を検証後、`sendMethod("client/registerCapability", params)` を呼び出す
- **261-270行目**: `registerCapabilitySync` は `registerCapabilityAsync` を呼び出した後、`awaitResponse` で応答を待ち、エラーがあれば `trace` ログに出力して `false` を返す
- **310-324行目**: `sendMethodRaw` は `RequestToken.next()` で一意のIDを生成し、JSON-RPCフォーマットのパケットを構築して `sendRawPacket` で送信する

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

```
extension.d: emitExtensionEvent!onRegisteredComponents (475行目)
    |
    +-- profilegc.d: setupProfileGCWatchers() (131-150行目)
            |
            +-- [条件] capabilities.workspace.didChangeWatchedFiles.dynamicRegistration == true
            |
            +-- jsonrpc.d: RPCProcessor.registerCapabilitySync() (261-270行目)
                    |
                    +-- jsonrpc.d: RPCProcessor.registerCapabilityAsync() (229-258行目)
                    |       |
                    |       +-- [テンプレート構造体 RegistrationT/RegistrationParamsT を構築]
                    |       +-- [static assert でフィールド名検証]
                    |       +-- jsonrpc.d: RPCProcessor.sendMethod() (292-295行目)
                    |               |
                    |               +-- jsonrpc.d: RPCProcessor.sendMethodRaw() (310-324行目)
                    |                       |
                    |                       +-- RequestToken.next() [リクエストID生成]
                    |                       +-- jsonrpc.d: RPCProcessor.sendRawPacket() (145-169行目)
                    |                               |
                    |                               +-- [Content-Length ヘッダ付与]
                    |                               +-- [stdout へ書き込み]
                    |
                    +-- jsonrpc.d: RPCProcessor.awaitResponse() (444-448行目)
                            |
                            +-- jsonrpc.d: RPCProcessor.prepareWait() (389-407行目)
                            +-- jsonrpc.d: RPCProcessor.resolveWait() (411-427行目)
                                    |
                                    +-- Fiber.yield() [応答到着までyield]
```

### データフロー図

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

ClientCapabilities             setupProfileGCWatchers()              JSON-RPC Request
(初期化時にクライアント         (dynamicRegistration確認)             (client/registerCapability)
 から受信済み)                        |                                    |
       |                             v                                    v
       +------->  capabilities    registerCapabilitySync()         クライアント(stdout)
                  .workspace         |                              {
                  .didChange...      v                                "id": <auto>,
                  .dynamic...     registerCapabilityAsync()           "method": "client/registerCapability",
                                     |                                "params": {
                                     v                                  "registrations": [{
                                  sendMethod()                            "id": "profilegc.watchfiles",
                                     |                                    "method": "workspace/didChangeWatchedFiles",
                                     v                                    "registerOptions": {
                                  sendRawPacket()                           "watchers": [{"globPattern": "**/profilegc.log"}]
                                     |                                    }
                                     v                                  }]
                                  stdout (Content-Length + JSON)       }
                                                                    }
                                                                         |
                                                                         v
                                                                    クライアント応答
                                                                    (成功/エラー)
                                                                         |
                                                                         v
                                                                    awaitResponse()で受信
                                                                    (true/false を返却)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| jsonrpc.d | `lsp/source/served/lsp/jsonrpc.d` | ソース | RPCProcessor本体。`registerCapabilityAsync`/`registerCapabilitySync`/`sendMethod`/`awaitResponse` 等のRPC通信インフラを提供 |
| protocol.d | `protocol/source/served/lsp/protocol.d` | ソース | LSPプロトコルのデータ構造定義。`Registration`, `RegistrationParams`, `FileSystemWatcher`, `DidChangeWatchedFilesRegistrationOptions` 等 |
| profilegc.d | `source/served/workers/profilegc.d` | ソース | ProfileGCワーカー。`setupProfileGCWatchers()` で `client/registerCapability` の唯一の呼び出し元 |
| extension.d | `source/served/extension.d` | ソース | サーバー初期化処理。`onRegisteredComponents` イベントの発火元 |
| types.d | `source/served/types.d` | ソース | グローバル変数 `capabilities` の定義と `onRegisteredComponents` UDAの定義 |
