v1
PoC / 契約素案
データモデル

エンティティ一覧

新基盤コアのエンティティ定義です。集約ルートは Room(WebRTC ルーム)で、用途は kindinterview / connection_test)で区別します。テナント側 PK との対応は物理 FK を張らず external_type / external_id で双方向解決します(リソース識別子 / external_ref 参照)。下図の ER に続けて各エンティティの責務・主要カラム・関連を示します。

ER 図

ドラッグで移動・ホイールで拡大縮小
erDiagram
    TENANTS ||--o{ SESSIONS : "scopes (tenant_id)"
    TENANTS ||--o{ USERS : "scopes (tenant_id)"
    USERS ||--o{ PARTICIPANTS : "enters as minedia_observer (nullable)"
    TENANTS ||--o{ ROOMS : "scopes (tenant_id)"
    TENANTS ||--o{ WEBHOOK_ENDPOINTS : "scopes (tenant_id)"
    TENANTS ||--o{ API_KEYS : "scopes (tenant_id)"
    TENANTS |o--o{ JWKS_KEYS : "scopes (tenant_id・基盤鍵は NULL)"
    USERS ||--o{ WEBHOOK_ENDPOINTS : "created_by (nullable)"
    USERS ||--o{ API_KEYS : "created_by (nullable)"
    API_KEYS ||--o{ API_KEY_EVENTS : "has_many (audit)"
    USERS ||--o{ API_KEY_EVENTS : "actor (nullable)"
    USERS ||--o{ JWKS_KEYS : "created_by (nullable)"
    ROOMS ||--o| SESSIONS : "has_one (0..1, booking)"
    ROOMS ||--o| CONNECTION_TESTS : "has_one (0..1, kind=connection_test)"
    SESSIONS ||--o{ HANDOUTS : "has_many"
    ROOMS ||--o{ PARTICIPANTS : "has_many"
    ROOMS ||--o{ RECORDINGS : "has_many"
    ROOMS ||--o{ CHAT_CHANNELS : "has_many"
    ROOMS ||--o{ ROOM_EVENTS : "has_many"
    ROOMS ||--o{ ENTRY_HISTORIES : "has_many"
    RECORDINGS ||--o{ TRANSCRIPTIONS : "has_many (G5: core)"
    RECORDINGS ||--o| PROCESSED_RECORDINGS : "has_one (0..1)"
    WEBHOOK_ENDPOINTS ||--o{ WEBHOOK_DELIVERIES : "has_many"
    CONNECTION_TESTS ||--o{ CONNECTION_TEST_EVENTS : "has_many"
    CHAT_CHANNELS ||--o{ CHAT_MESSAGES : "has_many"
    CHAT_CHANNELS ||--o{ CHAT_POSTS : "has_many"
    PARTICIPANTS ||--o{ CHAT_MESSAGES : "authors (nullable)"
    PARTICIPANTS ||--o{ CHAT_POSTS : "authors (nullable)"
    PARTICIPANTS ||--o{ ENTRY_HISTORIES : "linked (nullable)"

    TENANTS {
        bigint id PK
        string uuid UK
        string name
    }
    USERS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        string email UK
        string encrypted_password
        string name
        string role "admin/member"
        string locale
        string time_zone
        datetime deactivated_at
    }
    SESSIONS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint room_id FK "0..1 per room (booking)"
        string external_type "ext-ref (no FK)"
        string external_id "ext-ref (no FK)"
        datetime starts_at
        datetime ends_at
        string status "scheduled/active/cancelled/closed (booking)"
    }
    ROOMS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        string kind "interview/connection_test"
        string status "open/closed"
        string livekit_session_id "= LiveKit Room SID (RM_…); UK"
        string archive_mode
        string archive_resolution
        string transcription_language "default ja-JP"
        string transcription_service "default azure"
        string stream_resolution
        integer stream_frame_rate
        string access_token UK
        text entry_notice_text "nullable"
        string entry_name_field_label "nullable"
    }
    PARTICIPANTS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint room_id FK
        string role "moderator/observer/panelist/minedia_observer"
        bigint user_id FK "nullable (platform actor)"
        string external_type "ext-ref (no FK)"
        string external_id "ext-ref (no FK)"
        string display_name
    }
    HANDOUTS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        bigint session_id FK
        string external_type "ext-ref (no FK)"
        string external_id "ext-ref (no FK)"
        string file
        string content_type "image/video"
        integer sort
    }
    CONNECTION_TESTS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        bigint room_id FK "unique (kind=connection_test)"
        string external_type "ext-ref (no FK)"
        string external_id "ext-ref (no FK)"
        string status "created/completed/failed"
        integer dummy_stream_count
        float baseline_metrics "bitrate/loss/fps/jitter"
        float load_metrics "avg+worst x video/audio"
        json raw_data
    }
    CONNECTION_TEST_EVENTS {
        bigint id PK
        bigint tenant_id "default 0"
        bigint connection_test_id FK
        string action_type
        string browser_name
        string platform_name
        string device_name
        json data
    }
    RECORDINGS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint room_id FK
        string provider_archive_id
        string status
        string output_mode
        bigint size
        decimal duration_sec
        string storage_key
    }
    TRANSCRIPTIONS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        bigint recording_id FK
        string provider "eleven_labs/openai/azure/anyenv/rimo"
        string provider_transcription_id
        string status
        json result_json "行内格納 (As-Is 踏襲)"
        string language
        string kind "transcription/translation"
    }
    PROCESSED_RECORDINGS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid UK
        bigint recording_id FK "unique (1:1)"
        string source "trim/upload"
        decimal trim_start_sec "nullable"
        decimal trim_end_sec "nullable"
        string status "pending/processing/available/failed"
        string failure_reason "nullable"
        string storage_key
        bigint size
        decimal duration_sec
    }
    CHAT_CHANNELS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint room_id FK
        string visibility "public/private/observer_only"
    }
    CHAT_MESSAGES {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint chat_channel_id FK
        bigint participant_id FK "nullable"
        integer content_type
        text text
        string sender_ref
        string sender_name
    }
    CHAT_POSTS {
        bigint id PK
        bigint tenant_id "default 0"
        string uuid
        bigint chat_channel_id FK
        bigint participant_id FK "nullable"
        string file
    }
    ROOM_EVENTS {
        bigint id PK
        bigint tenant_id "default 0"
        bigint room_id FK
        string event_type
        json payload
    }
    ENTRY_HISTORIES {
        bigint id PK
        bigint tenant_id "default 0"
        bigint room_id FK
        bigint participant_id FK "nullable"
        string name
        string role
        string ip_address
    }
    WEBHOOK_DELIVERIES {
        bigint id PK
        bigint tenant_id "default 0"
        bigint webhook_endpoint_id FK
        string event_id
        string event_type
        string status "pending/delivered/failed/exhausted"
        integer attempt_count
        datetime last_attempt_at
        integer response_code
    }
    WEBHOOK_ENDPOINTS {
        bigint id PK
        bigint tenant_id FK "scope (配信スコープ)"
        string uuid UK
        bigint user_id FK "nullable (audit)"
        string url
        string secret
        json subscribed_types
        boolean enabled
        string description
    }
    API_KEYS {
        bigint id PK
        bigint tenant_id FK "scope"
        string uuid UK
        bigint user_id FK "nullable (audit)"
        string key_id UK
        string secret_hash "平文非保持・ハッシュのみ"
        json scopes "任意・MT-ready"
        boolean active
        datetime rotated_at
    }
    API_KEY_EVENTS {
        bigint id PK
        bigint tenant_id "default 0"
        bigint api_key_id FK
        string event_type "issued/revoked/updated"
        bigint user_id FK "nullable (actor)"
        string actor_email "CF Access スナップショット"
        json payload "digest/rotated_from/reason/changes"
        datetime created_at "追記専用 (updated_at なし)"
    }
    JWKS_KEYS {
        bigint id PK
        bigint tenant_id FK "scope・基盤鍵は NULL"
        string uuid UK
        bigint user_id FK "nullable (audit)"
        string issuer "JWT iss 値 (interview-platform / tenants.uuid)"
        string kid "unique [issuer, kid]"
        string alg "EdDSA/RS256"
        json public_jwk "JWK (RFC 7517)"
        string use "sig"
        string private_key_ref "AWS Secrets Manager ARN (秘密鍵は DB 非保持)"
        string status "active/retiring/revoked"
        datetime expires_at
    }

エンティティ定義

共通カラムの凡例

各表は全カラムを省略せず記載します。共通の id(内部 BIGINT 主キー・非公開)/ tenant_id(テナントスコープ・PoC は 0 固定)/ uuid(公開識別子・API では id として露出)/ created_at / updated_at も各表に明記しています。高ボリュームのログ系(RoomEvent / EntryHistory / ConnectionTestEvent / WebhookDelivery)は uuid を持たず整数 id のみ、Tenant は自身がテナントのため tenant_id を持ちません。

Room 集約ルート

WebRTC の接続先となるルーム。kind で用途を区別し、参加者・チャット・録画・イベント・入退室を束ねる集約ルート。プロバイダ束縛(livekit_session_id / archive_*)を保持する。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(PK / テナント / 公開ID)
kindstring NOT NULLinterview / connection_test。用途の権威
statusstring NOT NULL default openopen / closed。Room ライフサイクル。統一解放 API(POST /rooms/{id}/close)で closed に。閉室後も配下リソースは参照可
livekit_session_idstring NOT NULL= LiveKit Room SID(RM_…)。unique [livekit_session_id](生値は非公開・診断は派生 URL)
archive_modestringalways / manual(録画モード)
archive_resolutionstring default 640x480録画解像度
transcription_languagestring NOT NULL default ja-JP自動文字起こしの既定言語(BCP 47)
transcription_servicestring NOT NULL default azure文字起こしエンジン既定(eleven_labs/openai/azure/anyenv⚠/rimo⚠)。kind=interview でのみ有意味。Transcription の provider(実行事実)とは別物
stream_resolutionstring default 320x240publisher 映像解像度
stream_frame_rateinteger default 15publisher フレームレート(30/15/7/1)
access_tokenstring NOT NULL入室口(ルーム URL)。unique
entry_notice_texttext nullable入室前画面の注意文言。Project / ExtemporaryFolder 設定からテナントが作成時に渡す入力(archive_mode 等と同じ扱い)。未指定時は SPA 側の既定文言を使用
entry_name_field_labelstring nullable入室前の氏名入力欄のラベル / プレースホルダ。null は SPA 既定ラベルを使用(必須 / 任意制御・非表示化の要否は未確定)
created_at / updated_atdatetime共通

関連: has_many recordings / participants / chat_channels / room_events / entry_histories ・ has_one session(0..1) / connection_test(0..1)。media_mode は OpenTok 固有のため持たない。


Session (予約・旧 Slot / ExtemporarySlot の枠部分)

インタビューの予約(時間枠)。Room に従属し、調査ドメインの枠(Slot / ExtemporarySlot)を external_ref で指す。予約のない Room(接続テスト・常設ルーム)は Session を持たない。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
room_idbigint FK NOT NULL従属する Room(1 Room に 0..1 Session)
external_typestring NOT NULL調査側の出所種別(Slot / ExtemporarySlot
external_idstring NOT NULL調査側 PK(物理 FK なし。unique [tenant_id, external_type, external_id]
starts_atdatetime枠の開始時刻
ends_atdatetime枠の終了時刻
namestring表示名(任意)
statusstring default scheduledscheduled(開始前)/ active(時間窓内)/ cancelled(テナントによる解放・キャンセル)/ closed(時間窓の自然終了)
created_at / updated_atdatetime共通

関連: belongs_to room ・ has_many handouts。


Participant (参加者+役割)

ルームの参加者と役割。テナント委譲の参加者は external_ref、基盤運用者(minedia_observer)は user_id で身元を持つ。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
room_idbigint FK NOT NULL所属 Room
rolestring NOT NULLmoderator / observer / panelist / minedia_observer
user_idbigint FK nullable基盤運用者の入室時のみ User に紐づく
external_type / external_idstringテナント側参加者の identity(物理 FK なし。user_id と排他)
display_namestring表示名スナップショット
statusstring NOT NULL default activeactive / kicked / banned(再入室阻止の根拠)
kicked_atdatetime nullable強制退出 / BAN の発生時刻(監査用)
created_at / updated_atdatetime共通

関連: belongs_to room ・(任意)user。


Handout (提示物)

インタビュー中に提示する資料(画質保持のため事前アップロード+ブラウザ直表示)。調査側で割付解決済みのリストを Session 配下に登録する。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
session_idbigint FK NOT NULL所属 Session
external_type / external_idstring調査側の出所(重複排除キー・物理 FK なし)
filestringアップロードファイル(保存先はテナント設定)
content_typestringimage / video
sortinteger default 0提示順
created_at / updated_atdatetime共通

関連: belongs_to session。提示の表示/非表示は RoomEventhandout.show/handout.hide)。


Recording (録画の事実・旧 OtArchive)

録画の事実のみを保持。文字起こし・要約・加工は別(調査側 or Transcription)。インタビュー / 接続テストとも Room 配下で一様に扱う。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(uuid は調査側の録画参照キー)
room_idbigint FK NOT NULL所属 Room(旧 rtc_session_id。unique [room_id, provider_archive_id]
provider_archive_idstring NOT NULLエンジン側の録画 ID(LiveKit=Egress ID / Zoom=recording ID / OpenTok=Archive ID)
statusstring NOT NULLstarted / paused / stopped / uploaded / available / expired / failed
output_modestring NOT NULL default composedcomposed / individual
sizebigint nullableファイルサイズ(bytes・確定は uploaded 以降)
duration_secdecimal(10,4) nullable録画の長さ(秒・確定は uploaded 以降)
storage_keystringS3 キー(非公開・署名 URL で払い出し)
created_at / updated_atdatetime共通

関連: belongs_to room ・ has_many transcriptions ・ has_one processed_recording。


ProcessedRecording (加工動画・旧 ProcessedOtArchive)

録画から生成する加工済み動画。トリム指示 API + Workflows によるトリム処理(source=trim)と、加工済み動画の手動アップロード(source=upload・S3 署名付き PUT + complete)の 2 系統。Recording と 1:1(現行 has_one 踏襲・差替えは DELETE → 再 POST)。ブラー / 音声変換の自動化は対象外。録画の外部共有(OtArchiveAccess)は調査側に残置し、閲覧可否はテナント側が判断する。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(uuid は調査側の参照キー・Recording.uuid と同パターン。unique [uuid]
recording_idbigint FK NOT NULL加工元 Recording(コア内部 FK・unique [recording_id]=1:1 の DB 強制)
sourcestring NOT NULLtrim(Workflows によるトリム処理)/ upload(手動アップロード)
trim_start_sec / trim_end_secdecimal(10,4) nullablesource=trim 時のトリム範囲指定(Workflows への処理指示。upload は null)
statusstring NOT NULL default pendingpending →(processing →)available / failedtrim は Workflows が遷移、upload は署名 PUT 完了後の complete API で available
failure_reasonstring nullablefailed 時の要約理由(W7 processed_recording.status_changed に露出。内部エラー詳細は非公開=Transcription.error_message と同方針)
storage_keystring加工済みファイルの S3 キー(非公開・署名 URL で払い出し。Recording.storage_key と同方針)
size / duration_secbigint / decimal(10,4)加工後ファイルのメタデータ
created_at / updated_atdatetime共通

関連: belongs_to recording(recordings 1 — 0..1 processed_recordings・recording_id unique)。

責務分界:加工内容のメタはコアに持たない

個人情報保護加工(ブラー / 音声変換)の依頼管理(要否判断・Slack 依頼・依頼中 / 完了の状態=現行 processed_information の blur / voice_change 記録の後継)は調査ドメインの関心事として minedia-www 側に残置する。コアが持つのは source=trim/upload とファイルの事実のみ。現行 status の unprocessed / started / expired も依頼管理側の概念のため持ち込まない。


Transcription (文字起こし・G5 でコア化)

録画に対する文字起こし。複数言語 / 翻訳がぶら下がる。要約・分析は調査側が uuid 参照で付加する。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(uuid は調査側の参照キー)
recording_idbigint FK NOT NULL対象 Recording
providerstring NOT NULLeleven_labs / openai / azure / anyenv / rimo
provider_transcription_idstringベンダのジョブ ID(unique [provider, provider_transcription_id]
statusstring NOT NULLsending_request / processing / retrying / done / error / request_error
result_jsonjsonVTT / json の結果本文(行内格納・As-Is 踏襲。非公開で API は署名 URL result_url 経由で払い出す)
languagestring NOT NULL default ja-JP言語(BCP 47)
kindstring NOT NULL default transcriptiontranscription(自前文字起こし)/ translation(翻訳・日→英翻訳を統合)
error_messagetext失敗時のエラー(非公開・通知は要約のみ)
created_at / updated_atdatetime共通

関連: belongs_to recording。

result_json の格納形式(provider 別)

中身は provider ごとに形が異なる(As-Is 踏襲・読み出し時に VTT へ正規化)。現役の作成経路(ElevenLabs / OpenAI / Azure)はいずれも実体が VTT テキストで、単語タイムスタンプ付きの生レスポンスを保持するのはレガシー 2 系統のみ。新基盤コアが書き込むのは現役 3 形式で、anyenv / rimo 形式は旧データを移行する場合にのみ現れる。

provider格納される値読み出し
eleven_labs / openaiVTT テキストそのもの(JSON 値としては文字列)そのまま VTT
azuretranscription_status(ジョブ状態)+ vtt(本文)の 2 キーオブジェクトresult_json['vtt'](話者タグ <v 話者 1> 付き)
anyenv ⚠レガシーGoogle Speech 系の生レスポンス(transcript / speech_info / 単語タイムスタンプ words[]表示時に VTT へ都度変換
rimo ⚠レガシー独自形式(transcripts[].words[]・confidence 付き)表示時に VTT へ都度変換

例 — ElevenLabs / OpenAI(VTT 文字列)

WEBVTT

00:00:01.200 --> 00:00:04.500
本日はよろしくお願いします。

00:00:04.800 --> 00:00:09.120
まず、普段のお買い物についてお聞かせください。

例 — Azure(ジョブ状態+VTT)

{
  "transcription_status": {
    "self": "https://japaneast.api.cognitive.microsoft.com/speechtotext/v3.2/transcriptions/8a1f...",
    "status": "Succeeded",
    "createdDateTime": "2026-07-01T06:10:00Z",
    "properties": { "durationInTicks": 36125000000 }
  },
  "vtt": "WEBVTT\n\n00:00:01.200 --> 00:00:04.500\n<v 話者 1>本日はよろしくお願いします。\n..."
}

例 — anyenv(レガシー・単語タイムスタンプ付き生レスポンス)

{
  "name": "operations/xxxx",
  "done": true,
  "response": {
    "results": [{
      "alternatives": [{
        "transcript": "本日はよろしくお願いします。まず普段のお買い物について…",
        "speech_info": { "vowels_num": 1842, "duration": 3612.5, "speed": 6.2, "pitch_ave": 182.4, "intonation": 0.31 },
        "words": [
          { "startTime": "1.2s", "endTime": "1.5s", "word": "本日" },
          { "startTime": "1.5s", "endTime": "1.7s", "word": "は" }
        ]
      }]
    }]
  }
}

例 — Rimo(レガシー・独自形式)

{
  "id": "xxxx",
  "duration": 3612,
  "state": "ASR_DONE",
  "transcripts": [{
    "id": "t1",
    "content": "本日はよろしくお願いします。",
    "start_time": 1200,
    "end_time": 4500,
    "words": [
      { "id": "w1", "content": "本日", "confidence": 0.98, "start_time": 1200, "end_time": 1500 }
    ]
  }]
}
サイズと払い出し

現役 3 形式は実体が VTT で、60〜90 分のインタビューでも数百 KB 級(行内格納で成立するサイズ感)。MB 級に肥大しうるのは単語タイムスタンプを持つレガシー 2 形式のみ。API は生形式を露出せず、result_url(署名 URL)が VTT へ正規化した text/vtt を返す。


ChatChannel (チャットチャネル)

ルーム内のチャットチャネル。可視性で公開範囲を分ける。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
room_idbigint FK NOT NULL所属 Room
visibilitystring NOT NULLpublic / private / observer_onlyunique [room_id, visibility]
created_at / updated_atdatetime共通

関連: belongs_to room ・ has_many chat_messages / chat_posts。


ChatMessage (メッセージ)

チャネル内の個別メッセージ。送信者は participant 参照+表示用スナップショット。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
chat_channel_idbigint FK NOT NULL所属 ChatChannel
participant_idbigint FK nullable送信者(system メッセージは null)
content_typeinteger default 1text / stamp / archive_status
texttext本文
sender_refstringテナント不透明文字列の表示用送信者 ID
sender_namestring NOT NULL送信者名スナップショット
created_at / updated_atdatetime共通

関連: belongs_to chat_channel ・(任意)participant。


ChatPost (ファイル投稿)

チャネルへのファイルアップロード投稿。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通
chat_channel_idbigint FK NOT NULL所属 ChatChannel
participant_idbigint FK nullable投稿者
filestringアップロードファイル(保存先はテナント設定)
created_at / updated_atdatetime共通

関連: belongs_to chat_channel ・(任意)participant。


RoomEvent (イベントログ)

ルーム内で発生したイベント(提示物表示/非表示・再生・リアクション等)の永続ログ。

カラム説明
id / tenant_idbigint / bigint共通(uuid なし・整数 id)
room_idbigint FK NOT NULL発生した Room
event_typestring NOT NULLhandout.show / handout.hide(コア正典)・その他は不透明文字列
payloadjsonイベント本体
created_at / updated_atdatetime共通

関連: belongs_to room。整数 id(uuid なし)


EntryHistory (入退室ログ)

ルームへの入退室と滞在記録。出欠・課金・録画障害判定の根拠。

カラム説明
id / tenant_idbigint / bigint共通(uuid なし・整数 id)
room_idbigint FK NOT NULL対象 Room
participant_idbigint FK nullable入室者(匿名は null)
namestring NOT NULL表示名
rolestring NOT NULLmoderator / observer / panelist / minedia_observer
browser_name / browser_versionstringブラウザ情報
platform_name / platform_versionstringプラットフォーム情報
ip_addressstring接続元 IP
exited_atdatetime nullable退室時刻(disconnect webhook で更新)
duration_secinteger滞在秒数(出欠・課金・障害判定の根拠)
created_at / updated_atdatetime共通(created_at = 入室時刻)

関連: belongs_to room ・(任意)participant。整数 id(uuid なし)


ConnectionTest (接続テスト・旧 BrowserTestHistory)

被験者ブラウザ / 回線の品質計測。kind=connection_test の Room を 1 つ持つ。合格判定はテナント側、基盤は計測の事実のみ。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(uuid は調査側の結果参照キー)
room_idbigint FK NOT NULLkind=connection_testRoomunique
external_type / external_idstring被験者 identity(例 {"User","999"}・物理 FK なし)
statusstring NOT NULL default createdcreated / completed / failed
dummy_stream_countintegerダミー配信数(負荷シミュレーション構成)
hardware_concurrencyinteger端末の論理コア数
connected_subscriber_countinteger接続購読者数
ベースライン計測(baseline)
baseline_video_average_bitratefloat映像平均ビットレート
baseline_video_average_packets_loss_ratiofloat映像平均パケットロス率
baseline_frames_dropped_ratiofloatフレームドロップ率
baseline_fpsfloatフレームレート
baseline_audio_bitratefloat音声ビットレート
baseline_audio_packets_loss_ratiofloat音声パケットロス率
baseline_audio_jitterfloat音声ジッタ
負荷時計測(load)
load_video_average_bitratefloat負荷時 映像平均ビットレート
load_video_average_packets_loss_ratiofloat負荷時 映像平均パケットロス率
load_worst_video_bitratefloat負荷時 映像最悪ビットレート
load_worst_video_packets_loss_ratiofloat負荷時 映像最悪パケットロス率
load_overall_frames_dropped_ratiofloat負荷時 全体フレームドロップ率
load_worst_frames_dropped_ratiofloat負荷時 最悪フレームドロップ率
load_overall_fpsfloat負荷時 全体 FPS
load_worst_fpsfloat負荷時 最悪 FPS
load_audio_average_bitratefloat負荷時 音声平均ビットレート
load_audio_average_packets_loss_ratiofloat負荷時 音声平均パケットロス率
load_worst_audio_packets_loss_ratiofloat負荷時 音声最悪パケットロス率
load_audio_average_jitterfloat負荷時 音声平均ジッタ
load_worst_audio_jitterfloat負荷時 音声最悪ジッタ
総合集計(API summary
video_average_bitrate / video_median_bitratefloat映像ビットレート 平均 / 中央値
video_average_packets_loss_ratio / video_median_packets_loss_ratiofloat映像パケットロス 平均 / 中央値
audio_average_bitrate / audio_median_bitratefloat音声ビットレート 平均 / 中央値
audio_average_packets_loss_ratio / audio_median_packets_loss_ratiofloat音声パケットロス 平均 / 中央値
生統計・LiveKit ConnectionCheck
raw_datajson生サンプル系列(非公開・集計値を派生で露出)
checksjson default []チェック単位結果 [{type,status,data}](signaling/webrtc/turn/reconnect/publish_audio/publish_video/protocol/region)
best_protocolstring nullableUDP/TCP 比較の最適結果(OpenTok=null)
best_regionstring nullable最良 Cloud リージョン(非 Cloud/OpenTok=null)
created_at / updated_atdatetime共通

関連: belongs_to room ・ has_many connection_test_events ・録画は room 経由。


ConnectionTestEvent (診断ログ・旧 WebrtcHistory)

接続テスト UI の各ステップ・成否の診断ログ。サポート対応・ファネル分析用。

カラム説明
id / tenant_idbigint / bigint共通(uuid なし・整数 id)
connection_test_idbigint FK NOT NULLConnectionTest
action_typestring NOT NULLアクション種別(rtc_session_connected 等・ot_*→中立化)
check_typestring nullableどのチェックのログか(signaling/webrtc/turn/… )
user_agentstringUA 文字列
browser_name / browser_versionstringブラウザ情報
platform_name / platform_versionstringプラットフォーム情報
device_namestringデバイス名
datajsonエラー詳細等
created_at / updated_atdatetime共通

関連: belongs_to connection_test。整数 id(uuid なし)


Tenant (論理テナント)

論理テナント。PoC では行を作らず tenant_id=0 を予約値として運用(MT は将来サービス複製で対応する伏線)。

カラム説明
id / uuidbigint / string(36)共通(tenant_id なし・自身がテナント。unique [uuid]
namestring表示名(任意)
created_at / updated_atdatetime共通

User (基盤運用アカウント台帳・管理プレーン)

基盤自身の運用アクター(管理画面ログイン・録画運用・障害対応)。ルーム参加者(Participant)とは別物。テナント側パネルの個人情報は持たない。

カラム説明
id / tenant_id / uuidbigint / bigint / string(36)共通(unique [email] も)
emailstring NOT NULLログイン ID(unique
encrypted_passwordstring NOT NULLDevise 相当(参加 JWT とは別系統)
namestring NOT NULL表示名(first/last を統合)
rolestring NOT NULL default memberadmin / member
localestring default jaロケール
time_zonestring default Tokyoタイムゾーン
deactivated_atdatetime入退社管理(論理無効化・行は消さない)
sign_in 系sign_in_count / current_sign_in_at / failed_attempts / locked_at 等(Devise trackable/lockable・実装時確定)
created_at / updated_atdatetime共通

関連: has_many participants(基盤運用者の入室時のみ)。


webhook_endpoints (Webhook 配信先)

基盤 → テナントへの Webhook 配信先設定。用途別に複数登録でき、個別 secret と購読種別を持つ。

カラム説明
id / tenant_id / uuidbigint / bigint FK / string(36)共通。tenant_idTenant への FK=**所有・配信スコープ**(event.tenant_id == endpoint.tenant_id
user_idbigint FK nullable作成した User(監査・任意)
urlstring NOT NULL受信先 URL(minedia-www 側)
secretstring NOT NULLendpoint 単位の HMAC 署名シークレット
subscribed_typesjson NOT NULL default ["*"]購読イベント種別(["*"]=全種別)
enabledboolean NOT NULL default true無効化フラグ(secret 回転に使用)
descriptionstring用途メモ(本番/監視/再試験 等)
created_at / updated_atdatetime共通

webhook_deliveries (Webhook 配信ログ)

基盤 → テナントへの Webhook 配信試行の記録。署名付き配信・リトライの追跡と、イベント重複排除(冪等性)の根拠。dead-letter 保管(exhausted 後のペイロード永続化)・自動再送・endpoint 自動無効化は v2 スコープ。

カラム説明
id / tenant_idbigint / bigint共通(uuid なし・純ログ)
webhook_endpoint_idbigint FK NOT NULL配信先 webhook_endpointsunique [webhook_endpoint_id, event_id] で重複排除)
event_idstring NOT NULLWebhook エンベロープの id(冪等性キー)
event_typestring NOT NULL配信イベント種別(例 participant.entered
statusstring NOT NULL default pendingpending / delivered / failed / exhausted(5 回リトライ失効後)
attempt_countinteger NOT NULL default 0リトライ回数(最大 5)
last_attempt_atdatetime直近の配信試行時刻
response_codeinteger nullable直近レスポンスの HTTP ステータス
created_at / updated_atdatetime共通

関連: belongs_to webhook_endpoint(webhook_endpoints 1 — 0..N webhook_deliveries)。整数 id(uuid なし)。配信試行の記録のみを持ち、配信ペイロード本体は保持しない。


jwks_keys (参加 JWT 検証鍵)

参加 JWT(EdDSA 署名)の検証用公開鍵レジストリ。JWKS エンドポイントの裏付け。秘密鍵は DB に置かず Secrets Manager 参照。

カラム説明
id / tenant_id / uuidbigint / bigint FK nullable / string(36)共通。tenant_idTenant への FK=**所有スコープ**(Model B のテナント別鍵を区別)。基盤自身の鍵は tenant_id = NULL(tenant 0 = minedia-www の鍵との混同を避ける)
user_idbigint FK nullable登録した User(監査・任意。鍵ローテは自動の場合あり)
issuerstring NOT NULLこの鍵が検証する JWT の iss 値(基盤鍵 = interview-platform / テナント鍵 = tenants.uuid)。鍵解決は常に (iss, kid) 複合
kidstring NOT NULLJWT ヘッダ kid(ローテ世代キー・unique [issuer, kid]=テナント間の kid 衝突は許容し発行者内で一意)
algstring NOT NULL署名アルゴリズム(EdDSA / RS256 等)
public_jwkjson NOT NULL公開鍵(JWK 形式・RFC 7517)。JWKS エンドポイントは issuer=interview-platform かつ status=active の行のみを集約して返す(テナント鍵は保存しても公開しない)
usestring NOT NULL default sig用途(署名検証)
private_key_refstring秘密鍵の Secrets Manager ARN(DB 非保持・Model B は null)
statusstring NOT NULL default activeactive / retiring / revoked
not_before / expires_atdatetime nullable有効期間(ローテ窓)
created_at / updated_atdatetime共通

api_keys (テナント認証資格情報)

テナント(他社サーバー)が発行 API を叩くための server-to-server 認証鍵。参加 JWT とは別系統。secret 平文は保持せずハッシュのみ。

カラム説明
id / tenant_id / uuidbigint / bigint FK / string(36)共通。tenant_idTenant への FK=**所有スコープ**(API key → tenant 解決)
user_idbigint FK nullable発行した User(監査・任意。鍵自体はテナントの機械資格情報)
key_idstring NOT NULL公開識別子(非機密・unique
secret_hashstring NOT NULLsecret のハッシュ(平文非保持・定数時間比較)
name / descriptionstring用途メモ
scopesjsonスコープ(任意・MT-ready 予約)
activeboolean NOT NULL default true失効フラグ(回転で旧 key を false)
last_used_at / rotated_atdatetime nullable監査・回転履歴。last_used_at は条件付き間引き更新(60 秒以内は書かない・更新失敗でも認証は成功させる)
created_at / updated_atdatetime共通

監査: 物理削除禁止(失効は active=falserevoked イベント。行は消さない=監査帰属を保つ)。発行 / 失効 / メタデータ変更は api_key_events同一トランザクションで記録する。リクエスト単位の使用状況は構造化ログ(運用検知用)へ出し、DB の使用正本は last_used_at のみ(監査は DB 正本・ログに監査要件を負わせない)。


api_key_events (ApiKey ライフサイクル監査ログ)

api_keys の発行 / 失効 / メタデータ変更の監査ログ。追記専用(updated_at なし・アプリ層で UPDATE / DELETE を持たない)。汎用 audit_logs の決着を待たない狭い専用テーブル(webhook_deliveries と同じ前例)。

カラム説明
id / tenant_idbigint / bigint共通(uuid なし・純ログ)。tenant_id は対象 api_keys.tenant_id と一致(アプリ層整合)
api_key_idbigint FK NOT NULL対象 api_keys
event_typestring NOT NULLissued / revoked / updated(name・description 等の変更)。回転は独立イベントにせず、新キー issued+旧キー revoked の 2 イベントで表現
user_idbigint FK nullable操作した User(Cloudflare Access のメールを users.email に解決できた場合のみ。認可の実体は CF Access ポリシーで、この FK は帰属記録)
actor_emailstring NOT NULLCF Access JWT のメールのスナップショット(小文字化・trim 済み)。users 未登録でも監査証跡が常に残る
payloadjsonissued: secret_hash_digest(SHA-256(secret_hash)・改ざん照合の基準値)+rotated_from_api_key_id(回転時のみ)/revoked: reason(推奨値 rotation / compromised / unused)/updated: 変更フィールドの before / after。secret_hash / active / key_id / tenant_id は changes に現れないのが不変条件
created_atdatetime追記専用(updated_at なし)

関連: belongs_to api_key(api_keys 1 — 0..N api_key_events)。整数 id(uuid なし)

完全性監査(イベント突合型検知): 本テーブルを「正」とし、定期照合(Cron・1 時間毎)で api_keys の行状態と突合する — ① issued イベントを持たない行=不正挿入 ② secret_hash ダイジェスト不一致=改ざん ③ revoked 済みなのに active=true=不正再有効化。検知は運用チャンネルへ直接通知。イベントは書き込み時に構造化ログへも複写され(DB 外のアンカー)、DB 内で両テーブルを整合的に改ざんされても事後検知できる。DB ロール分離による完全な改ざん耐性は v2(外販 / マルチテナント時)。