# 通知設計書 105-Notify test workflow

## 概要

本ドキュメントは、Apache SparkプロジェクトにおけるGitHub Actions「Notify test workflow」の通知設計を記述する。このワークフローは、プルリクエストのオープン・再オープン・同期（新コミットプッシュ）時にフォークリポジトリでのテストワークフロー実行を検出し、その結果をGitHub Check Runとして通知する。

### 本通知の処理概要

プルリクエストに対するCIテストの実行状況をGitHub Check Runとして通知するGitHub Actionsワークフローである。PRがオープン・再オープン・同期されると、フォークリポジトリの`build_main.yml`ワークフローの実行を検出し、その結果へのリンクをCheck Runとして本体リポジトリに作成する。ワークフローが検出されない場合は、GitHub Actionsの有効化手順やブランチ同期手順を含むガイダンスをaction_requiredステータスのCheck Runとして表示する。

**業務上の目的・背景**：Apache Sparkのような大規模OSSプロジェクトでは、コントリビューターがフォークリポジトリでPRを作成し、テストはフォークリポジトリのGitHub Actionsで実行される。しかし、GitHub Checksのステータスは本体リポジトリ側で表示される必要があるため、フォークリポジトリのワークフロー実行結果を本体リポジトリのCheck Runとして中継する仕組みが必要となる。本ワークフローはこの橋渡しを行い、PRレビュアーがテスト結果を確認しやすくすることを目的とする。

**通知の送信タイミング**：`pull_request_target`イベントの`opened`（PRオープン）、`reopened`（PR再オープン）、`synchronize`（PRへの新コミットプッシュ）のいずれかが発生した際に、GitHub Actionsにより自動実行される。ワークフロー実行前に3秒の待機時間があり、フォークリポジトリのワークフロー起動を待つ。

**通知の受信者**：PR作成者およびPRレビュアー。GitHub Check Runとして表示されるため、PRページにアクセスするすべてのユーザーが確認可能。

**通知内容の概要**：テストワークフロー検出時は「Build」という名前のCheck Runが`queued`ステータスで作成され、テスト結果ページへのリンクが含まれる。テストワークフロー未検出時は`completed`ステータス・`action_required`コンクルージョンのCheck Runが作成され、GitHub Actions有効化手順やブランチ同期手順のガイダンスが表示される。

**期待されるアクション**：テストワークフロー検出時はCheck Runのリンクからテスト結果を確認する。未検出時はガイダンスに従い、(1) フォークリポジトリのGitHub Actionsを有効化する、(2) ブランチをupstream/masterに同期する、のいずれかを実施する。

## 通知種別

GitHub Check Run（GitHub API経由のステータス通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（GitHub Actions workflow内でGitHub API呼び出し） |
| 優先度 | 高（PRのマージ判断に影響） |
| リトライ | GitHub Actionsのデフォルトリトライ機構に従う。Check Run検出時は最大3回リトライ（3秒間隔） |

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

`pull_request_target`イベントのペイロードからPR情報を取得し、以下のロジックで送信内容を決定する：

1. フォークリポジトリ（`context.payload.pull_request.head.repo`）の`build_main.yml`ワークフロー実行を検索
2. ワークフロー実行が見つかった場合：Check Run「Build」を`queued`ステータスで作成
3. ワークフロー実行が見つからなかった場合：Check Run「Build」を`completed`/`action_required`で作成

## 通知テンプレート

### メール通知の場合

本通知はGitHub Check Runとして表示されるため、メールテンプレートは該当しない。ただし、GitHubの通知設定に応じてメール通知が送信される場合がある（GitHub側の仕組みによる）。

### 本文テンプレート

#### ワークフロー検出時

```
Check Run名: Build
ステータス: queued
タイトル: Test results
サマリー: [See test results]({check_run_url})
詳細URL: {actions_url}
```

#### ワークフロー未検出時

```
Check Run名: Build
ステータス: completed
コンクルージョン: action_required
タイトル: Workflow run detection failed
サマリー:
Unable to detect the workflow run for testing the changes in your PR.

1. If you did not enable GitHub Actions in your forked repository, please enable it...
2. It is possible your branch is based on the old `master` branch in Apache Spark, please sync your branch...
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | Check Runにはファイル添付機能はない |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| name | Check Run名（固定値「Build」） | ワークフロー内定数 | Yes |
| head_sha | PRのHEADコミットSHA | context.payload.pull_request.head.sha | Yes |
| status | Check Runステータス（queued/completed） | ワークフロー実行検出結果 | Yes |
| conclusion | Check Runコンクルージョン（action_required） | ワークフロー未検出時のみ | No |
| check_run_url | Check Run詳細URL | フォークリポジトリのCheck Run情報 | No |
| actions_url | GitHub Actions実行URL | フォークリポジトリのActions実行情報 | No |
| run_id | ワークフロー実行ID | フォークリポジトリのワークフロー検索結果 | No |
| owner | フォークリポジトリのオーナー | context.payload.pull_request.head.repo.owner.login | Yes |
| repo | フォークリポジトリ名 | context.payload.pull_request.head.repo.name | Yes |
| branch | PRのソースブランチ名 | context.payload.pull_request.head.ref | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| GitHub Webhook | pull_request_target: opened | PRが新規オープンされた時 | 新しいPRが作成された場合 |
| GitHub Webhook | pull_request_target: reopened | PRが再オープンされた時 | クローズされたPRが再オープンされた場合 |
| GitHub Webhook | pull_request_target: synchronize | PRに新コミットがプッシュされた時 | 既存のPRに新しいコミットが追加された場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| HEADコミットSHAの不一致（ワークフロー実行）| runs.data.workflow_runs[0].head_sha != context.payload.pull_request.head.sha の場合、Error("There was a new unsynced commit pushed.")をスロー |
| HEADコミットSHAの不一致（Check Run）| check_run_head.head_sha != context.payload.pull_request.head.sha の場合、Error("There was a new unsynced commit pushed.")をスロー |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[pull_request_target イベント発火] --> B[3秒待機]
    B --> C[フォークリポジトリのbuild_main.ymlワークフロー実行を検索]
    C --> D{ワークフロー実行が見つかった?}
    D -->|No| E[action_requiredのCheck Run作成]
    E --> F[ガイダンス表示: GitHub Actions有効化/ブランチ同期]
    D -->|Yes| G{head_shaが一致?}
    G -->|No| H[Error: 新しいコミットがプッシュされた]
    G -->|Yes| I[Check Run情報を検索 - 最大3回リトライ]
    I --> J{Check Run検出成功?}
    J -->|No| K[Error: Check Run検出失敗]
    J -->|Yes| L{check_run_head.head_shaが一致?}
    L -->|No| H
    L -->|Yes| M[Check Run URL構築]
    M --> N[queuedステータスのCheck Run作成]
    N --> O[テスト結果リンク付きで通知完了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| GitHub Actions API（workflow_runs） | フォークリポジトリのワークフロー実行を検索 | `GET /repos/:owner/:repo/actions/workflows/:id/runs?&branch=:branch` |
| GitHub API（check_runs） | コミットに関連するCheck Runを検索 | `GET /repos/:owner/:repo/commits/:ref/check-runs?per_page=100` |

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

#### GitHub Actions API（workflow_runs）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| workflow_runs[0].id | ワークフロー実行ID | build_main.ymlのワークフロー実行をブランチ名で検索 |
| workflow_runs[0].head_sha | ワークフロー実行のHEADコミットSHA | PRのhead_shaと一致確認 |

#### GitHub API（check_runs）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| check_runs[].name | Check Run名 | "Run / Check changes"に一致するものを検索 |
| check_runs[].id | Check Run ID | Check Run詳細URLの構築に使用 |
| check_runs[].head_sha | Check RunのHEADコミットSHA | PRのhead_shaと一致確認 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| GitHub Check Runs API | CREATE | 本体リポジトリにCheck Runを作成 |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| CREATE | name | "Build" | Check Run名 |
| CREATE | head_sha | PRのhead_sha | 対象コミット |
| CREATE | status | "queued" または "completed" | ワークフロー検出有無で分岐 |
| CREATE | conclusion | "action_required" | ワークフロー未検出時のみ |
| CREATE | output.title | "Test results" / "Workflow run detection failed" | ワークフロー検出有無で分岐 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ワークフロー未検出 | フォークリポジトリにbuild_main.ymlの実行が見つからない | action_requiredのCheck Runを作成し、ガイダンスを表示 |
| HEADコミットSHA不一致 | ワークフロー実行のhead_shaがPRのhead_shaと異なる | Error("There was a new unsynced commit pushed.")をスロー |
| Check Run検出失敗 | 3回リトライ後もCheck Run（"Run / Check changes"）が見つからない | Error("Failed to retrieve check_run_head after 3 attempts")をスロー |
| APIアクセスエラー | GitHub APIへのリクエストが失敗した場合 | catch(error)でエラーログを出力し、ワークフロー未検出として扱う |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 初回の3秒待機 + Check Run検出の最大3回リトライ（3秒間隔） |
| リトライ間隔 | 3秒（3000ms） |
| リトライ対象エラー | Check Run（"Run / Check changes"）の検出失敗 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | GitHub APIのレート制限に従う |
| 1日あたり上限 | GitHub APIのレート制限に従う |

### 配信時間帯

配信時間帯の制限なし。PRイベント発生時に即時実行される。

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

- `pull_request_target`イベントを使用しているため、ワークフローはベースブランチ（main/master）のコードで実行される。これにより、フォークリポジトリからの悪意あるワークフロー変更による攻撃を防止する
- `secrets.GITHUB_TOKEN`を使用してCheck Runを作成する。このトークンはリポジトリスコープに限定されている
- パーミッションは`actions: read`（フォークリポジトリのワークフロー実行情報読み取り）と`checks: write`（Check Run作成）に最小限に制限されている
- フォークリポジトリの情報（owner, repo, run_id）がCheck Runのoutput.textにJSON形式で含まれるが、セキュリティ上のリスクは限定的

## 備考

- ワークフロー名は「On pull request update」であるが、ジョブ名として「Notify test workflow」が使用されている。これは、GitHubのCheck Suiteがランダムにワークフローを選択する既知の制約に対応するためである（参照: notify_test_workflow.yml 20-24行目のコメント）
- `build_main.yml`がワークフロー検出対象のIDとして使用されている（行52）
- Check Run検出時に「Run / Check changes」という名前のCheck Runを探す（行122）。これはSPARK-37879に関連し、Actions viewではなくCheck Run viewを提供するための対応である
- テストワークフロー検出の初回待機時間は3秒（行65）であり、フォークリポジトリのワークフロー起動を待つ最小限の待機である

---

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

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

### 推奨読解順序

#### Step 1: ワークフロー定義を理解する

GitHub Actionsワークフローファイルの全体構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 25-28行目: トリガー定義（pull_request_target: opened, reopened, synchronize） |
| 1-2 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 30-36行目: ジョブ定義（name: Notify test workflow, runs-on: ubuntu-latest, permissions） |

**読解のコツ**: `pull_request_target`は通常の`pull_request`と異なり、ベースブランチのワークフロー定義で実行される。これはセキュリティ上の理由によるもの。

#### Step 2: ワークフロー検出ロジックを理解する

フォークリポジトリのワークフロー実行を検出するロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 43-58行目: APIエンドポイントの定義とパラメータ設定 |
| 2-2 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 64-73行目: 3秒待機後にワークフロー実行を検索。APIエラー時はrunsをundefinedとして扱う |

**主要処理フロー**:
1. **43行目**: `GET /repos/:owner/:repo/actions/workflows/:id/runs?&branch=:branch` エンドポイント定義
2. **49-54行目**: フォークリポジトリのowner, repo, branch, workflow_id(build_main.yml)でパラメータ構築
3. **65行目**: `await new Promise(r => setTimeout(r, 3000))` で3秒待機
4. **68-73行目**: GitHub APIでワークフロー実行を取得。エラー時はcatchで処理

#### Step 3: Check Run作成ロジックを理解する

ワークフロー検出/未検出時のCheck Run作成を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 79-109行目: ワークフロー未検出時の処理。action_requiredのCheck Run作成、ガイダンスメッセージ構築 |
| 3-2 | notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 110-168行目: ワークフロー検出時の処理。head_sha一致確認、Check Run検索（最大3回リトライ）、queuedのCheck Run作成 |

**主要処理フロー**:
- **79行目**: `!runs || runs.data.workflow_runs.length === 0` でワークフロー未検出判定
- **83-109行目**: `github.rest.checks.create()` でaction_requiredのCheck Run作成
- **111行目**: `runs.data.workflow_runs[0].id` でワークフロー実行IDを取得
- **113行目**: `head_sha`の一致確認
- **120-130行目**: Check Run検索のリトライループ（最大3回、3秒間隔）
- **151-167行目**: `github.rest.checks.create()` でqueuedのCheck Run作成

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

```
GitHub pull_request_target event (opened/reopened/synchronize)
    |
    +-- notify_test_workflow.yml
            |
            +-- actions/github-script@v7
                    |
                    +-- setTimeout(3000)  [3秒待機]
                    |
                    +-- github.request(workflow_runs_endpoint)  [ワークフロー実行検索]
                    |       |
                    |       +-- (成功) runs取得
                    |       +-- (失敗) runsをundefinedとして扱う
                    |
                    +-- [分岐: runs未検出]
                    |       |
                    |       +-- github.rest.checks.create()  [action_required Check Run作成]
                    |
                    +-- [分岐: runs検出]
                            |
                            +-- head_sha一致確認
                            |
                            +-- github.request(check_runs_endpoint)  [Check Run検索, 最大3回]
                            |       |
                            |       +-- "Run / Check changes" Check Run検索
                            |
                            +-- head_sha一致再確認
                            |
                            +-- github.rest.checks.create()  [queued Check Run作成]
```

### データフロー図

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

pull_request_target       --->  notify_test_workflow.yml        --->  GitHub Check Run
  |                              |                                       |
  +-- PR head_sha         --->  ワークフロー実行検索             --->  [検出時] queued Check Run
  +-- PR head.ref         --->  Check Run検索（3回リトライ）     --->    - title: "Test results"
  +-- PR head.repo.owner  --->  URL構築                          --->    - summary: リンク
  +-- PR head.repo.name                                          --->    - details_url: Actions URL
                                                                  |
                                                                  --->  [未検出時] completed Check Run
                                                                         - conclusion: action_required
                                                                         - title: "Workflow run detection failed"
                                                                         - summary: ガイダンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notify_test_workflow.yml | `.github/workflows/notify_test_workflow.yml` | 設定 | ワークフロー定義。通知ロジック全体を含む |
| build_main.yml | `.github/workflows/build_main.yml` | 設定 | 検出対象のテストワークフロー |
| workflow-enable-button.png | `.github/workflows/images/workflow-enable-button.png` | 画像 | ワークフロー未検出時のガイダンスに表示される画像 |
