# 通知設計書 120-remote_mirror_update_failed_email

## 概要

本ドキュメントは、GitLabにおけるリモートミラー更新失敗通知（remote_mirror_update_failed_email）の設計仕様を記述する。

### 本通知の処理概要

リモートミラー更新失敗通知は、プロジェクトのリモートミラー（プッシュミラー）への同期処理が失敗した際に、プロジェクトのメンテナーに対してその失敗を通知するメールを送信する。

**業務上の目的・背景**：リモートミラーはGitLabリポジトリの変更を外部のGitリポジトリに自動的にプッシュする機能である。CI/CD連携や災害復旧、バックアップ目的で使用される。ミラー同期の失敗は重要な運用上の問題となるため、迅速にメンテナーに通知し、対応を促す必要がある。

**通知の送信タイミング**：RemoteMirrorのステートマシンがfailed状態に遷移した際、RemoteMirrorNotificationWorkerを通じて非同期で送信される。ただし、既に通知済みの場合は重複送信しない。

**通知の受信者**：プロジェクトのメンテナー全員に送信される。

**通知内容の概要**：ミラー更新が失敗したこと、対象プロジェクト、リモートミラーURL、最終更新日時、およびエラーメッセージを通知する。

**期待されるアクション**：受信者はエラーメッセージを確認し、リモートミラーの設定（URL、認証情報など）を確認・修正する。設定ページへのリンクから直接設定画面にアクセスできる。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 通常 |
| リトライ | 3回（sidekiq_options retry: 3） |

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

NotificationServiceのproject_maintainers_recipientsを使用し、プロジェクトのメンテナー全員に送信される。カスタム通知設定で「update_failed」アクションを無効にしているユーザーは除外される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabインスタンス設定に従う |
| 送信元名称 | GitLab |
| 件名 | `{project_name} | Remote mirror update failed` |
| 形式 | テキスト |

### 本文テンプレート

```
A remote mirror update has failed.

Source Project: {project_name} ({project_url})
Remote mirror: {safe_url}
Last update at: {last_update_at}
Last error:
{last_error}
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @remote_mirror | リモートミラー情報 | RemoteMirror.find_by_id(remote_mirror_id) | Yes |
| @project | 対象プロジェクト | @remote_mirror.project | Yes |
| @target_url | 設定ページURL | project_settings_repository_url(@project, anchor: 'js-mirror-settings') | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 状態遷移コールバック | after_transition started: :failed | 常に | ステートマシンの遷移後コールバックでWorkerをキューイング |
| バックグラウンドジョブ | RemoteMirrorNotificationWorker | last_error存在 && !error_notification_sent | Worker内で追加条件チェック |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| error_notification_sent が true | 既に通知済みの場合は送信しない |
| last_error が nil | エラーメッセージがない場合は送信しない |
| プロジェクトのemails_disabledがtrue | プロジェクトでメール通知が無効の場合 |
| カスタム通知設定 | update_failedアクションを無効にしているユーザー |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ミラー更新失敗] --> B[update_fail!呼び出し]
    B --> C[after_transition started: :failed]
    C --> D[send_failure_notifications]
    D --> E[run_after_commit]
    E --> F[RemoteMirrorNotificationWorker.perform_async]
    F --> G{last_error存在?}
    G -->|No| H[終了]
    G -->|Yes| I{error_notification_sent?}
    I -->|Yes| H
    I -->|No| J[NotificationService.remote_mirror_update_failed]
    J --> K[project_maintainers_recipients取得]
    K --> L[各受信者にメール送信]
    L --> M[error_notification_sentをtrueに更新]
    M --> H
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| remote_mirrors | リモートミラー情報取得 | |
| projects | プロジェクト情報取得 | |
| members | メンテナー取得 | プロジェクトメンバー |
| notification_settings | 通知設定確認 | カスタム通知レベル |

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

#### remote_mirrors

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ミラー特定 | RemoteMirror.find_by_id(remote_mirror_id) |
| url | リモートURL（safe_url経由） | |
| last_update_at | 最終更新日時 | |
| last_error | エラーメッセージ | |
| error_notification_sent | 通知済みフラグ | |
| project_id | プロジェクトID | |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| remote_mirrors | UPDATE | error_notification_sentをtrueに更新 |

#### 送信後更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| UPDATE | error_notification_sent | true | after_sent_notificationで更新 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| RemoteMirror未存在 | find_by_idで見つからない | 処理を終了（returnのみ） |
| メール送信失敗 | SMTPエラー等 | Sidekiqのリトライに委ねる |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 3回 |
| リトライ間隔 | Sidekiqデフォルト設定 |
| リトライ対象エラー | メール送信に関連するエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | システム設定に従う |
| 1日あたり上限 | システム設定に従う |

### 配信時間帯

制限なし（24時間送信可能）

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

- リモートミラーURLはsafe_urlを使用し、認証情報（パスワード）をマスク処理
- エラーメッセージもGitlab::UrlSanitizer.sanitizeでURLの認証情報をマスク
- gitユーザー名は許可リストに含まれ、マスクされない

## 備考

- ミラー更新が成功した場合、error_notification_sentはfalseにリセットされる
- バックオフ遅延（PROTECTED_BACKOFF_DELAY: 1分、UNPROTECTED_BACKOFF_DELAY: 5分）の後に再試行される
- feature_category: source_code_managementとして分類

---

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

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

### 推奨読解順序

#### Step 1: RemoteMirrorモデルを理解する

ステートマシンと失敗時のコールバックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | remote_mirror.rb | `app/models/remote_mirror.rb` | ステートマシン、send_failure_notifications |

**読解のコツ**: after_transition started: :failedコールバックと、error_notification_sentフラグの管理に注目

**主要処理フロー**:
1. **41-84行目**: ステートマシン定義、特に81-83行目のfailedへの遷移後コールバック
2. **232-241行目**: send_failure_notificationsでWorkerをキューイング
3. **216-217行目**: after_sent_notificationでフラグ更新

#### Step 2: Workerを理解する

通知送信のWorkerを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | remote_mirror_notification_worker.rb | `app/workers/remote_mirror_notification_worker.rb` | perform、条件チェック |

**主要処理フロー**:
1. **13-24行目**: performでRemoteMirror取得、条件チェック、通知送信

#### Step 3: NotificationServiceを理解する

受信者決定ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | remote_mirror_update_failed（668-674行目） |

**主要処理フロー**:
- **668-674行目**: project_maintainers_recipientsで受信者取得、各受信者にメール送信

#### Step 4: メーラー実装を理解する

メール本文生成ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | remote_mirrors.rb | `app/mailers/emails/remote_mirrors.rb` | remote_mirror_update_failed_emailメソッド |

**主要処理フロー**:
- **5-11行目**: RemoteMirror取得、target_url生成、メール送信

#### Step 5: テンプレートを理解する

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | remote_mirror_update_failed_email.text.erb | `app/views/notify/remote_mirror_update_failed_email.text.erb` | エラー情報の表示 |

**主要処理フロー**:
- **1-7行目**: プロジェクト情報、リモートミラー情報、エラーメッセージの表示

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

```
RemoteMirror#mark_as_failed! / hard_fail!
    │
    └─ update_fail! (状態遷移)
           │
           └─ after_transition started: :failed
                  │
                  └─ send_failure_notifications
                         │
                         └─ run_after_commit
                                │
                                └─ RemoteMirrorNotificationWorker.perform_async
                                       │
                                       ├─ RemoteMirror.find_by_id
                                       │
                                       ├─ 条件チェック
                                       │      ├─ last_error.present?
                                       │      └─ !error_notification_sent?
                                       │
                                       └─ NotificationService.remote_mirror_update_failed
                                              │
                                              ├─ project_maintainers_recipients
                                              │
                                              └─ mailer.remote_mirror_update_failed_email.deliver_later
                                                     │
                                                     ├─ RemoteMirror.find_by_id
                                                     ├─ project_settings_repository_url
                                                     │
                                                     └─ mail_with_locale

RemoteMirror#after_sent_notification
    │
    └─ update_column(:error_notification_sent, true)
```

### データフロー図

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

ミラー更新失敗 ──────────────▶ RemoteMirror状態遷移（failed）
                                      │
                                      ▼
                               send_failure_notifications
                                      │
                                      ▼
                               RemoteMirrorNotificationWorker
                                      │
                                      ├─ last_error確認 ──────────────────┐
                                      └─ error_notification_sent確認 ──────┤
                                                                          │
                                                                          ▼
remote_mirror_id ─────────────▶ NotificationService.remote_mirror_update_failed
                                      │
RemoteMirror ─────────────────────────┤
    │                                 │
    ├─ safe_url ──────────────────────┤
    ├─ last_update_at ────────────────┤
    ├─ last_error ────────────────────┤
    └─ project ───────────────────────┤
                                      │
project_maintainers_recipients ───────┤
                                      │
                                      ▼
                               メール送信 ────────────▶ error_notification_sent更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| remote_mirror.rb | `app/models/remote_mirror.rb` | ソース | RemoteMirrorモデル、ステートマシン |
| remote_mirror_notification_worker.rb | `app/workers/remote_mirror_notification_worker.rb` | ソース | 通知Worker |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス |
| remote_mirrors.rb | `app/mailers/emails/remote_mirrors.rb` | ソース | メーラーメソッド定義 |
| remote_mirror_update_failed_email.text.erb | `app/views/notify/remote_mirror_update_failed_email.text.erb` | テンプレート | メール本文テンプレート |
