# 機能設計書 69-シークレット削除

## 概要

本ドキュメントは、Giselleシステムにおける「シークレット削除」機能の設計仕様を定義する。

### 本機能の処理概要

シークレット削除機能は、指定されたシークレットIDに基づいてストレージからシークレットを削除する機能である。シークレット本体の削除に加え、ワークスペースインデックスからの削除も行う。

**業務上の目的・背景**：不要になったシークレットや無効化したいAPIキーを安全に削除する必要がある。本機能により、ユーザーは不要なシークレットをシステムから完全に削除でき、セキュリティリスクを軽減できる。

**機能の利用シーン**：
- APIキー管理画面でのシークレット削除
- 期限切れや不正なAPIキーの削除
- ワークスペースクリーンアップ時のシークレット削除

**主要な処理内容**：
1. 入力としてSecretIdを受け取る
2. シークレットの存在確認
3. シークレット本体を取得（workspaceIdの確認のため）
4. ストレージからシークレット本体を削除
5. ワークスペースIDがある場合、インデックスからも削除

**関連システム・外部連携**：
- `@giselles-ai/storage`パッケージによるストレージ抽象化レイヤー

**権限による制御**：本機能自体には認証・認可チェックは含まれない。呼び出し元のAPIレイヤーにて適切な認可制御が行われることを前提とする。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 29 | APIキー管理画面 | 補助機能 | APIキーの削除処理 |

## 機能種別

データ削除（DELETE）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| context | `GiselleContext` | Yes | Giselleコンテキスト | - |
| secretId | `SecretId` | Yes | 削除対象のシークレットID | `scrt_`プレフィックスで始まる文字列 |

### 入力データソース

- APIリクエストのパラメータとして受け取る

## 出力仕様

### 出力データ

本機能は戻り値を持たない（`void`）。

### 出力先

- ストレージからシークレット本体を削除
- ストレージからワークスペースインデックスのエントリを削除（ワークスペースIDがある場合）

## 処理フロー

### 処理シーケンス

```
1. シークレットパス生成
   └─ secretPath関数でsecrets/{secretId}/secret.jsonパスを生成

2. 存在確認
   └─ storage.exists()でシークレットの存在を確認
   └─ 存在しない場合は早期リターン

3. シークレット取得
   └─ storage.getJson()でシークレットデータを取得（workspaceId確認のため）

4. シークレット本体削除
   └─ storage.remove()でシークレットを削除

5. ワークスペースIDチェック
   └─ workspaceIdがない場合は早期リターン（グローバルシークレット）

6. インデックス存在確認
   └─ インデックスが存在しない場合は早期リターン

7. インデックス更新
   └─ インデックスから対象シークレットを除外
   └─ 残りが0件の場合はインデックスファイル自体を削除
   └─ 残りがある場合は更新したインデックスを保存
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[secretPathでパス生成]
    B --> C{シークレット存在?}
    C -->|No| D[早期リターン]
    C -->|Yes| E[シークレット取得]
    E --> F[シークレット本体削除]
    F --> G{workspaceIdあり?}
    G -->|No| D
    G -->|Yes| H{インデックス存在?}
    H -->|No| D
    H -->|Yes| I[インデックスから対象を除外]
    I --> J{残りが0件?}
    J -->|Yes| K[インデックスファイル削除]
    J -->|No| L[インデックス更新保存]
    K --> D
    L --> D
    D --> M[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-69-01 | 冪等性 | 存在しないシークレットIDを指定してもエラーとしない | 常時 |
| BR-69-02 | グローバルシークレット | workspaceIdがないシークレットはインデックス処理をスキップ | workspaceId未設定時 |
| BR-69-03 | インデックスクリーンアップ | インデックスが空になった場合はインデックスファイル自体を削除 | インデックスが0件時 |
| BR-69-04 | 順次処理 | シークレット本体削除後にインデックス更新を行う | 常時 |

### 計算ロジック

インデックスフィルタリングロジック：
```typescript
const remaining = index.filter((item) => item.id !== secretId);
```

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

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

本機能はBlobストレージのみを使用し、リレーショナルデータベースへの直接アクセスは行わない。

| 操作 | 対象ストレージ | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 存在確認 | Blob Storage | EXISTS | シークレットの存在確認 |
| シークレット取得 | Blob Storage | GET | workspaceId確認のため取得 |
| シークレット削除 | Blob Storage | DELETE | シークレット本体を削除 |
| インデックス存在確認 | Blob Storage | EXISTS | インデックスの存在確認 |
| インデックス取得 | Blob Storage | GET | インデックスを取得 |
| インデックス削除 | Blob Storage | DELETE | インデックスが空の場合に削除 |
| インデックス更新 | Blob Storage | SET | 更新したインデックスを保存 |

### ストレージ操作詳細

#### Blobストレージ

| 操作 | パス | データ形式 | 備考 |
|-----|------|----------|------|
| EXISTS/GET/DELETE | secrets/{secretId}/secret.json | JSON | シークレット本体 |
| EXISTS/GET/SET/DELETE | secrets/byWorkspace/{workspaceId}.json | JSON | SecretIndex配列 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | (なし) | シークレットが存在しない | 早期リターン（エラーではない） |
| - | StorageError | ストレージからの削除失敗 | ストレージ接続を確認 |

### リトライ仕様

ストレージアクセス失敗時のリトライは、ストレージドライバ層で実装される。アプリケーション層でのリトライは行わない。

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

シークレット本体とインデックスの削除はアトミックではない。シークレット本体削除後にインデックス更新が失敗した場合、不整合が発生する可能性がある。

## パフォーマンス要件

- レスポンス時間: 200ms以内（複数のストレージ操作を含む）
- 同時リクエスト: ストレージの制限に依存

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

- 認証・認可: API呼び出し元で実装（本機能では未実装）
- データ保護: 削除操作は物理削除であり、復元不可
- 監査ログ: シークレット削除のログ記録を推奨

## 備考

- 削除対象のシークレットが存在しない場合でもエラーとしない（冪等性の確保）
- インデックスが空になった場合はインデックスファイル自体も削除される

---

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

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

### 推奨読解順序

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

まず、シークレット関連の型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | secret/index.ts | `packages/protocol/src/secret/index.ts` | Secret, SecretId, SecretIndexの型定義 |

**読解のコツ**:
- **5行目**: `SecretId = createIdGenerator("scrt")`で`scrt_`プレフィックス
- **8-15行目**: Secretスキーマ定義
- **19-26行目**: SecretIndexスキーマ定義

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | delete-secret.ts | `packages/giselle/src/secrets/delete-secret.ts` | deleteSecret関数の実装 |

**主要処理フロー**:
1. **6-12行目**: 関数シグネチャと引数定義
2. **13行目**: secretPathでパス生成
3. **15-18行目**: 存在確認、なければ早期リターン
4. **19-22行目**: シークレット取得
5. **23行目**: シークレット本体削除
6. **25-28行目**: workspaceIdがなければ早期リターン
7. **30-34行目**: インデックス存在確認
8. **35-38行目**: インデックス取得
9. **39行目**: フィルタリングで対象を除外
10. **40-43行目**: 残りが0件ならインデックスファイル削除
11. **44-47行目**: 残りがあれば更新して保存

#### Step 3: ユーティリティ関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | paths.ts | `packages/giselle/src/secrets/paths.ts` | secretPath, workspaceSecretIndexPathの定義 |

**主要処理フロー**:
- **3-5行目**: `secrets/{secretId}/secret.json`パスを生成
- **7-9行目**: `secrets/byWorkspace/{workspaceId}.json`パスを生成

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

```
Giselle.deleteSecret (giselle.ts:356-358)
    │
    └─ deleteSecret (delete-secret.ts:6-49)
           │
           ├─ secretPath(secretId) (paths.ts:3-5)
           │
           ├─ context.storage.exists(path)
           │
           ├─ context.storage.getJson({path, schema: Secret})
           │
           ├─ context.storage.remove(path)
           │
           ├─ workspaceSecretIndexPath(workspaceId) (paths.ts:7-9)
           │
           ├─ context.storage.exists(indexPath)
           │
           ├─ context.storage.getJson({path, schema: z.array(SecretIndex)})
           │
           ├─ filter(item => item.id !== secretId)
           │
           └─ context.storage.remove(indexPath) or setJson(indexPath, remaining)
```

### データフロー図

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

SecretId ───▶ secretPath() ───▶ Secret Path
                    │           "secrets/{scrtId}/secret.json"
                    ▼
              storage.exists()
                    │
                    ▼
              storage.getJson() → Secret {workspaceId?, ...}
                    │
                    ▼
              storage.remove()
                    │
                    ▼ (if workspaceId exists)
              workspaceSecretIndexPath()
                    │
                    ▼
              storage.exists() / getJson()
                    │
                    ▼
              filter(item.id !== secretId)
                    │
                    ▼
              storage.remove() or storage.setJson()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| delete-secret.ts | `packages/giselle/src/secrets/delete-secret.ts` | ソース | メイン処理の実装 |
| index.ts | `packages/protocol/src/secret/index.ts` | ソース | Secret型定義 |
| paths.ts | `packages/giselle/src/secrets/paths.ts` | ソース | パス生成ユーティリティ |
| types.ts | `packages/storage/src/types.ts` | ソース | ストレージインターフェース定義 |
| giselle.ts | `packages/giselle/src/giselle.ts` | ソース | Giselleクラスのエントリーポイント |
