# バッチ設計書 25-functions/publish.js

## 概要

本ドキュメントは、Decap CMSプロジェクトにおけるSlackコマンド経由でGitHub Actionsのパブリッシュワークフローをトリガーするサーバーレス関数 `functions/publish.js` の設計を記述する。

### 本バッチの処理概要

本スクリプトは、Slack Slash CommandのWebhookを受信し、Slack署名検証・ユーザー認可チェックを行った上でGitHub APIのrepository dispatchイベントを発行するサーバーレス関数である。これにより、承認されたチームメンバーがSlackからパッケージ公開を安全にトリガーできる。

**業務上の目的・背景**：Decap CMSのパッケージ公開プロセスを、特定の権限を持つチームメンバーがSlackから簡単かつ安全にトリガーできるようにする。直接GitHub UIにアクセスすることなく、日常的に使用するSlackインターフェースからリリースを開始でき、運用効率を向上させる。同時に、署名検証とユーザー認可により不正なリリースの防止を図る。

**バッチの実行タイミング**：Slack Slash Command受信時にオンデマンドで実行される。Slackワークスペースで設定されたコマンド（環境変数 `PUBLISH_COMMAND` で定義）が実行されたタイミングでトリガーされる。

**主要な処理内容**：
1. Slackリクエストの署名検証（HMAC-SHA256、タイムスタンプ検証含む）
2. リクエストボディからSlackコマンドとユーザーIDの抽出
3. ユーザー認可チェック（許可ユーザーリストとの照合）
4. 期待されるSlashコマンドとの一致確認
5. GitHub APIへのrepository dispatch イベント送信（`on-demand-github-action`）

**前後の処理との関連**：本関数が発行するrepository dispatchイベント（`on-demand-github-action`）は、GitHub Actionsワークフロー（`.github/workflows/publish.yml` 等）でキャッチされ、パッケージのビルド・テスト・npmへの公開が実行される。

**影響範囲**：GitHub Actionsワークフローのトリガー、npmレジストリへのパッケージ公開プロセスの起動。

## バッチ種別

Serverless Function（Slack連携パブリッシュトリガー）

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 随時（Slack Slash Commandイベント駆動） |
| 実行時刻 | N/A |
| 実行曜日 | N/A |
| 実行日 | N/A |
| トリガー | Slack Slash Command Webhook |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| SLACK_SIGNING_SECRET | 環境変数にSlack署名検証用のシークレットが設定されていること |
| GITHUB_TOKEN | 環境変数にGitHub APIアクセス用のトークンが設定されていること |
| GITHUB_REPO | 環境変数にターゲットリポジトリ（owner/repo形式）が設定されていること |
| PUBLISH_COMMAND | 環境変数に許可するSlashコマンド名が設定されていること |
| ALLOWED_USERS | 環境変数にカンマ区切りの許可ユーザーIDリストが設定されていること |
| Slackアプリ設定 | Slack AppのSlash CommandがこのFunctionエンドポイントに向けて設定されていること |

### 実行可否判定

以下の条件を全て満たす場合に処理を続行する：
1. Slack署名が有効であること（タイムスタンプが5分以内、HMAC-SHA256一致）
2. リクエスト送信ユーザーが許可ユーザーリストに含まれること
3. 実行されたSlashコマンドが `PUBLISH_COMMAND` と一致すること

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| event.headers['x-slack-request-timestamp'] | string | Yes | なし | Slackリクエストのタイムスタンプ |
| event.headers['x-slack-signature'] | string | Yes | なし | Slackリクエストの署名 |
| event.body | string | Yes | なし | URLエンコードされたSlackコマンドパラメータ |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| Slack Webhook | HTTP POST | Slash Commandのリクエストボディ（URLSearchParams形式） |
| 環境変数 | 文字列 | SLACK_SIGNING_SECRET, GITHUB_TOKEN, GITHUB_REPO, PUBLISH_COMMAND, ALLOWED_USERS |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| HTTPレスポンス（成功） | JSON | `{ status: 200, body: "Dispatched event to GitHub" }` |
| HTTPレスポンス（失敗） | JSON | `{ status: 401, body: "Unauthorized" }` |
| GitHub API | HTTP POST | repository dispatchイベント（`event_type: 'on-demand-github-action'`） |

### 出力ファイル仕様

本関数はファイル出力を行わない。HTTPレスポンスおよびGitHub API呼び出しのみ。

| 項目 | 内容 |
|-----|------|
| ファイル名 | N/A |
| 出力先 | N/A |
| 文字コード | N/A |
| 区切り文字 | N/A |

## 処理フロー

### 処理シーケンス

```
1. リクエスト情報のログ出力
   └─ event, 環境変数（PUBLISH_COMMAND, ALLOWED_USERS, GITHUB_REPO）をJSON形式で出力
2. Slack署名検証（verifySignature）
   ├─ タイムスタンプが現在時刻から5分以内であることを確認
   ├─ v0:{timestamp}:{body} 形式の署名文字列を生成
   ├─ HMAC-SHA256でハッシュを計算
   └─ crypto.timingSafeEqual で署名を比較
3. リクエストボディの解析
   └─ URLSearchParamsでcommandとuser_idを取得
4. ユーザー認可チェック
   ├─ ALLOWED_USERSをカンマ区切りで分割
   └─ user_idが許可リストに含まれるか確認（大文字小文字区別なし）
5. コマンド検証
   └─ 受信したcommandがPUBLISH_COMMANDと一致するか確認
6. GitHub Dispatchイベント送信
   ├─ axiosでPOSTリクエスト送信
   ├─ URL: https://api.github.com/repos/{repo}/dispatches
   └─ data: { event_type: 'on-demand-github-action' }
7. 成功レスポンス返却
   └─ status: 200, body: 'Dispatched event to GitHub'
```

### フローチャート

```mermaid
flowchart TD
    A[Slack Webhook受信] --> B[リクエスト情報ログ出力]
    B --> C{署名検証}
    C -->|失敗| D[401 Unauthorized]
    C -->|成功| E[コマンド・ユーザーID抽出]
    E --> F{ユーザー認可チェック}
    F -->|非許可| D
    F -->|許可| G{コマンド一致確認}
    G -->|不一致| D
    G -->|一致| H[GitHub Dispatch API呼び出し]
    H --> I[200 成功レスポンス]
```

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

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

本関数はデータベース操作を行わない。

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| N/A | N/A | N/A | データベース操作なし |

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

N/A

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 署名タイムスタンプ期限切れ | リクエストのタイムスタンプが5分以上前 | Slack Appの設定を確認し、リトライする |
| 401 | 署名不一致 | Slack署名の検証に失敗 | SLACK_SIGNING_SECRETの設定を確認する |
| 401 | ユーザー非許可 | 実行ユーザーがALLOWED_USERSに含まれない | ALLOWED_USERSにユーザーIDを追加する |
| 401 | コマンド不一致 | 受信コマンドがPUBLISH_COMMANDと一致しない | Slack Appのコマンド設定を確認する |
| 401 | GitHub API呼び出し失敗 | axiosリクエストが失敗 | GITHUB_TOKEN, GITHUB_REPOの設定を確認する |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | N/A（関数内でのリトライ機構なし。Slackから再実行可能） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

### 障害時対応

関数が401を返した場合、console.logに出力されたエラー情報をサーバーレスプラットフォームのログで確認する。署名検証エラーの場合はSLACK_SIGNING_SECRET、認可エラーの場合はALLOWED_USERS、API呼び出しエラーの場合はGITHUB_TOKEN/GITHUB_REPOの設定をそれぞれ確認する。

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | N/A（ステートレスなHTTPリクエスト処理） |
| コミットタイミング | N/A |
| ロールバック条件 | N/A |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 1リクエスト/実行 |
| 目標処理時間 | 3秒以内（Slackの応答タイムアウトに準拠） |
| メモリ使用量上限 | サーバーレスプラットフォームのデフォルト設定に依存 |

## 排他制御

排他制御は不要。各リクエストは独立して処理されるステートレス関数である。ただし、GitHub Dispatchイベントが重複して発行された場合、GitHub Actions側で複数のワークフロー実行が同時に開始される可能性がある。

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| リクエストログ | 関数実行開始時 | event全体、環境変数（PUBLISH_COMMAND, ALLOWED_USERS, GITHUB_REPO）をJSON形式で出力 |
| エラーログ | エラー発生時 | catch節で捕捉されたエラーオブジェクトをconsole.logで出力 |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 401レスポンス率 | 連続5回以上 | サーバーレスプラットフォームのアラート機能 |
| レスポンス時間 | 3秒超過 | サーバーレスプラットフォームのアラート機能 |

## 備考

- 本関数はNetlify Functions等のサーバーレスプラットフォーム上での実行を想定している
- Slack署名検証にはcrypto.timingSafeEqualを使用しており、タイミング攻撃への耐性がある
- GitHub API呼び出しにはaxiosライブラリを使用している
- エラー発生時は全て401ステータスで `Unauthorized` を返すため、Slack側ではエラーの詳細原因を特定できない（セキュリティ上の配慮）
- ALLOWED_USERSのユーザーIDはSlackのuser_id形式（例：U01234567）であり、大文字小文字を区別しない比較が行われる
- GitHub Dispatch APIのevent_typeは `on-demand-github-action` に固定されている
