# オンコール体制 — On-Call SSoT

> **このドキュメントが Parky における on-call 運用の SSoT (Single Source of Truth)。**
> Severity 区分・Channel 振り分け・Format 標準は
> [notification-strategy.md](./notification-strategy.md) に従う。本 doc は
> 「**誰が、どの device で、何分以内に、何をする**」を扱う。
>
> 想定読者: Parky 開発担当本人 (1 人運用フェーズ)、将来チーム拡大時の新 joiner。

---

## 1. 設計原則 — 1 行で

**1 人運用 + Discord push 二重化** で「P0 を 15 分以内に必ず ack できる」状態を作る。
PagerDuty / Opsgenie 等の専用 incident management SaaS は **MAU 1 万到達まで導入しない** (コスト判断)。
代わりに *Discord mobile push + GH Actions failure email + Cloudflare email alert* を物理経路を分けて二重化し、SPOF を作らない。

参照: [project_parky_finance_state_2026_04](../../.claude/memory/parky/project_parky_finance_state_2026_04.md)
(残予算 2,000 万 / Burn 120 / Runway 16.7 ヶ月 → 月額 SaaS 追加は最低限)

---

## 2. ロール定義

### 2.1 Primary on-call

| 項目             | 内容                                                                           |
| ---------------- | ------------------------------------------------------------------------------ |
| **氏名**          | Parky 開発担当 (当面 1 人体制)                                                  |
| **連絡先**        | `dev@parky.co.jp` (email backup 配信先と一致)                                   |
| **受信 device 1** | iPhone Discord アプリ (`#p0-alerts` の push 通知 ON、機内モード時は失敗扱い) |
| **受信 device 2** | デスクトップ Discord (作業時間中、音声通知 ON)                                   |
| **受信 device 3 (backup)** | Gmail (`dev@parky.co.jp`) — Resend 経由の P0 backup email + GH Actions failure email |
| **勤務時間外**    | 当面 24/7 自分。睡眠中の P0 は「起きてから対応」を許容 (§3 参照)                |

### 2.2 Secondary on-call

- **現状: 不在** (1 人運用)
- **将来 (Phase 3, チーム拡大時)**: rotation を組む。週次担当を `#p1-ops` の channel topic に明示。

### 2.3 担当範囲

| 領域                              | Primary | Secondary |
| --------------------------------- | :-----: | :-------: |
| API (Workers / Hyperdrive / R2)   |   o     |     -     |
| Web (Pages / public / portal)     |   o     |     -     |
| Supabase DB / migration           |   o     |     -     |
| Cron (PLACES_WEEKLY 等)            |   o     |     -     |
| Mobile (Flutter リリース後)       |   o     |     -     |
| Security (secret leak / CVE)      |   o     |     -     |

---

## 3. 応答 SLA (severity 別)

| Severity | 応答 SLA            | 解決目標         | 備考                                                                |
| -------- | ------------------- | ---------------- | ------------------------------------------------------------------- |
| **P0**   | **15 分以内 ack**   | 4 時間以内 復旧  | Discord で「対応開始」reply or `:eyes:` reaction。睡眠中も起きたら最優先 |
| **P1**   | **4 時間以内**      | 翌営業日 中      | 営業時間内のみ、夜間は朝対応 OK                                       |
| **P2**   | **翌営業日**        | 1 週間以内       | 朝の Discord チェックでまとめて triage                               |
| **P3**   | **週次 review**     | 月次 review      | 月曜 18:00 JST の `#p3-insights` digest をまとめて確認            |

### Ack の定義 (P0)

- **Ack** = 「自分が認識して対応に入った」を Discord で表明すること
- 具体的には次のいずれか (どれか 1 つで OK):
  1. Alert message に `:eyes:` reaction を付ける
  2. Alert message に reply で「対応開始」「調査中」等を投稿
  3. `#p0-alerts` で新規 message を投稿し、`status` を共有

> **Ack ≠ 解決**。Ack は「見えたよ」のシグナル。解決時は別途 thread で `:white_check_mark:` reaction or 「resolved」reply。

---

## 4. P0 受信の二重化 (重要)

監査指摘: 「アラートが Discord のみ → P0 を 7 日後に発見した PLACES_WEEKLY 失敗の前例あり」
→ **物理経路を分けた 3 経路冗長化** で対策する。

### 経路 A: Discord `#p0-alerts` への `@here` mention (一次)

- **配線元**: `synthetic-healthcheck.yml` / Sentry alert rule / GHA failure / DLQ monitor
- **形式**: [notification-strategy.md §5](./notification-strategy.md) の Discord embed format に従う
- **mention 形式**: P0 のみ `@here\n:rotating_light: **[P0] ...**` (mention は別行)
  参照: [feedback_discord_p0_mention_newline](../../.claude/memory/parky/feedback_discord_p0_mention_newline.md)

### 経路 B: iPhone Discord アプリの push 通知 (一次の届け先)

これが「夜中に音で気付く」物理経路。**最重要**。

#### iPhone 設定手順 (初回のみ、5 分)

1. **iOS 設定 → 通知 → Discord**
   - 通知許可: ON
   - サウンド: ON (集中モード時も鳴らすために重要)
   - バッジ: ON
   - ロック画面 / 通知センター / バナー: 全 ON
2. **Discord アプリ → ユーザー設定 (歯車) → 通知**
   - Push Notifications: Enabled
   - In-App Sounds: Enabled
3. **Discord → Parky サーバー → `#p0-alerts` channel**
   - channel 名長押し → Notification Settings
   - 設定: **All Messages**
   - Mute: **Off** (絶対 mute しない)
   - `@everyone` and `@here`: **Allow** (デフォルト ON、これを切ると意味なし)
4. **iOS 集中モード (Focus / Sleep) の例外設定**
   - 設定 → 集中モード → 睡眠 → 通知を許可するアプリ → **Discord を追加**
   - これがないと睡眠モード中の P0 通知が無音化される (致命的)
5. **動作確認**
   - 月次 smoke workflow (`.github/workflows/oncall-smoke.yml` 参照、§7 参照) で iPhone に通知が届くことを確認

#### `#p1-ops` (P1) の push 設定

- channel 名長押し → Notification Settings → **Only @mentions**
- P1 は mention なしなので push は飛ばないが、Discord アプリを開けば未読が見える状態を維持

### 経路 C: Email backup (二次、Discord 障害時の保険)

#### C-1. Resend 経由 P0 backup email

- 配線: [notification-strategy.md §7](./notification-strategy.md) Layer 3
- 条件: severity = P0 かつ Discord webhook 全 retry 失敗時のみ送信
- 送信先: `dev@parky.co.jp` (固定)
- Subject: `[P0 BACKUP] <title>`
- iOS Mail app の通知を ON にしておけば push で気付ける

#### C-2. GitHub Actions failure email (built-in)

GitHub の標準機能で workflow failure を email 通知。Discord webhook が落ちている場合の最終ラインとして稼働。

**設定手順 (GitHub.com)**:

1. github.com → 右上アバター → **Settings** → **Notifications** (左 sidebar)
2. **Actions** セクション
   - Email: ON
   - Notify me for: **Only failed workflows** にチェック
   - Send to: `dev@parky.co.jp` (primary email)
3. iOS Gmail / Mail アプリの通知 ON

> **注意**: GH の email 通知は per-user 設定。複数人運用時は全員が個別に設定する。

#### C-3. Cloudflare email alert (任意、Phase 2 で導入検討)

- CF dashboard → Notifications → Add → 各 alert (Workers outage / Pages deploy fail / R2 quota / WAF surge)
- Destination: email (`dev@parky.co.jp`)
- 既に [notification-strategy.md §4](./notification-strategy.md) に webhook 配線済みだが、CF 側 webhook 障害時の保険として **email を multi-destination で並走** させる
- 工数: CF dashboard で 10 分

### 経路の優先度と SLA

| 経路                       | 想定遅延     | 障害時の挙動                                |
| -------------------------- | ------------ | ------------------------------------------- |
| A: Discord webhook         | < 5 sec      | Layer 2 retry → 失敗時 Layer 3 へ            |
| B: iPhone Discord push     | < 30 sec     | 経路 A 成功時のみ。Discord アプリ次第        |
| C-1: Resend backup email   | 1-3 min      | 経路 A 全失敗時のみ起動                      |
| C-2: GHA failure email     | 1-5 min      | GHA workflow 失敗時に常時並走                |
| C-3: CF email (任意)       | 1-2 min      | CF 由来の P0 のみ、CF Discord webhook と並走 |

---

## 5. エスカレーション手順

### 5.1 1 人運用フェーズ (現状) のフロー

```
P0 alert 発火
  ↓
  ├─ 起きていて気付いた? → 即 ack → 対応 → resolve
  ├─ 寝ていて気付かない? → iPhone 経路 B push で起こす
  ├─ iPhone も気付かなかった (集中モード等) → 経路 C-1 / C-2 email backup で起こす
  └─ 全経路失敗 (確率限りなく 0) → 翌朝の DLQ digest (#p3-insights, 月曜 18:00 JST) で発見
                                  → postmortem 必須 (経路の見直し)
```

### 5.2 即時必要な P0 vs 翌日対応 OK な P1/P2 の判別フロー

「夜中の Discord 通知音で起きた」状況での 30 秒判断:

```
通知タイトルの先頭を見る
  ↓
  ├─ [P0] → 起きて即対応 (ack 15 分以内)
  ├─ [P1] → アプリで内容だけ確認、朝対応で OK (mention なしで起こされる事はない想定)
  └─ [P2] [P3] → 翌朝
```

> **重要**: P0 の判定は alert rule 作成時に確定済み ([notification-strategy.md §2](./notification-strategy.md))。
> 通知音で起きてから「これ P0?」と悩むのは時間の無駄。先頭の `[P0]` 表記だけを信じる。

### 5.3 Primary が応答できない場合

#### 1 人運用フェーズ (現状)

- **不在パターン 1: 短期不在 (旅行 / 休暇 < 1 週間)**
  - 事前: `#p1-ops` の channel topic に「YYYY-MM-DD ~ YYYY-MM-DD 不在、緊急時は status page で告知」と明記
  - 期間中: 自動 maintenance window にはせず、P0 のみは可能な限り対応 (海外旅行中も iPhone は持つ)
  - 期間中の P1 以下: 帰宅後に triage、ユーザー影響継続中なら status page 告知 ([sentry 流ステータスページ](https://status.stripe.com) を将来構築)

- **不在パターン 2: 緊急不能 (病気 / 事故)**
  - 第三者 (家族 / 共同創業者候補) が `#p0-alerts` を見られる状態を作る (Discord ID 共有)
  - 第三者の役割: 「alert が来ている」事実だけ気付く役。技術対応はできなくて OK
  - 第三者経由でユーザー告知 → 復旧は復帰後

#### Phase 3 (チーム拡大後)

- Primary が 30 分以内に ack しない場合、Secondary を escalate
- Discord webhook で `@<secondary-user>` mention を 30 分後に自動 fire (Phase 3 で実装、現状未配線)
- 週次 rotation を `#p1-ops` channel topic に固定明記

---

## 6. 月次レビュー

### 6.1 レビュー時刻

- **毎月 1 日 09:00 JST** (土日祝なら次の平日)
- Calendar に reminder 登録 (Google Calendar 等)
- 所要時間: 30-60 分

### 6.2 レビュー内容

過去 30 日を対象に、以下を `.work/parky/oncall-review/YYYY-MM-DD_oncall_review.md` に記録:

| 項目                    | 確認内容                                                                                  |
| ----------------------- | ----------------------------------------------------------------------------------------- |
| **発火した P0 件数**    | `admin.notification_failures` + Sentry alerts dashboard + GHA run history を集計           |
| **応答時間の中央値**    | 各 P0 の「発火 → ack」までの時間 (Discord reaction timestamp で測定)                       |
| **SLA 達成率**          | 15 分以内 ack の割合 (目標 100%)                                                          |
| **見逃し / 遅延 alert** | 30 分以上 ack 遅延した P0 を全件列挙 → 原因分析 (デバイス / 設定 / 経路 / 集中モード)        |
| **抜けた監視**          | 「あの障害が alert で気付けなかった」事象を列挙 → 該当 alert rule を追加                    |
| **誤検知 (false alarm)**| P0 で発火したが実害なかった事象 → 閾値見直し                                              |
| **抑制ミス**            | Cascade 抑制が効かず重複通知になった事象                                                  |
| **DLQ 蓄積件数**        | `admin.notification_failures` で `status='failed'` の件数 (目標 < 5/月)                    |
| **smoke test 結果**     | `oncall-smoke.yml` (§7) の月次実行結果 (push が iPhone に届いたか)                          |

### 6.3 SLO Error Budget との連動

- [slo-error-budget.md](./slo-error-budget.md) の burn rate と本レビューの SLA 達成率を併せて見る
- どちらかが目標未達 → 次月の優先タスク (例: alert rule 追加 / 閾値再調整 / 経路強化)

### 6.4 Action Item 起票

- 改善項目は GitHub Issue で起票 (label: `oncall-review`, milestone: 翌月)
- SMART 形式: Owner / 期限 / 検証方法を明記 ([incident-response.md](./incident-response.md) 同準拠)

---

## 7. 受信確認用 smoke workflow (任意)

P0 push が本当に iPhone に届くかを月 1 回検証する。

### 7.1 配線

- Workflow: [.github/workflows/oncall-smoke.yml](../../.github/workflows/oncall-smoke.yml)
- Cron: 毎月 1 日 09:00 JST (`0 0 1 * *` UTC)
- 配信先: `#p0-alerts` (P0 channel) に `@here` 付き smoke message
- 形式: `[SMOKE] On-call 受信確認 YYYY-MM-DD` + 「iPhone push が届いた? Discord で `:eyes:` reaction を付けてください」

### 7.2 確認手順

1. 月初にこの smoke message が iPhone に push される
2. push 通知音で気付いたら `:eyes:` reaction を付ける
3. 24 時間以内に reaction が付かない → 翌月の月次レビューで「設定見直し」を action item 化

### 7.3 手動 trigger

緊急時 (デバイス変更後等) は workflow_dispatch で即時実行可能:

```bash
gh workflow run oncall-smoke.yml
```

---

## 8. 移行プラン

### Phase 1 — 現状 (~MAU 1 万)

- **コスト**: $0/mo
- **構成**: Discord webhook + iPhone push + Resend backup email + GHA email + (任意) CF email
- **想定 SLA**: P0 15 分 ack 95% 以上

### Phase 2 — MAU 1 万到達後 (Parky [phase_strategy](../../.claude/memory/parky/project_parky_phase_strategy.md) Phase 1 中盤)

- **PagerDuty Pro plan 検討** ($21/user/mo)
  - 機能: SMS / 電話 escalation / on-call rotation / 自動 escalation
  - 採用条件: Discord push の miss 率 > 5% (月次レビュー集計) または P0 件数 > 月 5 件
  - 不採用条件: 上記未達なら継続して Discord メイン
- **Status page 構築** (Better Uptime Free or Atlassian Statuspage Free)
  - customer-facing 透明性の向上 (status.parky.co.jp)
  - on-call 不在時のユーザー告知導線

### Phase 3 — チーム拡大後 (2-3 人体制)

- **Rotation 開始**: 週次 primary / secondary を `#p1-ops` topic で明記
- **自動 escalation 配線**: Primary が 30 分以内 ack しないと Secondary に mention
- **PagerDuty 本格運用**: rotation を PagerDuty 側で管理、Discord は配信先の 1 つに格下げ
- **月次レビュー**: チーム全員参加、postmortem は必ず blameless

---

## 9. 関連 doc

このドキュメントが on-call 運用 SSoT。以下は関連 runbook / 設計:

- [notification-strategy.md](./notification-strategy.md) — 通知配線 SSoT (severity / channel / format / DLQ)
- [incident-response.md](./incident-response.md) — P0/P1 発火後の対応コマンド集 + エスカレーションパス
- [postmortem-template.md](./postmortem-template.md) — P0 解決後の postmortem 雛形
- [slo-error-budget.md](./slo-error-budget.md) — SLO 定義 + burn rate alert + 月次レビュー連動
- [synthetic-healthcheck.md](./synthetic-healthcheck.md) — 5 分粒度の外部監視 (P0 経路 A の主要発火源)
- [secret-rotation.md](./secret-rotation.md) — Webhook / email provider key の rotation 手順

### 監査根拠

- 2026-04-29 監査指摘: 「Discord 単一経路で P0 見逃しリスク (PLACES_WEEKLY 7 日後発見の前例)」
  → 本 doc 作成で iPhone push + email backup の二重化を文書化

### 変更履歴

- 2026-04-30: 初版作成。M-4 監査対応 (オンコール体制 doc 整備)。
  Discord + iPhone push + email 三重化、月次レビュー、PagerDuty 移行プランを定義。
