運用 / デプロイ
ローカル開発
前提
- Bun (リポジトリルートで
bun install済み) - Wrangler (Cloudflare 認証済み)
apps/noisy-crow-app/wrangler.tomlとapps/noisy-crow-events/wrangler.tomlのREPLACE_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 で動く。必要に応じてそれぞれ起動する。
# 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 devUI 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 installでbun.lockを更新してからコミットすること。CI はbun install --frozen-lockfileを使うため、package.jsonとbun.lockがずれると失敗する。UI Worker の型生成はbun run --filter @tied-workspace/noisy-crow-app typegen(=react-router typegen+wrangler types)。
デプロイ
2 つの Worker をそれぞれデプロイ する。
# 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.com は noisy-crow-web に、Slack の Request URL nc-events.tied-workspace.com/slack/events は noisy-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 を待たない緊急時):
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_KV | KV binding | (UI からは未使用だが共有リソース) |
EMOJI_DB | D1 binding | 絵文字メタデータ + 同期ジョブ状態/履歴の正本。DB tied-workspace (スキーマは packages/d1/ で統一管理) |
AUTH_DB | D1 binding | 共有認証基盤 (better-auth)。DB tied-auth (yamabiko-web と共有。スキーマは packages/d1/migrations/tied-auth/) |
EMOJI_BUCKET | R2 binding | 除外絵文字 |
EMOJI_VECTORIZE | Vectorize binding | 絵文字ベクトル |
SYNC_WORKFLOW | Workflow binding | 絵文字インデックス同期 (Workflow noisy-crow-sync) |
CLOUDFLARE_ACCOUNT_ID | var | AI Gateway URL 構築 |
AI_GATEWAY_ID | var | AI Gateway URL 構築 |
BETTER_AUTH_URL | var | better-auth のオリジン (https://nc.tied-workspace.com)。OIDC コールバック URL の生成に使う |
SLACK_BOT_TOKEN | secret | emoji.list / 同期 |
AI_GATEWAY_TOKEN | secret | Authenticated Gateway のトークン (cf-aig-authorization)。全 AI リクエストに必須 |
GOOGLE_API_KEY | secret | 全 Gemini リクエストに x-goog-api-key として付与 (Gateway 経由のまま直接認証)。Unified Billing の認証注入は Batch 系と gemini-3.5-flash の generateContent に効かず 403 になるため、sync / caption 再解析を持つ UI Worker では必須 |
BETTER_AUTH_SECRET | secret | better-auth のセッション署名鍵 (共有認証基盤) |
SLACK_CLIENT_ID | secret | Sign in with Slack (OIDC) の client id |
SLACK_CLIENT_SECRET | secret | Sign in with Slack (OIDC) の client secret |
GEMINI_MODEL | var (任意) | caption / reaction text 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-3.5-flash) |
GEMINI_EMBEDDING_MODEL | var (任意) | embedding 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-embedding-001) |
UI Worker は cron trigger (0 18 * * * = 03:00 JST) を持ち、diff 同期を毎日自動起動する (実行中ジョブがあれば no-op)。
UI Worker には
SLACK_SIGNING_SECRETもSIMILARITY_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_KV | KV binding | イベント重複排除 (event:<id>) |
EMOJI_DB | D1 binding | UI Worker と共有 (実行時には参照しないが composition root が要求) |
EMOJI_BUCKET | R2 binding | 除外絵文字 |
EMOJI_VECTORIZE | Vectorize binding | 絵文字ベクトル |
CLOUDFLARE_ACCOUNT_ID | var | AI Gateway URL 構築 |
AI_GATEWAY_ID | var | AI Gateway URL 構築 |
SIMILARITY_THRESHOLD | var | 類似度しきい値 ("0.6") |
SLACK_BOT_TOKEN | secret | reactions.add / fetchThread |
SLACK_SIGNING_SECRET | secret | /slack/events の Slack 署名検証 |
AI_GATEWAY_TOKEN | secret | Authenticated Gateway のトークン (cf-aig-authorization)。全 AI リクエストに必須 |
GOOGLE_API_KEY | secret | 全 Gemini リクエストに x-goog-api-key として付与 (Gateway 経由のまま直接認証)。検索 (embedContent) も Unified Billing の注入対象外で 403 になることが本番確認されたため必須 |
GEMINI_MODEL | var (任意) | caption / reaction text 用 Gemini モデルの上書き。省略時はコード側デフォルト (gemini-3.5-flash) |
GEMINI_EMBEDDING_MODEL | var (任意) | 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-001のembedContent) で認証注入が効かず 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 Worker の
https://nc-events.tied-workspace.com/slack/events(UI のnc.tied-workspace.comではない)。subscribe するイベントはmessage.channels。 - events Worker は Slack がユーザー認証を通過できないため ユーザー認証を持たない公開 Worker とする。リクエスト真正性は Slack 署名検証 (
SLACK_SIGNING_SECRET、src/verify-signature.ts) で担保する (UI Worker は better-auth で保護する別 Worker)。 /slack/eventsは署名検証後 即 200 を返し (Slack の 3 秒 ack)、実処理はctx.waitUntil(processStampRequest)で非同期に走らせる。url_verificationチャレンジにも応答する。- Queue を廃止したため 自動リトライ / DLQ は無い (旧構成)。重複配送は
SESSIONS_KVのevent:<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)。
- Voyage → Gemini embedding 移行 (2026-06) もこの手順。次元は同じ 1024 だが ベクトル空間が別物のため、デプロイ後に Rebuild を 1 回実行するまでは検索品質が保証されない (query は Gemini・既存ベクトルは Voyage の混在になる)。旧 secret
- embedding モデルの次元が変わる場合: Vectorize index は次元固定のため、(1) 新 index を作成 → (2) 両 Worker の wrangler 設定の
index_nameを更新してデプロイ → (3) Rebuild 実行 → (4) 完了確認後に旧 index を削除。 - 進捗 / 失敗の確認: Web の
/syncsの進捗パネルと履歴テーブル (いずれも D1sync_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 Workernoisy-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 冒頭で
- 同一オリジン内で 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.tomlとapps/noisy-crow-events/wrangler.toml) を更新し、両者が同一リソースを指すよう揃えたか (SYNC_WORKFLOWは UI Worker のみ) - [ ] D1 のスキーマを変えた場合、
packages/d1/migrations/tied-workspace/に migration を追加したか (スキーマ管理はpackages/d1/に限定。アプリ配下に migrations を置かない。本番へはアプリと独立した CIdeploy-d1.ymlが自動適用する。スキーマ変更を伴う場合は migration を先にマージする) - [ ] 依存を変えたら
bun installでbun.lockを更新してコミットしたか (CI は frozen-lockfile) - [ ] 認証は共有認証基盤
@tied-workspace/authに集約し、新しいページの loader / action にrequireSessionを入れたか (GET/POST 両方。/sign-in/api/auth/*を除く)。アプリ独自の認証ロジックを足していないか - [ ] 仕様変更がある場合、本ドキュメント (
docs/docs/apps/noisy-crow/) を更新したか