Skip to content

運用 / デプロイ

ローカル開発

前提

  • Bun (リポジトリルートで bun install 済み)
  • Wrangler (Cloudflare 認証済み)
  • apps/noisy-crow-app/wrangler.tomlapps/noisy-crow-events/wrangler.tomlREPLACE_WITH_* を実環境のリソース ID で埋める (両 Worker が同一リソースを指すよう揃える)
  • 必要な secret は Worker ごとに wrangler secret put -c apps/<worker>/wrangler.toml で投入
  • D1 (EMOJI_DB = tied-workspace) のスキーマをローカルに適用: bunx wrangler d1 migrations apply tied-workspace --local --persist-to apps/noisy-crow-app/.wrangler/state -c packages/d1/wrangler.toml (UI Worker の dev では Miniflare がローカル D1 を供給する。スキーマは packages/d1/ で統一管理)

起動

noisy-crow は 2 つの Worker で動く。必要に応じてそれぞれ起動する。

bash
# UI Worker noisy-crow-web (React Router v7 + Cloudflare Vite plugin)
bun run --filter @tied-workspace/noisy-crow-app dev

# Events Worker noisy-crow-events (Slack Events 受信, wrangler dev)
bun run --filter @tied-workspace/noisy-crow-events dev

UI Worker は @cloudflare/vite-plugin 経由で起動し、Miniflare が KV / D1 / R2 / Vectorize バインディングをローカル供給する。events Worker は wrangler dev で起動する。旧構成の Bot Container / Queue Consumer / Vite proxy / VITE_API_URL は廃止済み。Vectorize 等のローカルエミュレーションが必要なバインディングは各 wrangler.toml の設定に従う。

⚠️ 依存を変更したら bun installbun.lock を更新してからコミットすること。CI は bun install --frozen-lockfile を使うため、package.jsonbun.lock がずれると失敗する。UI Worker の型生成は bun run --filter @tied-workspace/noisy-crow-app typegen (= react-router typegen + wrangler types)。

デプロイ

2 つの Worker をそれぞれデプロイ する。

bash
# UI Worker noisy-crow-web (RR7 Worker)
bun run --filter @tied-workspace/noisy-crow-app deploy      # react-router build && wrangler deploy

# Events Worker noisy-crow-events (Slack Events 受信)
bun run --filter @tied-workspace/noisy-crow-events deploy   # wrangler deploy

# ビルド検証のみ (デプロイしない。Cloudflare クレデンシャル不要)
bun run --filter @tied-workspace/noisy-crow-app build       # react-router build
bun run --filter @tied-workspace/noisy-crow-events build    # wrangler deploy --dry-run

横断 CI (ci.yml) が全 PR で bun run --filter '*' build (events Worker は dry-run) と bun run --filter '*' test を path フィルタ無しで実行する。

UI Worker は Cloudflare Workers (Workers Assets でクライアント資産を配信 + SSR) にデプロイする。本番ドメイン nc.tied-workspace.comnoisy-crow-web に、Slack の Request URL nc-events.tied-workspace.com/slack/eventsnoisy-crow-events にルーティングする (旧 Pages プロジェクト / nc-api Worker / Bot Container / Consumer Worker は不要)。secret は Worker ごとに wrangler secret put -c apps/<worker>/wrangler.toml <NAME> で投入する (下表参照)。

D1 (EMOJI_DB) のセットアップ / スキーマ適用

絵文字メタデータと同期ジョブ状態に D1 (tied-workspace、作成済み) を使う。スキーマは packages/d1/ で統一管理する (packages/d1/README.md 参照)。

本番へのスキーマ適用は専用 CI (deploy-d1.yml) が自動実行する: packages/d1/** の変更が main にマージされると、アプリのデプロイとは独立して wrangler d1 migrations apply tied-workspace --remote が走る (適用済みの migration はスキップされる)。この D1 は noisy-crow 以外のアプリも使うため、アプリの deploy workflow にはスキーマ適用を入れない。migration を追加したら packages/d1/migrations/tied-workspace/ にコミットして main にマージするだけでよい (スキーマ変更を伴うコード変更は migration を先にマージする)。

手元から即時適用したい場合 (CI を待たない緊急時):

bash
bunx wrangler d1 migrations apply tied-workspace --remote -c packages/d1/wrangler.toml

旧 KV (EMOJI_META_KV) からのデータ移行は行わない。デプロイ直後は D1 が空で /emojis も空になるため、/syncs から Sync (再バッチ) を 1 回実行して Slack から全件再構築する (cron の自動実行を待ってもよい)。caption / embedding は Gemini で再生成されるため、旧 KV にしか無い手動編集 caption は引き継がれない (必要なら旧 KV から控えて /emojis/:name で再設定する)。同期ジョブ履歴もこの再バッチから記録される。

環境変数 / Secrets

バインディング / var は両 Worker の wrangler.toml に定義し、env から受け取る。secret は Worker ごとに wrangler secret put -c apps/<worker>/wrangler.toml <NAME> で投入する。

UI Worker noisy-crow-web (apps/noisy-crow-app/wrangler.toml)

変数種別用途
SESSIONS_KVKV binding(UI からは未使用だが共有リソース)
EMOJI_DBD1 binding絵文字メタデータ + 同期ジョブ状態/履歴の正本。DB tied-workspace (スキーマは packages/d1/ で統一管理)
AUTH_DBD1 binding共有認証基盤 (better-auth)。DB tied-auth (yamabiko-web と共有。スキーマは packages/d1/migrations/tied-auth/)
EMOJI_BUCKETR2 binding除外絵文字
EMOJI_VECTORIZEVectorize binding絵文字ベクトル
SYNC_WORKFLOWWorkflow binding絵文字インデックス同期 (Workflow noisy-crow-sync)
CLOUDFLARE_ACCOUNT_IDvarAI Gateway URL 構築
AI_GATEWAY_IDvarAI Gateway URL 構築
BETTER_AUTH_URLvarbetter-auth のオリジン (https://nc.tied-workspace.com)。OIDC コールバック URL の生成に使う
SLACK_BOT_TOKENsecretemoji.list / 同期
AI_GATEWAY_TOKENsecretAuthenticated Gateway のトークン (cf-aig-authorization)。全 AI リクエストに必須
GOOGLE_API_KEYsecret全 Gemini リクエストに x-goog-api-key として付与 (Gateway 経由のまま直接認証)。Unified Billing の認証注入は Batch 系と gemini-3.5-flash の generateContent に効かず 403 になるため、sync / caption 再解析を持つ UI Worker では必須
BETTER_AUTH_SECRETsecretbetter-auth のセッション署名鍵 (共有認証基盤)
SLACK_CLIENT_IDsecretSign in with Slack (OIDC) の client id
SLACK_CLIENT_SECRETsecretSign in with Slack (OIDC) の client secret
GEMINI_MODELvar (任意)caption / reaction text 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-3.5-flash)
GEMINI_EMBEDDING_MODELvar (任意)embedding 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-embedding-001)

UI Worker は cron trigger (0 18 * * * = 03:00 JST) を持ち、diff 同期を毎日自動起動する (実行中ジョブがあれば no-op)。

UI Worker には SLACK_SIGNING_SECRETSIMILARITY_THRESHOLD不要 (Slack イベント処理を持たないため)。Slack の OIDC アプリ (Sign in with Slack) のリダイレクト URL 許可リストに https://nc.tied-workspace.com/api/auth/oauth2/callback/slack を追加すること (yamabiko-web と同じ Slack アプリを使う場合は URL を併記する)。

Events Worker noisy-crow-events (apps/noisy-crow-events/wrangler.toml)

変数種別用途
SESSIONS_KVKV bindingイベント重複排除 (event:<id>)
EMOJI_DBD1 bindingUI Worker と共有 (実行時には参照しないが composition root が要求)
EMOJI_BUCKETR2 binding除外絵文字
EMOJI_VECTORIZEVectorize binding絵文字ベクトル
CLOUDFLARE_ACCOUNT_IDvarAI Gateway URL 構築
AI_GATEWAY_IDvarAI Gateway URL 構築
SIMILARITY_THRESHOLDvar類似度しきい値 ("0.6")
SLACK_BOT_TOKENsecretreactions.add / fetchThread
SLACK_SIGNING_SECRETsecret/slack/events の Slack 署名検証
AI_GATEWAY_TOKENsecretAuthenticated Gateway のトークン (cf-aig-authorization)。全 AI リクエストに必須
GOOGLE_API_KEYsecret全 Gemini リクエストに x-goog-api-key として付与 (Gateway 経由のまま直接認証)。検索 (embedContent) も Unified Billing の注入対象外で 403 になることが本番確認されたため必須
GEMINI_MODELvar (任意)caption / reaction text 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-3.5-flash)
GEMINI_EMBEDDING_MODELvar (任意)embedding 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-embedding-001)
  • MAX_EMOJI_LIMIT (reactions 上限) / MIN_EMOJIS_REQUIRED (必要件数) はコード側のデフォルト (3 / 2) を使う。
  • secret GOOGLE_API_KEY両 Worker で必須。コード上はキー未設定だと Unified Billing にフォールバックするが、本リポジトリが使う全ての組み合わせ (Batch 系 / gemini-3.5-flash の generateContent / gemini-embedding-001embedContent) で認証注入が効かず 403 になることが本番確認済みのため、フォールバックで動く呼び出しは実質無い。events Worker へは wrangler secret put GOOGLE_API_KEY -c apps/noisy-crow-events/wrangler.toml で設定する。
  • 旧 REST-from-Container 用の var (CLOUDFLARE_QUEUE_ID / CLOUDFLARE_KV_NAMESPACE_ID / CLOUDFLARE_API_TOKEN) は不要 (旧構成。Worker はネイティブバインディングを使う)。
  • VITE_API_URL も廃止 (旧構成)。

Slack Events API

旧構成の Socket Mode は廃止し、Slack は HTTP Events API で events Worker に POST する。

  • Slack App の Socket Mode は無効 にする。
  • Event Subscriptions の Request URL は events Workerhttps://nc-events.tied-workspace.com/slack/events (UI の nc.tied-workspace.com ではない)。subscribe するイベントは message.channels
  • events Worker は Slack がユーザー認証を通過できないため ユーザー認証を持たない公開 Worker とする。リクエスト真正性は Slack 署名検証 (SLACK_SIGNING_SECRETsrc/verify-signature.ts) で担保する (UI Worker は better-auth で保護する別 Worker)。
  • /slack/events は署名検証後 即 200 を返し (Slack の 3 秒 ack)、実処理は ctx.waitUntil(processStampRequest) で非同期に走らせる。url_verification チャレンジにも応答する。
  • Queue を廃止したため 自動リトライ / DLQ は無い (旧構成)。重複配送は SESSIONS_KVevent:<id> 重複排除で抑止する (簡素さを優先した意図的なトレードオフ)。

絵文字インデックス同期 (Workflow noisy-crow-sync) の運用

  • 通常運用: 何もしなくてよい。cron が毎日 diff 同期を起動し、新しい絵文字が自動で入る。即時反映したいときは Web の /syncs ページの Sync from Slack
  • モデルのバージョンアップ (次元が同じ場合): CfAiGateway のモデル設定を更新してデプロイ → Web の Rebuild を 1 回実行。メタデータのモデル記録と現行モデルを突き合わせ、古いモデルで作られたものだけ再処理される。
    • Voyage → Gemini embedding 移行 (2026-06) もこの手順。次元は同じ 1024 だが ベクトル空間が別物のため、デプロイ後に Rebuild を 1 回実行するまでは検索品質が保証されない (query は Gemini・既存ベクトルは Voyage の混在になる)。旧 secret VOYAGE_API_KEY は両 Worker から削除してよい (wrangler secret delete)。
  • embedding モデルの次元が変わる場合: Vectorize index は次元固定のため、(1) 新 index を作成 → (2) 両 Worker の wrangler 設定の index_name を更新してデプロイ → (3) Rebuild 実行 → (4) 完了確認後に旧 index を削除。
  • 進捗 / 失敗の確認: Web の /syncs の進捗パネルと履歴テーブル (いずれも D1 sync_jobs、過去ジョブも遡れる)。Workflow インスタンスの詳細は Cloudflare ダッシュボード (Workers & Pages → Workflows → noisy-crow-sync) でも確認できる。失敗時は status に failureReason が入り、再実行はボタンを押し直すだけ (plan から作り直す)。
  • caption は Gemini Batch API (非同期・50% 割引) で生成するため、ジョブの所要時間はバッチの混雑次第で数分〜数十分かかることがある (SLA 24h)。ジョブはバックグラウンドで動くのでページを閉じて構わない。

アクセス制御

  • 管理 Web (nc.tied-workspace.com, UI Worker noisy-crow-web) は better-auth (共有認証基盤 packages/auth) のセッションで保護 する。IdP は Sign in with Slack (OIDC)。Cloudflare Access (Zero Trust) は使わない — やまびこに次ぐ意図的な例外で、認証ロジックは共有認証基盤 packages/auth のみに置き、noisy-crow 側には書かない (instantiation/requireSession の薄い配線のみ)。
    • 各ページの loader / action 冒頭で requireSession(request, env) を呼び、未サインインなら /sign-in へ redirect する。loader (GET) と action (POST) の両方に入れること (action はレイアウト loader では守れない)。
    • /sign-in/api/auth/* (better-auth ハンドラ) は保護対象外。workers_dev = false / preview_urls = false*.workers.dev / preview URL の素通りも塞ぐ。
    • セッション / ユーザーは共有 D1 tied-auth (AUTH_DB バインディング) に入り、yamabiko-web と同じユーザーを共用する。
  • 同一オリジン内で loader / action を呼ぶため CORS / credentials: "include" は不要
  • events Worker (noisy-crow-events) は公開 Worker とする。Slack は better-auth も Zero Trust も通過できないため、ユーザー認証は持たず Slack 署名検証 (SLACK_SIGNING_SECRET) でリクエスト真正性を担保する (ユーザー認証ではなくリクエスト検証であり、CLAUDE.md の「認証ロジックを足さない」方針に対する意図的かつスコープ限定の例外)。
  • 上記を除き、アプリコードに独自の認証 / 認可ロジックを足してはいけない (認証は packages/auth に集約する)。

改善時のチェックリスト

新機能 / リファクタを入れる前に必ず確認:

  • [ ] 仕様書トップ の "設計の不変条件" に違反していないか
  • [ ] レイヤー依存方向 (domain ← application ← infrastructure) を保てているか
  • [ ] 新しい外部依存があれば共有コア (packages/noisy-crow-core) の ports.ts に抽象を追加してから実装したか
  • [ ] データ操作を足す場合、loader / action から buildWebDeps(env) 越しにユースケースを呼んでいるか (バインディング直叩きをしていないか)。型は共有コア @tied-workspace/noisy-crow-core を import しているか
  • [ ] サーバー専用コードは *.server.ts / loader / action に閉じ込め、ブラウザバンドルに混ぜていないか
  • [ ] Cloudflare リソースを足したら 両 Worker の wrangler.toml (apps/noisy-crow-app/wrangler.tomlapps/noisy-crow-events/wrangler.toml) を更新し、両者が同一リソースを指すよう揃えたか (SYNC_WORKFLOW は UI Worker のみ)
  • [ ] D1 のスキーマを変えた場合、packages/d1/migrations/tied-workspace/ に migration を追加したか (スキーマ管理は packages/d1/ に限定。アプリ配下に migrations を置かない。本番へはアプリと独立した CI deploy-d1.yml が自動適用する。スキーマ変更を伴う場合は migration を先にマージする)
  • [ ] 依存を変えたら bun installbun.lock を更新してコミットしたか (CI は frozen-lockfile)
  • [ ] 認証は共有認証基盤 @tied-workspace/auth に集約し、新しいページの loader / action に requireSession を入れたか (GET/POST 両方。/sign-in /api/auth/* を除く)。アプリ独自の認証ロジックを足していないか
  • [ ] 仕様変更がある場合、本ドキュメント (docs/docs/apps/noisy-crow/) を更新したか