# 通知戦略 — Severity / Channel / Source 設計

> **このドキュメントが Parky における通知設計の SSoT (Single Source of Truth)。**
> 個別 source の手順 (Sentry alert rule / Synthetic healthcheck / Incident response) は
> 別 doc に詳細を持つが、Severity 区分・Channel 振り分け・Format 標準・Anti-pattern は
> すべて本 doc に従う。新規 alert を追加するときも本 doc の 4 軸 (severity / channel /
> format / dedup) を必ず守ること。

---

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

**Severity 4 段 (P0/P1/P2/P3) を Discord 4 channel に 1 対 1 マッピング**。各 source は severity に応じて自動振り分け。Stripe 流 SLA + Linear 流 silent-by-default + Cloudflare 流 native notification のハイブリッド。1 人運用 / pre-launch に最適化。

---

## 2. Severity Taxonomy

| Severity | 名称     | 判断基準                                                       | SLA            | Mention            |
| -------- | -------- | -------------------------------------------------------------- | -------------- | ------------------ |
| **P0**   | Critical | 本番 user 影響 / データ損失 / セキュリティ侵害 / 手動復旧必須  | 15 分以内       | `@here` 必須        |
| **P1**   | High     | 本番一部影響 or 業務支障、放置で P0 化、自動 retry でしのげる   | 4 時間以内      | なし                |
| **P2**   | Info     | 確認必要だが緊急ではない、自動回復済 or 進捗通知                | 翌営業日        | なし                |
| **P3**   | Digest   | 個別 alert は不要、定期サマリーで把握すれば十分                  | 週次 review     | なし                |

### 判定フロー

1. **本番 user に影響あるか?** → Yes なら最低 P1、影響大 (全停止 / データ損失) なら P0
2. **放置で P0 化するか?** → Yes なら P1
3. **個別に見る価値あるか?** → Yes なら P2、No なら P3 (digest)

> **1 人運用での簡略化**: P0 のみ通知音 ON / mention 有り。P1 はサイレント通知 (見たら対応)。
> P2 / P3 は寝かせて朝チェック。これにより「夜中に起きる必要があるのは P0 のみ」が明確化する。

---

## 3. Discord Channel 設計

通知 channel は **2 軸構成** で設計する:

- **A. Operational alert (4 channel)** — システム異常・運用イベント。Severity (P0/P1/P2/P3) と 1 対 1 マッピング。
- **B. Business workflow event (N channel, topic 別)** — 業務フローのトリガー (新規 task / 申請 / 重要 user action 等)。severity ではなく topic で分類。

両者は **完全に分離** する。Business event を operational channel に混ぜると alert 疲れの典型 anti-pattern (Stripe / Linear で確立された原則)。

### A. Operational alert channels (severity-based)

Severity と 1 対 1 マッピング。1 人運用なら source 別 (Sentry / GHA / CF) 分割よりも severity 別の方が認知負荷が低い (Linear / Vercel 同パターン)。

| Channel               | Severity | Mention      | 想定頻度        | 反応 SLA       |
| --------------------- | -------- | ------------ | --------------- | -------------- |
| `#parky-alerts`       | P0       | `@here` 必須 | 月 0-3 件        | 15 分以内       |
| `#parky-ops`          | P1       | なし          | 週 5-20 件       | 4 時間以内      |
| `#parky-deploys`      | P2       | なし          | 日 2-10 件       | 翌営業日        |
| `#parky-insights`     | P3       | なし          | 週 1 件 + 月 1 件 | 週次 review     |

### B. Business workflow event channels (topic-based)

| Channel               | Topic                              | Severity 相当 | Mention | 想定頻度       |
| --------------------- | ---------------------------------- | ------------- | ------- | -------------- |
| `#parky-admin-tasks`  | 管理者向け新規 task / 緊急 task     | P2 (info)、urgency=high で P1 相当 | なし     | 日 1-10 件      |

> 将来追加候補 (launch 後):
>
> | Channel                    | Topic                            |
> | -------------------------- | -------------------------------- |
> | `#parky-owner-applications` | 新規オーナー申請 / 書類提出       |
> | `#parky-revenue`            | 大口決済 / 売上速報              |
> | `#parky-signups`            | 新規ユーザー登録 (digest)         |
> | `#parky-support`            | サポート chat 着信               |

**Business event channel の設計原則**:

1. **必ず severity を付与** (上表の "Severity 相当" 列) — 同じ format 標準を使い、視覚的に operational alert と一貫させる
2. **1 topic = 1 channel** (混ぜない) — 「タスク全部」じゃなく「admin タスク作成」「owner 申請」のように具体的に
3. **mention は基本 false** — operational P0 だけが mention、business event は mute された情報源
4. **新規 channel 追加時は本 doc の表に追記必須** — channel 数が増えすぎないようゲートする (5+ になったら統合検討)
5. **alert ではなく "event log"** として扱う — 必ずしも対応必須ではなく、業務状況を見渡す目的

### 各 channel の用途まとめ

#### `#parky-alerts` (P0)
本番障害、データ損失、セキュリティ侵害、Critical CVE。15 分以内に確認 + Discord で「対応開始」リアクション or reply。

#### `#parky-ops` (P1)
業務に支障あるが緊急じゃない、修正計画立てる対象。営業時間内、4 時間以内に対応。

#### `#parky-deploys` (P2)
進捗確認、後追いで見ればよい情報。翌営業日に確認 OK、対応不要も多い。

#### `#parky-insights` (P3)
定期サマリー、傾向把握、business KPI。週次 review でまとめて見る。

---

## 4. Source × Channel × Action 完全表

新規 alert を追加するときは必ずこの表に行を足してから実装すること。各行が「① system / ② 頻度 / ③ check / ④ 条件 / ⑤ メッセージ / ⑥ channel / ⑦ action」の 7 軸を満たす。

### P0 → `#parky-alerts`

| ① system | ② 頻度 | ③ check 対象 | ④ 条件 | ⑤ メッセージ内容 | ⑦ アクション |
|---|---|---|---|---|---|
| Sentry (parky-api prod) | 連続 (event drive) | error イベント数 | 5xx が 5分窓で 50件超 (=10/min) | `[P0] parky-api 5xx burst (52/5min)` + Sentry issue link + runbook | 即 Sentry 開く → wrangler tail → Worker rollback 判断 |
| Sentry (全 prod project) | 1時間粒度 | Error budget burn rate | 14.4× (1h で 30日 budget の 2% 焼失) | `[P0] Error budget fast burn 14.4×` + SLO link | SLO doc → 障害源特定 → deploy freeze 検討 |
| Synthetic healthcheck | 5 分 cron | `api.parky.co.jp/healthz` | 連続 2 回 fail (5-10 分継続) | `[P0] prod healthz down` + workflow run link + runbook | wrangler tail prod → Hyperdrive/R2 切り分け → status page 更新 |
| Cloudflare Notifications | リアルタイム | Workers script | platform-side outage | `[P0] CF Workers outage detected` + CF status link | CF status page → ユーザー告知 |
| Cloudflare Notifications | リアルタイム | WAF / DDoS | 大規模攻撃検知 | `[P0] WAF blocking surge` + analytics link | WAF rule 確認 → IP rate limit 判断 |
| Supabase Cron | 1 分粒度 | DB 接続 | prod DB 接続喪失 (test query fail) | `[P0] Supabase prod DB unreachable` + Supabase dashboard | Supabase status → connection pool 確認 |
| GitHub security alert | リアルタイム | secret scan | secret leak detected (push に key 等) | `[P0] Secret leaked in commit <sha>` + GitHub Security tab link | 即 rotate → git history purge 判断 |

### P1 → `#parky-ops`

| ① system | ② 頻度 | ③ check 対象 | ④ 条件 | ⑤ メッセージ内容 | ⑦ アクション |
|---|---|---|---|---|---|
| Sentry | 5 分粒度 | new issue rate | 新規 issue が 100/h 以上 | `[P1] New high-volume issue` + Sentry link | issue triage → fix sprint に追加 |
| Sentry Performance | 1 時間粒度 | p95 latency | 通常比 +50% | `[P1] p95 regression on /v1/...` + Performance tab | trace 確認 → 直近 deploy revert 判断 |
| Synthetic healthcheck | 5 分 cron | `dev-api.parky.co.jp/healthz` | 連続 2 回 fail | `[P1] dev healthz down` + run link | dev wrangler tail → 修正 push |
| GHA (deploy-*-prod) | event drive | workflow conclusion | failure | `[P1] prod deploy failed: <workflow>` + run link + last commit | run log 確認 → re-run or fix → re-merge |
| GHA (E2E / lint / drift) | event drive | main branch 連続 fail | 連続 3 回失敗 | `[P1] CI failing on main: <workflow>` + run link | 安定化 PR 作成、main の merge 凍結 |
| DLQ monitor | 5 分 cron | Cloudflare Queue dead letter | 蓄積 >10 件 | `[P1] DLQ accumulating: <queue> (<n> messages)` + summary | dead letter 内容確認 → re-enqueue or 修正 |
| Cost monitor (週 1 cron) | 週 1 月曜朝 | 各 service quota | 80% 到達 (CF / Sentry / GH Actions / Supabase) | `[P1] <service> quota at 80% (X / Y)` | 使用パターン確認 → optimize or upgrade plan 判断 |
| Cloudflare Notifications | リアルタイム | R2 / KV quota | 80% 到達 | `[P1] R2 storage at 80% (X GB / Y GB)` | 古い object 削除 or plan upgrade |
| Dependabot | event drive | Critical CVE | 新規 Critical CVE 発見 | `[P1] Critical CVE: <package> <version>` + advisory link | PR review → merge 判断 |
| Supabase Cron | 5 分 cron | connection pool | 使用率 >80% | `[P1] Supabase pool 85% (90/100)` | slow query 探索、pool size 拡張判断 |
| Supabase Cron | 1 時間 cron | slow query | p99 >1s | `[P1] Slow query spike: <query digest>` | EXPLAIN → index 追加 |

### P2 → `#parky-deploys`

| ① system | ② 頻度 | ③ check 対象 | ④ 条件 | ⑤ メッセージ内容 | ⑦ アクション |
|---|---|---|---|---|---|
| GHA (deploy-*-prod) | event drive | workflow conclusion | success | `[P2] prod deploy success: <workflow>` + commit / changelog link | 任意で smoke test、通常は確認のみ |
| GHA (deploy-*-dev) | event drive | workflow conclusion | success / failure | `[P2] dev deploy <result>: <workflow>` + run link | dev URL で動作確認 |
| Sentry | event drive | new low-volume issue | 新規 issue (<10/h) | `[P2] New issue: <title>` + Sentry link | 朝確認、優先度判定 |
| Sentry | event drive | regression | 解決済 issue が再発 | `[P2] Regression: <title>` + last fix commit | 該当 commit 調査 |
| Migration apply | event drive | DB migration 適用 | apply success | `[P2] Migration applied: <filename> (env: <env>)` | 適用ログ確認のみ |
| Workers Versioned Uploads (将来) | gradual rollout step | rollout 進捗 | 10% → 50% → 100% 遷移 | `[P2] Rollout progressed: 10%→50% (<version>)` | error rate 増加なければ続行 |
| Dependabot | event drive | High / Medium CVE | 新規 High/Medium CVE | `[P2] High CVE: <package>` + advisory | 翌週の dep update PR にまとめる |
| Cloudflare Notifications | event drive | Pages / Worker deploy fail | platform-side fail | `[P2] CF Pages deploy failed: <project>` + CF dashboard | CF UI で deploy log 確認 |

### Business events → topic-based channels

| ① system | ② 頻度 | ③ check 対象 | ④ 条件 | ⑤ メッセージ内容 | ⑥ channel | ⑦ アクション |
|---|---|---|---|---|---|---|
| BFF (`bff/admin/tasks.ts`) | event drive | admin_tasks INSERT | `is_new=true` (xmax = 0 で判定) | `[P2] Admin task created: <task_kind>` + ref_id / urgency / memo / due_at | `#parky-admin-tasks` | 管理者ポータルで該当タスク確認 |
| BFF (owner inquiry submit) | event drive | 同上 (auto-task) | 同上 | `[P2] Admin task created` + applicant / lot / assets | `#parky-admin-tasks` | 書類確認 + approve/reject 判断 |
| BFF future: owner application | event drive | owner_inquiries INSERT | 新規申請 | `[P2] Owner application: <name>` | `#parky-owner-applications` (将来) | LP 経由申請の review |
| BFF future: revenue | event drive | revenue_transactions INSERT | 1件 ≥ 1万円 | `[P2] Big revenue: ¥X,XXX` | `#parky-revenue` (将来) | 大口決済の確認 |
| BFF future: signups | 日次 cron | app_users INSERT | 過去 24h 集計 | `[P2] Daily signups: 12` | `#parky-signups` (将来) | 集計 trend 確認 |

### P3 → `#parky-insights`

| ① system | ② 頻度 | ③ check 対象 | ④ 条件 | ⑤ メッセージ内容 | ⑦ アクション |
|---|---|---|---|---|---|
| GHA cron (週 1 月曜朝) | 週 1 (cron) | Sentry top issues | 過去 7 日 sample | `[P3] Weekly Sentry digest: top 10 / new N / resolved M` + dashboard link | 月曜 review で確認、優先度割当 |
| GHA cron (週 1) | 週 1 | synthetic healthcheck history | 過去 7 日 fail rate | `[P3] Weekly uptime: dev 99.7% / prod 100%` | trend 確認 |
| GHA cron (週 1) | 週 1 | GH Actions usage | 過去 7 日 minutes 消費 | `[P3] GH Actions: 412 min used (Free 2000 残)` | 圧縮検討タイミング |
| GHA cron (月 1, 1 日) | 月 1 | 全 service cost | 各 service 月額 | `[P3] Monthly cost: CF $0 / Supabase $0 / Sentry $0 / Mapbox $X` | コスト review |
| Dependabot | 週 1 | Low CVE + outdated | 過去 7 日 まとめ | `[P3] Weekly deps: 3 low CVE / 12 outdated` + PR list | 週次でまとめて update PR |
| Supabase Cron | 週 1 | storage growth | 過去 7 日 増分 | `[P3] DB storage: +120MB / week (total 480MB)` | scale plan 判断 |
| Business KPI (launch 後) | 週 1 | DAU/MAU/signup/revenue | 過去 7 日 集計 | `[P3] Weekly KPI: DAU 1,234 / signup +56 / 駐車場登録 +12` | trend 確認 |

---

## 5. Alert Format 標準

### 必須 field (1 つでも欠けたら NG)

| Field | 内容 | 例 |
|---|---|---|
| **severity badge** | title 先頭に `[P0]`/`[P1]`/`[P2]`/`[P3]` + 絵文字 | `:rotating_light: [P0]` / `:warning: [P1]` / `:information_source: [P2]` / `:bar_chart: [P3]` |
| **service** | 影響を受けるシステム名 | `parky-api (prod)` / `parky-admin (dev)` |
| **symptom** | 1 行で症状を要約 | `5xx errors burst (52/min, threshold 10)` |
| **env** | dev / stg / prod の明示 | `env: prod` |
| **triggered_at** | UTC + JST 両方記載 | `2026-04-29 10:23 JST (01:23 UTC)` |
| **runbook** | 対応手順への直リンク | `[runbook: incident-response.md#sev1](...)` |
| **dashboard / log** | 該当する観測 dashboard URL | `[Sentry issue](https://...)` / `[Workflow run](https://github.com/.../runs/123)` |

### 任意 field

- **owner**: 主担当 (1 人運用なら省略 or `dev@parky.co.jp` 固定)
- **related_alerts**: 同 window で発火した別 alert ID (cascade 検知用)
- **auto_resolve**: 自動解決された時刻 (transient flap 等)

### Discord embed の実例 (P0)

```json
{
  "content": "@here :rotating_light: **[P0] parky-api (prod) 5xx burst**",
  "embeds": [{
    "title": "Sentry alert: 5xx errors burst",
    "color": 14431557,
    "fields": [
      { "name": "Service",   "value": "parky-api (prod)", "inline": true },
      { "name": "Env",       "value": "prod",             "inline": true },
      { "name": "Symptom",   "value": "52 errors / 5min (threshold: 50)" },
      { "name": "Sentry",    "value": "[Issue PARKY-123](https://parky-72.sentry.io/issues/123)" },
      { "name": "Runbook",   "value": "[incident-response.md#sev1-checklist](https://github.com/...)" },
      { "name": "Triggered", "value": "2026-04-29 10:23 JST (01:23 UTC)", "inline": true }
    ],
    "footer": { "text": "Parky Sentry alert · rule R-01" },
    "timestamp": "2026-04-29T01:23:00Z"
  }]
}
```

### Discord embed 色コード (severity 視覚化)

| Severity | Decimal | Hex | 色 |
|---|---|---|---|
| P0 | `14431557` | `#DC2626` | red |
| P1 | `15105570` | `#E67E22` | orange |
| P2 | `3447003` | `#3498DB` | blue |
| P3 | `9807270` | `#95A5A6` | gray |

---

## 6. Dedup / Suppression ルール

| ルール | 適用条件 | 挙動 |
|---|---|---|
| **Frequency cap** | 同一 alert rule | 5 分に最大 1 回 (Sentry の "At most once per X minutes" 機能で実装) |
| **Flap retry** | healthcheck / synthetic monitor | 連続 2 回 fail で初めて発火 (`synthetic-healthcheck.yml` 既存実装) |
| **Auto-resolve** | transient な障害 (5 分以内に回復) | 後続 message を thread reply で「自動回復」と通知、新規 alert は出さない |
| **Maintenance window** | 計画 deploy / migration 中 | 事前に `gh workflow run notify-suppress.yml --duration 30m` で全通知 mute (Phase 3) |
| **Cascade suppression** | 1 つの根本原因が複数 alert を発火 | 同 time window (5 分) で 5 件以上の alert は最初の 1 件のみ通知、残りは `5 alerts in window, see thread` 形式に集約 |
| **Severity dedup** | 同 issue が P0 → 後で P1 にダウングレード | thread reply で severity 変化を通知、新規 alert は出さない |
| **Dev fail tolerance** | dev 環境の transient failure | 連続 5 回 fail で初めて P1 に格上げ (dev は壊れて当たり前を許容) |

> **夜中 mute は 1 人運用では不要**: Stripe / GitHub は on-call rotation があるので "wake up only for P0" は意味あるが、Parky 1 人運用なら寝てる間の P0 も「起きてから」対応で OK。むしろ dedup を強くして「鳴ったら 100% 本物」状態を作る方が効果大。

---

## 7. Failure handling & resilience — Webhook 失敗時の振る舞い

### 設計判断: Discord 直 + retry + DLQ (P0 のみ email backup)

「より可用性の高い relay サービス (PagerDuty / Opsgenie / 自前 Worker) を経由する」案は **delivery 目的だけなら採用しない**。理由:

| 反対理由 | 詳細 |
|---|---|
| **Relay 自体が SPOF** | hop が増えると availability は乗算で減る (99.9% × 99.95% = 99.85%) |
| **可視性が落ちる** | 失敗時「Discord/relay/Worker のどこ?」の切り分けに調査時間が増える |
| **PagerDuty 等の本当の価値は配信ではない** | on-call rotation / escalation / dedup / runbook 機能。最終 hop は結局 Discord/Slack/Email/SMS で同じ |
| **コスト過大** | $9-19/user/mo を 1 人運用で払うなら Sentry Team plan upgrade の方が ROI 高い |
| **複雑さ vs 利益** | 多 hop で staging テスト難、設定 drift 発生しやすい |

採用するアーキテクチャは **3 層防御 + 多チャネル冗長化 (P0 のみ)**:

```
[Layer 1] Direct Discord webhook
    ↓ 失敗 (5xx / 429 / network error)
[Layer 2] Exponential backoff retry (3 attempts: 1s → 4s → 16s)
    ↓ 全失敗
[Layer 3] DLQ 永続化 (admin.notification_failures table)
    + P0 のみ email fallback (Resend, 別物理経路で送信)
    ↓ 後で
[Layer 4] 週次 cron が DLQ 集計 → #parky-insights に digest
```

### 故障モード別の挙動

| 故障モード | Layer 1 | Layer 2 (retry) | Layer 3 (DLQ + email) | 結果 |
|---|---|---|---|---|
| Discord rate limit (429) | 失敗 | retry 後成功 | 触らず | 数秒遅延だけ |
| Discord transient 5xx | 失敗 | retry 後成功 | 触らず | 数秒遅延だけ |
| Discord 完全停止 (SLA 99.95% = 22min/月) | 失敗 | 全 retry 失敗 | DLQ 記録 + (P0 なら email 送信) | DLQ で後で見える、P0 は email で届く |
| Webhook URL revoked | 失敗 (404) | retry しない (4xx 即 DLQ) | DLQ 記録 + (P0 なら email) | DLQ digest で気付く、P0 は email で届く |
| Worker → Discord network 障害 | 失敗 | retry で大半復旧 | 全失敗時のみ DLQ | ほぼ自動回復 |
| Payload 不正 (4xx) | 失敗 | retry しない | DLQ 記録 | dev で発見、prod に出さない |
| Discord + Email 同時停止 | 失敗 | 全 retry 失敗 | DLQ 記録のみ | 確率限りなく 0、対策不要 |

### Retry policy (詳細)

| HTTP ステータス | Retry? | 理由 |
|---|---|---|
| 200 / 204 | しない | 成功 |
| 429 (rate limit) | する | Discord 30 msg/min/webhook 制限。指数 backoff で待つ |
| 5xx | する | Discord 側の transient |
| 408 / 499 (timeout) | する | network transient |
| 4xx (それ以外) | しない | payload 不正 / URL 失効、retry しても同じ |
| network error (fetch reject) | する | TLS / DNS transient |

Backoff: **1s → 4s → 16s** (4× 倍増)。最大 3 回試行 = 21 秒以内に決着。

### P0 backup email の設計

- **対象**: severity = `P0` のみ (P1/P2/P3 は DLQ 記録のみで email しない、過剰通知防止)
- **provider**: 既存 Resend (`lib/transactional-email.ts`)。物理経路が Discord と完全分離 (Resend 障害 ≠ Discord 障害)
- **送信先**: `dev@parky.co.jp` (固定、複数人運用時に env で多人数化)
- **Subject**: `[P0 BACKUP] <title>` で先頭明示 (重複時の dedup 容易化)
- **Body**: HTML + text 両対応、Discord embed と同じ field を平文化
- **失敗時の挙動**: email も失敗したら DLQ の `fallback_email_sent=false` のまま、digest で目立つ
- **将来拡張**: 別 provider (SES / SendGrid) を二段目 fallback として追加検討、launch 後の規模次第

### DLQ schema (admin.notification_failures)

| 列 | 型 | 用途 |
|---|---|---|
| `id` | UUID | PK |
| `attempted_at` | TIMESTAMPTZ | 最初の試行時刻 |
| `channel` | TEXT | `ALERTS` / `OPS` / `DEPLOYS` / `INSIGHTS` / `ADMIN_TASKS` |
| `severity` | TEXT | `P0` / `P1` / `P2` / `P3` |
| `title` | TEXT | alert title |
| `summary` | TEXT | 1 行要約 |
| `payload` | JSONB | full Discord payload (replay できる shape) |
| `error_message` | TEXT | 最後のエラー文字列 |
| `http_status` | INT | 最後の HTTP ステータス (network error は NULL) |
| `retry_count` | INT | 実施した retry 回数 |
| `status` | TEXT | `failed` / `replayed_success` / `manual_acknowledged` |
| `fallback_email_sent` | BOOLEAN | P0 backup email を送ったか |
| `source` | TEXT | `sentry` / `gha` / `admin-task` / etc. (debugging 用) |
| `created_at` | TIMESTAMPTZ | 作成時刻 |

### 週次 DLQ digest cron

- **配信先**: `#parky-insights` (P3)
- **頻度**: 毎週月曜 09:00 UTC (JST 18:00) — HOURLY スロット同居
- **内容**: 過去 7 日 status='failed' の集計 (channel 別 / 件数 / top 3 エラーメッセージ)
- **集計後**: 該当行の status を `manual_acknowledged` に更新 (再 digest 防止)

---

## 8. アンチパターン

新規 alert / channel 設計時に以下を作らないこと:

- **全部 1 channel に流す** — P0 と info が同じ channel に並ぶと user は info の山から P0 を見つけられない、3 日で「もう見ない」状態に
- **`@here` を P1/P2 で使う** — @here が無意味化、本当の P0 で誰も気付かない、P0 のみ mention を厳守
- **同じ事象を複数 source から重複通知** — 例: 5xx burst で Sentry alert + healthcheck + GHA E2E fail が同時発火 → 3 通知、Cascade suppression 必須
- **Email + Discord 並行** — どちらか一本に絞る、Parky は Discord メイン、email は Sentry 個別 issue triage 用にだけ残す
- **Runbook link なし** — 「何が起きたか」だけ書いて「どうすればいいか」が無いと寝起き / 移動中に対応できない、全 alert に runbook link 必須
- **平文だけ (Discord embed 不使用)** — 色 / icon / structured field がないと一瞥で severity 判別不可、必ず embed 形式
- **Quiet hours を 1 人運用で強制** — on-call rotation があるなら意味あるが、1 人運用では結局自分で見るので mute しても意味薄い、むしろ dedup を強化
- **Severity の判断を都度** — 各 alert で「これ P0?」を悩むのは時間の無駄、alert rule 作成時に severity を必ず固定、後で見直しは月次

---

## 9. テック企業ベンチマーク

| 会社 | Severity 段階 | Channel 戦略 | Dedup | 1 人運用への適用 |
|---|---|---|---|---|
| **Stripe** | SEV1/2/3 (15min/4h/24h SLA) | severity 別 Slack channel + PagerDuty escalation | Frequency cap + cascade detection | そのまま 4 段に拡張可、PagerDuty は不要 |
| **GitHub** | SEV1-4 + Public | service area × severity の matrix channels | Datadog + custom dedup | service area 分割は overkill、severity のみで十分 |
| **Linear** | P0/P1/P2/Info (silent-by-default) | severity 別 Slack 1 channel each + opt-in subscription | Linear notification engine | **Parky の理想形に最も近い** |
| **Vercel** | Critical / Warning / Info | internal: severity 3 channels / external: status page | Native dedup in Vercel platform | 3 channel 構成で OK、status page は launch 後 |
| **Cloudflare** | Critical / Warning / Info / Healthy | Notification destinations per severity (webhook / email / PagerDuty) | Per-rule frequency setting | built-in CF Notifications を活用 |
| **Plaid** | P0-P3 + Customer-facing | customer-facing: status page / internal: 5 channels | Datadog incident management | customer-facing は status page を将来検討 |
| **Parky 採用** | P0/P1/P2/P3 (Stripe 準拠) | severity 別 4 Discord channels (Linear 同型) | Frequency cap + flap retry + cascade group | 1 人運用 / pre-launch 最適化 |

**結論**: Linear 流の severity-based 4 channel + Stripe 流の SLA + format 標準 + Cloudflare 流の native notification 活用 のハイブリッドを Parky で採用。

---

## 10. 実装ロードマップ

### Phase 1 — 今週 (工数 2-3h)

1. **Discord channel 5 本作成**: `#parky-alerts` / `#parky-ops` / `#parky-deploys` / `#parky-insights` / `#parky-admin-tasks`
2. **Webhook URL 5 本発行** → 1Password に保存
   - Vault: `PJ｜Parky`
   - Item 名: `Discord Webhook｜#parky-alerts` / `#parky-ops` / `#parky-deploys` / `#parky-insights` / `#parky-admin-tasks`
   - Field: `url` (secret)
3. **GH Secrets に登録**: `DISCORD_WEBHOOK_ALERTS` / `DISCORD_WEBHOOK_OPS` / `DISCORD_WEBHOOK_DEPLOYS` / `DISCORD_WEBHOOK_INSIGHTS` / `DISCORD_WEBHOOK_ADMIN_TASKS`
   - 既存 `DISCORD_HEALTHCHECK_WEBHOOK_URL` は廃止 → `DISCORD_WEBHOOK_OPS` に統合
4. **Wrangler secrets に投入**: `DISCORD_WEBHOOK_ADMIN_TASKS` を dev / prod に。`api/src/env/bindings.ts` に型宣言済み。
   ```bash
   cd parky/api
   wrangler secret put DISCORD_WEBHOOK_ADMIN_TASKS --env dev
   wrangler secret put DISCORD_WEBHOOK_ADMIN_TASKS --env prod
   ```
5. **Synthetic healthcheck workflow を severity-aware に改修**: dev fail → `OPS` / prod fail → `ALERTS`
6. **Reusable workflow `_notify-discord.yml` 作成**: severity / channel / message を input に取り、適切な webhook + format で送信
7. **全 prod deploy workflow に `if: failure()` notify step 追加**: `_notify-discord.yml` を使い `OPS` へ送信
8. **Admin task 通知の動作確認**: 管理者ポータルから新規 task 作成 → `#parky-admin-tasks` に投稿されるか確認 (未投入なら warn log のみ)

### Phase 2 — 1-2 週 (工数 4-6h)

1. **Sentry Discord native integration 設定**: 2024+ で native 提供、12 alert rule 全てを severity に応じた channel に振り分け
2. **Cloudflare Notifications 設定**: Workers outage / Pages deploy fail / R2 quota / WAF を該当 channel に webhook 配線
3. **DLQ monitor を `OPS` へ統合**: `DLQ_WEBHOOK_URL` を `DISCORD_WEBHOOK_OPS` に向け直し
4. **Cost / Quota 監視 cron 作成**: 週 1 回 GH Actions で各 service API を叩いて集計、80% 超過で `OPS` へ
5. **Dependabot 通知整理**: GitHub Settings → Notifications → Dependabot を email デフォルト → repo 内 weekly digest 化

### Phase 3 — 1 ヶ月 (工数 6-8h)

1. **Supabase 健全性監視**: pg_cron で pool / slow query / RLS violation 集計 → BFF endpoint → Discord webhook
2. **Weekly digest workflow**: 月曜朝に Sentry + GHA + uptime + cost を集約 → `INSIGHTS` へ HTML サマリ
3. **Business KPI digest** (launch 後): signup / DAU / 駐車場登録 etc. → `INSIGHTS`
4. **Maintenance window mute**: `scripts/notify/suppress.sh --duration 30m` で全 webhook 一時無音化
5. **Status page (status.parky.co.jp)**: launch 後の customer-facing 透明性のため Better Uptime Public Status / Atlassian で構築

### Phase 4 — 3 ヶ月 (工数 4h, 任意)

1. **Runbook automation**: alert に Discord button (interactive component) 追加で "ack" / "runbook 開く" / "mute 30m" を 1 click 化
2. **Postmortem 自動雛形生成**: P0 alert 解決後に GitHub Issue を自動生成、postmortem-template.md ベースで雛形プレフィル

---

## 11. コスト見積もり

| 項目 | 無料枠 | Parky 想定使用量 | 追加コスト |
|---|---|---|---|
| Discord webhook | 無制限 | 月 100-300 通知 | $0 |
| Sentry (Developer plan) | 5K errors/mo + 10K performance | pre-launch ~1K/mo / launch 後 5-30K/mo | launch 後超過時 $26/mo (Team) |
| GH Actions (private repo) | 2,000 min/mo | healthcheck 1,440 min + notify 100 min + digest 50 min ≈ 1,600 min/mo | Free 枠 80% (要圧縮) |
| Cloudflare Notifications | Workers Free / R2 Free 枠内 | monitor 数本 | $0 |
| Cloudflare Health Checks (本格化時) | Pro plan 必要 | 1 monitor (api.parky.co.jp) | $20/mo (Pro plan, 1 monitor 込) |
| Honeycomb (M-03 後) | 20M events/mo (Free) | pre-launch ~500K / launch 後 5-10M | $0 (Free 内) |
| Supabase (現 Free) | 500MB DB / 1GB bandwidth | schema 投入のみ、データ少 | launch 後 $25/mo (Pro plan + PITR) |
| Better Uptime / UptimeRobot (代替案) | 10 monitors (Free) | 2 monitor で十分 | $0 (Free 枠内、Cloudflare Pro 不要) |

### 段階別コスト

- **Phase 1-2 (現状)**: $0/mo (Discord + GH Actions + Sentry Dev + CF Notifications + Better Uptime Free)
- **launch 後 (~3 ヶ月)**: $25-50/mo (Supabase Pro + Sentry Team)
- **scale 後 (~6 ヶ月)**: $100-150/mo (CF Pro / Honeycomb Pro / 専用 monitor 等)

> **GH Actions 圧縮策**: synthetic healthcheck cron を 5 分 → 10 分間隔に変更で月使用 720 → 360 min。Better Uptime Free (10 monitors / 3 分間隔) に丸ごと移行すれば GH Actions 0 min に。

---

## 12. シナリオ別の挙動例

### シナリオ A: 平常時 (ローンチ前)
- 月曜朝に `#parky-insights` で weekly digest 4-5 通
- 日中に `#parky-deploys` で dev deploy 通知 5-10 通
- `#parky-ops` `#parky-alerts` は基本無音

### シナリオ B: dev healthcheck 1 回 fail
- `#parky-ops` に `[P1] dev healthz down` 1 通
- 5 分後の次 cron で復活したら何も来ない (auto-resolve)

### シナリオ C: prod に bad deploy
- `#parky-alerts` に `[P0] prod healthz down` (synthetic healthcheck)
- `#parky-alerts` に `[P0] 5xx burst` (Sentry)
- → cascade suppression で 2 通目以降は thread reply に集約
- `#parky-ops` に `[P1] prod deploy failed (post-deploy assert)` (GHA)
- 対応 → wrangler rollback → 5 分後 healthz 緑 → `#parky-alerts` thread に「auto-resolved」reply

### シナリオ D: Critical CVE (例: lodash 脆弱性)
- `#parky-ops` に `[P1] Critical CVE: lodash@4.17.x` 1 通 + Dependabot PR link
- 翌営業日に PR review → merge → `#parky-deploys` で deploy success 通知

---

## 13. 関連 doc

このドキュメントが SSoT。以下は本戦略に従って動く individual runbook:

- [incident-response.md](incident-response.md) — P0/P1 発火時の対応フロー、SEV1/2/3 → P0/P1/P2 に整合
- [sentry-alert-rules.md](sentry-alert-rules.md) — Sentry 12 alert rule の severity 振り分け実装
- [synthetic-healthcheck.md](synthetic-healthcheck.md) — `synthetic-healthcheck.yml` の channel routing
- [observability-hookup.md](observability-hookup.md) — Sentry / Honeycomb DSN 投入手順 + Discord native integration
- [postmortem-template.md](postmortem-template.md) — P0 解決後の postmortem 雛形
- [slo-error-budget.md](slo-error-budget.md) — SLO 定義、burn rate alert の根拠
- [secret-rotation.md](secret-rotation.md) — Webhook URL rotation を含む secret 全般
- [logging.md](logging.md) — log level 設計 (alert 発火条件の判断材料)

### 監査根拠 (社内 work)

- `.work/parky/2026-04-29_001_parky_comprehensive_evaluation_v2.html` — Operations 軸監査 (本戦略の動機)
- `.work/parky/2026-04-29_002_parky_notification_strategy.html` — 本 doc の HTML 版 (visual reference)

### 変更履歴

- 2026-04-29: 初版作成。既存 3 つの通知配線 (Sentry alert / DLQ / synthetic healthcheck) を整理し、Severity 4 段 + Discord 4 channel の framework を確立。
