# 機能設計書 56-パッド変更同期

## 概要

本ドキュメントは、Etherpadにおけるパッド変更同期機能の設計仕様を記載する。パッドへの変更をすべての接続クライアントにリアルタイムで同期する機能について詳細に解説する。

### 本機能の処理概要

パッド変更同期機能は、あるクライアントで行われたパッドの編集内容を、同じパッドに接続している他のすべてのクライアントにリアルタイムでブロードキャストする機能を提供する。これにより複数ユーザーが同時に同じドキュメントを編集できる。

**業務上の目的・背景**：
リアルタイム共同編集はEtherpadの核心機能である。複数のユーザーが地理的に離れた場所から同時に同じドキュメントを編集し、互いの変更を即座に確認できることで、効率的なコラボレーションが可能になる。会議中のメモ取り、ブレインストーミング、ドキュメントの共同作成など、多様なユースケースを支える。

**機能の利用シーン**：
- 複数人での会議メモのリアルタイム共同編集
- チームでのドキュメント共同作成
- ブレインストーミングセッションのリアルタイム記録
- オンライン授業でのノート共有

**主要な処理内容**：
1. クライアントからの変更通知（USER_CHANGES）を受信
2. 変更セット（Changeset）を現在のリビジョンにリベース
3. 変更をパッドに適用してリビジョンを作成
4. 変更元クライアントにACCEPT_COMMITを送信
5. 他の全クライアントにNEW_CHANGESをブロードキャスト
6. 各クライアントのセッション情報（rev）を更新

**関連システム・外部連携**：
- Socket.IO: WebSocket経由のリアルタイム通信
- ueberDB2: 変更の永続化

**権限による制御**：
読み取り専用ユーザーには変更同期を許可しない。書き込み権限のあるユーザーのみが変更を送信可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | パッド編集画面 | 主機能 | テキスト編集内容をリアルタイムで全クライアントに同期 |

## 機能種別

リアルタイム通信 / データ同期

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | string | Yes | メッセージタイプ（COLLABROOM） | 固定値 |
| data.type | string | Yes | データタイプ（USER_CHANGES） | 固定値 |
| data.baseRev | number | Yes | 変更の基準リビジョン番号 | 数値 |
| data.changeset | string | Yes | 変更セット文字列 | Changeset形式 |
| data.apool | object | Yes | 属性プール（Jsonable形式） | AttributePool形式 |

### 入力データソース

- Socket.IOメッセージ（クライアントからのWebSocket通信）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ACCEPT_COMMIT.newRev | number | 適用後の新リビジョン番号（変更元クライアントへ） |
| NEW_CHANGES.newRev | number | 新リビジョン番号（他クライアントへ） |
| NEW_CHANGES.changeset | string | 変更セット文字列 |
| NEW_CHANGES.apool | object | 属性プール |
| NEW_CHANGES.author | string | 変更を行った著者ID |
| NEW_CHANGES.currentTime | number | 変更時刻 |
| NEW_CHANGES.timeDelta | number | 前回からの経過時間 |

### 出力先

- Socket.IO経由で各クライアントに送信
- パッドデータベース（リビジョン保存）

## 処理フロー

### 処理シーケンス

```
1. USER_CHANGESメッセージ受信
   └─ padChannelsキューに追加
2. キュー処理開始
   └─ handleUserChanges実行
3. セッション検証
   └─ 接続中かつ権限あり確認
4. 変更セット検証
   └─ checkRepで構文・形式検証
5. 著者属性検証
   └─ 変更内の著者属性がセッション著者と一致確認
6. リベース処理
   └─ baseRevから現在headまでの変更をfollow
7. パッドに変更適用
   └─ pad.appendRevision
8. マーカー補正
   └─ _correctMarkersInPad
9. 末尾改行保証
   └─ 必要なら改行追加
10. 変更元クライアントに確認送信
    └─ ACCEPT_COMMITメッセージ
11. 他クライアントへブロードキャスト
    └─ updatePadClientsでNEW_CHANGES送信
```

### フローチャート

```mermaid
flowchart TD
    A[USER_CHANGESメッセージ受信] --> B[padChannelsキューに追加]
    B --> C[handleUserChanges開始]
    C --> D{セッション有効?}
    D -->|No| Z1[エラー: client disconnected]
    D -->|Yes| E[変更セット検証]
    E --> F{checkRep成功?}
    F -->|No| Z2[エラー: badChangeset]
    F -->|Yes| G[著者属性検証]
    G --> H{著者ID一致?}
    H -->|No| Z3[エラー: Author mismatch]
    H -->|Yes| I[moveOpsToNewPool]
    I --> J{baseRev < head?}
    J -->|Yes| K[リベース処理（follow）]
    J -->|No| L[変更適用]
    K --> L
    L --> M[pad.appendRevision]
    M --> N[_correctMarkersInPad]
    N --> O{末尾改行あり?}
    O -->|No| P[改行追加]
    O -->|Yes| Q[ACCEPT_COMMIT送信]
    P --> Q
    Q --> R[updatePadClients]
    R --> S[各クライアントにNEW_CHANGES]
    S --> T[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-56-01 | 変更セット検証 | 変更セットは正規形式でなければならない | 常時 |
| BR-56-02 | 著者一致 | 変更内の著者属性はセッション著者と一致必須 | 常時 |
| BR-56-03 | リベース処理 | baseRevが古い場合は最新リビジョンにリベース | baseRev < head時 |
| BR-56-04 | 重複検出 | 同一changeset・同一著者の再送は恒等変換として処理 | 重複検出時 |
| BR-56-05 | 末尾改行 | パッドは常に改行で終わる必要がある | 常時 |
| BR-56-06 | レート制限 | 本番環境ではレート制限を適用 | NODE_ENV=production |

### 計算ロジック

リベース処理（follow関数）:
- 2つの変更セットA, Bがある場合、Bに続いてAを適用できるよう変換
- OT（Operational Transformation）アルゴリズムに基づく

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| パッドデータ更新 | pad:{padId} | UPDATE | atext、pool、head更新 |
| リビジョン作成 | pad:{padId}:revs:{n} | INSERT | 新リビジョンの保存 |

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

#### pad:{padId}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | atext | 変更適用後のテキスト | |
| UPDATE | pool | マージ後の属性プール | |
| UPDATE | head | 新リビジョン番号 | インクリメント |

#### pad:{padId}:revs:{n}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | changeset | 変更セット文字列 | |
| INSERT | meta.author | 著者ID | |
| INSERT | meta.timestamp | 変更時刻 | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| badChangeset | 変更セット不正 | checkRepで検証失敗 | クライアント切断 |
| client disconnected | 接続切断 | セッション情報なし | 処理中止 |
| Author mismatch | 著者不正 | 著者属性がセッションと不一致 | エラーログ・切断 |
| rateLimited | レート制限 | リクエスト過多 | 一時的に切断 |

### リトライ仕様

サーバー側での自動リトライは実装されていない。クライアント側で再接続・再送信が行われる。

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

pad.appendRevisionで変更がアトミックに適用される。リビジョン作成とパッドデータ更新は一貫性を持って実行される。

## パフォーマンス要件

- メッセージ処理はパッド単位でシリアライズ（padChannels）
- 編集処理時間はstats.timer('edits')で計測
- 大量の同時編集でもキュー処理により順序保証
- revCacheで同一リビジョンの重複取得を回避

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

- レート制限によるDoS攻撃対策
- 著者属性の検証による偽装防止
- 読み取り専用ユーザーの変更をブロック
- handleMessageSecurityフックでプラグインによる追加検証可能

## 備考

- Changesetライブラリは元のEtherpadから継承
- OTアルゴリズムによりコンフリクト解決が自動化

---

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

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

### 推奨読解順序

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

変更セット（Changeset）と属性プールの構造を理解することが最も重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Changeset.ts | `src/static/js/Changeset.ts` | Changeset形式の構造と操作関数 |
| 1-2 | AttributePool.ts | `src/static/js/AttributePool.ts` | 属性プールの管理 |
| 1-3 | PadType.ts | `src/node/types/PadType.ts` | パッドの型定義 |

**読解のコツ**: Changesetは「Z:oldLen>insertedLen-deletedLen*attrib+insertedLen-deletedLen$insertedText」のような形式。unpack()で構造を分解、pack()で再構築。

#### Step 2: メッセージハンドラのエントリーポイントを理解する

Socket.IOメッセージの受信からキュー投入までの流れを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | メッセージ処理の全体像 |

**主要処理フロー**:
1. **273-436行目**: handleMessage関数 - メッセージ振り分け
2. **294-313行目**: CLIENT_READYの場合の認証情報設定
3. **330-336行目**: セキュリティチェック（checkAccess）
4. **403-405行目**: USER_CHANGESの場合、padChannelsにキュー追加
5. **157行目**: padChannelsの定義（Channelsクラス）

#### Step 3: 変更処理の詳細を理解する

実際の変更適用ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | handleUserChanges関数 |

**主要処理フロー**:
- **627-736行目**: handleUserChanges関数
- **645-648行目**: baseRev、apool、changeset取得
- **653行目**: checkRepで変更セット検証
- **656-670行目**: 著者属性検証
- **675行目**: moveOpsToNewPoolで属性プールマージ
- **683-695行目**: リベース処理（followループ）
- **705行目**: pad.appendRevisionで変更適用
- **710-713行目**: マーカー補正
- **716-719行目**: 末尾改行保証
- **724行目**: ACCEPT_COMMIT送信
- **727行目**: updatePadClients呼び出し

#### Step 4: ブロードキャスト処理を理解する

他クライアントへの変更通知を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | updatePadClients関数 |

**主要処理フロー**:
- **738-795行目**: updatePadClients関数
- **740-741行目**: ルーム内ソケット取得
- **753行目**: revCacheでリビジョンキャッシュ
- **760-766行目**: 各クライアントに未送信リビジョン取得
- **772-784行目**: NEW_CHANGESメッセージ構築
- **786行目**: socket.emit送信

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

```
Socket.IO message受信
    │
    └─ handleMessage (PadMessageHandler.ts:273)
           │
           ├─ securityManager.checkAccess
           │
           ├─ hooks.aCallAll('handleMessageSecurity')
           │
           └─ [USER_CHANGES]
                  │
                  └─ padChannels.enqueue
                         │
                         └─ handleUserChanges (PadMessageHandler.ts:627)
                                │
                                ├─ checkRep (Changeset検証)
                                │
                                ├─ deserializeOps → AttributeMap
                                │      └─ 著者属性検証
                                │
                                ├─ moveOpsToNewPool
                                │
                                ├─ [リベース] follow (複数回)
                                │
                                ├─ pad.appendRevision
                                │
                                ├─ _correctMarkersInPad
                                │
                                ├─ [末尾改行] makeSplice
                                │
                                ├─ socket.emit('ACCEPT_COMMIT')
                                │
                                └─ updatePadClients
                                       │
                                       └─ 各socket.emit('NEW_CHANGES')
```

### データフロー図

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

クライアントA        ┌─────────────────┐
USER_CHANGES ──────▶│  padChannels    │
(changeset,         │  キュー追加     │
 baseRev,           └────────┬────────┘
 apool)                      │
                             ▼
                    ┌─────────────────┐
                    │  handleUser     │
                    │  Changes        │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
現在のパッド ─────▶ │  リベース処理   │
(head, atext)       │  (follow)       │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  appendRevision │ ───────▶ Database
                    └────────┬────────┘          (revs:{n})
                             │
        ┌────────────────────┴────────────────────┐
        │                                         │
        ▼                                         ▼
┌───────────────┐                        ┌───────────────┐
│ ACCEPT_COMMIT │                        │ NEW_CHANGES   │
│ to クライアントA│                        │ to 他クライアント│
└───────────────┘                        └───────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | メッセージ処理・同期ロジック |
| Changeset.ts | `src/static/js/Changeset.ts` | ソース | 変更セット操作 |
| AttributePool.ts | `src/static/js/AttributePool.ts` | ソース | 属性プール管理 |
| AttributeMap.ts | `src/static/js/AttributeMap.ts` | ソース | 属性マップ操作 |
| Builder.ts | `src/static/js/Builder.ts` | ソース | Changeset構築 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | パッドモデル・リビジョン管理 |
| SecurityManager.ts | `src/node/db/SecurityManager.ts` | ソース | アクセス権チェック |
| SocketIORouter.ts | `src/node/handler/SocketIORouter.ts` | ソース | Socket.IOルーティング |
| stats.ts | `src/node/stats.ts` | ソース | 統計収集 |
