v1
PoC / 開発計画

開発プラン

実査機能 新基盤(OpenTok → LiveKit 移行)の開発計画書。レーンA(新基盤)minedia-www レーンBレーンC(インフラ構築)の3レーン並走を Phase 0〜6 で構成し、全ユースケースのトレーサビリティと工数見積を示す。インプットは実査機能ユースケース統合版 PDF(全41ページ)・To-Be シーケンス図 v2・本設計リファレンス(API / 認可 / Webhook / エンティティ / minedia-www 変更点 / インフラ / オブザーバビリティ)。

1. 全体像

1.1 To-Be アーキテクチャ(3層分離)

  • 調査ドメイン(minedia-www 残置): Project / Slot / Answer / Interview、パネル紐付け、リマインド(Mail/SMS/Push)、出欠・評価、謝礼(Minedia::Point)、要約依頼(StatementSummaryRequest
  • 基盤コア(新規開発): Room / Session / Participant / EntryHistory / Recording / Transcription / ConnectionTest / Handout / Chat履歴 / RoomEvent の REST API、入室ゲート(JWT検証 → RoomPolicy → LiveKit grants)、LiveKit webhook 受信、調査側への W1〜W7 配信
  • インタビュールーム SPA(新規開発): Vite + React SPA + Workers Assets 配信(確定)。全ロール共通のルーム UI、チャット(サーバー権威型・LiveKit データチャネル再利用)、接続テスト自前計測画面。インフラ配置はインフラ構成(Cloudflare 案)を参照
  • 管理画面(新規開発・開発者向け): Refine SPA を Workers Assets で配信し Cloudflare Access でゲート。コアへは Service Bindings の内部プロキシ /internal/admin 経由(ApiKey 非露出)。詳細は §1.3
  • LiveKit Cloud: SFU+Egress(録画)。Ship プラン・録画は video composite(確定)

1.2 実査ライフサイクル(To-Be シーケンス v2 の5フェーズ)

#フェーズ主な処理
作成Project/Slot 作成(調査) → POST /rooms(kind=interview, nested session, 2-phase commit) → connect_url 保存 → POST /sessions/{id}/handouts
配布安定URL(connect_url)の共有・リマインド。JWT/Participant はこの時点で作らない
準備POST /connection-tests → 基盤の自前計測画面で双方向計測 → W5 → 調査側で合否判定(User.webrtc_test_status
参加入室時はテナントが自己署名した参加 JWT を SPA が提示(基盤への発行往復なし・Participant は初回検証時に JIT 生成) → 二重トークンゲート(JWT検証 → grants 導出 → LiveKit トークン) → メディア接続 → EntryHistory / W1 / BAN+revocation
振り返りEgress 完了 → Recording → 自動文字起こし → W3/W6 → 出欠評価(entry-histories)→ 謝礼 → 要約(Transcription.uuid 参照)→ 録画閲覧(署名URL)

1.3 管理画面(開発者向け内部ツール)

項目要件
目的・対象開発チーム専用の内部管理画面。Room(URL発行)/ Session / Handout の作成・参照、Participant / EntryHistory の確認、ApiKey の発行・失効、テナント署名鍵の生成・登録・ローテーション・失効を行う。Operator 向け・minedia-www 側の管理画面(Phase 5)とは別物
フロントエンドRefine(React)SPAWorkers Assets(静的配信)でホスティング(ルーム SPA・基盤コアと Workers 系ツールチェーンに統一。Pages は不使用)
認証Cloudflare Access(Zero Trust)でメールアドレスベースにゲート
コアとの接続Service Bindings で内部接続(公開ホップなし・CORS 不要。インフラ構成(Cloudflare 案)の Worker 構成参照)
内部プロキシ/internal/admin/api/v1/*(基盤コア Worker 内)。Cloudflare Access の JWT(Cf-Access-Jwt-Assertion)を CF の公開鍵で検証してアクター(メールアドレス)を特定 → サーバー側保持のテナント ApiKey を付与して公開 API へ転送。テナント ApiKey の実値はブラウザに渡さない
監査書き込み操作は検証済みメールアドレスを X-Admin-User-Id として転送し、将来の監査ログ(RoomEvent payload / audit_logs)に接続できるようにする。ApiKey の発行・失効・変更は専用の監査ログ api_key_events に記録が確定(アクター=CF Access メール。エンティティ一覧参照)
実装フェーズRoom / Session / Participant / EntryHistory・ApiKey 管理・署名鍵管理画面=Phase 1、Handout 画面=Handout API 実装と同時に Phase 2 で追加

2. 開発フェーズ計画

G1 OpenAPI 契約凍結ゲート

Phase 0 末

これ以降レーンBはモック(契約テスト)で先行着手できる。レーンCの完了は不要(インフラ非依存)。

G2 コア・ステージング稼働ゲート

Phase 3 末目安

レーンBの結合テスト開始条件。

Phase 0: 環境整備・契約凍結

ゴール:OpenAPI 契約が凍結されて(G1)、レーンBが並走開始できる(レーンCは Phase 0 開始と同時に着手済み)。実インフラの構築はレーンC(並走)で進めるため、Phase 0 自体はコード・契約整備に専念する。

0-1. 基盤コア リポジトリのスケルトン

作業項目内容
雛形pnpm + TypeScript + Hono + @hono/zod-openapi
エントリポイント4種fetch(API)/ queue(配信 consumer)/ Workflows / scheduled(Cron)の骨格。1 Worker 同居 or 分割の初期判断込み
wrangler.jsonccompatibility_date(開始日固定)、nodejs_compatobservability(traces + head_sampling_rate)、bindings(Hyperdrive / Queues / Analytics Engine / Rate Limiting)の定義のみ先行。実リソースへの接続はレーンC完了後(それまではローカル/モックで進行)。Env 型は wrangler types で生成(手書き禁止)
Prisma 7 初期化@prisma/adapter-pg、schema.prisma の空枠、prisma migrate を CI から TCP 実行する経路確立(エンティティ定義は P1。実 Aurora への疎通はレーンC完了後)
テスト基盤vitest + @cloudflare/vitest-pool-workers
convention test 初期セットtruffle-survey-v2 から移植・翻案: ①エラーエンベロープ形式 ②全ルートが認証ミドルウェア経由 ③zod-openapi ルート定義とハンドラの網羅 ④no-floating-promises(ESLint)
CI/CDGitHub Actions: typecheck / lint / test / migrate / wrangler deploy(dev→stg。stg デプロイの実行はレーンCの Workers 環境分離完了後)
シークレット運用wrangler secret put の手順化(IAM キー・LiveKit キー・webhook 署名鍵)

0-2. 横断規約の実装

作業項目内容
ApiKey 認証ミドルウェアApiKey <key_id>.<secret>、bcrypt ハッシュ照合 + timingSafeEqual、L0/L1 テナント分離を PoC 段階から実装。キーのスコープ分離はここで仮決め
Idempotency-Keyミドルウェア実装(Postgres ユニーク制約 + ON CONFLICT
エラーエンベロープsuccess/data|error + Hono onError での一元変換
カーソルページネーション共通ヘルパー
標準エンドポイント/health/health/ready/.well-known/jwks.json(jose・EdDSA 鍵の生成・保管・ローテーション方針を含む)

0-3. OpenAPI 契約の整備・凍結(G1)

作業項目内容
全エンドポイントの契約化interview-api-docs 準拠の全エンドポイントを @hono/zod-openapi のルート定義(スタブ実装)として起こし、openapi.yaml をコードから生成(定義=契約=実行時検証の単一ソース)。本計画で追加が確定した契約(チャット定型スタンプ、翻訳 kind、加工動画 API、Room/Session 作成時の設定封入〈録画・文字起こし・入室画面〉等・P2/P3/P5 参照)も api-design へ反映した上で凍結する。Phase 0 の最重量作業。凍結対象は公開 API(ApiKey 面)+Webhook のみ——インタビュールーム(参加 JWT 面)の SEND/RECEIVE はインタビュールーム設計が正典で G1 凍結対象外(基盤とルーム SPA が同一システム内のクローズド契約。実装は同じ zod-openapi でルート定義し実行時検証は行う)
lint・ドキュメント配信redocly lint を CI に、UI は Scalar(@scalar/hono-api-reference・確定)をコア Worker の /docs で配信。interview-api-docs(本サイト)との整合手順を決める
レーンB 先行の足場Prism モックサーバー起動手順 + minedia-www 側 committee 契約テスト雛形
G1 判定契約凍結の宣言、凍結後の変更管理ルール(versioning 手順)

0-4. LiveKit Cloud セットアップ + 前倒しスパイク

作業項目内容
プロジェクト作成dev / stg、リージョン確認
Workers 実機動作確認livekit-server-sdk(AccessToken 生成 / CreateRoom / WebhookReceiver)を Workers 上で実証。依存構成上は動く見込みだが未実証のため、「薄い E2E スパイク」の前半を P0 に前倒し(dev 環境の Workers で検証可能な範囲。レーンCの環境分離完了前でも着手できる)
webhook 疎通署名検証・raw body・失敗 401

0-5. ルーム SPA スケルトン

作業項目内容
形態の決定完了 Vite + React SPA + Workers Assets で確定(SSR 不要のため OpenNext / Next.js は不採用。OpenNext 動作検証タスクは消滅)
雛形・デプロイVite + React + livekit-client + @livekit/components-react 導入、Workers Assets で dev/stg デプロイ
チャットの枠サーバー権威型(コアの HTTP ingest → sendData 配信)準拠。SPA 側は DataReceived 購読・履歴 API 取得の枠のみ用意(実装は P2)

0-6. オブザーバビリティ基盤

作業項目内容
メトリクスAnalytics Engine binding + writeDataPoint ヘルパー、標準属性契約(tenant.id/room.uuid)のコード化(メトリクスは OTLP と別経路)
エラートラッキング@sentry/cloudflaretracesSampleRate: 0Team プラン確定
構造化ログJSON・PII 排除の規約をログヘルパー + convention test で強制

レビュー稼働 4 人日幅 3〜5 人日 / 想定カレンダー 6〜9 営業日

レーンC: インフラ構築(AWS / Cloudflare・Phase 0 と並走)

ゴール:Phase 1 でステージング環境に実接続する作業(Hyperdrive 経由の Aurora 接続、Queues 経由の Webhook 配信等)までに、AWS/Cloudflare の実インフラが整っている。

C-1. AWS 側基盤

作業項目内容
アカウント・環境分離dev / stg / prd 方針(S3 は既存アカウント流用か要判断)
Aurora Serverless v2 作成PostgreSQL・東京、min 0.5 ACU 常時(auto-pause 不使用)、public access 有効、rds.force_ssl=1
セキュリティグループHyperdrive の公式公開 IP レンジのみ許可(0.0.0.0/0 にしない)
DB ユーザー設計master 不使用。アプリ用・マイグレーション用を分離
S3 バケット録画 / handout +保持ポリシー(録画180日相当)。文字起こし本文は S3 に置かず Aurora 行内(result_json・As-Is 踏襲)
IAMWorker 用最小権限キー(S3 presign / put)、LiveKit Egress 書き込み用

C-2. Cloudflare 側基盤

作業項目内容
Workers Paid・環境分離wrangler environments で dev / stg / prd
Hyperdrive 設定環境ごとに Aurora 接続文字列を登録、TLS verify-full
Queueswebhook-deliveries + DLQ(W1〜W7 配信用)
Workflows / Cron Triggers有効化確認(録画→文字起こしパイプライン・リコンサイルバッチの実装は P3/P5)
Smart Placement有効化(Worker を東京 Aurora 近傍で実行)
Rate Limiting bindingトークン発行エンドポイント用

レビュー稼働 1 人日幅 1〜2 人日 / 想定カレンダー 3〜5 営業日(Phase 0 と並走・クリティカルパス外)

Phase 1: コアAPI — ルーム・認可・入退室

ゴール:ルームの作成〜入室〜退出〜強制退出が API+入室ゲートで一気通貫に動く(実査の背骨)。

作業項目内容
RoomsPOST /rooms(kind=interview は nested session{}、empty_timeout=枠長+30m、2-phase commit、RTC_PROVISION_FAILED)、GET /rooms/{id}PATCH /rooms/{id}(文字起こし構成の更新)、POST /rooms/{id}/close(トークン全失効)
SessionsGET /sessions(external_type/external_id 逆引き)、GET/PATCH/DELETE /sessions/{id}(キャンセルは Room close へカスケード)
入室ゲートJWT 検証(署名/expnbf/aud/jti失効/Participant.status)→ RoomPolicy から LiveKit grants 導出(observer は canPublish:false を物理強制)→ LiveKit アクセストークン発行。違反時は発行拒否(認証設計
失効ストア(jti)失効済み jti を TTL 付きで記録する格納先を確定(Cloudflare KV か Aurora か・未決)。入室ゲートが検証のたびに参照する
RoomPolicy単一 can(ctx, capability, resource?) に認可を集約(HTTP・チャット・メディアトークン発行の全実行点が同一関数を呼ぶ)
Participants / BANGET /rooms/{id}/participantsPOST /rooms/{id}/participants/{participant_id}/banstatus=banned 更新・理由必須・冪等200・接続中なら LiveKit RemoveParticipant)。再入室阻止は入室ゲートでの status 参照により実効化
LiveKit webhook 受信署名検証(raw body・失敗 401)、イベント id dedup、participant_joined/left → EntryHistory 作成・更新(exited_at / duration_sec)、createRoom(metadata: {room_id}) による同定
EntryHistoryGET /rooms/{id}/entry-histories(出欠判定材料)
調査側 Webhook 配信基盤署名付き配信・リトライ・配信ログ(WebhookDelivery)。W1 participant.entered を実装(退室は GET entry-histories の pull で読む)
管理画面(Refine)開発者向け内部ツール(§1.3)。Room/Session/Participant/EntryHistory の一覧・作成・参照画面(Workers Assets 配信) + Cloudflare Access(Zero Trust)でのアクセスゲート + 内部プロキシ(/internal/admin/api/v1/*・CF Access JWT検証。コアへは Service Bindings 内部接続)
ApiKey 管理発行(secret は生成時に一度だけ表示・以後は bcrypt ハッシュのみ保持)・一覧(key_id / 作成日 / 最終使用日)・失効。管理画面(CF Access 認証・/internal/admin)から操作し、公開 OpenAPI 契約(G1 凍結対象)には含めない。ローテーションは複数キー並行有効で実現(新キー発行 → minedia-www 切替 → 旧キー失効)。監査: 発行 / 失効 / 変更を api_key_eventsエンティティ一覧)に記録(状態変更と同一トランザクション・構造化ログへ複写・アクター=CF Access メール)+ 照合 Cron(1 時間毎・イベント突合で不正挿入 / 改ざん / 不正再有効化を検知 → 運用チャンネルへ直接通知)
署名鍵管理テナント署名鍵(Ed25519 公開鍵レジストリ jwks_keysエンティティ一覧)のライフサイクル一式。鍵ペアは管理画面のブラウザ内(WebCrypto)で生成し、秘密鍵はその場で 1 回だけダウンロード・公開鍵 JWK のみを登録(基盤サーバーは秘密鍵に一切触れない・登録口は d 混入を拒否)。kid は RFC 7638 サムプリントをサーバーが確定。自前生成テナント向けに公開鍵(JWK / SPKI PEM)の貼り付け登録を併設。ローテーションは複数鍵並行有効(active → retiring → revoked の一方向遷移・失効伝播は鍵解決キャッシュ TTL 60 秒が上限)。基盤鍵(issuer=interview-platform)は読み取り専用表示のみ。ApiKey 管理と同じく公開 OpenAPI 契約には含めない監査: 登録 / retire / 失効を jwks_key_events に記録(状態変更と同一トランザクション・アクター=CF Access メール)+ 全変更の即時通知 + 照合 Cron(1 時間毎・不正挿入 / 改ざん / 不正再有効化を検知)。詳細は 認証設計 §署名方式

レビュー稼働 8 人日幅 6〜10 人日 / 想定カレンダー 8〜13 営業日(署名鍵管理 +2 人日・参加トークン発行 API 廃止 −1 人日を含む)

Phase 2: インタビュールーム実装(SPA+チャット)

ゴール:全ロール(パネリスト / モデレーター / オブザーバー / Operator 観察 / Extemporary 匿名)が同一ルーム UI でインタビューを実施できる。

作業項目内容
入室フローconnect_url → 参加 JWT 提示 → 入室ゲート → LiveKit 接続。無効/期限外トークンの拒絶 UI、ブラウザ非対応・デバイスアクセス拒否のエラー画面
入室前画面デバイス選択(カメラ/マイク)+映像プレビュー+マイク音量確認+名前入力(モデレーター/パネリスト)、名前入力のみ画面(observer 系)、パネリスト待機画面(司会者未着時のチェックリスト・開始時刻表示)、注意文言表示
メディア基本機能publish/subscribe(role別 grants)、マイク/カメラ ON/OFF、入室後のデバイス切替(マイク/カメラ/スピーカー・テスト音再生)、受信音量調整、画面共有、参加者タイル・レイアウト(拡大/縮小・全画面)、iOS の音声再生有効化(autoplay 制約対応)、退出(確認ダイアログ+遷移先)
チャットpublic / private(モデ↔オブザーバー)/ observer_only の 3 visibility(チャットは chat_messages 単一テーブル・チャネルエンティティなし)、定型スタンプ(visibility×ロール別セット・初期 13 種)、ファイル添付(現行どおりモデレーターのみ)、履歴の永続化(コアへ)と入室時取得。現行の observer 系チャットの Slack 転送の扱いは未定
チャット送信・添付 API(コア側・インタビュールーム)POST /rooms/{uuid}/chat_messages(参加 JWT・RoomPolicy 認可・クライアント採番 uuid の冪等送信〈リトライ再送は既存を 200 返却〉・保存→sendData 配信)。添付は署名 PUT → 送信確定の 2 段POST /rooms/{uuid}/chat_attachments(moderator のみ・MIME/サイズ検証・レコード作成なし)→ S3 直接 PUT → 送信時 attachment_key で確定(S3 キーのみ永続化・URL は都度署名。未確定オブジェクトは S3 ライフサイクル/定期清掃で回収)。スタンプは GET /rooms/{id}/chat-stamps(定義は基盤設定ファイル)+送信時 INVALID_STAMP 検証。契約正典はインタビュールーム設計(参加 JWT 面=G1 凍結対象外
チャット履歴 API(コア側)GET /rooms/{id}/chat-messages(全 visibility・添付は取得都度の短期署名 GET URL)、DELETE(運用 purge・添付 S3 オブジェクトは非同期削除)
提示物(Handouts)コア側: POST/GET /sessions/{id}/handouts(multipart)、PATCH/DELETE /handouts/{id}。SPA側: モデレーターの配布操作 → handout.show RoomEvent → 全員の画面に表示。途中入室時の表示状態復元(room-events から最新 show/hide を再生。現行 playback_events 相当)
動画同期再生play / pause / mute の同期イベント(モデレーターのみ操作可)
BAN UIモデレーター/Operator 向け確認モーダル → ban API 呼び出し。BAN された側の切断・再入室拒否表示
オブザーバー一覧見学者(observer / Operator 観察)の一覧・人数カウンタ表示(モデレーター / observer 系向け)
ルームイベント送信 API(コア側・インタビュールーム)POST /rooms/{uuid}/room_events(参加 JWT)。handout.show/hidecan?(:present)=moderator のみをサーバー側で強制し、永続化→sendData 配信。動画同期の play/pause/mute イベントも本エンドポイント経由(現行 ActionCable speak にはサーバー側認可が無く、moderator 制約が UI のみだった穴を構造的に是正)
RoomEvent 記録GET /rooms/{id}/room-events(handout.show 等の時系列取得)
録画状態表示録画中/停止のルーム内表示(Phase 3 のイベントを受けるUI枠を先行実装)、録画失敗のルーム内警告(参加者がいるのに録画イベントが来ない場合の警告・現行踏襲)
再接続 UXLiveKit 自動再接続中の状態表示・復帰、切断時の再読み込み誘導・再読み込みボタン
ルーム内ネットワーク品質表示受信品質(パケットロス・ビットレート等)の表示(現行 Help 相当。Phase 4 の「事前」接続テストとは別)
observer 付帯機能Document Picture-in-Picture(PiP 中はメイン映像停止で負荷軽減)、無人時の自動リロード相当の整理、オブザーバー招待リンクの表示/コピー
管理画面 Handout 追加管理画面(§1.3)に Handout の一覧・アップロード・更新・削除画面を追加(Handout API と同時実装)

レビュー稼働 13 人日幅 11〜15 人日 / 想定カレンダー 13〜19 営業日

Phase 3: 録画・文字起こしパイプライン

ゴール:録画が自動で回り、文字起こしまで基盤内で完結、調査側へ W3/W6 が届く(G2: ステージング一気通貫)。

作業項目内容
Egress 制御always=room 開始時 / manual=初 publish 時の録画開始、解像度・composite レイアウトの指定、egress_started/updated/ended webhook の分岐処理(COMPLETE → storage_key/size/duration 保存、FAILED → error 保存)
Recordings APIGET /rooms/{id}/recordingsGET /recordings/{id}GET /recordings/{id}/download-url(署名URL)、DELETE(個人情報消去)
ストレージAWS S3 東京(確定済み・バケットはレーンC(C-1)で作成)、保持ポリシー
自動文字起こし録画完了を契機に基盤内で自動起動(現行 minedia-www の起動4条件ロジックを基盤へ移管・再定義)。AI 処理(Azure / OpenAI Whisper / ElevenLabs)連携、VTT 出力。話者分離付き VTT(<v 話者> タグ)を出力仕様とする(現行 Azure の話者欠落バグは移管時に解消)
Transcriptions APIGET /recordings/{id}/transcriptionsGET /transcriptions/{id}POST /recordings/{id}/transcriptions(別言語・再実行に加え、kind=translation で日→英翻訳を統合。現行 Whisper translation・local/translated 相当の移管)、GET /transcriptions/{id}/result(VTT 本文取得・result_url は TTL 3600 秒)
加工動画(ProcessedRecording)Recording と 1:1 シングルトンPOST /recordings/{id}/processed-recordingsource=trim=トリム指示 → Workflows で非同期処理/source=upload=署名 PUT 払い出し)+POST /recordings/{id}/processed-recording/complete(S3 HEAD 検証で available 化)+GETGET .../download-urlDELETE。ブラー/音声変換の自動化は対象外(現行も未自動化・将来拡張)。保護加工の依頼管理・録画の外部共有は www 残置(Phase 5)
W3 / W6 / W7 配信recording.status_changed(available/failed)、transcription.completed(翻訳完了を含む・kind で区別)、processed_recording.status_changed(加工動画 available/failed・W7)
録画状態のルーム内通知recording_status topic(LiveKit データチャネル)でのライブ表示に加え、ChatMessage(content_type=archive_status) を基盤ランタイムが自動生成(egress webhook 契機・observer_only へ永続化・語彙は recording_started/recording_stopped の 2 値)。振り返り画面の履歴表示は履歴 GET で現行同等(Egress に一時停止は無いため現行の paused 通知は廃止
アラート録画失敗・provision 失敗 → P1 即時オンコール(observability 設計準拠)

レビュー稼働 8 人日幅 6〜10 人日 / 想定カレンダー 9〜13 営業日

Phase 4: 接続テスト

ゴール:パネリストが事前に自前計測画面で接続テストでき、結果が調査側の合否判定(User.webrtc_test_status)に届く。

作業項目内容
ConnectionTests APIPOST /connection-tests(+Room kind=connection_test、test_url 払い出し)、GET /connection-tests(external_type/external_id 必須)、GET /connection-tests/{id}(summary/baseline/load)
自前計測画面signaling / webrtc / turn / reconnect / publish / protocol / region の双方向計測。短命トークン(当該テストにスコープ)
計測結果保存ConnectionTest(checks/summary) 更新+ConnectionTestEvent 記録
W5 配信connection_test.completed

レビュー稼働 4 人日幅 3〜5 人日 / 想定カレンダー 5〜7 営業日

Phase 5: minedia-www 連携改修(レーンB・G1 後にモック先行)

ゴール:調査ドメインが新基盤を呼び出し、W1〜W7 を受信して既存業務(出欠・謝礼・要約・録画管理)が回る。

作業項目内容
Core::ApiClientApiKey 認証・Idempotency-Key・リトライを備えた API クライアント。契約テスト(OpenAPI モック)で G2 前に先行実装
スキーマ変更interview_room_refs(薄い参照モデル: slot_id NOT NULL の Project 系専用・uuid群・first_entered_atteardown_state・同期鮮度)、extemporary_sessions(即席の薄い実体: ExtemporarySlot 廃止の後継・フォルダ紐付け+コア Session 直参照+ライトスルーキャッシュ)、recording_refs(録画1行=1レコードの行キャッシュ: status/duration/recorded_at・W3 で room 単位 full sync・フォルダ集計と月次統計の材料)、interview_transcripts(文字起こし本文ローカルコピー+status・fulltext 検索)、recording_processing_requests(保護加工の依頼管理)、projects.webrtc_engine / extemporary_folders.webrtc_engine(即席のエンジン分岐はフォルダ単位)、事後処理モデルへ recording_uuid / transcription_uuid / 自前 slot_id/project_id 追加。いずれも core 非依存のため G1/G2 を待たず先行実装可
アダプタ層OpentokRoom(既存の薄いラッパ)/ LivekitRoom(コアAPI連携)+ Slot#interview_room 統一IF。エンジン切替をフラグで制御
① 作成連携Slot 確定時に POST /rooms(nested session)→ connect_url/Session.uuid 保存、割付解決済みリストで POST /sessions/{id}/handouts、キャンセル時 DELETE /sessions/{id}project 設定(録画モード/解像度・文字起こし言語/エンジン・入室画面の注意文言/氏名欄)を Room/Session 作成パラメータに封入して引き渡す(G1 凍結前に契約へ反映)
② 配布招待メール・SMS・Push・リマインドを connect_url(安定URL)ベースへ差し替え(配信基盤は既存流用)。Operator の強制入室URL 再生成・再送フローの置き換え
④ 入室導線各ロールの入室起点(User: 自社認証 force_access_token 維持 / Employee: Devise / Operator: 観察 / Extemporary: 匿名・役割別URL)から tenant 0 の秘密鍵で参加 JWT を自己署名(基盤への発行往復なし)し SPA へ引き渡す。JWT 発行 BFF の認可必須化: 発行前に room_access_token 照合または認証済みセッション+Ability 検証を必須とし、role はクライアント申告でなく入室経路から BFF が導出(現行 /api/v1/employees/rooms/:id の実質無認証の構造的是正)
Webhook 受信(W1/W3/W5/W6/W7)W1 → Interview.attend_at(panelist 初回のみ・冪等)+ ref/即席実体の first_entered_at 記録、W3 → payload 直書きせず core GET /rooms/{id}/recordings を read-back して recording_refs を room 単位 full sync(冪等 upsert・削除突合)+録画失敗アラート、W5 → 合否判定 User.webrtc_test_status、W6 → interview_transcripts 保存(VTT Pull・error 行も保存result_url TTL 失効時は再取得からリトライ)→ 要約起動、W7 → recording_processing_requests の completed 遷移/failed→オペ通知。署名検証・dedup + 日次リコンサイル(webhook 欠落補足・last_synced_at 鮮度監視。退室時刻・在室時間は評価時に GET entry-histories
⑤ 振り返り事後評価(GET /rooms/{id}/entry-histories)→ Interview(is_attend/is_reward/score)、謝礼(Minedia::Point.add + DepositHistory)、要約(StatementSummaryRequestTranscription.uuid を参照)、録画閲覧を download-url 経由に置換
Operator 管理画面録画アーカイブ管理を Recordings API 経由へ、録画失敗表示を W3 起点へ、スロット詳細の Room 情報表示(Session・inspector_url 等)をコア参照へ
チャット履歴の運用画面スロット詳細の visibility 別チャット履歴の閲覧・CSV 出力・全削除を、コアの chat-messages API 経由へ置換(チャット正本はコアに移るため)。CSV は www 側 BFF が履歴 API を全ページ吸い上げて生成(基盤に CSV/エクスポート API は追加しない)。添付は attachment_name のみ出力(現行の署名 URL 焼き込み=3h 失効リンクの欠陥を解消)、スタンプ・system メッセージの日本語化は www 側 I18n 辞書(現行 operator 側の translation missing バグの解消込み)
文字起こし・翻訳・発言録の運用画面録画ごとの文字起こし/翻訳の実行(言語・エンジン選択)を Transcriptions API(POST・kind)経由へ、字幕の閲覧・書き出し(VTT/docx/text/csv)を interview_transcripts(W6 で取得した VTT ローカルコピー)ベースへ再実装。発言録・レポートサマリー生成は www 残置のまま入力を同テーブルに差し替え
ユーザ詳細の Web RTC 表示ブラウザテスト結果の表示を ConnectionTests API 経由へ置換(録画動画列は廃止=P4 の「計測値のみ・録画しない」挙動変更に追随)、「インタビュー動画(全て)」リンクを recording_uuid 参照へ
使用時間集計・ダッシュボードダッシュボード「即席インタビュー使用時間(月別)」・即席調査一覧「録画時間」列の集計ソースを OtArchive → recording_refs(W3 同期のローカル行キャッシュ)へ置換、「直近アウトプット」欄は interview_transcriptsrecording_processing_requests 材料に縮退(完了/失敗のみ)、即席フォルダ一覧の Room 数バッジは extemporary_sessions.first_entered_at IS NOT NULL カウントへ(W1 契機)、「OpenTok Server status」ウィジェットを LiveKit ステータスへ差し替え。移行期は新旧合算: 月次統計は OtArchive 由来と recording_refs 由来を月バケットで合算、フォルダ単位の数字は folder を webrtc_engine で二分しレガシー/新クエリをマージ
Extemporary(即席調査)フォルダ管理は残置。ExtemporarySlot は廃止し、即席枠は薄い実体 extemporary_sessions(コア Session 直参照)に置換。保存時に Room 作成+役割別トークンURL発行(URL の role パラメータ廃止・トークン封入で詐称防止)、招待URL CSV 出力の形式変更、フォルダ検索(SearchCop)に extemporary_sessions.name/memo/access_token を併記追加(移行期は新旧両属性でヒット)
読み取り経路の改変ダッシュボード・一覧・集計 → ローカル参照/行キャッシュ(recording_refsinterview_transcriptsrecording_processing_requestsfirst_entered_at)で即返し、詳細・振り返り → コア API on demand(障害時デグレード)
録画の外部共有OtArchiveAccess(URLキー+アクセスキー+期限の公開共有)は www 残置のまま、参照先を recording_uuid/コア管理の加工動画へ対応(現行のポリモーフィック参照の置換。DEC-08 の責務分界どおり閲覧可否はテナント側)
保護加工の依頼管理recording_processing_requests のフロー実装: オペの「個人情報保護加工が必要」選択で作成(requested)→ Slack 依頼(現行踏襲)→ 人手加工後にコアへ手動アップロード(署名 PUT + complete)→ W7(available) 受信で completed 化・processed_recording_uuid 記録。トリムのみの加工は行を作らずコア直行
文字起こし横断検索(interview_transcripts)依存機能(Employee ジョブ横断検索・captions 書き出し・cue 表示・要約/AI チャットボット入力)を interview_transcripts 読みに差し替え(1 文字起こし=1 行・現行の 64KB チャンク分割は踏襲しない・検索は status: done 条件)。二重運用期は旧 transcription_request_analyses とアプリ層 2 系統検索で research_project id を union(dual-write なし・1 ジョブ=1 エンジンのためマージがクリーンに成立)
レガシー整理belongs_to :ot_archive 廃止 → recording_uuid 参照、旧 Room/OtSession/OtArchive の read-only 化。応募フロー内回線テスト(AnswerConnectivity・「事前質問の録画」)は未使用のため廃止(P6 の OpenTok 解約のブロッカー解消)。projects_controller.rb:18includes(room: :ot_archives) prefetch は dead code につき削除(view の room = slot.room は未使用)

レビュー稼働 10 人日幅 9〜12 人日 / 想定カレンダー 11〜15 営業日

Phase 6: 並行稼働・移行・カットオーバー

ゴール:案件単位で新旧を切り替えながら安全に全面移行し、OpenTok を廃止する。

作業項目内容
E2E リハーサル全ロール同席の模擬インタビュー(作成→配布→準備→参加→振り返りの一気通貫)を stg で実施
パイロットwebrtc_engine フラグで社内テスト案件 → 限定本番案件の順に適用(切替単位: Project 系=project 単位・即席=フォルダ単位extemporary_folders.webrtc_engine・枠単位の混在不可〉)。どの案件/フォルダをどちらのエンジンで回すかの運用ルールを策定し、安定後に新規案件・新規フォルダの default engine を livekit へ切替。ロールバック手順を用意
二重運用監視新旧の品質・障害・コスト比較、W1〜W7 配信成功率・録画成功率の監視
移行新規案件から新基盤へ。過去データ(旧録画・チャット・入退室)の core 移送 or read-only 残置は未決定・別途確定(即席は既存データ移行なしで確定済み)。全案件切替後に OpenTok 廃止・解約、legacy Room/OtSession/OtArchive は read-only 化 → アーカイブ。完全移行後に文字起こし検索を一本化: transcription_request_analysesinterview_transcripts へバックフィル(チャンク行を連結して 1 行化)し 2 系統検索コードを退役(legacy 参照キー設計はデータ移行範囲の決定と連動)
レガシー退役(OpenTok 廃止時)OpentokRoom アダプタ・webrtc_engine 分岐・レガシー読み取り経路の除去。即席は extemporary_slots/extemporary_project_handouts を read-only 化 → 削除、www 即席入室ページ(10桁トークン)の廃止、フォルダ検索(SearchCop)の旧属性(extemporary_slots.*)削除
運用整備ランブック、オンコール体制、アラート閾値の本番調整、テナント/APIキーの運用手順

レビュー稼働 5 人日幅 4〜6 人日 / 想定カレンダー 12〜24 営業日

3. 工数サマリー

フェーズ内容レビュー稼働(人日)想定カレンダー(営業日)
Phase 0環境整備・契約凍結(0-1〜0-6)43〜56〜9
レーンCインフラ構築(AWS/Cloudflare 基盤・Phase 0 と並走)11〜23〜5(並走)
Phase 1コアAPI(ルーム・認可・入退室)+ 管理画面(§1.3)基盤(署名鍵管理含む・参加トークン発行 API 廃止)86〜108〜13
Phase 2インタビュールーム実装(SPA+チャット・現行踏襲UI含む)+ 管理画面 Handout 追加1311〜1513〜19
Phase 3録画・文字起こしパイプライン(翻訳・話者分離・加工動画含む)86〜109〜13
Phase 4接続テスト43〜55〜7
Phase 5minedia-www 連携改修(運用画面の API 経由化含む)109〜1211〜15
Phase 6並行稼働・移行・カットオーバー54〜612〜24
小計5343〜65
バッファ(15%)未決事項の確定・仕様手戻り・LiveKit 検証8
合計61 人日

本ページは実査機能 新基盤(OpenTok → LiveKit 移行)の開発計画書です。3レーン並走の Phase 0〜6 と工数サマリーを整理しています。