ADR-0170: 常駐サービスの失活事前検知
- Status: Proposed
- Mode: Standard
- Kruchten Type: Executive/Property
- Scope: ops
- Implementation Status: Not Started
- 起案者: [email protected]
- 起案日時 (JST): 2026-06-27 02:28
- 承認日時 (JST): -
- Approver Role: ops
- Approver Who: [email protected]
- Driver: [email protected]
- Consulted: Decision Pipeline AI 審査 (Gate 0-4)
- Review After: 2026-12-27
コンテキスト
§1.1 背景
1 人法人で常駐サービス (launchd 3 件 / GitHub Actions cron / ADC) を業務常用しているが、失活を運用者が目視に頼って気付く構造のまま運用している。ADR-0104 では deploy.yml が 60 run 連続失敗 (100%・2026-05-29〜06-01) し、長期間気付かれなかった前例がある。
§1.2 現状 (As-Is)
- launchd 常駐 3 件:
com.bizlp.min-minutes(議事録 transcribe + minute 生成 / 業務常用) /com.bizlp.claude-archive-sync(Claude 会話正本 GCS 同期 / 監査要件) /com.user.claude-memory-backup(memory persist)。KeepAlive=trueで再起動はするが、設定不整合や認証切れで起動直後に die-loop することがあり、stderr ログを毎日見ない限り気付かない。 - GitHub Actions cron: ADR-0104 の
deploy.yml60 run 連続失敗 (100%・2026-05-29〜06-01) の前例。CI 長時間連敗を運用者が気付かないまま放置する事故が再発する余地がある。 - ADC 認証切れ:
~/.config/gcloud/application_default_credentials.jsonの refresh token 失効で ADR-0155 Phase f の GCP SM 経由 GEMINI_API_KEY 取得 (= min-minutes の起動条件) が即時破綻する。 - ADR-0155 Confirmation #4 では Cloudflare token に限定して残日数 alert (30/14 日) を既決定済み。
§1.3 課題
障害発生から復旧着手まで数日〜数週間気付かない構造的リスクが残る。ADR-0155 Confirmation #4 のスコープが Cloudflare token に限定されており、launchd / GitHub Actions / ADC には適用範囲が及んでいない。
§1.4 制約・要件
- ADR-0155 §1.4 「ディスクに長寿命 SA 鍵ファイルを置かない」に違反しない (SA json key 発行案は不採用)。
- 月額 $0/月 を維持する ($0 超過時は即代替案再評価)。
- 1 ADR = 1 決定 (
[adr-granularity-rq097]) を守り、ADR-0155 の amend ではなく独立 ADR として起案する。 - 監視メカニズム自体が新たな攻撃面を増やさない (webhook URL / Issue 起票 token の漏洩リスク管理含む)。
§1.5 目標 (To-Be)
失活から運用者通知までの MTTD (Mean Time To Detect) を「数日〜数週間」から「≤ 24 時間」に短縮する。ADR-0155 Confirmation #4 のスコープを「全常駐認証 + 全 cron + ADC」に拡張し、運用面の Single Source of Truth を強化する。Non-Goals: 監査ログ欠損のバックフィル機構の設計 (§6.2 / Confirmation #1 で取り扱い、別途切り出し可能)。
決定
launchd 3 件のヘルスチェック + GitHub Actions 連敗検知 + ADC 失効残日数モニタを、自前 cron + Slack webhook / GitHub Issue 二重通知で構築する。Phase a (launchd) / Phase b (GitHub Actions) / Phase c (ADC) の 3 段階で段階投入し、ADR-0155 Confirmation #4 を横展開する。launchd ヘルスチェックは GitHub Actions hosted runner から launchctl list を実行できない物理制約により、launchd 上の watchdog job が外部 Dead Man's Switch (Healthchecks.io 無料枠) にハートビートを送信する方式を採用する。
判断基準 (Decision Drivers)
3.1 評価軸
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #operable (observable に近い意味で使用) | [Must] (×2.0) | 失活から運用者通知までの MTTD ≤ 24 時間。K.O.: MTTD > 1 週間の方式は不採用 |
| 2 | #maintainable | [Must] (×2.0) | 監視 cron の追加保守工数 ≤ 0.1 人日/月。K.O.: 月次 0.5 人日超の保守を要求する方式は不採用 |
| 3 | #secure | [High] (×1.0) | 監視メカニズムが新たな攻撃面を増やさない (Slack webhook URL や Issue 起票 token の漏洩リスク管理含む) |
| 4 | #flexible | [Medium] (×0.5) | 監視対象を将来追加する際の登録コストが軽い (= 各サービスごとに 1 行追記程度) |
K.O. criterion: Must 軸の score < 3 は不採用。
3.2 評価軸 × 案スコア表
各案 0-5 で評価。加重和 = (Σ score × 係数) / (満点 × Σ 係数) で正規化 (0.0-1.0)。満点 = 5 × (2.0 + 2.0 + 1.0 + 0.5) = 27.5。
| 軸 | 係数 | 案 Z (採択: 自前 cron + Slack/Issue 二重 + Dead Man's Switch) | 案 A (現状維持) | 案 B (SA 鍵で認証回避) | 案 C (Datadog 等 SaaS) |
|---|---|---|---|---|---|
#operable (Must) | ×2.0 | 4 | 1 | 2 | 5 |
#maintainable (Must) | ×2.0 | 4 | 5 | 3 | 4 |
#secure (High) | ×1.0 | 4 | 3 | 2 | 4 |
#flexible (Medium) | ×0.5 | 4 | 1 | 3 | 4 |
| 加重和 (正規化) | 0.800 | 0.527 | 0.473 | 0.873 | |
| K.O. 通過 (Must ≥3) | ✓ | ❌ (#operable=1) | ❌ (#secure=2) | ✓ (※コスト制約で不採用) |
案 C は加重和最高だが §1.4 「月額 $0/月」制約に違反するため不採用。K.O. 通過した残り候補のうち案 Z を採用する。
検討した代替案 (Alternatives Considered)
- 案 Z (採用): 自前 cron + GitHub Issue / Slack webhook 二重通知 + Healthchecks.io Dead Man's Switch。詳細は §3.3 相当 (決定セクション)。
- 案 A (不採用): 現状維持・何も入れない — 失活を運用者がたまたま気付くまで放置する形になり
#operable=1 で K.O.。ADR-0104 60 連敗の再発を許容することになる。 - 案 B (不採用): ADR-0155 amend で SA 鍵許容 + 認証切れ自体を回避 — SA json key をローカルに置くため ADR-0155 §1.4 違反 + 漏洩時の blast radius 増 (
#secure=2 で K.O.)。また「launchd が落ちる」「cron が連敗する」事象は認証以外の原因 (設定ミス・依存 binary 消失・disk full 等) でも起きるため代替にならない。 - 案 C (不採用): Datadog 等 SaaS 監視導入 — 月額 $15-30 程度の追加コスト発生で §1.4 「月額 $0/月」制約に違反。1 人法人スケールには過剰。
影響 (Consequences)
§5.1 正の影響 (Good)
- 観測可能性: 失活 MTTD が「数日〜数週間」から「≤ 24 時間」に短縮する。
- 運用統合: ADR-0155 Confirmation #4 のローカル拡張により、運用監視の Single Source of Truth (= GitHub Issue / Slack alert channel) が一本化される。
- 解析容易性: 自前 cron なので障害解析時に通知履歴を全て手元で grep できる (SaaS 依存なし)。
§5.2 負の影響 (Bad)
- 保守工数: 監視 cron 自体の保守工数発生 (見積 ≤ 0.1 人日/月)。
- alert fatigue: false positive 通知が頻発すれば運用者が無視するようになり、検知の実効性が劣化するリスク。
- 偽 alert によるオペレーションミス誘発リスク (盲点 7 反映): Slack webhook URL が GitHub Actions secret に保存される構造上、workflow YAML を書き換える権限を持つ攻撃者が URL を取得し業務用 Slack へ偽 alert を送信できる。「軽微なスパムリスク」ではなく意思決定を誤らせる中程度のリスクとして扱う。緩和策: webhook URL の定期ローテーション + GitHub Issue 経路 (GITHUB_TOKEN のみ使用) を Phase a で先行評価し、Slack 単独依存を回避する。
- 監査証跡の穴 (盲点 5 反映):
com.bizlp.claude-archive-syncが失活した期間中の会話ログは MTTD ≤ 24 時間で検知できても GCS に同期されず、Claude API 側に 24 時間以上保持される保証がないため監査証跡に永久に穴が残りうる。「検知できた = 監査要件を満たせた」ではない点を明示する。Confirmation #1 にリカバリ手順を追加 (再取得 API があれば手順化、なければ欠損を監査記録として明示)。
§5.3 中立・トレードオフ (Neutral / Trade-offs)
- 通知経路の二重化 (盲点 3 反映): Slack webhook が無効化されると全通知経路がサイレント障害になるため、各 cron の実行結果を GitHub Issue 追記 (Slack とは独立した経路) に同時書き込み する設計を採用する。加えて webhook への POST が非 2xx を返した場合は cron の exit code を非ゼロにして GitHub Actions job failure として記録し、Phase b の連敗検知で自己捕捉する。
- launchd 実行基盤の物理制約 (盲点 1 反映): GitHub Actions hosted runner から
launchctl listを実行する経路は物理的に存在しない (Mac launchd への接続 API も SSH 経路もない)。self-hosted runner を Mac に立てる代替案も runner 自体が launchd 依存のため self-monitor 問題が解消しない。したがって launchd ヘルスチェックは launchd 上の watchdog job がハートビートを Healthchecks.io 無料枠に送信し、途絶を Dead Man's Switch として検知する 方式を採用する。GitHub Actions schedule は GitHub Actions workflow 連敗検知 (Phase b) と ADC 残日数モニタ (Phase c) にのみ使用する。 - GitHub schedule 60 日自動停止対策 (盲点 2 反映): 監視リポジトリへのアクティビティが 60 日間途絶えると GitHub が schedule を自動 disable する公式仕様への対策として、毎月 1 回空 commit を push する keepalive workflow を併設し、かつ Healthchecks.io 等の外部 Dead Man's Switch サービスを併用する。
- ADC 残日数取得手段の不確実性 (盲点 4 反映):
gcloud auth application-default print-access-tokenは access token (有効期限 1 時間) の取得可否のみで refresh token の失効日を返さない。Google の token introspection endpoint も refresh token のexpを返さない。第一実装は~/.config/gcloud/application_default_credentials.jsonのexpiryフィールドを直接 parse する方針を試し、フィールドが存在しない or 不正確な場合の fallback として 月次手動 reauth を Confirmation #2 に組み込む。 - 無料枠廃止時の代替経路 (達希指摘 / Standard 10_long_term_impact 改善): Healthchecks.io / Slack incoming webhook / GitHub Actions schedule のいずれかが将来有料化または廃止された場合、ping 送信先 URL と Slack webhook URL を環境変数 (
HEALTHCHECKS_URL/SLACK_WEBHOOK_URL) で抽象化し、代替先 SaaS (Better Stack / UptimeRobot / Mackerel 無料枠 等) へ URL 1 行変更で切替できる構造で実装する (Gemini suggestion 反映)。半年後のreview_after(2026-12-27) で各 SaaS の無料枠供給状況を再評価する (Confirmation #6 で指標化)。
撤退条件 (Rollback Plan)
- alert fatigue 顕在化: Phase a 投入後 4 週間以内に false positive 通知が週 3 件超で観測されたら、閾値調整 (連続失敗回数の閾値引き上げ) を 1 ヶ月以内に実施する。それでも収まらなければ Phase a を一時 disable して原因を切り分ける。
- ADC モニタ誤検知: Phase c で「30 日前 alert」が複数回 false 検出した場合、ADC token introspection 手段を再評価する。第一実装の
expiryparse が機能しない場合は月次手動 reauth に切り替える。 - GitHub Actions schedule 自体の停止: keepalive workflow と Healthchecks.io Dead Man's Switch の二重化を実施するが、両方とも沈黙した場合は本 ADR 全体の運用方式を再評価する。月次手動チェックが 2 ヶ月連続で漏れたら同じく全体再評価。
- コスト超過: 月額が $0 を超えたら (Slack 有料化 / GitHub Actions schedule 無料枠超過 / Healthchecks.io 有料化 等)、即代替案を再評価する。
- 通知経路の二重沈黙: Slack webhook と GitHub Issue 経路の両方が 2 週間以上稼働しない場合、Phase a/b/c の運用継続可否を再判断する。
コスト試算
前提条件
| 項目 | 値 | 根拠 |
|---|---|---|
| 監視対象 launchd | 3 件 | min-minutes / claude-archive-sync / claude-memory-backup |
| 監視対象 GitHub Actions workflow | 主要 ~5 件 | deploy.yml 等 |
| ヘルスチェック頻度 | 1 時間毎 (launchd) / weekly (ADC) | MTTD ≤ 24 時間目標から逆算 |
| 通知単価 | $0 | Slack webhook 無料 + GitHub Issue 無料 + GitHub Actions schedule 無料枠内 + Healthchecks.io 無料枠 |
月コスト試算
- 初期実装: 約 0.8 人日 (Phase a 0.3 + Phase b 0.3 + Phase c 0.2)。盲点反映による Dead Man's Switch 連携・keepalive workflow・GitHub Issue 二重通知の追加実装で +0.2 人日見込み (Phase a 完了時に実測で更新)。
- 運用コスト: $0/月 (GitHub Actions schedule 無料枠内・Slack incoming webhook 無料・Healthchecks.io 無料枠・gcloud 操作は ADC 込みで無料)。
- 保守工数: 想定 0.05 人日/月 (false positive 調整・新規常駐サービス追加時の 1 行追記・月次定期テスト発火確認)。
Confirmation
- launchd 検知の稼働 + 監査バックフィル可否確認: launchd 3 件 (
com.bizlp.min-minutes/com.bizlp.claude-archive-sync/com.user.claude-memory-backup) の watchdog job が Healthchecks.io に 1 時間毎のハートビートを送信し、途絶時に通知される。com.bizlp.claude-archive-sync失活時は失活期間中の会話ログ欠損可否を確認し、Claude 側に再取得 API があれば手順を実行、なければ監査記録として欠損を明示する。検証手段 = Healthchecks.io ダッシュボード + watchdog job のログ確認 / 実行頻度 = 1 時間毎 / 違反時対応 = watchdog 再起動 + 閾値再評価 + 監査欠損記録。 - ADC 残日数 alert: ADC 失効残日数が 30 日 / 14 日 2 段階で通知される (ADR-0155 Confirmation #4 パターン踏襲)。第一実装は
application_default_credentials.jsonのexpiryフィールド parse、fallback は月次手動 reauth。検証手段 = weekly cron の出力確認 + 月次手動 reauth ログ / 実行頻度 = 週次 (cron) + 月次 (手動) / 違反時対応 =gcloud auth application-default loginで reauth。 - GitHub Actions 連敗起票: 主要 workflow (
deploy.yml等) が 3 連敗したら Issue が自動起票される。webhook POST 非 2xx 時は cron 自体を job failure として記録する。検証手段 = Issue list の自動起票件数 + GitHub Actions job failure 履歴 / 実行頻度 = workflow 実行ごと / 違反時対応 = 連敗原因の調査と修正。 - keepalive workflow の動作確認 (盲点 2 反映): 監視リポジトリに毎月 1 回空 commit を push する keepalive workflow が稼働し、60 日自動 disable を防止する。検証手段 = commit log + workflow run 履歴 / 実行頻度 = 月次 / 違反時対応 = workflow 再有効化 + 手動 commit 投入。
- 月次定期テスト発火 (盲点 6 反映): 毎月 1 日に
launchctl unload→ 通知到達確認 →launchctl loadを自動化し、「通知が来た実績」を GitHub Issue で月次記録として残す。Phase a 完了時の単発確認に加え、継続的に end-to-end 疎通を確認する。検証手段 = 月次 Issue の存在確認 / 実行頻度 = 月次 / 違反時対応 = 監視 cron の全面点検。 - 半年後の Phase a/b/c 実効性再評価 (達希指摘 / Standard 10_long_term_impact 改善): 受理後 6 ヶ月時点 (= 2026-12-27 / frontmatter
review_afterと一致) で 3 指標を集計する。指標 (i) MTTD 実測 ≤ 24 時間 (= 失活発生から最初の通知到達までの時刻差を Healthchecks.io ダッシュボード + GitHub Issue 起票履歴から逆算)。指標 (ii) false positive 件数 ≤ 月 1 件 (= 撤退条件 (1) の閾値より厳しめ・実運用 KPI)。指標 (iii) 無料枠供給状況 (= Healthchecks.io / Slack incoming webhook / GitHub Actions schedule の各 SaaS の料金プラン or 利用上限の変化)。検証手段 = Healthchecks.io ダッシュボード + GitHub Issue list + 月次定期テスト発火の通知到達ログ / 実行頻度 = 半年毎 (本 ADRreview_afterと整合) / 違反時対応 = 別 ADR で Phase 改修 or 代替 SaaS 移行 PoC を起案。
参照 (References)
- 関連 ADR:
- ADR-0155 (長寿命 token 残日数アラート) — 補完 / refines: Confirmation #4 の Cloudflare token のみだったスコープを常駐サービス + cron + ADC に拡張する関係。amend ではなく独立 ADR (
[adr-granularity-rq097]) として起案 (frontmatterrefines: [ADR-0155]で両端宣言)。 - ADR-0104 (
deploy.yml60 run 連続失敗の起点) — 補完 / relates_to: 同種の「連敗を気付かず放置する」事故の再発防止策として本 ADR Phase b が直接対応 (frontmatterrelates_to: [ADR-0104]で両端宣言)。 - ADR-0157 / ADR-0158 (DRP の撤退条件監視) — 並立 / relates_to: cron / SQL 集計の運用パターンと相互参照可能。本 ADR は新たに Slack webhook + GitHub Issue 二重経路 + Healthchecks.io Dead Man's Switch を追加する点が差分 (frontmatter
relates_to: [ADR-0157, ADR-0158]で両端宣言)。 - ADR-0150 (Pipeline 補強 checklist) — 並立 / relates_to: 本 ADR PR body の reinforcement 13 件 check が ADR-0150 §採用機構の正典化に従う (frontmatter
relates_to: [ADR-0150]で両端宣言)。
- ADR-0155 (長寿命 token 残日数アラート) — 補完 / refines: Confirmation #4 の Cloudflare token のみだったスコープを常駐サービス + cron + ADC に拡張する関係。amend ではなく独立 ADR (
- 関連 PR/Issue: -
- 外部資料: GitHub Docs「Disabling scheduled workflows」(60 日アクティビティなしで schedule 自動 disable 仕様) / Healthchecks.io 無料枠 / Google Cloud ADC 仕様