Skip to content

receiver: graceful shutdown・/healthz・実効設定サマリを追加する (#99) - #104

Merged
kosako merged 2 commits into
mainfrom
feat/99-ops-hardening
Jul 5, 2026
Merged

receiver: graceful shutdown・/healthz・実効設定サマリを追加する (#99)#104
kosako merged 2 commits into
mainfrom
feat/99-ops-hardening

Conversation

@kosako

@kosako kosako commented Jul 5, 2026

Copy link
Copy Markdown
Owner

概要

Issue #99(receiver 運用ハードニング)。アーキテクチャレビュー(2026-07-02)の F-2 / F-3 に対応し、公開デプロイ(systemd / ALB 配下)の最低要件を揃えます。

変更内容

graceful shutdown(F-2)

  • SIGTERM / SIGINT で server.close() → in-flight リクエストの完了を待つ → store.close() → exit 0
  • リクエストがハングした場合に備えて 10 秒の drain deadline で強制終了(タイマーは unref 済みで、正常終了を遅延させない)
  • シャットダウン開始後の /healthz は 503(draining)を返す

GET /healthz(F-2)

  • store 疎通(count())込みで 200 / 異常時 503
  • 認証不要: probe は資格情報を持てず、応答は liveness 以外を漏らさない
  • rate limit 対象外: route table に rateLimit: false を宣言。厳しい RATE_LIMIT_MAX 設定でもヘルスチェックがフラップしない。404/405 を含む他の全パスは従来通り rate limit の対象(スキャン flood は引き続き数える)

実効設定サマリ + 不正値の警告(F-3)

  • numberSetting / positiveIntSetting にラベルを渡し、不正値の silent fallback 時に ignored invalid setting MAX_IMPORT_ITEMS (env): "0" — using 500 形式で警告
  • 起動時に limits:(body/screenshot/disk/count/importItems/fieldLength/arrayLength/objectDepth)と rate limit:(req/window/maxClients/trustProxy)の実効値を 1 ブロック出力。secret 値は出さない
  • 空文字の env は従来通り「未設定」扱いで警告なし(テストハーネス等の互換)

受け入れ条件との対応

  • ✅ 再起動で in-flight リクエストが正常完了 → raw socket で body 送信途中に SIGTERM → 201 完了 + exit 0 のテストを追加
  • /healthz が 200 / 異常時に非 200(認証: 不要、rate limit: 対象外と決定)
  • ✅ 不正な設定値で起動ログに警告

テスト

npm run check 全 103 件パス(新規 3 件: healthz + rate limit 免除 / SIGTERM drain の in-flight 完了 / 設定警告 + サマリ)。実機スモーク済み(不正 env → 警告 + サマリ出力、healthz 200、SIGTERM → draining ログ + exit 0)。

Closes #99

🤖 Generated with Claude Code

https://claude.ai/code/session_01LRdDjwxbAAaTMwwnzipkau

アーキテクチャレビュー F-2 / F-3 の運用ハードニング。

- SIGTERM / SIGINT で graceful shutdown: server.close() で新規接続を
  止めて in-flight リクエストの完了を待ち、store.close() 後に exit 0。
  ハング対策に 10 秒の drain deadline(unref なので正常終了は遅延しない)
- GET /healthz を追加: store 疎通(count)込みで 200、draining 中と
  store 異常時は 503。認証不要(liveness しか返さない)・rate limit
  対象外(route table の rateLimit: false 宣言。厳しい制限設定でも
  ヘルスチェックがフラップしない)。404/405 を含む他のパスは従来通り
  rate limit の対象
- 数値系設定の不正値(非数値・上限系の 0/負/小数)は fallback 時に
  「ignored invalid setting <名前>」を起動ログに警告し、実効値を
  limits: / rate limit: のサマリ行で出力(secret 値は出さない)

Closes #99

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LRdDjwxbAAaTMwwnzipkau
@kosako

kosako commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

🔍 レビュー依頼(→ Codex)

  • 観点: receiver 運用ハードニング: graceful shutdown・/healthz・起動時の実効設定サマリ #99 運用ハードニング。重点:
    • shutdown の正しさ: server.close() / store.close() / process.exit の順序と競合(in-flight リクエスト・二重シグナル・listen 前のシグナル)、drain deadline の unref
    • dispatch 再構成の退行: rate limit 適用順の変更(route 照合 → rate limit)で 404/405/認証前の挙動が変わっていないか、OPTIONS・CORS との順序
    • healthz の方針: 認証なし + rate limit 免除の妥当性(DoS 面・情報漏えい面)
    • 設定警告: 警告条件の正しさ(空文字 = 未設定で警告なし、env 不正 → config 値へ fallback)、二重警告・警告漏れ、secret がログに出ないこと
    • production-rail review lens(フォールバック握り潰し・契約変更の整合・デッドコード)
  • ランク: 🔴 must(merge 前必須)/ 🟡 should(推奨・対応は依頼元判断)/ ⚪ nit(任意)
  • 結果はこの PR にコメントで返します

- SIGTERM/SIGINT ハンドラをモジュールロード時(async startup 前)に登録。
  legacy migration や screenshot scan 中の停止でも graceful に drain する
  (Codex レビュー 🔴)。server 未 listen・store 未初期化でも安全に畳む
- MAX_BODY_BYTES / SCREENSHOT_MAX_BYTES を positiveIntSetting に変更。
  0/負は POST を全部壊す誤設定なので警告 + fallback(Codex レビュー 🟡、
  README の記述とも一致)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LRdDjwxbAAaTMwwnzipkau
@kosako

kosako commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

📋 レビュー結果(by Codex GPT-5.5)

初回判定: ⛔ 要対応(must 1 件)→ 対応後の再レビューで ✅ merge 可(must 0 件)

🔴 must(→ fa64807 で対応済み、再レビューで解消確認)

  • server/receive.js:391 — SIGTERM/SIGINT ハンドラの登録が store.init() / screenshot scan のなので、startup 中にシグナルを受けると Node の既定動作で終了し graceful にならない(legacy migration 中の停止で再現)。→ ハンドラをモジュールロード時(async startup 前)に移動し、store 未初期化・server 未 listen でも安全に畳むよう修正

🟡 should(→ fa64807 で対応済み)

  • server/receive.js:20 — README は「上限系の 0・負・小数は fallback + warning」と説明するが、MAX_BODY_BYTES / SCREENSHOT_MAX_BYTESnumberSetting のままで 0/負を受け入れる(MAX_BODY_BYTES=0 は実質 POST 全滅)。→ positiveIntSetting に統一し警告テストを追加

⚪ nit

再レビュー(fa64807)

✅ 解消確認: 二重シグナルの idempotent 抑止 / 未 listen server.close() の callback 経路 / store 未初期化の安全性 / ハンドラ登録が startup 前、をコードと最小再現で確認。新規指摘なし。

検証範囲(要旨)

diff と receive.js / store.js / テスト / 両 README の静的突き合わせ。dispatch の 404/405/401 rate limit・OPTIONS/CORS 順序・healthz の auth/rateLimit 免除は目的通り。sandbox 制約によりテスト実行は依頼元がローカル代替(npm run check 全 103 件パス + 実機スモーク: 不正 env 警告 / healthz 200 / SIGTERM drain + exit 0)。

@kosako
kosako marked this pull request as ready for review July 5, 2026 11:55
@kosako
kosako merged commit 07b289f into main Jul 5, 2026
1 check passed
@kosako
kosako deleted the feat/99-ops-hardening branch July 5, 2026 11:55
kosako added a commit that referenced this pull request Jul 5, 2026
#103(inbox ログイン + CORS 制限)・#104(healthz / graceful shutdown /
設定警告)・#106(ingest key)で README.md に入れた receiver 契約の変更を
英語版にミラーする:

- RECEIVER_TOKEN の説明を「閲覧系も保護 + inbox ログイン(HMAC cookie)」に更新
- INGEST_KEYS / ALLOWED_ORIGINS の項目を追加
- healthz・graceful shutdown・invalid setting warning を追記
- Public Deployment (EC2 etc.) 節を新設
- Init options に ingestKey を追加、Current Boundary の Auth 行を更新

Closes #105


Claude-Session: https://claude.ai/code/session_01LRdDjwxbAAaTMwwnzipkau

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

receiver 運用ハードニング: graceful shutdown・/healthz・起動時の実効設定サマリ

1 participant