# 通知設計書 74-repository_rewrite_history_failure_email

## 概要

本ドキュメントは、GitLabのリポジトリ履歴書き換え処理が失敗した際に送信されるメール通知 `repository_rewrite_history_failure_email` の設計仕様を定義する。

### 本通知の処理概要

本通知は、プロジェクトのリポジトリ履歴書き換え処理（RewriteHistory RPC経由でのBLOB削除やテキスト置換）が失敗したことをユーザーに通知するメール機能を提供する。

**業務上の目的・背景**：リポジトリ履歴の書き換えは重要なセキュリティ・保守操作であり、失敗した場合はユーザーに迅速に通知して、問題の診断と再試行を促す必要がある。センシティブなデータの削除が目的の場合、失敗の認識が遅れるとセキュリティリスクが継続する可能性がある。

**通知の送信タイミング**：リポジトリ履歴書き換えワーカー（`Repositories::RewriteHistoryWorker`）でRewriteHistoryServiceの実行結果がエラーを返した場合に送信される。

**通知の受信者**：履歴書き換え処理を開始したユーザー（current_user）に送信される。ユーザーはプロジェクトに対するオーナー権限を持っている必要がある。

**通知内容の概要**：履歴書き換えが失敗したこと、対象プロジェクトのURL、およびエラーメッセージを含む失敗通知メールである。

**期待されるアクション**：受信者はエラー内容を確認し、問題を解決した上で再度履歴書き換え処理を実行するか、GitLab管理者に支援を求める。特にセンシティブなデータの削除が目的だった場合は、早急な対応が必要である。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 高（エラー通知のため） |
| リトライ | Sidekiqのデフォルトリトライ機構に従う |

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

履歴書き換え処理を開始したユーザー（`user`パラメータ）の通知用メールアドレスに送信される。メールアドレスは `user.notification_email_for(project.group)` メソッドで決定される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabのデフォルト送信元アドレス |
| 送信元名称 | GitLab |
| 件名 | `[Project Name] Project history rewrite failure` |
| 形式 | HTML/テキスト |

### 本文テンプレート

```html
<p>
  Repository history rewrite failed on {project_web_url}.
</p>
<p>
  {error_message}
</p>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @project | 対象プロジェクト | パラメータから取得 | Yes |
| @project.web_url | プロジェクトURL | プロジェクトモデルから取得 | Yes |
| @error | エラーメッセージ | ServiceResponseのmessageから取得 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バックグラウンドジョブ | RewriteHistoryWorker実行結果エラー | result.error?がtrue | ワーカー内でServiceResponseがエラーの場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| プロジェクトのメール無効化 | `project.emails_disabled?` がtrueの場合、通知は送信されない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーが履歴書き換えをリクエスト] --> B[RewriteHistoryWorkerをキューに追加]
    B --> C[ワーカーがRewriteHistoryService.execute実行]
    C --> D{処理成功?}
    D -->|Yes| E[成功通知処理へ]
    D -->|No| F[ServiceResponseでエラー返却]
    F --> G[NotificationService.repository_rewrite_history_failure呼び出し]
    G --> H{プロジェクトのメール有効?}
    H -->|Yes| I[失敗通知メール送信]
    H -->|No| J[送信スキップ]
    I --> K[終了]
    J --> K
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| projects | プロジェクト情報取得 | メール無効化フラグ確認 |
| users | ユーザー情報取得 | 通知先メールアドレス取得 |
| namespaces | グループ情報取得 | 通知メールアドレス決定用 |

### テーブル別参照項目詳細

#### projects

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | プロジェクト識別 | パラメータで指定されたproject_id |
| name | プロジェクト名表示 | - |
| emails_disabled | メール送信可否判定 | - |
| namespace_id | グループ情報取得 | - |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 失敗通知のみで更新なし（リポジトリは ensureブロックで書き込み可能に戻される） |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| アクセス拒否 | オーナー権限がない | 「Access Denied」メッセージで通知 |
| 引数不足 | blob_oidsとredactionsが両方空 | 「not enough arguments」メッセージで通知 |
| リポジトリ読み取り専用エラー | 既に読み取り専用状態 | エラーメッセージで通知 |
| Gitaly エラー | RewriteHistory RPC失敗 | Gitalyのエラーメッセージで通知 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | メール送信のSidekiqデフォルト |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なネットワークエラー等（履歴書き換え自体のリトライはワーカーレベルで制御） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | GitLabのデフォルト設定に従う |
| 1日あたり上限 | GitLabのデフォルト設定に従う |

### 配信時間帯

特に制限なし。エラー発生時に即時送信される。

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

- エラーメッセージにセンシティブな情報が含まれないよう注意が必要
- 通知は履歴書き換えを実行したユーザー本人にのみ送信される
- 削除しようとしたBLOBのOID等のセンシティブな情報は含まれない
- リポジトリは失敗後も自動的に書き込み可能状態に復元される（ensureブロック）

## 備考

- 履歴書き換えにはオーナー権限が必要
- 処理中はリポジトリが読み取り専用モードになるが、ensureブロックで必ず解除される
- 失敗の原因特定には、Sidekiqのログやアプリケーションログの確認が必要
- センシティブなデータの削除が目的だった場合、早急な再試行が推奨される

---

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

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

### 推奨読解順序

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

まず、通知に必要なデータモデルとエラー情報の構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | project.rb | `app/models/project.rb` | プロジェクトモデルの構造 |
| 1-2 | service_response.rb | `app/services/service_response.rb` | ServiceResponseクラス、error?メソッド |

**読解のコツ**: ServiceResponseパターンを理解し、success?/error?の判定方法を確認する。

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

失敗処理の起点となるワーカークラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | rewrite_history_worker.rb | `app/workers/repositories/rewrite_history_worker.rb` | performメソッドでの成功/失敗判定 |

**主要処理フロー**:
1. **22-25行目**: RewriteHistoryService.executeの呼び出し
2. **27行目**: result.success?の場合は成功通知
3. **29行目**: それ以外（error）の場合は失敗通知、result.messageをエラーとして渡す

#### Step 3: サービス層を理解する

エラーが発生する可能性のある処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | rewrite_history_service.rb | `app/services/repositories/rewrite_history_service.rb` | エラーを返す可能性のある箇所 |

**主要処理フロー**:
- **34-39行目**: validate_inputでアクセス権限と引数のチェック
- **45-50行目**: mark_repository_read_onlyで読み取り専用設定エラー
- **52-64行目**: rewrite_historyでGitaly RPCエラー

#### Step 4: 通知サービスを理解する

通知の送信ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | notification_service.rb | `app/services/notification_service.rb` | repository_rewrite_history_failureメソッド（662-666行目） |

**主要処理フロー**:
- **662-666行目**: プロジェクトのメール無効化チェック後、メーラーを呼び出す

#### Step 5: メーラーを理解する

実際のメール生成処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | projects.rb | `app/mailers/emails/projects.rb` | repository_rewrite_history_failure_emailメソッド（74-82行目） |

**主要処理フロー**:
- **74-82行目**: @project、@errorを設定してemail_with_layoutで送信

#### Step 6: ビューテンプレートを理解する

メール本文のテンプレートを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | repository_rewrite_history_failure_email.html.haml | `app/views/notify/repository_rewrite_history_failure_email.html.haml` | メール本文のテンプレート |

**テンプレート内容**:
- **1-2行目**: プロジェクトURLを含む失敗メッセージ
- **3-4行目**: エラーメッセージを表示

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

```
Repositories::RewriteHistoryWorker#perform
    │
    └─ Repositories::RewriteHistoryService#execute
           │
           ├─ validate_input → ServiceResponse.error (アクセス拒否/引数不足)
           │
           ├─ mark_repository_read_only → ServiceResponse.error (読み取り専用エラー)
           │
           └─ rewrite_history
                  │
                  ├─ 成功 → ServiceResponse.success
                  │
                  └─ 失敗 → ServiceResponse.error (Gitalyエラー)
                              │
                              └─ NotificationService#repository_rewrite_history_failure
                                     └─ Notify#repository_rewrite_history_failure_email
                                            └─ email_with_layout
```

### データフロー図

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

project_id, user_id ──▶ RewriteHistoryWorker ──────────▶ 失敗
blob_oids, redactions          │
                               │
        error ◀─────── RewriteHistoryService
                               │
                               └─ NotificationService
                                      └─ 失敗通知メール送信
                                             └─ プロジェクトURL
                                                エラーメッセージ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| rewrite_history_worker.rb | `app/workers/repositories/rewrite_history_worker.rb` | ソース | バックグラウンドジョブ処理 |
| rewrite_history_service.rb | `app/services/repositories/rewrite_history_service.rb` | ソース | 履歴書き換え実行サービス |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知送信サービス |
| projects.rb | `app/mailers/emails/projects.rb` | ソース | メール生成メーラー |
| repository_rewrite_history_failure_email.html.haml | `app/views/notify/repository_rewrite_history_failure_email.html.haml` | テンプレート | メール本文テンプレート |
| service_response.rb | `app/services/service_response.rb` | ソース | サービス層のレスポンスクラス |
