# 機能設計書 4-ワークスペースコピー

## 概要

本ドキュメントは、Giselleにおけるワークスペースコピー機能の設計を定義する。既存のワークスペースを複製して新しいワークスペースを作成し、トリガー設定やファイルも含めて完全なコピーを生成する機能である。

### 本機能の処理概要

ワークスペースコピー機能は、既存のワークスペースを複製して新しいワークスペースを作成する機能である。ノード、接続、UI状態に加え、設定済みのトリガーや関連ファイルも複製する。

**業務上の目的・背景**：ユーザーが既存のワークフローを基に新しいワークフローを作成したい場合、ゼロから構築するのではなく既存ワークスペースをコピーして編集することで効率的に作業できる。テンプレートとして共有されたワークスペースを自分の環境にコピーする場合や、既存ワークフローのバリエーションを作成する場合に活用される。トリガー設定も新しいIDで複製されるため、コピー元とコピー先で独立した自動実行が可能となる。

**機能の利用シーン**：ワークスペース一覧画面で「複製」アクションを実行した時、テンプレートワークスペースから新規プロジェクトを開始する時、既存ワークフローを基にした派生ワークフローを作成する時などに利用される。

**主要な処理内容**：
1. 元ワークスペースのデータを取得
2. 新しいワークスペースIDを生成
3. 設定済みトリガーノードの検出と新規トリガーIDでの複製
4. ノードのトリガーID参照を更新
5. 新しいワークスペースオブジェクトを構築
6. ストレージに保存し、関連ファイルもコピー

**関連システム・外部連携**：GiselleStorageを通じたストレージ操作、トリガー設定のコピー処理を含む。

**権限による制御**：呼び出し元のAPIエンドポイントでワークスペースへの読み取り権限とチームへの書き込み権限が確認される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 16 | ワークスペース一覧画面 | 主機能 | ワークスペース複製アクション |

## 機能種別

CRUD操作（Create - 複製による作成）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| context | GiselleContext | Yes | Giselleの実行コンテキスト | 型チェック |
| workspaceId | WorkspaceId | Yes | コピー元のワークスペースID | wrks_プレフィックス形式 |
| name | string | No | 新しいワークスペースの名前 | 未指定時は「Copy of {元の名前}」 |

### 入力データソース

- ストレージ: 元ワークスペースのworkspace.json
- ストレージ: 元ワークスペースの関連ファイル
- ストレージ: 設定済みトリガーデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | WorkspaceId | 新しいワークスペースの一意識別子 |
| name | string | ワークスペース名（「Copy of {元の名前}」形式） |
| schemaVersion | string | スキーマバージョン |
| nodes | NodeLike[] | コピーされたノード配列（トリガーIDは更新済み） |
| connections | Connection[] | コピーされた接続配列 |
| ui | UIState | コピーされたUI状態 |

### 出力先

- ストレージ: `workspaces/{newWorkspaceId}/workspace.json` に保存
- ストレージ: `workspaces/{newWorkspaceId}/files/` にファイルコピー
- ストレージ: 新しいトリガーデータ
- 戻り値: 新しいWorkspaceオブジェクト

## 処理フロー

### 処理シーケンス

```
1. getWorkspace()で元ワークスペースを取得
   └─ sourceWorkspaceに格納

2. generateInitialWorkspace()で新しいワークスペースの基盤を生成
   └─ 新しいワークスペースIDを取得

3. 設定済みトリガーノードをフィルタリング
   └─ isTriggerNode()でトリガーノードを抽出
   └─ status === "configured"のノードのみ対象

4. 各トリガーに対して複製処理
   └─ getTrigger()で元トリガー設定を取得
   └─ TriggerId.generate()で新しいトリガーIDを生成
   └─ setTrigger()で新しいトリガー設定を保存

5. ノードのトリガーID参照を更新
   └─ updatedNodesを構築

6. 新しいワークスペースオブジェクトを構築
   └─ 名前を設定（指定があれば使用、なければ「Copy of {元の名前}」）

7. 並列でストレージ保存とファイルコピー
   └─ setWorkspace()でワークスペース保存
   └─ copyFiles()でファイル複製
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[元ワークスペース取得]
    B --> C[新ワークスペースID生成]
    C --> D[設定済みトリガーノード抽出]
    D --> E{トリガーあり?}
    E -->|Yes| F[各トリガーを複製]
    E -->|No| G[ノード更新スキップ]
    F --> H[ノードのトリガーID参照更新]
    G --> H
    H --> I[新ワークスペースオブジェクト構築]
    I --> J[並列: ストレージ保存 & ファイルコピー]
    J --> K[新ワークスペース返却]
    K --> L[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 新規ID生成 | ワークスペースIDは新規生成される | 常に適用 |
| BR-002 | トリガー複製 | 設定済みトリガーは新しいIDで複製される | トリガーノードが存在する場合 |
| BR-003 | 名前生成 | 名前未指定時は「Copy of {元の名前}」形式 | name引数が未指定の場合 |
| BR-004 | ファイル複製 | 関連ファイルはすべて新しいパスにコピー | 常に適用 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 元ワークスペース取得 | ストレージ | SELECT | workspace.jsonを読み込み |
| トリガー取得 | ストレージ | SELECT | 各トリガー設定を読み込み |
| トリガー保存 | ストレージ | INSERT | 新しいトリガー設定を保存 |
| ワークスペース保存 | ストレージ | INSERT | 新しいworkspace.jsonを保存 |
| ファイルコピー | ストレージ | INSERT | ファイルを新しいパスにコピー |

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

#### ストレージ操作

| 操作 | パス | 内容 |
|-----|------|------|
| 読み込み | workspaces/{sourceId}/workspace.json | 元ワークスペース |
| 読み込み | triggers/{triggerId}.json | 元トリガー設定 |
| 保存 | triggers/{newTriggerId}.json | 新しいトリガー設定 |
| 保存 | workspaces/{newId}/workspace.json | 新しいワークスペース |
| コピー | workspaces/{sourceId}/files/* → workspaces/{newId}/files/* | ファイル群 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NOT_FOUND | データ未存在 | 元ワークスペースが存在しない | IDの確認 |
| TRIGGER_NOT_FOUND | トリガー未存在 | 参照されたトリガーが存在しない | 不整合データの修復 |
| STORAGE_ERROR | ストレージエラー | 読み書き失敗 | リトライ、接続確認 |

### リトライ仕様

ストレージ操作失敗時は呼び出し元でリトライを検討する。

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

複数のストレージ操作が含まれるが、RDBトランザクションは発生しない。Promise.allによる並列処理で一括実行される。部分的な失敗時はロールバックされない点に注意。

## パフォーマンス要件

- レスポンス時間: ファイル数に依存（基本500ms以内、大量ファイル時は延長）
- 並列処理: Promise.allによるトリガー複製とファイルコピーの並列化

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

- 元ワークスペースへの読み取り権限が必要
- 新しいワークスペースは呼び出し元のチームに所属
- 認証・認可は呼び出し元のAPIレイヤーで実施

## 備考

- コピー後のワークスペースは完全に独立しており、元ワークスペースへの変更は影響しない
- トリガーIDが更新されるため、Webhookなどの外部連携は再設定が必要な場合がある

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | trigger.ts | `packages/protocol/src/node/operations/trigger.ts` | TriggerContentの構造、状態管理 |
| 1-2 | index.ts | `packages/protocol/src/trigger/index.ts` | TriggerId生成器 |

**読解のコツ**: TriggerConfigurationStateの3つの状態（unconfigured, configured, reconfiguring）を理解する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | copy-workspace.ts | `packages/giselle/src/workspaces/copy-workspace.ts` | メイン関数の全体フロー（105行） |

**主要処理フロー**:
1. **18-21行目**: 元ワークスペースを取得
2. **23行目**: 新しいワークスペースIDを生成
3. **25-28行目**: 設定済みトリガーノードをフィルタリング
4. **30-59行目**: トリガーの複製処理（Promise.all）
5. **61-80行目**: ノードのトリガーID参照を更新
6. **82-88行目**: 新しいワークスペースオブジェクトを構築
7. **90-101行目**: 並列でストレージ保存とファイルコピー
8. **103行目**: 新しいワークスペースを返却

#### Step 3: トリガー複製処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | utils.ts | `packages/giselle/src/triggers/utils.ts` | getTrigger, setTrigger関数 |

#### Step 4: ファイルコピー処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | utils.ts | `packages/giselle/src/workspaces/utils.ts` | copyFiles関数（71-114行目） |

**主要処理フロー**:
- **80-101行目**: ファイル一覧取得（ページネーション対応）
- **103-110行目**: 各ファイルをコピー

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

```
copyWorkspace (copy-workspace.ts)
    │
    ├─ getWorkspace (utils.ts)
    │      └─ context.storage.getJson
    │
    ├─ generateInitialWorkspace (workspace/index.ts)
    │
    ├─ isTriggerNode (operation-node.ts)
    │
    ├─ getTrigger (triggers/utils.ts)
    │      └─ storage.getJson
    │
    ├─ TriggerId.generate (trigger/index.ts)
    │
    ├─ setTrigger (triggers/utils.ts)
    │      └─ storage.setJson
    │
    ├─ setWorkspace (utils.ts)
    │      └─ context.storage.setJson
    │
    └─ copyFiles (utils.ts)
           ├─ storage.listBlobs
           └─ storage.copy
```

### データフロー図

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

sourceWorkspaceId ───▶ getWorkspace ───▶ sourceWorkspace
                              │
                              ▼
                    generateInitialWorkspace ───▶ newWorkspace (base)
                              │
                              ▼
                    フィルタ: configuredトリガー
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
            getTrigger           TriggerId.generate
                    │                   │
                    └─────────┬─────────┘
                              ▼
                        setTrigger (新ID)
                              │
                              ▼
                    ノード参照更新 ───▶ updatedNodes
                              │
                              ▼
                    ワークスペース構築
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
            setWorkspace          copyFiles
                    │                   │
                    └─────────┬─────────┘
                              ▼
                    workspaceCopy ───▶ 呼び出し元
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| copy-workspace.ts | `packages/giselle/src/workspaces/copy-workspace.ts` | ソース | メイン機能実装 |
| utils.ts | `packages/giselle/src/workspaces/utils.ts` | ソース | ストレージ操作、ファイルコピー |
| utils.ts | `packages/giselle/src/triggers/utils.ts` | ソース | トリガー操作 |
| trigger.ts | `packages/protocol/src/node/operations/trigger.ts` | ソース | TriggerContent定義 |
| operation-node.ts | `packages/protocol/src/node/operations/operation-node.ts` | ソース | isTriggerNode判定関数 |
| index.ts | `packages/protocol/src/workspace/index.ts` | ソース | generateInitialWorkspace |
