Service の「安全なデフォルト」は、どれも意図的に目立つようになっています。マスターキー未設定なら警告し、memory ブラウザーなら警告し、devセッションシークレットはよく知られた定数です。evaluateReadiness はそれらの警告をそれぞれ hard gate に変えます。これは起動チェックと GET /ready ルートの両方を支える単一の純粋関数であり、同じ8つのチェックが1つの信頼できる情報源から2つの利用側を駆動します。
これは Service の深い readiness probe であり、エンジンの liveness check ではありません。/health(エンジンアプリがマウントします)は「プロセスが起動しているか」に答えます。/ready は「このデプロイが本当に本番トラフィックを処理して安全か」に答えます。Postgres が接続されていること、シークレットが永続化されていること、実認証があること、などです。

同じチェックリストが2つのゲートを駆動する仕組み

1

起動ゲート — server.ts

起動時に、注入されたスナップショットが production: trueJT_ENV=production)を示している場合、サーバーは evaluateReadiness を1回実行します。失敗したゲートが1つでもあると、すべての失敗をログに出し、process.exit(1) を呼び出します。つまり、プロセスは起動しません。ゲートを修正しない限り、本番タスクは docker run を越えられません。
2

ルートゲート — GET /ready

同じ評価は、リクエストごと(または ALB ヘルスチェック間隔ごと)に、新しく取得したスナップショットに対して再実行されます。そのため、起動に発生した設定ドリフト(たとえばタスクの足元から KMS キーがローテーションされて外れた場合)でも probe は反転します。問題がなければ 200 {ready:true,failures:[],warnings:[]}、いずれかのゲートが発火すれば 503 {ready:false,failures:[…]} を返します。これは ALB ターゲットグループや運用チェックが消費する形です。
hard/soft の分岐は、厳密に JT_ENV === "production" をキーにしています。JT_ENV=staging のデプロイ(docs/11 §1.2 によれば、jinbatrail.indx.jp の参照フットプリントは本番ではなく staging です)でも同じチェックリストが評価されますが、発火したゲートはすべて failure ではなく warning になります。SQLite、memory ブラウザーバックエンド、devセッションシークレットが残っていても、/ready200 のままで、起動ゲートも終了しません。staging をインターネット到達可能にし、本番と同じようにゲートを強制する意図があるなら、これは owner に確認すべき実際のギャップです。staging の /ready が green であることを本番対応状況のシグナルとして扱う前に確認してください。

チェックリスト

これをローンチ前のランブックにコピーしてください。各行は、evaluateReadiness が実際に評価するゲートです(docs/11 §12)。
[ ] Browser backend is not "memory" (E2B_API_KEY or JT_LOCAL_BROWSER set)
[ ] A real master key or KMS is configured (JINBA_MASTER_KEY or JINBA_KMS_KEY_ARN)
[ ] JT_SESSION_SECRET is not the dev default "jt-dev-session-secret"
[ ] Dev-session auto-issue is disabled (ensureDevSession is OFF)
[ ] The wrapper store resolved to Postgres, not SQLite/memory (JT_DATABASE_URL/DATABASE_URL)
[ ] Replicas are not >1 without the §6 multi-replica gaps closed (Redis + tick + engine DB)
[ ] E2B capability.close() is wired in the run gateway (only checked when E2B is the backend)
[ ] ALB idle timeout is not below the 255s SSE/WS window
ゲート: browserBackend === "memory"発火条件: E2B_API_KEYJT_LOCAL_BROWSER のどちらも設定されておらず、selectBrowserCapability がインメモリの fake にフォールバックする。何を防ぐか: 記録されたすべての Flow が実行されたように見えます。手順は完了し、抽出は値を返します。しかし、ブラウザーは一度も開かれていません。memory バックエンドでの本番デプロイは、もっともらしく見えるものの完全に fake なオートメーションを提供してしまいます。修正: E2B_API_KEY(推奨。Runごとにサンドボックス化された microVM)または JT_LOCAL_BROWSER=1(セルフホストの Chromium プール、docs/11 §3.2)を設定します。
ゲート: !masterKeyOrKmsConfigured発火条件: JINBA_MASTER_KEY(64-hex)が未設定かつ JINBA_KMS_KEY_ARN が未設定で、envelope(envelope.ts)が一時的なプロセスローカルキーにフォールバックする。何を防ぐか: 一時キーで暗号化されたすべてのシークレットは、プロセスが再起動した瞬間、または2つ目のレプリカが独自のランダムキーで起動した瞬間に読めなくなります。ECS のローリングデプロイにより、顧客は保存済みのすべての資格情報から静かに締め出されます。修正: 単一インスタンスのデプロイでは、JINBA_MASTER_KEY に永続的な 64-hex 値を設定します。レプリカ間で実際のキー管理を行う場合は、JINBA_KMS_KEY_ARN(+ JINBA_KMS_ENCRYPTED_MASTER_KEY、docs/11 §5.1)を接続します。
ゲート: sessionSecretIsDevDefault発火条件: JT_SESSION_SECRET が、ソースから見えるリテラル定数 "jt-dev-session-secret" のままになっている。何を防ぐか: (公開)ソースを読んだ人なら誰でも署名シークレットを知ることができ、任意のユーザーの有効なセッションクッキーを偽造できます。これは単に弱いデフォルトではなく、完全な認証バイパスです。修正: JT_SESSION_SECRET に、そのデプロイに固有のランダム値を設定します。
ゲート: !realAuthWired発火条件: 認証なしで自動プロビジョニングされる devセッション(ensureDevSession)がまだ有効になっている。つまり、この環境で resolveAllowDevSessiontrue を返す。何を防ぐか: 保護されたルート(または GET /api/session)に到達した未認証の呼び出し元に、完全な権限を持つ owner セッションが静かに渡されます。タブを開いた人は誰でも、ログインなしで組織全体にアクセスできます。修正: デプロイされた任意の JT_ENV では、JT_DEV_AUTH を未設定のままにします(かつ JT_DISABLE_DEV_SESSION も未設定にします)。resolveAllowDevSession は、明示的に設定された dev / test / ci 以外の JT_ENV を、すでにデフォルトで OFF として扱います。JT_DEV_AUTH=true は明示的な escape hatch です。公開デプロイでは決して設定してはいけません。
ゲート: wrapperStore !== "postgres"発火条件: JT_DATABASE_URL / DATABASE_URL が未設定(または使える場所を指していない)で、buildServiceDeps がオンディスク SQLite にフォールバックする。何を防ぐか: SQLite は単一ノードで、タスクの一時ファイルシステム上に存在します。ECS タスクの置き換え(デプロイ、スポット中断、autoscale-in)により、他の場所に存在しないすべてのオートメーション、デプロイ記録、監査ログ行が失われます。これは1を含む任意のレプリカ数で hard gate です。単一の本番レプリカであっても、再起動時にすべてのメタデータを失ってはいけません。
このゲートが報告するのは、URL 環境変数が設定されているかどうかだけではなく、composition root が実際に構築した store です。到達できないホストを指す DATABASE_URL があり、静かにフォールスルーした場合でも、このゲートは発火します。wrapperStoreKindbuildServiceDeps が実際に構築したものを反映するためです。
修正: JT_DATABASE_URL(または DATABASE_URL)を RDS Postgres に向けます(docs/11 §4.1)。
ゲート: replicaCount > 1 && !multiReplicaGapsClosedreplicaCount <= 1 では不活性です。単一インスタンスには、自身と不一致を起こすレプリカ間状態がありません。multiReplicaGapsClosed が true になるのは、docs/11 §6 の単一プロセスギャップのうち、3つすべてが閉じられている場合だけです。
  • 共有 Redis rate limiter / pub-sub relay(JT_REDIS_URL / REDIS_URL)、
  • スケジュール発火元がローカル croner ではなく、署名付き EventBridge tick であること(JT_INTERNAL_TICK_SECRET)、
  • エンジン store(runs/journal)もプロセスごとの SQLite ではなく Postgres であること(BROWSERFLOW_DATABASE_URL / DATABASE_URL)。
何を防ぐか: これらを閉じないまま API を2つ以上のタスクにスケールすると、プロセスごとの rate limiter が過少カウントします(実効的な制限はレプリカごとに倍増します)。ローカル croner は各スケジュールを1回ではなく N 回発火し、1つのプロセスのメモリに固定された live-view / オーサリングストリームは、別タスクへ再接続したときに静かに落ちます。
JT_REPLICA_COUNT は Terraform スタックから設定されません(api_desired_count は ECS スケーリングを駆動しますが、この環境変数には通されていません)。そのため、現在の本番ではスナップショットがデフォルトで 1 になります。JT_REPLICA_COUNT も一致するように設定せずに api_desired_count を1より大きくすると、実際の §6 ギャップが存在していても、このゲートは静かに不活性のままです。スケールアウト前に、デプロイされたレプリカ数を反映するよう JT_REPLICA_COUNT を明示的に設定してください。これを owner に共有してください。本来それを検知するためのゲートを一度も発火させずに、ECS をスケールするのは簡単です。
修正: api_desired_count を1より大きくするに、Redis、署名付き tick エンドポイント、Postgres のエンジン store を接続します。そのうえで、JT_REPLICA_COUNT を一致するよう設定します。
ゲート: browserBackend === "e2b" && !e2bCloseWired。E2B がアクティブなバックエンドの場合だけ評価されます。ローカル Playwright バックエンドは対象外です。何を防ぐか: 明示的に閉じられない E2B sandbox は、それを開いた Run が終了した後も課金を継続します(かつブラウザーセッションも生き続けます)。Run の量に応じて増幅するリークです。
現在のソースでは、e2bCloseWiredbuildServiceDeps 内で true にハードコードされています(run gateway は現在 capability.close() を無条件に接続しており、docs/11 §3.1 が当初指摘していたギャップを閉じています)。これはもう環境変数からは読み取られません。このゲートは回帰ガードとして evaluateReadiness に残されています。将来 gateway の変更で E2B sandbox を閉じなくなったとき、静かに出荷されるのではなく、このチェックが再び発火するようにするためです。
ゲート: albIdleTimeoutSeconds < sseWindowSeconds。サービス自身の値は固定です。server.ts は Bun の idleTimeout: 255(Bun の最大値)を設定し、遅いモデルターンや human-in-the-loop の一時停止をまたいでも、オーサリング SSE と live-view WS を維持します。何を防ぐか: idle timeout が 255s より短い ALB(または任意のリバースプロキシ)は、長時間生きるオーサリング / Run ストリームを途中で切断します。ALB のデフォルト idle timeout は 60s で、この window を大きく下回ります。
albIdleTimeoutSecondsJT_ALB_IDLE_TIMEOUT_S から読み取られ、未設定の場合はデフォルトで 300 になります。しかし、その環境変数は現在 ECS タスク定義のどこにも設定されておらず、実際の ALB を introspect するわけでもありません。Terraform の alb_idle_timeout 変数は別途 >= 300 として検証されています(deploy/terraform/variables.tf)。そのため、この2つの数値はドリフトする可能性があります。誰かが Terraform の外で ALB の実際の idle timeout を手動編集したとしても、このゲートは平然と 300(コード上のデフォルト)を報告します。実際にデプロイされている alb_idle_timeout を反映するよう、JT_ALB_IDLE_TIMEOUT_S を明示的に設定してください。そうしなければ、このゲートが確認しているのはインフラではなく、ハードコードされた仮定だけです。
修正: ALB ターゲットグループの idle timeout を >= 300s に保ち(Terraform 変数の検証で強制されます)、JT_ALB_IDLE_TIMEOUT_S を同じ値に設定して、/ready が現実を反映するようにします。

/ready がチェックしない docs/11 §12 の2項目

docs/11-production-deployment-aws.md §12 には10個のチェックリスト項目があります。evaluateReadiness はそのうち8個を自動化します。残り2つはこのゲートでは評価されません。環境変数ではなく、手動レビューが必要です。
  • Browser tier SG can reach instance metadata / private CIDRs (SSRF) — ネットワーク / セキュリティグループのレビュー(docs/11 §8)です。サービスが自身について観測できる runtime condition ではありません。
  • Any log line carries a resolved secret value — コードレビュー / テストスイート上の懸念(assertNoSecretLeak、docs/11 §8)です。起動時スナップショットで検証できるものではありません。
上記8つの自動ゲートと並行して、この2つをデプロイチェックリストの一部として実行してください。/ready が green であっても、docs/11 §12 の全リストを満たしているとは限りません。

Service(コントロールプレーン)

/ready が守っているアーキテクチャです。API、registry、scheduler、secrets broker、run store が含まれます。

マネージドシークレット

ゲート2の背後にある境界の不変条件です。なぜマスターキー / KMS ゲートが重要になる場所にシークレット値が保存されないのかを説明します。

Access control & audit

実認証(ゲート4)によって意味を持つ RBAC と監査証跡です。

可観測性

ゲート5の永続的な Postgres store に依存する Run inspector です。