openvibers/openvibe.chat Commits

Drop the retired bridge_applied and bridge_refs tables in a contract migration (#23)

OP OpenVibers committed 5 Oct 2026 · e65a05101d · 6 files, +384 −337

* Drop the retired bridge_applied and bridge_refs tables in a contract migration

**Changes**
- `migrations/0005_drop_bridge_tables.sql:1` — new contract migration (`-- phase: contract`, `-- after: 0001`), `DROP TABLE IF EXISTS bridge_applied;` / `DROP TABLE IF EXISTS bridge_refs;` (next free number: 0001–0004 exist here).
- `test/pg-schema.test.js:27` — removed `'bridge_applied'` and `'bridge_refs'` from `TABLES` (the `TABLES.length + 1` assertion at :57 now counts correctly).
- `test/pg-schema.test.js:12,17,41-49` — added `createDb`/`quiet` and apply the contract migration with `windowDays: 0` (PGlite handle, or the owner connection under `store: 'pg'`), because the SDK's `createTestDb` migrates with the production N-1 window and leaves a contract migration held.
- `test/rehearsal.test.js:44-45` — same `windowDays: 0` on the second migrate.
- `docs/cutover.md:17-18` and `:87-88` — the note now says the tables were dropped by J3b / `migrations/0005_drop_bridge_tables.sql` instead of "stay as data".
- Left `deploy_releases.bridge_at` / `first_via` untouched.

**Tests** — `ov test test/pg-schema.test.js test/rehearsal.test.js`: 2 passed in 11 s.

**Where reality differed from the brief:** the line numbers all matched (`0001_initial.sql:411,448`; `pg-schema.test.js:27`, count at `:49`; `cutover.md:17,87`), and 0005 was free. The gap was that a contract migration is *held* for its 7-day N-1 window (ADR-028) — `createTestDb`/`db.migrate` default to `windowDays: 7`, so adding the file plus deleting the names from `TABLES` left both tests failing until the tests explicitly request the final schema. This checkout has no prior contract migration (`grep '-- phase: contract'` found none); the header form came from `openvibe-sdk/src/db/migrate.js` and the sibling precedent `OpenVibe.Tips/migrations/0002_drop_legacy_import.sql`. No commit or push made.

* Re-record the N-1 fixtures from production 360bb4e and assert the bridge tables are gone (review)

Both review findings fixed.

**1. N-1 fixture re-recorded from production release 360bb4e** — ran `N1_LIVE_REPO=~/OpenVibers/OpenVibe.Live N1_LIVE_REF=ee2ad948… npm run n-1:record -- 360bb4e` (Live widget sha taken from the last re-record commit `c90745a`). New `test/fixtures/n-1/worker.json:1` is `engine: postgresql`, release `360bb4e`, migrations `0001–0004`, 330 statements with **0 bridge statements** (was 6 from 04d2625); `test/fixtures/n-1/client.json:1` now names release `360bb4e` and Live `ee2ad948` — no hand-editing. The only remaining `bridge_applied`/`bridge_refs` text in the fixture is inside N-1's append-only migration SQL (0001/0003), which is correct: those tables exist on an N-1 database and this release's 0005 drops them.

**2. Drop is now asserted** — `test/pg-schema.test.js:60-66` adds `t('the retired bridge tables are gone')` asserting `to_regclass('bridge_applied')` and `to_regclass('bridge_refs')` are `NULL` after the windowDays-0 migration.

**Tests:** `ov test test/pg-schema.test.js test/rehearsal.test.js test/n-1.test.js` → 3 passed in 24 s (`n-1.test.js` 14.4 s, `pg-schema.test.js` 4.8 s, `rehearsal.test.js` 4.7 s; n-1 no longer skips, it now runs the PostgreSQL replay).

Changes

ddiff --git a/docs/cutover.md b/docs/cutover.md
iindex c3946c1..c95efc8 100644
---- a/docs/cutover.md
++++ b/docs/cutover.md
@@@ -14,7 +14,8 @@ The cutover itself ran on 2026-09-23; the rehearsal, flip and check steps below
ingress (`/internal/chat/*`, `docs/chat-ingress.md`) and dropped Live's bridge outbox, so Chat no
longer mounts `POST /internal/live/calls` or `GET /internal/live/presence`, and deploy notices arrive
only as `live.release.deployed`. Where the steps below mention the bridge, they record the cutover as
--it ran. Its tables `bridge_applied` / `bridge_refs` stay as data until a later migration (J3b).
++it ran. Its tables `bridge_applied` / `bridge_refs` stayed as data until J3b dropped them
++(`migrations/0005_drop_bridge_tables.sql`).
## Rehearsal
@@@ -84,7 +85,8 @@ writers are repointed; deploy the three new capability grants before that Live r
Chat also keeps `ctx_users`, `ctx_streams`, `ctx_managed_streams`, `ctx_channels` — projections of
Live data maintained by `live-context` (never authority), `events_outbox`, `live_mirror_outbox`,
--`bridge_applied` (the retired bridge's, kept as data), `import_hold`, `import_runs`.
++`import_hold`, `import_runs`. The retired bridge's `bridge_applied` / `bridge_refs` are dropped
++(`migrations/0005_drop_bridge_tables.sql`).
## Prerequisites
ddiff --git a/migrations/0005_drop_bridge_tables.sql b/migrations/0005_drop_bridge_tables.sql
nnew file mode 100644
iindex 0000000..39d408e
---- /dev/null
++++ b/migrations/0005_drop_bridge_tables.sql
@@@ -0,0 +1,11 @@
++-- phase: contract
++-- after: 0001
++--
++-- The Live chat bridge is retired (T3 J3a): Live #20/#21 route every producer through Chat's own
++-- ingress (`/internal/chat/*`, docs/chat-ingress.md), Chat no longer mounts the bridge routes, and
++-- nothing reads or writes bridge_applied (the bridge's idempotency ledger for Live's outbox
++-- deliveries) or bridge_refs (its map from a Live boot's placeholder ids to the real chat_messages
++-- ids) any more. docs/cutover.md recorded the cutover with them kept as data until this migration
++-- (J3b). deploy_releases.bridge_at / first_via stay as data: they record how a release was applied.
++DROP TABLE IF EXISTS bridge_applied;
++DROP TABLE IF EXISTS bridge_refs;
ddiff --git a/test/fixtures/n-1/client.json b/test/fixtures/n-1/client.json
iindex d65ddbb..415a93b 100644
---- a/test/fixtures/n-1/client.json
++++ b/test/fixtures/n-1/client.json
@@@ -1,32 +1,32 @@
{
"service": "chat",
-- "release": "04d2625d98d26fc3b7a55f7a3996e42a1ad6e4d4",
-- "recorded_at": "2026-09-29T17:34:13.359Z",
++ "release": "360bb4ebcaa8906d47187623d447effd18bade95",
++ "recorded_at": "2026-10-05T18:52:56.885Z",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
"clients": {
"live": "ee2ad948a4c25b9af9c27be16e731025afa6c2d3"
},
"manifest": {
"service": "chat",
-- "release": "04d2625d98d2",
-- "released_at": "2026-09-29T16:52:43.000Z",
-- "booted_at": "2026-09-29T17:34:08.214Z",
-- "contracts_version": "0.80.0",
++ "release": "360bb4ebcaa8",
++ "released_at": "2026-10-05T18:18:22.000Z",
++ "booted_at": "2026-10-05T18:52:51.633Z",
++ "contracts_version": "0.96.0",
"packages": {
-- "openvibe-shared": "2.0.0",
-- "openvibe-sdk": "0.23.1",
-- "openvibe-contracts": "0.80.0"
++ "openvibe-shared": "2.5.0",
++ "openvibe-sdk": "0.26.0",
++ "openvibe-contracts": "0.96.0"
},
"min_client_release": null,
"mixed_version_window_hours": 24,
"components": {
"shell": {
"kind": "script",
-- "version": "04d2625d98d2"
++ "version": "360bb4ebcaa8"
},
"server": {
"kind": "server",
-- "version": "04d2625d98d2"
++ "version": "360bb4ebcaa8"
}
},
"assets": {},
@@@ -1387,7 +1387,7 @@
},
{
"method": "GET",
-- "path": "/shared/navbar.js?v=b8089ee3bd11",
++ "path": "/shared/navbar.js?v=017a740747b4",
"auth": "anon",
"accept": "html",
"status": 200,
@@@ -1409,7 +1409,7 @@
},
{
"method": "GET",
-- "path": "/web/chat.js?v=aedc88ef89",
++ "path": "/web/chat.js?v=64ef4815d2",
"auth": "anon",
"accept": "html",
"status": 200,
@@@ -1783,7 +1783,8 @@
{
"type": "join_room",
"keys": [
-- "room"
++ "room",
++ "token"
]
},
{
ddiff --git a/test/fixtures/n-1/worker.json b/test/fixtures/n-1/worker.json
iindex 3b7eef9..936e625 100644
---- a/test/fixtures/n-1/worker.json
++++ b/test/fixtures/n-1/worker.json
@@@ -1,344 +1,360 @@
{
"service": "chat",
-- "release": "04d2625d98d26fc3b7a55f7a3996e42a1ad6e4d4",
-- "recorded_at": "2026-09-29T17:34:13.359Z",
++ "release": "360bb4ebcaa8906d47187623d447effd18bade95",
++ "recorded_at": "2026-10-05T18:52:56.885Z",
"note": "N-1 fixture: written by `npm run n-1:record`, replayed by test/n-1.test.js",
-- "user_version": 0,
-- "schema": [
-- "CREATE TABLE audio_requests (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n room TEXT NOT NULL,\n stream_id INTEGER,\n channel_user_id INTEGER,\n kind TEXT NOT NULL CHECK(kind IN ('tts', 'channel-sound', 'soundboard')),\n state TEXT NOT NULL DEFAULT 'queued' CHECK(state IN ('queued', 'playing', 'played', 'skipped', 'failed')),\n requested_by TEXT,\n identity_key TEXT,\n label TEXT,\n payload TEXT NOT NULL DEFAULT '{}',\n dedupe_key TEXT,\n attempts INTEGER NOT NULL DEFAULT 0,\n duration_ms INTEGER,\n error TEXT,\n actor TEXT,\n created_at INTEGER NOT NULL,\n started_at INTEGER,\n finished_at INTEGER,\n UNIQUE(room, dedupe_key)\n)",
-- "CREATE TABLE bridge_applied (\n key TEXT PRIMARY KEY,\n result TEXT,\n applied_at INTEGER NOT NULL\n)",
-- "CREATE TABLE bridge_refs (\n boot TEXT NOT NULL,\n ref INTEGER NOT NULL,\n id INTEGER NOT NULL,\n at INTEGER NOT NULL,\n PRIMARY KEY (boot, ref)\n)",
-- "CREATE TABLE calls (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK(kind IN ('channel', 'direct', 'stream')),\n state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'ringing', 'active', 'ended', 'missed', 'declined', 'failed')),\n channel_id TEXT NOT NULL,\n stream_id INTEGER,\n created_by INTEGER,\n created_by_subject TEXT,\n target_user_id INTEGER,\n target_subject TEXT,\n end_reason TEXT,\n created_at INTEGER NOT NULL,\n started_at INTEGER,\n answered_at INTEGER,\n ended_at INTEGER\n)",
-- "CREATE TABLE channel_moderation_settings (\n channel_id INTEGER PRIMARY KEY,\n slow_mode_seconds INTEGER DEFAULT 0,\n followers_only INTEGER DEFAULT 0,\n emote_only INTEGER DEFAULT 0,\n allow_anonymous INTEGER DEFAULT 1,\n links_allowed INTEGER DEFAULT 1,\n gifs_enabled INTEGER DEFAULT 1,\n account_age_gate_hours INTEGER DEFAULT 0,\n caps_percentage_limit INTEGER DEFAULT 0,\n aggressive_filter INTEGER DEFAULT 0,\n max_message_length INTEGER DEFAULT 500,\n slur_filter_enabled INTEGER DEFAULT 0,\n slur_filter_use_builtin INTEGER DEFAULT 1,\n slur_filter_terms TEXT DEFAULT '',\n slur_filter_regexes TEXT DEFAULT '',\n slur_filter_nudge_message TEXT DEFAULT '',\n slur_filter_disabled_categories TEXT DEFAULT '[]',\n ip_approval_mode INTEGER DEFAULT 0,\n soundboard_enabled INTEGER DEFAULT 1,\n soundboard_allow_pitch INTEGER DEFAULT 1,\n soundboard_allow_speed INTEGER DEFAULT 1,\n soundboard_banned_ids TEXT DEFAULT '',\n viewer_auto_delete_enabled INTEGER DEFAULT 1,\n viewer_delete_all_enabled INTEGER DEFAULT 1,\n custom_emotes_enabled INTEGER DEFAULT 1,\n custom_sounds_enabled INTEGER DEFAULT 1,\n max_sound_seconds INTEGER DEFAULT 10,\n uploads_mods_only INTEGER DEFAULT 0,\n mods_can_edit_about INTEGER DEFAULT 0,\n donation_sound_url TEXT,\n donation_sound_mime TEXT,\n goal_sound_url TEXT,\n goal_sound_mime TEXT,\n emote_scale INTEGER DEFAULT 100,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n tts_max_length INTEGER DEFAULT 200,\n sound_min_speed REAL DEFAULT 0.5,\n sound_max_speed REAL DEFAULT 3.0,\n sound_min_pitch_cents INTEGER DEFAULT -1200,\n sound_max_pitch_cents INTEGER DEFAULT 1200,\n emote_size_min INTEGER DEFAULT 50,\n emote_size_max INTEGER DEFAULT 200,\n sounds_mods_only INTEGER DEFAULT 0,\n sub_only INTEGER DEFAULT 0 -- sub-only chat: active subscribers and the room's moderators\n)",
-- "CREATE TABLE channel_moderators (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_id INTEGER NOT NULL,\n user_id INTEGER NOT NULL,\n added_by INTEGER NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(channel_id, user_id)\n)",
-- "CREATE TABLE channel_sounds (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_owner_id INTEGER NOT NULL, -- streamer whose channel this sound belongs to\n command TEXT NOT NULL, -- trigger word (without leading '!'), lowercased\n url TEXT NOT NULL, -- on-disk path of the audio file\n mime TEXT DEFAULT 'audio/mpeg',\n duration_seconds REAL DEFAULT 0,\n created_by INTEGER, -- uploader user id (NULL if removed user)\n created_by_name TEXT DEFAULT '',\n is_approved INTEGER DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n emote_code TEXT DEFAULT '',\n created_by_subject_id TEXT\n -- Multiple sounds may share a command; playback picks one at random.\n -- media_url / media_asset_id stay on Live's copy (its asset-sync mirrors files to Media).\n)",
-- "CREATE TABLE chat_ai_summaries (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL,\n subject_id INTEGER NOT NULL DEFAULT 0,\n window TEXT NOT NULL,\n overview TEXT DEFAULT '',\n memory_json TEXT DEFAULT '',\n timeline_json TEXT DEFAULT '[]',\n message_count INTEGER DEFAULT 0,\n window_message_count INTEGER DEFAULT 0,\n last_message_id INTEGER DEFAULT 0,\n window_label TEXT DEFAULT '',\n window_start DATETIME,\n window_end DATETIME,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(scope, subject_id, window)\n)",
-- "CREATE TABLE chat_event_inbox (\n consumer TEXT NOT NULL,\n event_id TEXT NOT NULL,\n processed_at INTEGER NOT NULL,\n PRIMARY KEY (consumer, event_id)\n)",
-- "CREATE TABLE chat_messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream_id INTEGER, -- Live stream (session) id\n user_id INTEGER, -- Live user id; NULL for anon\n anon_id TEXT, -- 'anon12345' format\n username TEXT,\n message TEXT NOT NULL,\n message_type TEXT DEFAULT 'chat' CHECK(message_type IN ('chat', 'system', 'donation', 'command', 'tts', 'channel-sound', 'soundboard', 'clip')),\n metadata TEXT, -- JSON sidecar for rich events (donation/goal-reached)\n is_global INTEGER DEFAULT 0,\n is_deleted INTEGER DEFAULT 0,\n is_filtered INTEGER DEFAULT 0,\n reply_to_id INTEGER REFERENCES chat_messages(id) ON DELETE SET NULL,\n source_platform TEXT,\n deleted_by INTEGER,\n deleted_at DATETIME,\n auto_delete_at DATETIME,\n timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,\n channel_user_id INTEGER, -- the broadcaster whose channel room this is\n subject_id TEXT -- Network subject of the author (usr_…), when known\n)",
-- "CREATE TABLE chat_meta (\n key TEXT PRIMARY KEY,\n value TEXT\n)",
-- "CREATE TABLE chat_timeline_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL DEFAULT 'global',\n subject_id INTEGER NOT NULL DEFAULT 0,\n ts DATETIME NOT NULL,\n label TEXT NOT NULL,\n detail TEXT DEFAULT '',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n)",
-- "CREATE TABLE ctx_channels (\n id INTEGER PRIMARY KEY,\n user_id INTEGER UNIQUE,\n title TEXT,\n emote_sources TEXT, -- the channel's emote source switches (Live's channels.emote_sources)\n synced_at INTEGER\n)",
-- "CREATE TABLE ctx_managed_streams (\n id INTEGER PRIMARY KEY,\n user_id INTEGER,\n slug TEXT,\n title TEXT,\n sort_order INTEGER DEFAULT 0,\n created_at DATETIME,\n synced_at INTEGER\n)",
-- "CREATE TABLE ctx_streams (\n id INTEGER PRIMARY KEY,\n user_id INTEGER,\n channel_id INTEGER,\n managed_stream_id INTEGER,\n title TEXT,\n is_live INTEGER DEFAULT 0,\n started_at DATETIME,\n ended_at DATETIME,\n created_at DATETIME,\n synced_at INTEGER\n)",
-- "CREATE TABLE ctx_sync (\n key TEXT PRIMARY KEY,\n value TEXT,\n updated_at INTEGER\n)",
-- "CREATE TABLE ctx_users (\n id INTEGER PRIMARY KEY,\n username TEXT,\n display_name TEXT,\n avatar_url TEXT,\n profile_color TEXT,\n role TEXT DEFAULT 'user',\n is_banned INTEGER DEFAULT 0,\n ban_reason TEXT,\n is_owner INTEGER DEFAULT 0,\n created_at DATETIME,\n subject_id TEXT,\n synced_at INTEGER\n)",
-- "CREATE TABLE deploy_releases (\n head TEXT PRIMARY KEY,\n message_id INTEGER,\n first_via TEXT NOT NULL CHECK(first_via IN ('bridge', 'events')),\n bridge_at INTEGER,\n event_at INTEGER,\n event_id TEXT,\n commit_count INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL\n)",
-- "CREATE TABLE dm_blocks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n blocker_id INTEGER NOT NULL,\n blocked_id INTEGER NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n blocker_subject_id TEXT,\n UNIQUE(blocker_id, blocked_id)\n)",
-- "CREATE TABLE dm_conversations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT,\n is_group INTEGER DEFAULT 0,\n created_by INTEGER NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n)",
-- "CREATE TABLE dm_messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n conversation_id INTEGER NOT NULL,\n sender_id INTEGER NOT NULL,\n message TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n sender_subject_id TEXT,\n FOREIGN KEY (conversation_id) REFERENCES dm_conversations(id) ON DELETE CASCADE\n)",
-- "CREATE TABLE dm_participants (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n conversation_id INTEGER NOT NULL,\n user_id INTEGER NOT NULL,\n last_read_at DATETIME DEFAULT '1970-01-01 00:00:00',\n joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n subject_id TEXT,\n UNIQUE(conversation_id, user_id),\n FOREIGN KEY (conversation_id) REFERENCES dm_conversations(id) ON DELETE CASCADE\n)",
-- "CREATE TABLE emotes (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL,\n code TEXT NOT NULL,\n url TEXT NOT NULL,\n animated INTEGER DEFAULT 0,\n width INTEGER DEFAULT 28,\n height INTEGER DEFAULT 28,\n is_global INTEGER DEFAULT 0,\n is_approved INTEGER DEFAULT 1,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n channel_owner_id INTEGER,\n size INTEGER DEFAULT 100,\n media_url TEXT,\n media_asset_id INTEGER\n)",
-- "CREATE TABLE events_outbox (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n event_type TEXT NOT NULL,\n event TEXT NOT NULL,\n created_at TEXT NOT NULL,\n sent_at TEXT,\n attempts INTEGER DEFAULT 0,\n last_error TEXT\n)",
-- "CREATE TABLE hidden_relay_users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_id INTEGER,\n platform TEXT NOT NULL,\n external_username TEXT NOT NULL,\n action TEXT DEFAULT 'hide' CHECK(action IN ('hide','ban')),\n reason TEXT,\n created_by INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n)",
-- "CREATE TABLE import_hold (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source_table TEXT NOT NULL,\n source_pk TEXT NOT NULL,\n reason TEXT NOT NULL,\n row_json TEXT NOT NULL,\n held_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(source_table, source_pk)\n)",
-- "CREATE TABLE import_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n live_db TEXT,\n dry_run INTEGER DEFAULT 0,\n counts TEXT,\n started_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n finished_at DATETIME\n)",
-- "CREATE TABLE live_mirror_outbox (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n tbl TEXT NOT NULL,\n op TEXT NOT NULL CHECK(op IN ('upsert', 'delete')),\n pk TEXT NOT NULL,\n created_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER))\n)",
-- "CREATE TABLE moderation_actions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope_type TEXT NOT NULL DEFAULT 'site',\n scope_id INTEGER,\n actor_user_id INTEGER,\n target_user_id INTEGER,\n action_type TEXT NOT NULL,\n details TEXT DEFAULT '{}',\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n actor_subject_id TEXT\n)",
-- "CREATE TABLE network_blocks (\n blocker_subject TEXT NOT NULL,\n blocked_subject TEXT NOT NULL,\n active INTEGER NOT NULL,\n revision INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (blocker_subject, blocked_subject)\n )",
-- "CREATE TABLE pending_ip_messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_id INTEGER NOT NULL,\n stream_id INTEGER,\n ip_address TEXT NOT NULL,\n user_id INTEGER,\n anon_id TEXT,\n username TEXT,\n message TEXT NOT NULL,\n status TEXT DEFAULT 'pending' CHECK(status IN ('pending','approved','denied')),\n reviewed_by INTEGER,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP\n)",
-- "CREATE TABLE relay_users (\n platform TEXT NOT NULL,\n username TEXT NOT NULL,\n display_name TEXT,\n first_seen DATETIME DEFAULT CURRENT_TIMESTAMP,\n last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,\n message_count INTEGER DEFAULT 0,\n PRIMARY KEY (platform, username)\n)",
-- "CREATE TABLE room_attachments (\n room_id INTEGER NOT NULL,\n service TEXT NOT NULL,\n resource TEXT NOT NULL,\n title TEXT,\n attached_by INTEGER NOT NULL,\n attached_by_subject TEXT,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (room_id, service, resource)\n )",
-- "CREATE TABLE room_members (\n room_id INTEGER NOT NULL,\n user_id INTEGER NOT NULL,\n role TEXT NOT NULL DEFAULT 'member',\n last_read_id INTEGER NOT NULL DEFAULT 0,\n joined_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_seen_at DATETIME,\n PRIMARY KEY (room_id, user_id)\n )",
-- "CREATE TABLE room_messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n room_id INTEGER NOT NULL,\n user_id INTEGER NOT NULL,\n subject_id TEXT,\n message TEXT NOT NULL,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n is_deleted INTEGER NOT NULL DEFAULT 0,\n deleted_by INTEGER\n )",
-- "CREATE TABLE rooms (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n slug TEXT UNIQUE NOT NULL,\n name TEXT NOT NULL,\n topic TEXT,\n kind TEXT NOT NULL DEFAULT 'community',\n visibility TEXT NOT NULL DEFAULT 'public',\n owner_id INTEGER NOT NULL,\n owner_subject TEXT,\n slow_seconds INTEGER NOT NULL DEFAULT 0,\n message_count INTEGER NOT NULL DEFAULT 0,\n last_message_at DATETIME,\n created_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n archived_at DATETIME\n , join_role TEXT)",
-- "CREATE TABLE stream_first_chats (\n chatter_key TEXT NOT NULL,\n channel_user_id INTEGER NOT NULL,\n first_chat_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (chatter_key, channel_user_id)\n)",
-- "CREATE TABLE table_authority (\n table_name TEXT PRIMARY KEY,\n authority TEXT NOT NULL CHECK(authority IN ('chat', 'live')),\n note TEXT\n)",
-- "CREATE TABLE tts_voice_overrides (\n identity_key TEXT PRIMARY KEY,\n voice TEXT,\n pitch INTEGER,\n speed INTEGER,\n gap INTEGER DEFAULT 0,\n set_by INTEGER,\n updated_at DATETIME DEFAULT CURRENT_TIMESTAMP\n)",
-- "CREATE TABLE user_tags (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL,\n tag_id TEXT NOT NULL,\n source TEXT DEFAULT 'shop',\n granted_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n UNIQUE(user_id, tag_id)\n)",
-- "CREATE INDEX idx_audio_requests_room_state ON audio_requests(room, state, id)",
-- "CREATE INDEX idx_calls_channel_state ON calls(channel_id, state)",
-- "CREATE INDEX idx_calls_open ON calls(state) WHERE state IN ('pending', 'ringing', 'active')",
-- "CREATE INDEX idx_channel_mods_channel ON channel_moderators(channel_id)",
-- "CREATE INDEX idx_channel_mods_user ON channel_moderators(user_id)",
-- "CREATE INDEX idx_channel_sounds_cmd ON channel_sounds(channel_owner_id, command)",
-- "CREATE INDEX idx_channel_sounds_owner ON channel_sounds(channel_owner_id)",
-- "CREATE INDEX idx_chat_anon ON chat_messages(anon_id)",
-- "CREATE INDEX idx_chat_autodelete ON chat_messages(auto_delete_at)",
-- "CREATE INDEX idx_chat_channel_user_ts ON chat_messages(channel_user_id, timestamp)",
-- "CREATE INDEX idx_chat_stream_id ON chat_messages(stream_id)",
-- "CREATE INDEX idx_chat_stream_ts ON chat_messages(stream_id, timestamp)",
-- "CREATE INDEX idx_chat_timestamp ON chat_messages(timestamp)",
-- "CREATE UNIQUE INDEX idx_chat_tl_dedup ON chat_timeline_events(scope, subject_id, ts, label)",
-- "CREATE INDEX idx_chat_ts_deleted ON chat_messages(timestamp, is_deleted)",
-- "CREATE INDEX idx_chat_user_id ON chat_messages(user_id)",
-- "CREATE INDEX idx_ctx_ms_user ON ctx_managed_streams(user_id)",
-- "CREATE INDEX idx_ctx_streams_channel ON ctx_streams(channel_id)",
-- "CREATE INDEX idx_ctx_streams_managed ON ctx_streams(managed_stream_id)",
-- "CREATE INDEX idx_ctx_streams_user ON ctx_streams(user_id, id)",
-- "CREATE INDEX idx_ctx_users_display ON ctx_users(display_name COLLATE NOCASE)",
-- "CREATE INDEX idx_ctx_users_subject ON ctx_users(subject_id)",
-- "CREATE INDEX idx_ctx_users_username ON ctx_users(username COLLATE NOCASE)",
-- "CREATE INDEX idx_dm_blocks_blocked ON dm_blocks(blocked_id)",
-- "CREATE INDEX idx_dm_blocks_blocker ON dm_blocks(blocker_id)",
-- "CREATE INDEX idx_dm_messages_conv ON dm_messages(conversation_id, created_at)",
-- "CREATE INDEX idx_dm_messages_sender ON dm_messages(sender_id)",
-- "CREATE INDEX idx_dm_participants_conv ON dm_participants(conversation_id)",
-- "CREATE INDEX idx_dm_participants_user ON dm_participants(user_id)",
-- "CREATE UNIQUE INDEX idx_emotes_channel_code ON emotes(COALESCE(channel_owner_id, user_id), code)",
-- "CREATE INDEX idx_emotes_channel_owner ON emotes(channel_owner_id)",
-- "CREATE INDEX idx_emotes_user ON emotes(user_id)",
-- "CREATE INDEX idx_events_outbox_unsent ON events_outbox(sent_at, seq)",
-- "CREATE INDEX idx_hidden_relay_channel ON hidden_relay_users(channel_id, platform)",
-- "CREATE INDEX idx_mod_actions_actor ON moderation_actions(actor_user_id)",
-- "CREATE INDEX idx_mod_actions_created ON moderation_actions(created_at DESC)",
-- "CREATE INDEX idx_mod_actions_scope ON moderation_actions(scope_type, scope_id)",
-- "CREATE INDEX idx_network_blocks_blocked ON network_blocks(blocked_subject, active)",
-- "CREATE INDEX idx_pending_ip_channel ON pending_ip_messages(channel_id, status)",
-- "CREATE INDEX idx_room_members_user ON room_members(user_id)",
-- "CREATE INDEX idx_room_messages_room ON room_messages(room_id, id)",
-- "CREATE INDEX idx_sfc_channel ON stream_first_chats(channel_user_id)",
-- "CREATE INDEX idx_user_tags_user ON user_tags(user_id)"
++ "engine": "postgresql",
++ "migrations": [
++ {
++ "file": "0001_initial.sql",
++ "sql": "-- phase: expand\n-- OpenVibe.Chat on PostgreSQL (ADR-035, plan T3): the tables as they were on SQLite, converted by openvibe-sdk\n-- tools/asyncify/sqlite-schema-to-pg (text COLLATE \"C\" compares like SQLite, integers are bigint, identities keep\n-- their ids). Generated once; never edited after it runs. Timestamps stay SQLite-format text (ov_now(), datetime()).\n-- SQLite's text timestamps and date functions (openvibe-sdk tools/asyncify SQLITE_DATE_FUNCTIONS).\nCREATE FUNCTION ov_ts(t text) RETURNS timestamp LANGUAGE plpgsql STABLE AS $$\nBEGIN\n IF t IS NULL THEN RETURN NULL; END IF;\n IF t = 'now' THEN RETURN statement_timestamp() AT TIME ZONE 'UTC'; END IF;\n IF t ~ '\\d\\d:\\d\\d(:\\d\\d(\\.\\d+)?)?\\s*(Z|[+-]\\d\\d(:?\\d\\d)?)$' THEN RETURN t::timestamptz AT TIME ZONE 'UTC'; END IF;\n RETURN t::timestamp;\nEXCEPTION WHEN others THEN RETURN NULL;\nEND $$;\nCREATE FUNCTION ov_now() RETURNS text LANGUAGE sql STABLE AS $$ SELECT to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') $$;\nCREATE FUNCTION ov_now_iso() RETURNS text LANGUAGE sql STABLE AS $$ SELECT to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"') $$;\nCREATE FUNCTION ov_now_iso(modifier text) RETURNS text LANGUAGE sql STABLE AS $$ SELECT to_char((statement_timestamp() AT TIME ZONE 'UTC') + modifier::interval, 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"') $$;\nCREATE FUNCTION datetime(t text, modifier text DEFAULT NULL) RETURNS text LANGUAGE plpgsql STABLE AS $$\nDECLARE ts timestamp := ov_ts(t);\nBEGIN\n IF ts IS NULL THEN RETURN NULL; END IF;\n IF modifier IS NOT NULL THEN ts := ts + modifier::interval; END IF;\n RETURN to_char(ts, 'YYYY-MM-DD HH24:MI:SS');\nEXCEPTION WHEN others THEN RETURN NULL;\nEND $$;\nCREATE FUNCTION julianday(t text) RETURNS double precision LANGUAGE sql STABLE AS $$ SELECT extract(epoch FROM ov_ts(t))::double precision / 86400.0 + 2440587.5 $$;\n-- SQLite's JSON1 as the code uses it: json_valid(t), and json_extract(t, '$.a.b') as text (CAST it for a number).\nCREATE FUNCTION json_valid(t text) RETURNS boolean LANGUAGE sql IMMUTABLE AS $$ SELECT t IS JSON $$;\nCREATE FUNCTION json_extract(t text, path text) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT jsonb_extract_path_text(t::jsonb, VARIADIC string_to_array(substr(path, 3), '.')) $$;\nCREATE FUNCTION json_type(t text) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT jsonb_typeof(t::jsonb) $$;\nCREATE FUNCTION instr(t text, sub text) RETURNS integer LANGUAGE sql IMMUTABLE AS $$ SELECT strpos(t, sub) $$;\n\n\n-- ── 1. Chat-owned ──────────────────────────────────────────────\n\nCREATE TABLE chat_messages (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n stream_id bigint, -- Live stream (session) id\n user_id bigint, -- Live user id; NULL for anon\n anon_id text COLLATE \"C\", -- 'anon12345' format\n username text COLLATE \"C\",\n message text COLLATE \"C\" NOT NULL,\n message_type text COLLATE \"C\" DEFAULT 'chat' CHECK(message_type IN ('chat', 'system', 'donation', 'command', 'tts', 'channel-sound', 'soundboard', 'clip')),\n metadata text COLLATE \"C\", -- JSON sidecar for rich events (donation/goal-reached)\n is_global bigint DEFAULT 0,\n is_deleted bigint DEFAULT 0,\n is_filtered bigint DEFAULT 0,\n reply_to_id bigint REFERENCES chat_messages(id) ON DELETE SET NULL,\n source_platform text COLLATE \"C\",\n deleted_by text COLLATE \"C\",\n deleted_at text COLLATE \"C\",\n auto_delete_at text COLLATE \"C\",\n timestamp text COLLATE \"C\" DEFAULT ov_now(),\n channel_user_id bigint, -- the broadcaster whose channel room this is\n subject_id text COLLATE \"C\" -- Network subject of the author (usr_…), when known\n);\nCREATE INDEX idx_chat_stream_id ON chat_messages(stream_id);\nCREATE INDEX idx_chat_timestamp ON chat_messages(timestamp);\nCREATE INDEX idx_chat_user_id ON chat_messages(user_id);\nCREATE INDEX idx_chat_channel_user_ts ON chat_messages(channel_user_id, timestamp);\nCREATE INDEX idx_chat_stream_ts ON chat_messages(stream_id, timestamp);\nCREATE INDEX idx_chat_autodelete ON chat_messages(auto_delete_at);\nCREATE INDEX idx_chat_ts_deleted ON chat_messages(timestamp, is_deleted);\n-- The page order is id DESC alone (plan T3 decision 2): page() and delta() read live rows only, and\n-- this partial index serves that order straight from the index tree — no sort, no seq scan. With the\n-- base plan PostgreSQL chose a bitmap scan + Sort (p95 ≈ 1.5 s at 200k rows); this index makes it an\n-- Index Scan (p95 ≈ 12 ms on PGlite). Verified in test/history-latency.test.js.\nCREATE INDEX idx_chat_page_live ON chat_messages (id DESC) WHERE is_deleted = 0;\nCREATE INDEX idx_chat_anon ON chat_messages(anon_id);\n\nCREATE TABLE dm_conversations (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n name text COLLATE \"C\",\n is_group bigint DEFAULT 0,\n created_by bigint NOT NULL,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n updated_at text COLLATE \"C\" DEFAULT ov_now()\n);\n\nCREATE TABLE dm_participants (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n conversation_id bigint NOT NULL,\n user_id bigint NOT NULL,\n last_read_at text COLLATE \"C\" DEFAULT '1970-01-01 00:00:00',\n joined_at text COLLATE \"C\" DEFAULT ov_now(),\n subject_id text COLLATE \"C\",\n UNIQUE(conversation_id, user_id),\n FOREIGN KEY (conversation_id) REFERENCES dm_conversations(id) ON DELETE CASCADE\n);\nCREATE INDEX idx_dm_participants_conv ON dm_participants(conversation_id);\nCREATE INDEX idx_dm_participants_user ON dm_participants(user_id);\n\nCREATE TABLE dm_messages (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n conversation_id bigint NOT NULL,\n sender_id bigint NOT NULL,\n message text COLLATE \"C\" NOT NULL,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n sender_subject_id text COLLATE \"C\",\n FOREIGN KEY (conversation_id) REFERENCES dm_conversations(id) ON DELETE CASCADE\n);\nCREATE INDEX idx_dm_messages_conv ON dm_messages(conversation_id, created_at);\nCREATE INDEX idx_dm_messages_sender ON dm_messages(sender_id);\n\nCREATE TABLE dm_blocks (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n blocker_id bigint NOT NULL,\n blocked_id bigint NOT NULL,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n blocker_subject_id text COLLATE \"C\",\n UNIQUE(blocker_id, blocked_id)\n);\nCREATE INDEX idx_dm_blocks_blocker ON dm_blocks(blocker_id);\nCREATE INDEX idx_dm_blocks_blocked ON dm_blocks(blocked_id);\n\nCREATE TABLE tts_voice_overrides (\n identity_key text COLLATE \"C\" PRIMARY KEY,\n voice text COLLATE \"C\",\n pitch bigint,\n speed bigint,\n gap bigint DEFAULT 0,\n set_by bigint,\n updated_at text COLLATE \"C\" DEFAULT ov_now()\n);\n\nCREATE TABLE channel_sounds (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n channel_owner_id bigint NOT NULL, -- streamer whose channel this sound belongs to\n command text COLLATE \"C\" NOT NULL, -- trigger word (without leading '!'), lowercased\n url text COLLATE \"C\" NOT NULL, -- on-disk path of the audio file\n mime text COLLATE \"C\" DEFAULT 'audio/mpeg',\n duration_seconds double precision DEFAULT 0,\n created_by bigint, -- uploader user id (NULL if removed user)\n created_by_name text COLLATE \"C\" DEFAULT '',\n is_approved bigint DEFAULT 1,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n emote_code text COLLATE \"C\" DEFAULT '',\n created_by_subject_id text COLLATE \"C\"\n -- Multiple sounds may share a command; playback picks one at random.\n -- media_url / media_asset_id stay on Live's copy (its asset-sync mirrors files to Media).\n);\nCREATE INDEX idx_channel_sounds_owner ON channel_sounds(channel_owner_id);\nCREATE INDEX idx_channel_sounds_cmd ON channel_sounds(channel_owner_id, command);\n\nCREATE TABLE relay_users (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n platform text COLLATE \"C\" NOT NULL,\n username text COLLATE \"C\" NOT NULL,\n display_name text COLLATE \"C\",\n first_seen text COLLATE \"C\" DEFAULT ov_now(),\n last_seen text COLLATE \"C\" DEFAULT ov_now(),\n message_count bigint DEFAULT 0,\n UNIQUE(platform, username)\n);\n\nCREATE TABLE hidden_relay_users (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n channel_id bigint,\n platform text COLLATE \"C\" NOT NULL,\n external_username text COLLATE \"C\" NOT NULL,\n action text COLLATE \"C\" DEFAULT 'hide' CHECK(action IN ('hide','ban')),\n reason text COLLATE \"C\",\n created_by bigint,\n created_at text COLLATE \"C\" DEFAULT ov_now()\n);\nCREATE INDEX idx_hidden_relay_channel ON hidden_relay_users(channel_id, platform);\n\nCREATE TABLE pending_ip_messages (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n channel_id bigint NOT NULL,\n stream_id bigint,\n ip_address text COLLATE \"C\" NOT NULL,\n user_id bigint,\n anon_id text COLLATE \"C\",\n username text COLLATE \"C\",\n message text COLLATE \"C\" NOT NULL,\n status text COLLATE \"C\" DEFAULT 'pending' CHECK(status IN ('pending','approved','denied')),\n reviewed_by bigint,\n created_at text COLLATE \"C\" DEFAULT ov_now()\n);\nCREATE INDEX idx_pending_ip_channel ON pending_ip_messages(channel_id, status);\n\nCREATE TABLE stream_first_chats (\n chatter_key text COLLATE \"C\" NOT NULL,\n channel_user_id bigint NOT NULL,\n first_chat_at text COLLATE \"C\" DEFAULT ov_now(),\n PRIMARY KEY (chatter_key, channel_user_id)\n);\nCREATE INDEX idx_sfc_channel ON stream_first_chats(channel_user_id);\n\nCREATE TABLE moderation_actions (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n scope_type text COLLATE \"C\" NOT NULL DEFAULT 'site',\n scope_id text COLLATE \"C\",\n actor_user_id bigint,\n target_user_id bigint,\n action_type text COLLATE \"C\" NOT NULL,\n details text COLLATE \"C\" DEFAULT '{}',\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n actor_subject_id text COLLATE \"C\"\n);\nCREATE INDEX idx_mod_actions_created ON moderation_actions(created_at DESC);\nCREATE INDEX idx_mod_actions_actor ON moderation_actions(actor_user_id);\nCREATE INDEX idx_mod_actions_scope ON moderation_actions(scope_type, scope_id);\n\n-- ── 2. The six chat-target tables (Chat is their only writer since C-04) ──\n\nCREATE TABLE channel_moderators (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n channel_id bigint NOT NULL,\n user_id bigint NOT NULL,\n added_by bigint NOT NULL,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n UNIQUE(channel_id, user_id)\n);\nCREATE INDEX idx_channel_mods_channel ON channel_moderators(channel_id);\nCREATE INDEX idx_channel_mods_user ON channel_moderators(user_id);\n\nCREATE TABLE channel_moderation_settings (\n channel_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n slow_mode_seconds bigint DEFAULT 0,\n followers_only bigint DEFAULT 0,\n emote_only bigint DEFAULT 0,\n allow_anonymous bigint DEFAULT 1,\n links_allowed bigint DEFAULT 1,\n gifs_enabled bigint DEFAULT 1,\n account_age_gate_hours bigint DEFAULT 0,\n caps_percentage_limit bigint DEFAULT 0,\n aggressive_filter bigint DEFAULT 0,\n max_message_length bigint DEFAULT 500,\n slur_filter_enabled bigint DEFAULT 0,\n slur_filter_use_builtin bigint DEFAULT 1,\n slur_filter_terms text COLLATE \"C\" DEFAULT '',\n slur_filter_regexes text COLLATE \"C\" DEFAULT '',\n slur_filter_nudge_message text COLLATE \"C\" DEFAULT '',\n slur_filter_disabled_categories text COLLATE \"C\" DEFAULT '[]',\n ip_approval_mode bigint DEFAULT 0,\n soundboard_enabled bigint DEFAULT 1,\n soundboard_allow_pitch bigint DEFAULT 1,\n soundboard_allow_speed bigint DEFAULT 1,\n soundboard_banned_ids text COLLATE \"C\" DEFAULT '',\n viewer_auto_delete_enabled bigint DEFAULT 1,\n viewer_delete_all_enabled bigint DEFAULT 1,\n custom_emotes_enabled bigint DEFAULT 1,\n custom_sounds_enabled bigint DEFAULT 1,\n max_sound_seconds bigint DEFAULT 10,\n uploads_mods_only bigint DEFAULT 0,\n mods_can_edit_about bigint DEFAULT 0,\n donation_sound_url text COLLATE \"C\",\n donation_sound_mime text COLLATE \"C\",\n goal_sound_url text COLLATE \"C\",\n goal_sound_mime text COLLATE \"C\",\n emote_scale bigint DEFAULT 100,\n updated_at text COLLATE \"C\" DEFAULT ov_now(),\n tts_max_length bigint DEFAULT 200,\n sound_min_speed double precision DEFAULT 0.5,\n sound_max_speed double precision DEFAULT 3.0,\n sound_min_pitch_cents bigint DEFAULT -1200,\n sound_max_pitch_cents bigint DEFAULT 1200,\n emote_size_min bigint DEFAULT 50,\n emote_size_max bigint DEFAULT 200,\n sounds_mods_only bigint DEFAULT 0,\n sub_only bigint DEFAULT 0 -- sub-only chat: active subscribers and the room's moderators\n);\n\nCREATE TABLE emotes (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n user_id bigint NOT NULL,\n code text COLLATE \"C\" NOT NULL,\n url text COLLATE \"C\" NOT NULL,\n animated bigint DEFAULT 0,\n width bigint DEFAULT 28,\n height bigint DEFAULT 28,\n is_global bigint DEFAULT 0,\n is_approved bigint DEFAULT 1,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n channel_owner_id bigint,\n size bigint DEFAULT 100,\n media_url text COLLATE \"C\",\n media_asset_id bigint\n);\nCREATE INDEX idx_emotes_user ON emotes(user_id);\nCREATE INDEX idx_emotes_channel_owner ON emotes(channel_owner_id);\nCREATE UNIQUE INDEX idx_emotes_channel_code ON emotes(COALESCE(channel_owner_id, user_id), code);\n\nCREATE TABLE user_tags (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n user_id bigint NOT NULL,\n tag_id text COLLATE \"C\" NOT NULL,\n source text COLLATE \"C\" DEFAULT 'shop',\n granted_at text COLLATE \"C\" DEFAULT ov_now(),\n UNIQUE(user_id, tag_id)\n);\nCREATE INDEX idx_user_tags_user ON user_tags(user_id);\n\nCREATE TABLE chat_ai_summaries (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n scope text COLLATE \"C\" NOT NULL,\n subject_id bigint NOT NULL DEFAULT 0,\n \"window\" text COLLATE \"C\" NOT NULL,\n overview text COLLATE \"C\" DEFAULT '',\n memory_json text COLLATE \"C\" DEFAULT '',\n timeline_json text COLLATE \"C\" DEFAULT '[]',\n message_count bigint DEFAULT 0,\n window_message_count bigint DEFAULT 0,\n last_message_id bigint DEFAULT 0,\n window_label text COLLATE \"C\" DEFAULT '',\n window_start text COLLATE \"C\",\n window_end text COLLATE \"C\",\n updated_at text COLLATE \"C\" DEFAULT ov_now(),\n UNIQUE(scope, subject_id, \"window\")\n);\n\nCREATE TABLE chat_timeline_events (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n scope text COLLATE \"C\" NOT NULL DEFAULT 'global',\n subject_id bigint NOT NULL DEFAULT 0,\n ts text COLLATE \"C\" NOT NULL,\n label text COLLATE \"C\" NOT NULL,\n detail text COLLATE \"C\" DEFAULT '',\n created_at text COLLATE \"C\" DEFAULT ov_now()\n);\nCREATE UNIQUE INDEX idx_chat_tl_dedup ON chat_timeline_events(scope, subject_id, ts, label);\n\n-- ── 3. Bookkeeping ──────────────────────────────────────────────\n\n-- Projections of Live data, maintained by server/live-context.js only. Never authority.\nCREATE TABLE ctx_users (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n username text COLLATE \"C\",\n display_name text COLLATE \"C\",\n avatar_url text COLLATE \"C\",\n profile_color text COLLATE \"C\",\n role text COLLATE \"C\" DEFAULT 'user',\n is_banned bigint DEFAULT 0,\n ban_reason text COLLATE \"C\",\n is_owner bigint DEFAULT 0,\n created_at text COLLATE \"C\",\n subject_id text COLLATE \"C\",\n synced_at bigint\n);\nCREATE INDEX idx_ctx_users_username ON ctx_users(lower(username));\nCREATE INDEX idx_ctx_users_display ON ctx_users(lower(display_name));\nCREATE INDEX idx_ctx_users_subject ON ctx_users(subject_id);\n\nCREATE TABLE ctx_streams (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n user_id bigint,\n channel_id bigint,\n managed_stream_id bigint,\n title text COLLATE \"C\",\n is_live bigint DEFAULT 0,\n started_at text COLLATE \"C\",\n ended_at text COLLATE \"C\",\n created_at text COLLATE \"C\",\n synced_at bigint\n);\nCREATE INDEX idx_ctx_streams_user ON ctx_streams(user_id, id);\nCREATE INDEX idx_ctx_streams_channel ON ctx_streams(channel_id);\nCREATE INDEX idx_ctx_streams_managed ON ctx_streams(managed_stream_id);\n\nCREATE TABLE ctx_managed_streams (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n user_id bigint,\n slug text COLLATE \"C\",\n title text COLLATE \"C\",\n sort_order bigint DEFAULT 0,\n created_at text COLLATE \"C\",\n synced_at bigint\n);\nCREATE INDEX idx_ctx_ms_user ON ctx_managed_streams(user_id);\n\nCREATE TABLE ctx_channels (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n user_id bigint UNIQUE,\n title text COLLATE \"C\",\n emote_sources text COLLATE \"C\", -- the channel's emote source switches (Live's channels.emote_sources)\n synced_at bigint\n);\n\nCREATE TABLE ctx_sync (\n key text COLLATE \"C\" PRIMARY KEY,\n value text COLLATE \"C\",\n updated_at bigint\n);\n\n-- Transactional outbox (events.event-envelope@1), written in the same transaction as the change.\nCREATE TABLE events_outbox (\n seq bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n event_id text COLLATE \"C\" NOT NULL UNIQUE,\n event_type text COLLATE \"C\" NOT NULL,\n event text COLLATE \"C\" NOT NULL,\n created_at text COLLATE \"C\" NOT NULL,\n sent_at text COLLATE \"C\",\n attempts bigint DEFAULT 0,\n last_error text COLLATE \"C\"\n);\nCREATE INDEX idx_events_outbox_unsent ON events_outbox(sent_at, seq);\n\n-- Row changes to mirror into Live's copy of Chat's tables (filled by per-connection TEMP\n-- triggers, so only writes made by this service are captured — never the importer's).\nCREATE TABLE live_mirror_outbox (\n seq bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n tbl text COLLATE \"C\" NOT NULL,\n op text COLLATE \"C\" NOT NULL CHECK(op IN ('upsert', 'delete')),\n pk text COLLATE \"C\" NOT NULL,\n created_at bigint NOT NULL DEFAULT (EXTRACT(EPOCH FROM statement_timestamp())::bigint)\n);\n\n-- Writes Live forwarded over the bridge, by idempotency key (Live's outbox id): a retried\n-- delivery returns the first result instead of writing twice.\nCREATE TABLE bridge_applied (\n key text COLLATE \"C\" PRIMARY KEY,\n result text COLLATE \"C\",\n applied_at bigint NOT NULL\n);\n\n-- The TTS and sound queue (server/chat/audio-queue.js): one row per TTS utterance, channel !sound\n-- or 101soundboards clip, played one at a time per room ('stream:<id>' | 'channel:<userId>').\n-- queued → playing → played, or skipped (moderators, the broadcaster) / failed. payload holds what\n-- is needed to make the audio when its turn comes, so the queue survives a restart. Times are ms.\nCREATE TABLE audio_requests (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n room text COLLATE \"C\" NOT NULL,\n stream_id bigint,\n channel_user_id bigint,\n kind text COLLATE \"C\" NOT NULL CHECK(kind IN ('tts', 'channel-sound', 'soundboard')),\n state text COLLATE \"C\" NOT NULL DEFAULT 'queued' CHECK(state IN ('queued', 'playing', 'played', 'skipped', 'failed')),\n requested_by text COLLATE \"C\",\n identity_key text COLLATE \"C\",\n label text COLLATE \"C\",\n payload text COLLATE \"C\" NOT NULL DEFAULT '{}',\n dedupe_key text COLLATE \"C\",\n attempts bigint NOT NULL DEFAULT 0,\n duration_ms bigint,\n error text COLLATE \"C\",\n actor text COLLATE \"C\",\n created_at bigint NOT NULL,\n started_at bigint,\n finished_at bigint,\n UNIQUE(room, dedupe_key)\n);\nCREATE INDEX idx_audio_requests_room_state ON audio_requests(room, state, id);\n\n-- Live's placeholder ids (a large negative number per Live boot) → the real chat_messages id, so a\n-- later op of the same Live boot that carries the placeholder is rewritten even after a Chat\n-- restart (the batch that acknowledged the insert and the one carrying its broadcast can straddle\n-- one). Kept a day.\nCREATE TABLE bridge_refs (\n boot text COLLATE \"C\" NOT NULL,\n ref bigint NOT NULL,\n id bigint NOT NULL,\n at bigint NOT NULL,\n PRIMARY KEY (boot, ref)\n);\n\n-- Rows the importer could not represent. Never dropped; reviewed by hand.\nCREATE TABLE import_hold (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n source_table text COLLATE \"C\" NOT NULL,\n source_pk text COLLATE \"C\" NOT NULL,\n reason text COLLATE \"C\" NOT NULL,\n row_json text COLLATE \"C\" NOT NULL,\n held_at text COLLATE \"C\" DEFAULT ov_now(),\n UNIQUE(source_table, source_pk)\n);\n\nCREATE TABLE import_runs (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n live_db text COLLATE \"C\",\n dry_run bigint DEFAULT 0,\n counts text COLLATE \"C\",\n started_at text COLLATE \"C\" DEFAULT ov_now(),\n finished_at text COLLATE \"C\"\n);\n\nCREATE TABLE chat_meta (\n key text COLLATE \"C\" PRIMARY KEY,\n value text COLLATE \"C\"\n);\n\n-- Deploys announced in chat, one row per release: Live's head commit (live.release.deployed\n-- subject.id; the first commit of the bridge's deployNotice). The bridge and the Events consumer\n-- both claim the head here in the transaction that stores or folds the card\n-- (server/chat/deploy-notice.js), so a head never makes two cards whichever path comes first.\n-- bridge_at / event_at: when each path delivered it (ms), to see that both carry every deploy.\nCREATE TABLE deploy_releases (\n head text COLLATE \"C\" PRIMARY KEY,\n message_id bigint,\n first_via text COLLATE \"C\" NOT NULL CHECK(first_via IN ('bridge', 'events')),\n bridge_at bigint,\n event_at bigint,\n event_id text COLLATE \"C\",\n commit_count bigint NOT NULL DEFAULT 0,\n created_at bigint NOT NULL\n);\n\n-- OpenVibe.Events deliveries already applied (openvibe-sdk inbox: consumer + event id), claimed in\n-- the transaction that applies the event (server/events/consumer.js). Pruned after 35 days\n-- (Events keeps events 30).\nCREATE TABLE chat_event_inbox (\n consumer text COLLATE \"C\" NOT NULL,\n event_id text COLLATE \"C\" NOT NULL,\n processed_at bigint NOT NULL,\n PRIMARY KEY (consumer, event_id)\n);\n\n-- Calls (server/calls/lifecycle.js): one row per call. kind 'direct' is a ring from one person to\n-- another (POST /api/streams/voice-channels/call-user): pending → ringing → active → ended, or\n-- missed (no answer within the ring timeout, or the callee's no-answer), declined (declined or busy)\n-- or failed (the invite could not be delivered; `end_reason` says why). kind 'channel' / 'stream' is\n-- a session of a voice channel (a stream-linked one for 'stream'): active from the first person in\n-- until the channel empties or is closed → ended. Times are ms.\nCREATE TABLE calls (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n kind text COLLATE \"C\" NOT NULL CHECK(kind IN ('channel', 'direct', 'stream')),\n state text COLLATE \"C\" NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'ringing', 'active', 'ended', 'missed', 'declined', 'failed')),\n channel_id text COLLATE \"C\" NOT NULL,\n stream_id bigint,\n created_by bigint,\n created_by_subject text COLLATE \"C\",\n target_user_id bigint,\n target_subject text COLLATE \"C\",\n end_reason text COLLATE \"C\",\n created_at bigint NOT NULL,\n started_at bigint,\n answered_at bigint,\n ended_at bigint\n);\nCREATE INDEX idx_calls_channel_state ON calls(channel_id, state);\nCREATE INDEX idx_calls_open ON calls(state) WHERE state IN ('pending', 'ringing', 'active');\n\n\nCREATE TABLE rooms (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n slug text COLLATE \"C\" UNIQUE NOT NULL,\n name text COLLATE \"C\" NOT NULL,\n topic text COLLATE \"C\",\n kind text COLLATE \"C\" NOT NULL DEFAULT 'community',\n visibility text COLLATE \"C\" NOT NULL DEFAULT 'public',\n owner_id bigint NOT NULL,\n owner_subject text COLLATE \"C\",\n slow_seconds bigint NOT NULL DEFAULT 0,\n message_count bigint NOT NULL DEFAULT 0,\n last_message_at text COLLATE \"C\",\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n archived_at text COLLATE \"C\",\n join_role text COLLATE \"C\"\n);\nCREATE TABLE room_members (\n room_id bigint NOT NULL,\n user_id bigint NOT NULL,\n role text COLLATE \"C\" NOT NULL DEFAULT 'member',\n last_read_id bigint NOT NULL DEFAULT 0,\n joined_at text COLLATE \"C\" DEFAULT ov_now(),\n last_seen_at text COLLATE \"C\",\n PRIMARY KEY (room_id, user_id)\n);\nCREATE INDEX idx_room_members_user ON room_members(user_id);\nCREATE TABLE room_messages (\n id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n room_id bigint NOT NULL,\n user_id bigint NOT NULL,\n subject_id text COLLATE \"C\",\n message text COLLATE \"C\" NOT NULL,\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n is_deleted bigint NOT NULL DEFAULT 0,\n deleted_by bigint\n);\nCREATE INDEX idx_room_messages_room ON room_messages(room_id, id);\nCREATE TABLE room_attachments (\n room_id bigint NOT NULL,\n service text COLLATE \"C\" NOT NULL,\n resource text COLLATE \"C\" NOT NULL,\n title text COLLATE \"C\",\n attached_by bigint NOT NULL,\n attached_by_subject text COLLATE \"C\",\n created_at text COLLATE \"C\" DEFAULT ov_now(),\n PRIMARY KEY (room_id, service, resource)\n);\nCREATE TABLE token_revocations (\n subject_id text COLLATE \"C\" PRIMARY KEY,\n valid_after_ms bigint NOT NULL,\n reason text COLLATE \"C\",\n updated_at text COLLATE \"C\" DEFAULT ov_now()\n);\nCREATE TABLE network_blocks (\n blocker_subject text COLLATE \"C\" NOT NULL,\n blocked_subject text COLLATE \"C\" NOT NULL,\n active bigint NOT NULL,\n revision bigint NOT NULL,\n updated_at bigint NOT NULL,\n PRIMARY KEY (blocker_subject, blocked_subject)\n);\nCREATE INDEX idx_network_blocks_blocked ON network_blocks(blocked_subject, active);\nCREATE TABLE account_data_events (\n id text COLLATE \"C\" PRIMARY KEY,\n kind text COLLATE \"C\" NOT NULL,\n subject text COLLATE \"C\" NOT NULL,\n outcome text COLLATE \"C\",\n sent_at text COLLATE \"C\",\n applied_at text COLLATE \"C\" NOT NULL DEFAULT ov_now_iso()\n);\n\n-- ── Live read-mirror capture (C-02/C-03): a row change on any of the twelve tables Chat owns queues a\n-- (table, op, pk) row for server/bridge/live-mirror.js. Skipped when ov.mirror_skip = '1' is set for the\n-- transaction (the bridge applying Live's own writes, and the importer's inserts). ──\nCREATE FUNCTION mirror_chat_messages() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('chat_messages', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('chat_messages', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_chat_messages AFTER INSERT OR UPDATE OR DELETE ON chat_messages FOR EACH ROW EXECUTE FUNCTION mirror_chat_messages();\nCREATE FUNCTION mirror_dm_conversations() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_conversations', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_conversations', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_dm_conversations AFTER INSERT OR UPDATE OR DELETE ON dm_conversations FOR EACH ROW EXECUTE FUNCTION mirror_dm_conversations();\nCREATE FUNCTION mirror_dm_participants() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_participants', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_participants', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_dm_participants AFTER INSERT OR UPDATE OR DELETE ON dm_participants FOR EACH ROW EXECUTE FUNCTION mirror_dm_participants();\nCREATE FUNCTION mirror_dm_messages() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_messages', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_messages', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_dm_messages AFTER INSERT OR UPDATE OR DELETE ON dm_messages FOR EACH ROW EXECUTE FUNCTION mirror_dm_messages();\nCREATE FUNCTION mirror_dm_blocks() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_blocks', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('dm_blocks', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_dm_blocks AFTER INSERT OR UPDATE OR DELETE ON dm_blocks FOR EACH ROW EXECUTE FUNCTION mirror_dm_blocks();\nCREATE FUNCTION mirror_tts_voice_overrides() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('tts_voice_overrides', 'delete', jsonb_build_object('identity_key', OLD.identity_key)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('tts_voice_overrides', 'upsert', jsonb_build_object('identity_key', NEW.identity_key)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_tts_voice_overrides AFTER INSERT OR UPDATE OR DELETE ON tts_voice_overrides FOR EACH ROW EXECUTE FUNCTION mirror_tts_voice_overrides();\nCREATE FUNCTION mirror_channel_sounds() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('channel_sounds', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('channel_sounds', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_channel_sounds AFTER INSERT OR UPDATE OR DELETE ON channel_sounds FOR EACH ROW EXECUTE FUNCTION mirror_channel_sounds();\nCREATE FUNCTION mirror_relay_users() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('relay_users', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('relay_users', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_relay_users AFTER INSERT OR UPDATE OR DELETE ON relay_users FOR EACH ROW EXECUTE FUNCTION mirror_relay_users();\nCREATE FUNCTION mirror_hidden_relay_users() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('hidden_relay_users', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('hidden_relay_users', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_hidden_relay_users AFTER INSERT OR UPDATE OR DELETE ON hidden_relay_users FOR EACH ROW EXECUTE FUNCTION mirror_hidden_relay_users();\nCREATE FUNCTION mirror_pending_ip_messages() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('pending_ip_messages', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('pending_ip_messages', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_pending_ip_messages AFTER INSERT OR UPDATE OR DELETE ON pending_ip_messages FOR EACH ROW EXECUTE FUNCTION mirror_pending_ip_messages();\nCREATE FUNCTION mirror_stream_first_chats() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('stream_first_chats', 'delete', jsonb_build_object('chatter_key', OLD.chatter_key, 'channel_user_id', OLD.channel_user_id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('stream_first_chats', 'upsert', jsonb_build_object('chatter_key', NEW.chatter_key, 'channel_user_id', NEW.channel_user_id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_stream_first_chats AFTER INSERT OR UPDATE OR DELETE ON stream_first_chats FOR EACH ROW EXECUTE FUNCTION mirror_stream_first_chats();\nCREATE FUNCTION mirror_moderation_actions() RETURNS trigger LANGUAGE plpgsql AS $$\nBEGIN\n IF current_setting('ov.mirror_skip', true) = '1' THEN RETURN NULL; END IF;\n IF TG_OP = 'DELETE' THEN\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('moderation_actions', 'delete', jsonb_build_object('id', OLD.id)::text);\n RETURN OLD;\n END IF;\n INSERT INTO live_mirror_outbox (tbl, op, pk) VALUES ('moderation_actions', 'upsert', jsonb_build_object('id', NEW.id)::text);\n RETURN NEW;\nEND $$;\nCREATE TRIGGER mirror_moderation_actions AFTER INSERT OR UPDATE OR DELETE ON moderation_actions FOR EACH ROW EXECUTE FUNCTION mirror_moderation_actions();\n"
++ },
++ {
++ "file": "0002_audio_claim_owner.sql",
++ "sql": "-- phase: expand\n-- The audio queue's claims are owned and one per room (server/chat/audio-queue.js):\n-- claimed_by the Chat process (its instance id) that claimed the row and plays it;\n-- lease_until ms; that process renews it while the row is playing. A playing row whose lease has passed\n-- belongs to a process that died: recover() and the sweeper settle it, never a live owner's.\n-- At most one playing row per room, across processes: the partial unique index is the store's guarantee\n-- (claim() also takes a per-room advisory lock, so a losing claimer just gets nothing).\n-- Safe on the live table and idempotent: nullable columns, no rewrite. The index is built in this transaction\n-- under a short SHARE ROW EXCLUSIVE lock (the table holds about a week of requests), so the duplicates it\n-- would refuse can be settled first and no claim of a previous-release process slips in between; CREATE INDEX\n-- CONCURRENTLY could not run next to that clean-up, and a failed concurrent build leaves an invalid index that\n-- IF NOT EXISTS would then keep.\nALTER TABLE audio_requests ADD COLUMN IF NOT EXISTS claimed_by text COLLATE \"C\";\nALTER TABLE audio_requests ADD COLUMN IF NOT EXISTS lease_until bigint;\n\nLOCK TABLE audio_requests IN SHARE ROW EXCLUSIVE MODE;\n-- Two clips playing at once in one room (the race this closes): keep the oldest, the others reached the room too.\nUPDATE audio_requests a SET state = 'played', error = 'delivered before a restart',\n finished_at = COALESCE(a.finished_at, a.started_at, a.created_at)\n WHERE a.state = 'playing'\n AND EXISTS (SELECT 1 FROM audio_requests b WHERE b.room = a.room AND b.state = 'playing' AND b.id < a.id);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_audio_requests_one_playing ON audio_requests(room) WHERE state = 'playing';\n"
++ },
++ {
++ "file": "0003_chat_ingress.sql",
++ "sql": "-- phase: expand\n-- Durable results for the Chat-owned ingress. Keep separate from bridge_applied so J3 can\n-- retire the Live bridge without losing retry protection for the replacement routes.\nCREATE TABLE IF NOT EXISTS chat_ingress_applied (\n principal text COLLATE \"C\" NOT NULL,\n family text COLLATE \"C\" NOT NULL,\n key text COLLATE \"C\" NOT NULL,\n body_hash text COLLATE \"C\" NOT NULL,\n result text COLLATE \"C\" NOT NULL,\n delivered bigint NOT NULL DEFAULT 0,\n delivery_claimed_at bigint,\n applied_at bigint NOT NULL,\n PRIMARY KEY (principal, family, key)\n);\n"
++ },
++ {
++ "file": "0004_channel_sound_media.sql",
++ "sql": "-- phase: expand\n-- Where Live's asset sync put each channel sound on OpenVibe.Media (plan T3 J4b, N5): Live reads the\n-- sounds still to upload (GET /internal/chat/sounds?pending_asset=1) and writes the asset back\n-- (POST /internal/chat/sounds/asset), instead of using its mirrored copy of channel_sounds.\n-- Both stay NULL until the sound is uploaded.\nALTER TABLE channel_sounds ADD COLUMN IF NOT EXISTS media_url text COLLATE \"C\";\nALTER TABLE channel_sounds ADD COLUMN IF NOT EXISTS media_asset_id bigint;\nCREATE INDEX IF NOT EXISTS idx_channel_sounds_pending_asset ON channel_sounds(id) WHERE media_asset_id IS NULL;\n"
++ }
],
"ledger": {
"chat_meta": []
},
-- "lazy": [
-- "CREATE TABLE IF NOT EXISTS token_revocations ( subject_id TEXT PRIMARY KEY, valid_after_ms INTEGER NOT NULL, reason TEXT, updated_at INTEGER NOT NULL )",
-- "CREATE TABLE IF NOT EXISTS account_data_events ( id TEXT PRIMARY KEY, kind TEXT NOT NULL, subject TEXT NOT NULL, outcome TEXT, sent_at TEXT, applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) )"
-- ],
"statements": [
-- "DELETE FROM audio_requests WHERE state IN ('played', 'skipped', 'failed') AND COALESCE(finished_at, created_at) < ?",
-- "DELETE FROM bridge_applied WHERE applied_at < ?",
-- "DELETE FROM bridge_refs WHERE at < ?",
-- "DELETE FROM channel_moderators WHERE channel_id = ? AND user_id = ?",
-- "DELETE FROM channel_sounds WHERE id = ?",
-- "DELETE FROM dm_blocks WHERE blocker_id = ? AND blocked_id = ?",
-- "DELETE FROM dm_blocks WHERE id = ?",
++ "DELETE FROM audio_requests WHERE channel_user_id = ANY($1)",
++ "DELETE FROM audio_requests WHERE state IN ('played', 'skipped', 'failed') AND COALESCE(finished_at, created_at) < $1",
++ "DELETE FROM calls WHERE created_by_subject = ANY($1) OR target_subject = ANY($1) OR created_by = ANY($2) OR target_user_id = ANY($2)",
++ "DELETE FROM channel_moderation_settings WHERE channel_id IN (SELECT id FROM ctx_channels WHERE user_id = ANY($1))",
++ "DELETE FROM channel_moderators WHERE channel_id = $1 AND user_id = $2",
++ "DELETE FROM channel_moderators WHERE channel_id IN (SELECT id FROM ctx_channels WHERE user_id = ANY($1))",
++ "DELETE FROM channel_moderators WHERE user_id = ANY($1)",
++ "DELETE FROM channel_sounds WHERE created_by_subject_id = ANY($1) OR created_by = ANY($2)",
++ "DELETE FROM channel_sounds WHERE id = $1",
++ "DELETE FROM chat_ai_summaries WHERE scope = 'user' AND subject_id = ANY($1)",
++ "DELETE FROM chat_messages WHERE subject_id = ANY($1) OR user_id = ANY($2)",
++ "DELETE FROM chat_timeline_events WHERE scope = 'user' AND subject_id = ANY($1)",
++ "DELETE FROM ctx_channels WHERE user_id = ANY($1)",
++ "DELETE FROM ctx_managed_streams WHERE user_id = ANY($1)",
++ "DELETE FROM ctx_streams WHERE user_id = ANY($1)",
++ "DELETE FROM dm_blocks WHERE blocker_id = $1 AND blocked_id = $2",
++ "DELETE FROM dm_blocks WHERE blocker_subject_id = ANY($1) OR blocker_id = ANY($2) OR blocked_id = ANY($2)",
++ "DELETE FROM dm_blocks WHERE id = $1",
"DELETE FROM dm_conversations WHERE id NOT IN (SELECT conversation_id FROM dm_participants)",
-- "DELETE FROM dm_messages WHERE id = ?",
-- "DELETE FROM dm_participants WHERE conversation_id = ? AND user_id = ?",
-- "DELETE FROM emotes WHERE id = ?",
-- "DELETE FROM hidden_relay_users WHERE id = ?",
-- "DELETE FROM hidden_relay_users WHERE platform = ? AND external_username = ? AND channel_id IS ?",
-- "DELETE FROM live_mirror_outbox WHERE seq <= ?",
-- "DELETE FROM room_attachments WHERE room_id = ? AND service = ? AND resource = ?",
-- "DELETE FROM room_members WHERE room_id = ? AND user_id = ?",
-- "DELETE FROM room_messages WHERE room_id = ?",
-- "DELETE FROM rooms WHERE id = ?",
-- "DELETE FROM tts_voice_overrides WHERE identity_key = ?",
-- "DELETE FROM user_tags WHERE user_id = ? AND tag_id = ?",
-- "INSERT INTO account_data_events (id, kind, subject, outcome) VALUES (?, ?, ?, ?)",
-- "INSERT INTO audio_requests (room, stream_id, channel_user_id, kind, state, requested_by, identity_key, label, payload, dedupe_key, created_at) VALUES (?, ?, ?, ?, 'queued', ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO bridge_applied (key, result, applied_at) VALUES (?, ?, ?)",
-- "INSERT INTO bridge_refs (boot, ref, id, at) VALUES (?, ?, ?, ?) ON CONFLICT(boot, ref) DO UPDATE SET id = excluded.id, at = excluded.at",
-- "INSERT INTO calls (kind, state, channel_id, stream_id, created_by, created_by_subject, target_user_id, target_subject, created_at, started_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO channel_moderation_settings ( channel_id, slow_mode_seconds, followers_only, emote_only, allow_anonymous, links_allowed, gifs_enabled, account_age_gate_hours, caps_percentage_limit, aggressive_filter, max_message_length, slur_filter_enabled, slur_filter_use_builtin, slur_filter_terms, slur_filter_regexes, slur_filter_nudge_message, slur_filter_disabled_categories, ip_approval_mode, soundboard_enabled, soundboard_allow_pitch, soundboard_allow_speed, soundboard_banned_ids, viewer_auto_delete_enabled, viewer_delete_all_enabled, custom_emotes_enabled, custom_sounds_enabled, max_sound_seconds, uploads_mods_only, emote_scale, emote_size_min, emote_size_max, sounds_mods_only, mods_can_edit_about ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO channel_moderation_settings (channel_id) VALUES (?)",
-- "INSERT INTO channel_sounds (channel_owner_id, command, url, mime, duration_seconds, created_by, created_by_name, emote_code, created_by_subject_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO chat_ai_summaries (scope, subject_id, window, overview, memory_json, timeline_json, message_count, window_message_count, last_message_id, window_label, window_start, window_end, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(scope, subject_id, window) DO UPDATE SET overview = excluded.overview, memory_json = excluded.memory_json, timeline_json = excluded.timeline_json, message_count = excluded.message_count, window_message_count = excluded.window_message_count, last_message_id = excluded.last_message_id, window_label = excluded.window_label, window_start = excluded.window_start, window_end = excluded.window_end, updated_at = CURRENT_TIMESTAMP",
-- "INSERT INTO chat_messages (stream_id, channel_user_id, user_id, anon_id, username, message, message_type, is_global, reply_to_id, source_platform, auto_delete_at, metadata, subject_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO chat_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
-- "INSERT INTO ctx_channels (id, user_id, title, emote_sources, synced_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET user_id = excluded.user_id, title = excluded.title, emote_sources = excluded.emote_sources, synced_at = excluded.synced_at",
-- "INSERT INTO ctx_streams (id, user_id, channel_id, managed_stream_id, title, is_live, started_at, ended_at, created_at, synced_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET user_id = excluded.user_id, channel_id = excluded.channel_id, managed_stream_id = excluded.managed_stream_id, title = excluded.title, is_live = excluded.is_live, started_at = excluded.started_at, ended_at = excluded.ended_at, created_at = excluded.created_at, synced_at = excluded.synced_at",
-- "INSERT INTO ctx_users (id, username, display_name, avatar_url, profile_color, role, is_banned, ban_reason, is_owner, created_at, subject_id, synced_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET username = excluded.username, display_name = excluded.display_name, avatar_url = excluded.avatar_url, profile_color = excluded.profile_color, role = excluded.role, is_banned = excluded.is_banned, ban_reason = excluded.ban_reason, is_owner = excluded.is_owner, created_at = excluded.created_at, subject_id = excluded.subject_id, synced_at = excluded.synced_at",
-- "INSERT INTO dm_conversations (name, is_group, created_by) VALUES (?, ?, ?)",
-- "INSERT INTO dm_messages (conversation_id, sender_id, message, sender_subject_id) VALUES (?, ?, ?, ?)",
-- "INSERT INTO emotes (user_id, code, url, animated, width, height, is_global, channel_owner_id, size, media_url, media_asset_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO events_outbox (event_id, event_type, event, created_at) VALUES (?, ?, ?, ?)",
-- "INSERT INTO moderation_actions (scope_type, scope_id, actor_user_id, target_user_id, action_type, details, actor_subject_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO network_blocks (blocker_subject, blocked_subject, active, revision, updated_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(blocker_subject, blocked_subject) DO UPDATE SET active = excluded.active, revision = excluded.revision, updated_at = excluded.updated_at WHERE excluded.revision > network_blocks.revision",
-- "INSERT INTO pending_ip_messages (channel_id, stream_id, ip_address, user_id, anon_id, username, message) VALUES (?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO relay_users (platform, username, display_name, first_seen, last_seen, message_count) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1) ON CONFLICT(platform, username) DO UPDATE SET last_seen = CURRENT_TIMESTAMP, message_count = message_count + 1, display_name = excluded.display_name",
-- "INSERT INTO room_attachments (room_id, service, resource, title, attached_by, attached_by_subject) VALUES (?, ?, ?, ?, ?, ?)",
-- "INSERT INTO room_members (room_id, user_id, role) VALUES (?, ?, ?)",
-- "INSERT INTO room_members (room_id, user_id, role, last_read_id, last_seen_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)",
-- "INSERT INTO room_members (room_id, user_id, role, last_seen_at) VALUES (?, ?, 'owner', CURRENT_TIMESTAMP)",
-- "INSERT INTO room_messages (room_id, user_id, subject_id, message) VALUES (?, ?, ?, ?)",
-- "INSERT INTO rooms (slug, name, topic, kind, visibility, owner_id, owner_subject, join_role) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
-- "INSERT INTO token_revocations (subject_id, valid_after_ms, reason, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(subject_id) DO UPDATE SET valid_after_ms = excluded.valid_after_ms, reason = excluded.reason, updated_at = excluded.updated_at WHERE excluded.valid_after_ms > token_revocations.valid_after_ms",
-- "INSERT INTO tts_voice_overrides (identity_key, voice, pitch, speed, gap, set_by, updated_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT(identity_key) DO UPDATE SET voice=excluded.voice, pitch=excluded.pitch, speed=excluded.speed, gap=excluded.gap, set_by=excluded.set_by, updated_at=CURRENT_TIMESTAMP",
-- "INSERT OR IGNORE INTO channel_moderators (channel_id, user_id, added_by) VALUES (?, ?, ?)",
-- "INSERT OR IGNORE INTO chat_timeline_events (scope, subject_id, ts, label, detail) VALUES (?, ?, ?, ?, ?)",
-- "INSERT OR IGNORE INTO dm_blocks (blocker_id, blocked_id, blocker_subject_id) VALUES (?, ?, ?)",
-- "INSERT OR IGNORE INTO dm_participants (conversation_id, user_id, subject_id) VALUES (?, ?, ?)",
-- "INSERT OR IGNORE INTO stream_first_chats (chatter_key, channel_user_id) VALUES (?, ?)",
-- "INSERT OR IGNORE INTO user_tags (user_id, tag_id, source) VALUES (?, ?, ?)",
-- "INSERT OR REPLACE INTO account_data_events (id, kind, subject, outcome, sent_at) VALUES (?, ?, ?, ?, ?)",
-- "INSERT OR REPLACE INTO hidden_relay_users (channel_id, platform, external_username, action, reason, created_by) VALUES (?, ?, ?, ?, ?, ?)",
-- "SELECT * FROM account_data_events WHERE id = ?",
-- "SELECT * FROM audio_requests WHERE id = ?",
-- "SELECT * FROM audio_requests WHERE id = ? AND room = ? AND state = 'playing'",
-- "SELECT * FROM audio_requests WHERE id = ? AND room = ? AND state IN ('queued', 'playing')",
-- "SELECT * FROM audio_requests WHERE room = ? AND state = 'playing' ORDER BY id LIMIT 1",
-- "SELECT * FROM audio_requests WHERE room = ? AND state = 'queued' ORDER BY id",
-- "SELECT * FROM audio_requests WHERE room = ? AND state = 'queued' ORDER BY id LIMIT 1",
-- "SELECT * FROM audio_requests WHERE room = ? AND state IN ('played', 'skipped', 'failed') ORDER BY COALESCE(finished_at, created_at) DESC, id DESC LIMIT ?",
-- "SELECT * FROM audio_requests WHERE room = ? AND state IN ('queued', 'playing') ORDER BY id",
-- "SELECT * FROM calls WHERE id = ?",
-- "SELECT * FROM calls WHERE kind = 'direct' AND created_by = ? AND target_user_id = ? AND channel_id = ? AND state IN ('pending', 'ringing') ORDER BY id DESC LIMIT 1",
-- "SELECT * FROM calls WHERE kind = 'direct' AND created_by = ? AND target_user_id = ? AND channel_id = ? AND state IN ('pending', 'ringing', 'active') ORDER BY id DESC LIMIT 1",
-- "SELECT * FROM calls WHERE kind IN ('channel', 'stream') AND channel_id = ? AND state = 'active' ORDER BY id DESC LIMIT 1",
-- "SELECT * FROM channel_moderation_settings WHERE channel_id = ?",
-- "SELECT * FROM channel_moderators WHERE channel_id = ? AND user_id = ?",
-- "SELECT * FROM channel_sounds WHERE channel_owner_id = ? AND command = ? AND is_approved = 1 ORDER BY RANDOM() LIMIT 1",
-- "SELECT * FROM channel_sounds WHERE channel_owner_id = ? AND is_approved = 1 ORDER BY command",
-- "SELECT * FROM channel_sounds WHERE id = ?",
-- "SELECT * FROM chat_ai_summaries WHERE scope = ? AND subject_id = ? AND window = ?",
-- "SELECT * FROM chat_ai_summaries WHERE scope = ? AND subject_id = ? ORDER BY window",
-- "SELECT * FROM chat_messages WHERE id = ?",
-- "SELECT * FROM chat_timeline_events WHERE id = ?",
-- "SELECT * FROM ctx_channels WHERE id = ?",
-- "SELECT * FROM ctx_channels WHERE user_id = ?",
++ "DELETE FROM dm_messages WHERE id = $1",
++ "DELETE FROM dm_messages WHERE sender_subject_id = ANY($1) OR sender_id = ANY($2)",
++ "DELETE FROM dm_participants WHERE conversation_id = $1 AND user_id = $2",
++ "DELETE FROM dm_participants WHERE subject_id = ANY($1) OR user_id = ANY($2)",
++ "DELETE FROM emotes WHERE id = $1",
++ "DELETE FROM emotes WHERE user_id = ANY($1)",
++ "DELETE FROM hidden_relay_users WHERE id = $1",
++ "DELETE FROM hidden_relay_users WHERE platform = $1 AND external_username = $2 AND channel_id IS NOT DISTINCT FROM $3",
++ "DELETE FROM live_mirror_outbox",
++ "DELETE FROM live_mirror_outbox WHERE seq <= $1",
++ "DELETE FROM network_blocks WHERE blocked_subject = $1 AND blocker_subject = $2",
++ "DELETE FROM network_blocks WHERE blocker_subject = $1 AND blocked_subject = $2",
++ "DELETE FROM network_blocks WHERE blocker_subject = ANY($1) OR blocked_subject = ANY($1)",
++ "DELETE FROM pending_ip_messages WHERE user_id = ANY($1)",
++ "DELETE FROM room_attachments WHERE attached_by_subject = ANY($1)",
++ "DELETE FROM room_attachments WHERE room_id = $1 AND service = $2 AND resource = $3",
++ "DELETE FROM room_members WHERE room_id = $1 AND user_id = $2",
++ "DELETE FROM room_members WHERE user_id = ANY($1)",
++ "DELETE FROM room_messages WHERE room_id = $1",
++ "DELETE FROM room_messages WHERE subject_id = ANY($1) OR user_id = ANY($2)",
++ "DELETE FROM rooms WHERE id = $1",
++ "DELETE FROM stream_first_chats WHERE channel_user_id = ANY($1)",
++ "DELETE FROM tts_voice_overrides WHERE identity_key = $1",
++ "DELETE FROM user_tags WHERE user_id = $1 AND tag_id = $2",
++ "DELETE FROM user_tags WHERE user_id = ANY($1)",
++ "INSERT INTO account_data_events (id, kind, subject, outcome) VALUES ($1, $2, $3, $4)",
++ "INSERT INTO account_data_events (id, kind, subject, outcome, sent_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO UPDATE SET kind = excluded.kind, subject = excluded.subject, outcome = excluded.outcome, sent_at = excluded.sent_at, applied_at = ov_now_iso()",
++ "INSERT INTO audio_requests (room, stream_id, channel_user_id, kind, state, requested_by, identity_key, label, payload, dedupe_key, created_at) VALUES ($1, $2, $3, $4, 'queued', $5, $6, $7, $8, $9, $10) ON CONFLICT (room, dedupe_key) DO NOTHING",
++ "INSERT INTO audio_requests (room, stream_id, channel_user_id, kind, state, requested_by, identity_key, label, payload, dedupe_key, created_at) VALUES ($1, $2, $3, $4, 'queued', $5, $6, $7, $8, $9, $10) ON CONFLICT (room, dedupe_key) DO NOTHING RETURNING id",
++ "INSERT INTO calls (kind, state, channel_id, stream_id, created_by, created_by_subject, target_user_id, target_subject, created_at, started_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
++ "INSERT INTO channel_moderation_settings ( channel_id, slow_mode_seconds, followers_only, emote_only, allow_anonymous, links_allowed, gifs_enabled, account_age_gate_hours, caps_percentage_limit, aggressive_filter, max_message_length, slur_filter_enabled, slur_filter_use_builtin, slur_filter_terms, slur_filter_regexes, slur_filter_nudge_message, slur_filter_disabled_categories, ip_approval_mode, soundboard_enabled, soundboard_allow_pitch, soundboard_allow_speed, soundboard_banned_ids, viewer_auto_delete_enabled, viewer_delete_all_enabled, custom_emotes_enabled, custom_sounds_enabled, max_sound_seconds, uploads_mods_only, emote_scale, emote_size_min, emote_size_max, sounds_mods_only, mods_can_edit_about ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33)",
++ "INSERT INTO channel_moderation_settings (channel_id) VALUES ($1)",
++ "INSERT INTO channel_moderators (channel_id, user_id, added_by) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
++ "INSERT INTO channel_sounds (channel_owner_id, command, url, mime, duration_seconds, created_by, created_by_name, emote_code, created_by_subject_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
++ "INSERT INTO chat_ai_summaries (scope, subject_id, \"window\", overview, memory_json, timeline_json, message_count, window_message_count, last_message_id, window_label, window_start, window_end, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, ov_now()) ON CONFLICT(scope, subject_id, \"window\") DO UPDATE SET overview = excluded.overview, memory_json = excluded.memory_json, timeline_json = excluded.timeline_json, message_count = excluded.message_count, window_message_count = excluded.window_message_count, last_message_id = excluded.last_message_id, window_label = excluded.window_label, window_start = excluded.window_start, window_end = excluded.window_end, updated_at = ov_now()",
++ "INSERT INTO chat_ingress_applied (principal, family, key, body_hash, result, applied_at) VALUES ($1, $2, $3, $4, '{}', $5) ON CONFLICT DO NOTHING",
++ "INSERT INTO chat_messages (stream_id, channel_user_id, user_id, anon_id, username, message, message_type, is_global, reply_to_id, source_platform, auto_delete_at, metadata, subject_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
++ "INSERT INTO chat_messages (stream_id, channel_user_id, user_id, anon_id, username, message, message_type, is_global, reply_to_id, source_platform, auto_delete_at, metadata, subject_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id",
++ "INSERT INTO chat_meta (key, value) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
++ "INSERT INTO chat_timeline_events (scope, subject_id, ts, label, detail) VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO NOTHING",
++ "INSERT INTO ctx_channels (id, user_id, title, emote_sources, synced_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT(id) DO UPDATE SET user_id = excluded.user_id, title = excluded.title, emote_sources = excluded.emote_sources, synced_at = excluded.synced_at",
++ "INSERT INTO ctx_streams (id, user_id, channel_id, managed_stream_id, title, is_live, started_at, ended_at, created_at, synced_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT(id) DO UPDATE SET user_id = excluded.user_id, channel_id = excluded.channel_id, managed_stream_id = excluded.managed_stream_id, title = excluded.title, is_live = excluded.is_live, started_at = excluded.started_at, ended_at = excluded.ended_at, created_at = excluded.created_at, synced_at = excluded.synced_at",
++ "INSERT INTO ctx_users (id, username, display_name, avatar_url, profile_color, role, is_banned, ban_reason, is_owner, created_at, subject_id, synced_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT(id) DO UPDATE SET username = excluded.username, display_name = excluded.display_name, avatar_url = excluded.avatar_url, profile_color = excluded.profile_color, role = excluded.role, is_banned = excluded.is_banned, ban_reason = excluded.ban_reason, is_owner = excluded.is_owner, created_at = excluded.created_at, subject_id = excluded.subject_id, synced_at = excluded.synced_at",
++ "INSERT INTO dm_blocks (blocker_id, blocked_id, blocker_subject_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
++ "INSERT INTO dm_conversations (name, is_group, created_by) VALUES ($1, $2, $3)",
++ "INSERT INTO dm_conversations (name, is_group, created_by) VALUES ($1, $2, $3) RETURNING id",
++ "INSERT INTO dm_messages (conversation_id, sender_id, message, sender_subject_id) VALUES ($1, $2, $3, $4)",
++ "INSERT INTO dm_messages (conversation_id, sender_id, message, sender_subject_id) VALUES ($1, $2, $3, $4) RETURNING id",
++ "INSERT INTO dm_participants (conversation_id, user_id, subject_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
++ "INSERT INTO emotes (user_id, code, url, animated, width, height, is_global, channel_owner_id, size, media_url, media_asset_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
++ "INSERT INTO events_outbox (event_id, event_type, event, created_at) VALUES ($1, $2, $3, $4)",
++ "INSERT INTO hidden_relay_users (channel_id, platform, external_username, action, reason, created_by) VALUES ($1, $2, $3, $4, $5, $6)",
++ "INSERT INTO moderation_actions (scope_type, scope_id, actor_user_id, target_user_id, action_type, details, actor_subject_id) VALUES ($1, $2, $3, $4, $5, $6, $7)",
++ "INSERT INTO moderation_actions (scope_type, scope_id, actor_user_id, target_user_id, action_type, details, actor_subject_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id",
++ "INSERT INTO network_blocks (blocker_subject, blocked_subject, active, revision, updated_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT(blocker_subject, blocked_subject) DO UPDATE SET active = excluded.active, revision = excluded.revision, updated_at = excluded.updated_at WHERE excluded.revision > network_blocks.revision",
++ "INSERT INTO pending_ip_messages (channel_id, stream_id, ip_address, user_id, anon_id, username, message) VALUES ($1, $2, $3, $4, $5, $6, $7)",
++ "INSERT INTO relay_users (platform, username, display_name, first_seen, last_seen, message_count) VALUES ($1, $2, $3, ov_now(), ov_now(), 1) ON CONFLICT(platform, username) DO UPDATE SET last_seen = ov_now(), message_count = relay_users.message_count + 1, display_name = excluded.display_name",
++ "INSERT INTO room_attachments (room_id, service, resource, title, attached_by, attached_by_subject) VALUES ($1, $2, $3, $4, $5, $6)",
++ "INSERT INTO room_members (room_id, user_id, role) VALUES ($1, $2, $3)",
++ "INSERT INTO room_members (room_id, user_id, role, last_read_id, last_seen_at) VALUES ($1, $2, $3, $4, ov_now())",
++ "INSERT INTO room_members (room_id, user_id, role, last_seen_at) VALUES ($1, $2, 'owner', ov_now())",
++ "INSERT INTO room_messages (room_id, user_id, subject_id, message) VALUES ($1, $2, $3, $4)",
++ "INSERT INTO room_messages (room_id, user_id, subject_id, message) VALUES ($1, $2, $3, $4) RETURNING id",
++ "INSERT INTO rooms (slug, name, topic, kind, visibility, owner_id, owner_subject, join_role) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
++ "INSERT INTO rooms (slug, name, topic, kind, visibility, owner_id, owner_subject, join_role) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id",
++ "INSERT INTO stream_first_chats (chatter_key, channel_user_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
++ "INSERT INTO token_revocations (subject_id, valid_after_ms, reason, updated_at) VALUES ($1, $2, $3, $4) ON CONFLICT(subject_id) DO UPDATE SET valid_after_ms = excluded.valid_after_ms, reason = excluded.reason, updated_at = excluded.updated_at WHERE excluded.valid_after_ms > token_revocations.valid_after_ms",
++ "INSERT INTO tts_voice_overrides (identity_key, voice, pitch, speed, gap, set_by, updated_at) VALUES ($1, $2, $3, $4, $5, $6, ov_now()) ON CONFLICT(identity_key) DO UPDATE SET voice=excluded.voice, pitch=excluded.pitch, speed=excluded.speed, gap=excluded.gap, set_by=excluded.set_by, updated_at=ov_now()",
++ "INSERT INTO user_tags (user_id, tag_id, source) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
++ "SELECT * FROM account_data_events WHERE id = $1",
++ "SELECT * FROM audio_requests WHERE id = $1",
++ "SELECT * FROM audio_requests WHERE id = $1 AND room = $2 AND state = 'playing'",
++ "SELECT * FROM audio_requests WHERE id = $1 AND room = $2 AND state IN ('queued', 'playing')",
++ "SELECT * FROM audio_requests WHERE room = $1 AND state = 'playing' ORDER BY id LIMIT 1",
++ "SELECT * FROM audio_requests WHERE room = $1 AND state = 'queued' ORDER BY id",
++ "SELECT * FROM audio_requests WHERE room = $1 AND state = 'queued' ORDER BY id LIMIT 1",
++ "SELECT * FROM audio_requests WHERE room = $1 AND state IN ('played', 'skipped', 'failed') ORDER BY COALESCE(finished_at, created_at) DESC, id DESC LIMIT $2",
++ "SELECT * FROM audio_requests WHERE room = $1 AND state IN ('queued', 'playing') ORDER BY id",
++ "SELECT * FROM calls WHERE id = $1",
++ "SELECT * FROM calls WHERE kind = 'direct' AND created_by = $1 AND target_user_id = $2 AND channel_id = $3 AND state IN ('pending', 'ringing') ORDER BY id DESC LIMIT 1",
++ "SELECT * FROM calls WHERE kind = 'direct' AND created_by = $1 AND target_user_id = $2 AND channel_id = $3 AND state IN ('pending', 'ringing', 'active') ORDER BY id DESC LIMIT 1",
++ "SELECT * FROM calls WHERE kind IN ('channel', 'stream') AND channel_id = $1 AND state = 'active' ORDER BY id DESC LIMIT 1",
++ "SELECT * FROM channel_moderation_settings WHERE channel_id = $1",
++ "SELECT * FROM channel_moderators WHERE channel_id = $1 AND user_id = $2",
++ "SELECT * FROM channel_sounds WHERE channel_owner_id = $1 AND command = $2 AND is_approved = 1 ORDER BY RANDOM() LIMIT 1",
++ "SELECT * FROM channel_sounds WHERE channel_owner_id = $1 AND is_approved = 1 ORDER BY command",
++ "SELECT * FROM channel_sounds WHERE id = $1",
++ "SELECT * FROM chat_ai_summaries WHERE scope = $1 AND subject_id = $2 AND \"window\" = $3",
++ "SELECT * FROM chat_ai_summaries WHERE scope = $1 AND subject_id = $2 ORDER BY \"window\"",
++ "SELECT * FROM chat_messages WHERE id = $1",
++ "SELECT * FROM chat_timeline_events WHERE id = $1",
++ "SELECT * FROM ctx_channels WHERE id = $1",
++ "SELECT * FROM ctx_channels WHERE user_id = $1",
"SELECT * FROM ctx_users",
-- "SELECT * FROM ctx_users WHERE id = ?",
-- "SELECT * FROM ctx_users WHERE subject_id = ? LIMIT 2",
-- "SELECT * FROM dm_conversations WHERE id = ?",
-- "SELECT * FROM dm_messages WHERE id = ?",
-- "SELECT * FROM emotes WHERE code = ? AND ((channel_owner_id = ?) OR (channel_owner_id IS NULL AND user_id = ?)) LIMIT 1",
-- "SELECT * FROM emotes WHERE id = ?",
-- "SELECT * FROM emotes WHERE user_id = ? ORDER BY code",
-- "SELECT * FROM pending_ip_messages WHERE id = ?",
-- "SELECT * FROM room_attachments WHERE room_id = ? AND service = ? AND resource = ?",
-- "SELECT * FROM room_attachments WHERE room_id = ? ORDER BY created_at, service, resource",
-- "SELECT * FROM room_messages WHERE id = ? AND room_id = ?",
-- "SELECT * FROM rooms WHERE id = ?",
-- "SELECT * FROM rooms WHERE slug = ? AND archived_at IS NULL",
-- "SELECT * FROM rooms WHERE visibility = 'public' AND archived_at IS NULL ORDER BY COALESCE(last_message_at, created_at) DESC LIMIT ?",
-- "SELECT * FROM user_tags WHERE user_id = ? AND tag_id = ?",
-- "SELECT 1 FROM audio_requests WHERE room = ? AND dedupe_key = ?",
-- "SELECT 1 FROM channel_moderation_settings WHERE channel_id = ?",
-- "SELECT 1 FROM channel_moderators WHERE user_id = ? AND channel_id = ?",
-- "SELECT 1 FROM chat_messages cm JOIN ctx_streams s ON cm.stream_id = s.id WHERE s.channel_id = ? AND cm.is_deleted = 0 AND (cm.user_id = ? OR cm.anon_id = ?) LIMIT 1",
-- "SELECT 1 FROM ctx_streams WHERE id = ?",
-- "SELECT 1 FROM ctx_users WHERE id = ?",
-- "SELECT 1 FROM dm_blocks WHERE (blocker_id = ? AND blocked_id = ?) OR (blocker_id = ? AND blocked_id = ?)",
-- "SELECT 1 FROM dm_blocks WHERE blocker_id = ? AND blocked_id = ?",
-- "SELECT 1 FROM dm_messages WHERE conversation_id = ? AND sender_id = ? AND message = ? AND created_at > datetime('now', '-' || ? || ' seconds') LIMIT 1",
-- "SELECT 1 FROM dm_participants WHERE conversation_id = ? AND user_id = ?",
-- "SELECT 1 FROM hidden_relay_users WHERE platform = ? AND external_username = ? AND (channel_id = ? OR channel_id IS NULL)",
-- "SELECT 1 FROM network_blocks WHERE blocker_subject = ? AND blocked_subject = ? AND active = 1",
-- "SELECT 1 FROM room_members WHERE room_id = ? LIMIT 1",
-- "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
-- "SELECT 1 FROM stream_first_chats WHERE chatter_key = ? AND channel_user_id = ?",
++ "SELECT * FROM ctx_users WHERE id = $1",
++ "SELECT * FROM ctx_users WHERE subject_id = $1 LIMIT 2",
++ "SELECT * FROM dm_blocks WHERE id = $1",
++ "SELECT * FROM dm_conversations WHERE id = $1",
++ "SELECT * FROM dm_messages WHERE id = $1",
++ "SELECT * FROM dm_participants WHERE id = $1",
++ "SELECT * FROM emotes WHERE code = $1 AND ((channel_owner_id = $2) OR (channel_owner_id IS NULL AND user_id = $3)) LIMIT 1",
++ "SELECT * FROM emotes WHERE id = $1",
++ "SELECT * FROM emotes WHERE user_id = $1 ORDER BY code",
++ "SELECT * FROM hidden_relay_users WHERE id = $1",
++ "SELECT * FROM moderation_actions WHERE id = $1",
++ "SELECT * FROM pending_ip_messages WHERE channel_id = $1 AND status = 'pending' ORDER BY created_at ASC, id ASC LIMIT $2",
++ "SELECT * FROM pending_ip_messages WHERE id = $1",
++ "SELECT * FROM relay_users WHERE id = $1",
++ "SELECT * FROM relay_users WHERE platform = $1 AND username = $2",
++ "SELECT * FROM room_attachments WHERE room_id = $1 AND service = $2 AND resource = $3",
++ "SELECT * FROM room_attachments WHERE room_id = $1 ORDER BY created_at, service, resource",
++ "SELECT * FROM room_messages WHERE id = $1 AND room_id = $2",
++ "SELECT * FROM rooms WHERE id = $1",
++ "SELECT * FROM rooms WHERE slug = $1 AND archived_at IS NULL",
++ "SELECT * FROM rooms WHERE visibility = 'public' AND archived_at IS NULL ORDER BY COALESCE(last_message_at, created_at) DESC LIMIT $1",
++ "SELECT * FROM stream_first_chats WHERE chatter_key = $1 AND channel_user_id = $2",
++ "SELECT * FROM tts_voice_overrides WHERE identity_key = $1",
++ "SELECT * FROM user_tags WHERE user_id = $1 AND tag_id = $2",
++ "SELECT 1 AS one FROM audio_requests WHERE room = $1 AND state = 'playing'",
++ "SELECT 1 FROM audio_requests WHERE room = $1 AND dedupe_key = $2",
++ "SELECT 1 FROM channel_moderation_settings WHERE channel_id = $1",
++ "SELECT 1 FROM channel_moderators WHERE user_id = $1 AND channel_id = $2",
++ "SELECT 1 FROM chat_messages cm JOIN ctx_streams s ON cm.stream_id = s.id WHERE s.channel_id = $1 AND cm.is_deleted = 0 AND (cm.user_id = $2 OR cm.anon_id = $3) LIMIT 1",
++ "SELECT 1 FROM ctx_streams WHERE id = $1",
++ "SELECT 1 FROM dm_blocks WHERE (blocker_id = $1 AND blocked_id = $2) OR (blocker_id = $3 AND blocked_id = $4)",
++ "SELECT 1 FROM dm_blocks WHERE blocked_id = $1 AND blocker_id = $2",
++ "SELECT 1 FROM dm_blocks WHERE blocker_id = $1 AND blocked_id = $2",
++ "SELECT 1 FROM dm_messages WHERE conversation_id = $1 AND sender_id = $2 AND message = $3 AND created_at > datetime('now', '-' || $4 || ' seconds') LIMIT 1",
++ "SELECT 1 FROM dm_participants WHERE conversation_id = $1 AND user_id = $2",
++ "SELECT 1 FROM hidden_relay_users WHERE platform = $1 AND external_username = $2 AND (channel_id = $3 OR channel_id IS NULL)",
++ "SELECT 1 FROM network_blocks WHERE blocked_subject = $1 AND blocker_subject = $2",
++ "SELECT 1 FROM network_blocks WHERE blocker_subject = $1 AND blocked_subject = $2",
++ "SELECT 1 FROM network_blocks WHERE blocker_subject = $1 AND blocked_subject = $2 AND active = 1",
++ "SELECT 1 FROM room_members WHERE room_id = $1 AND user_id = $2",
++ "SELECT 1 FROM room_members WHERE room_id = $1 LIMIT 1",
++ "SELECT 1 FROM stream_first_chats WHERE chatter_key = $1 AND channel_user_id = $2",
++ "SELECT CAST(COALESCE(SUM(unread), 0) AS BIGINT) as total FROM ( SELECT COUNT(*) as unread FROM dm_messages m JOIN dm_participants p ON p.conversation_id = m.conversation_id AND p.user_id = $1 WHERE m.created_at > p.last_read_at AND m.sender_id != $2 ) AS per_conversation",
"SELECT COALESCE(MAX(id), 0) AS m FROM ctx_streams",
"SELECT COALESCE(MAX(id), 0) AS m FROM ctx_users",
-- "SELECT COALESCE(SUM(unread), 0) as total FROM ( SELECT COUNT(*) as unread FROM dm_messages m JOIN dm_participants p ON p.conversation_id = m.conversation_id AND p.user_id = ? WHERE m.created_at > p.last_read_at AND m.sender_id != ? )",
-- "SELECT COUNT(*) AS c FROM chat_messages WHERE anon_id = ? AND user_id IS NULL AND is_deleted = 0 AND (auto_delete_at IS NULL OR datetime(auto_delete_at) > CURRENT_TIMESTAMP)",
-- "SELECT COUNT(*) AS n FROM audio_requests WHERE room = ? AND identity_key = ? AND state IN ('queued', 'playing')",
-- "SELECT COUNT(*) AS n FROM audio_requests WHERE room = ? AND state IN ('queued', 'playing')",
++ "SELECT COUNT(*) AS c FROM chat_messages WHERE anon_id = $1 AND user_id IS NULL AND is_deleted = 0 AND (auto_delete_at IS NULL OR datetime(auto_delete_at) > ov_now())",
++ "SELECT COUNT(*) AS n FROM audio_requests WHERE room = $1 AND identity_key = $2 AND state IN ('queued', 'playing')",
++ "SELECT COUNT(*) AS n FROM audio_requests WHERE room = $1 AND state IN ('queued', 'playing')",
++ "SELECT COUNT(*) AS n FROM channel_sounds WHERE channel_owner_id = $1",
"SELECT COUNT(*) AS n FROM live_mirror_outbox",
-- "SELECT COUNT(*) AS n FROM room_members WHERE room_id = ? AND role != 'blocked'",
-- "SELECT COUNT(*) AS n FROM room_members m JOIN rooms r ON r.id = m.room_id AND r.archived_at IS NULL JOIN room_messages x ON x.room_id = m.room_id AND x.id > m.last_read_id AND x.is_deleted = 0 AND x.user_id != m.user_id WHERE m.user_id = ? AND m.role != 'blocked'",
-- "SELECT COUNT(*) AS n FROM rooms WHERE owner_id = ? AND archived_at IS NULL",
-- "SELECT COUNT(*) as c FROM chat_messages WHERE user_id = ? AND is_deleted = 0 AND (auto_delete_at IS NULL OR datetime(auto_delete_at) > CURRENT_TIMESTAMP)",
-- "SELECT COUNT(*) as c FROM dm_participants WHERE conversation_id = ?",
-- "SELECT COUNT(*) as cnt FROM chat_messages WHERE is_global = 1 AND timestamp >= datetime(?) AND timestamp <= datetime(?) AND is_deleted = 0",
-- "SELECT COUNT(*) as cnt FROM chat_messages WHERE stream_id = ? AND timestamp >= ? AND is_deleted = 0 AND is_global = 0 AND message_type = 'chat'",
-- "SELECT COUNT(*) as cnt FROM chat_messages WHERE stream_id = ? AND timestamp >= datetime(?) AND timestamp <= datetime(?) AND is_deleted = 0",
-- "SELECT COUNT(*) as count FROM channel_sounds WHERE channel_owner_id = ?",
-- "SELECT COUNT(*) as count FROM channel_sounds WHERE channel_owner_id = ? AND created_by = ?",
-- "SELECT COUNT(*) as count FROM emotes WHERE (channel_owner_id = ?) OR (channel_owner_id IS NULL AND user_id = ?)",
++ "SELECT COUNT(*) AS n FROM moderation_actions WHERE actor_subject_id = ANY($1) OR target_user_id = ANY($2) OR actor_user_id = ANY($2)",
++ "SELECT COUNT(*) AS n FROM room_members WHERE room_id = $1 AND role != 'blocked'",
++ "SELECT COUNT(*) AS n FROM room_members m JOIN rooms r ON r.id = m.room_id AND r.archived_at IS NULL JOIN room_messages x ON x.room_id = m.room_id AND x.id > m.last_read_id AND x.is_deleted = 0 AND x.user_id != m.user_id WHERE m.user_id = $1 AND m.role != 'blocked'",
++ "SELECT COUNT(*) AS n FROM rooms WHERE owner_id = $1 AND archived_at IS NULL",
++ "SELECT COUNT(*) as c FROM chat_messages WHERE user_id = $1 AND is_deleted = 0 AND (auto_delete_at IS NULL OR datetime(auto_delete_at) > ov_now())",
++ "SELECT COUNT(*) as c FROM dm_participants WHERE conversation_id = $1",
++ "SELECT COUNT(*) as cnt FROM chat_messages WHERE is_global = 1 AND timestamp >= datetime($1) AND timestamp <= datetime($2) AND is_deleted = 0",
++ "SELECT COUNT(*) as cnt FROM chat_messages WHERE stream_id = $1 AND timestamp >= $2 AND is_deleted = 0 AND is_global = 0 AND message_type = 'chat'",
++ "SELECT COUNT(*) as cnt FROM chat_messages WHERE stream_id = $1 AND timestamp >= datetime($2) AND timestamp <= datetime($3) AND is_deleted = 0",
++ "SELECT COUNT(*) as count FROM channel_sounds WHERE channel_owner_id = $1",
++ "SELECT COUNT(*) as count FROM channel_sounds WHERE channel_owner_id = $1 AND created_by = $2",
++ "SELECT COUNT(*) as count FROM emotes WHERE (channel_owner_id = $1) OR (channel_owner_id IS NULL AND user_id = $2)",
"SELECT DISTINCT room FROM audio_requests WHERE state = 'queued'",
"SELECT MAX(id) AS id FROM chat_messages",
-- "SELECT MAX(id) AS id FROM room_messages WHERE room_id = ?",
++ "SELECT MAX(id) AS id FROM room_messages WHERE room_id = $1",
"SELECT MAX(id) AS m FROM chat_messages",
-- "SELECT MIN(timestamp) AS t FROM chat_messages WHERE anon_id = ? AND user_id IS NULL AND is_deleted = 0",
-- "SELECT blocker_subject FROM network_blocks WHERE blocked_subject = ? AND active = 1",
-- "SELECT c.* FROM dm_conversations c JOIN dm_participants p1 ON p1.conversation_id = c.id AND p1.user_id = ? JOIN dm_participants p2 ON p2.conversation_id = c.id AND p2.user_id = ? WHERE c.is_group = 0",
-- "SELECT c.id, c.name, c.is_group, c.updated_at, (SELECT dm.message FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_message, (SELECT dm.sender_id FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_sender_id, (SELECT dm.created_at FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_message_at, (SELECT COUNT(*) FROM dm_messages dm WHERE dm.conversation_id = c.id AND dm.created_at > p.last_read_at AND dm.sender_id != ?) AS unread_count FROM dm_conversations c JOIN dm_participants p ON p.conversation_id = c.id AND p.user_id = ? ORDER BY COALESCE((SELECT dm.created_at FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1), c.created_at) DESC",
-- "SELECT cm.*, s.title as stream_title FROM chat_messages cm LEFT JOIN ctx_streams s ON cm.stream_id = s.id WHERE cm.user_id = ? AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) ORDER BY cm.timestamp DESC LIMIT ? OFFSET ?",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.stream_id = ? AND cm.is_deleted = 0 AND cm.message_type = 'chat' AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP)",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.stream_id = ? AND cm.is_deleted = 0 AND cm.message_type = 'chat' AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) ORDER BY cm.timestamp ASC",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, COALESCE(su.username, cu.username) AS stream_channel, s.is_live AS source_is_live, s.managed_stream_id AS source_managed_id, COALESCE(ms.title, s.title) AS source_stream_title, ms.slug AS source_slug FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_users su ON s.user_id = su.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE cm.is_deleted = 0 AND cm.message_type IN ('chat', 'system', 'channel-sound', 'soundboard', 'donation') AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) AND cm.id > ? ORDER BY cm.id ASC LIMIT ?",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, COALESCE(su.username, cu.username) AS stream_channel, s.is_live AS source_is_live, s.managed_stream_id AS source_managed_id, COALESCE(ms.title, s.title) AS source_stream_title, ms.slug AS source_slug FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_users su ON s.user_id = su.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE cm.is_deleted = 0 AND cm.message_type IN ('chat', 'system', 'channel-sound', 'soundboard', 'donation') AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) ORDER BY cm.timestamp DESC, cm.id DESC LIMIT ?",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = ? AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP)",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = ? AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) ORDER BY cm.timestamp DESC LIMIT ?",
-- "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = ? AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) ORDER BY cm.timestamp DESC, cm.id DESC LIMIT ?",
-- "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP)",
-- "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) AND cm.user_id = ?",
-- "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > CURRENT_TIMESTAMP) AND cm.user_id = ? ORDER BY cm.timestamp DESC LIMIT ? OFFSET ?",
-- "SELECT cm.channel_id, cm.channel_id AS id, c.title, c.user_id AS user_id, c.user_id AS owner_user_id, u.username AS owner_username FROM channel_moderators cm LEFT JOIN ctx_channels c ON cm.channel_id = c.id LEFT JOIN ctx_users u ON c.user_id = u.id WHERE cm.user_id = ? ORDER BY cm.created_at ASC, cm.id ASC",
-- "SELECT cm.id, cm.user_id, cm.added_by, cm.created_at, u.username, u.display_name, u.avatar_url, a.username as added_by_username FROM channel_moderators cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_users a ON cm.added_by = a.id WHERE cm.channel_id = ? ORDER BY cm.created_at ASC, cm.id ASC",
-- "SELECT created_at FROM ctx_users WHERE id = ?",
-- "SELECT created_at FROM room_messages WHERE room_id = ? AND user_id = ? ORDER BY id DESC LIMIT 1",
-- "SELECT e.*, u.username FROM emotes e LEFT JOIN ctx_users u ON e.user_id = u.id WHERE e.id = ?",
++ "SELECT MIN(timestamp) AS t FROM chat_messages WHERE anon_id = $1 AND user_id IS NULL AND is_deleted = 0",
++ "SELECT blocked_subject AS o FROM network_blocks WHERE blocker_subject = $1",
++ "SELECT blocker_subject AS o FROM network_blocks WHERE blocked_subject = $1",
++ "SELECT blocker_subject FROM network_blocks WHERE blocked_subject = $1 AND active = 1",
++ "SELECT body_hash, result FROM chat_ingress_applied WHERE principal = $1 AND family = $2 AND key = $3",
++ "SELECT c.* FROM dm_conversations c JOIN dm_participants p1 ON p1.conversation_id = c.id AND p1.user_id = $1 JOIN dm_participants p2 ON p2.conversation_id = c.id AND p2.user_id = $2 WHERE c.is_group = 0",
++ "SELECT c.id, c.name, c.is_group, c.updated_at, (SELECT dm.message FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_message, (SELECT dm.sender_id FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_sender_id, (SELECT dm.created_at FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1) AS last_message_at, (SELECT COUNT(*) FROM dm_messages dm WHERE dm.conversation_id = c.id AND dm.created_at > p.last_read_at AND dm.sender_id != $1) AS unread_count FROM dm_conversations c JOIN dm_participants p ON p.conversation_id = c.id AND p.user_id = $2 ORDER BY COALESCE((SELECT dm.created_at FROM dm_messages dm WHERE dm.conversation_id = c.id ORDER BY dm.created_at DESC LIMIT 1), c.created_at) DESC",
++ "SELECT cm.*, s.title as stream_title FROM chat_messages cm LEFT JOIN ctx_streams s ON cm.stream_id = s.id WHERE cm.user_id = $1 AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now()) ORDER BY cm.timestamp DESC LIMIT $2 OFFSET $3",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.stream_id = $1 AND cm.is_deleted = 0 AND cm.message_type = 'chat' AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now())",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.stream_id = $1 AND cm.is_deleted = 0 AND cm.message_type = 'chat' AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now()) ORDER BY cm.timestamp ASC",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, COALESCE(su.username, cu.username) AS stream_channel, s.is_live AS source_is_live, s.managed_stream_id AS source_managed_id, COALESCE(ms.title, s.title) AS source_stream_title, ms.slug AS source_slug FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_users su ON s.user_id = su.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE cm.is_deleted = 0 AND cm.message_type IN ('chat', 'system', 'channel-sound', 'soundboard', 'donation') AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > datetime('now')) AND cm.id > $1 ORDER BY cm.id ASC LIMIT $2",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, COALESCE(su.username, cu.username) AS stream_channel, s.is_live AS source_is_live, s.managed_stream_id AS source_managed_id, COALESCE(ms.title, s.title) AS source_stream_title, ms.slug AS source_slug FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_users su ON s.user_id = su.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE cm.is_deleted = 0 AND cm.message_type IN ('chat', 'system', 'channel-sound', 'soundboard', 'donation') AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > datetime('now')) ORDER BY cm.id DESC LIMIT $1",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = $1 AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > datetime('now'))",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = $1 AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > datetime('now')) ORDER BY cm.id DESC LIMIT $2",
++ "SELECT cm.*, u.avatar_url, u.profile_color, u.role, u.display_name, u.username AS core_username, s.title AS source_stream_title, s.managed_stream_id AS source_managed_id, s.is_live AS source_is_live, ms.slug AS source_slug, COALESCE(bu.username, cu.username) AS source_channel FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_streams s ON cm.stream_id = s.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id LEFT JOIN ctx_users bu ON s.user_id = bu.id LEFT JOIN ctx_users cu ON cm.channel_user_id = cu.id WHERE cm.channel_user_id = $1 AND cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now()) ORDER BY cm.timestamp DESC LIMIT $2",
++ "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now())",
++ "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now()) AND cm.user_id = $1",
++ "SELECT cm.*, u.display_name, u.username as u_username, u.role, u.avatar_url, u.profile_color FROM chat_messages cm LEFT JOIN ctx_users u ON cm.user_id = u.id WHERE cm.is_deleted = 0 AND (cm.auto_delete_at IS NULL OR datetime(cm.auto_delete_at) > ov_now()) AND cm.user_id = $1 ORDER BY cm.timestamp DESC LIMIT $2 OFFSET $3",
++ "SELECT cm.channel_id, cm.channel_id AS id, c.title, c.user_id AS user_id, c.user_id AS owner_user_id, u.username AS owner_username FROM channel_moderators cm LEFT JOIN ctx_channels c ON cm.channel_id = c.id LEFT JOIN ctx_users u ON c.user_id = u.id WHERE cm.user_id = $1 ORDER BY cm.created_at ASC, cm.id ASC",
++ "SELECT cm.id, cm.user_id, cm.added_by, cm.created_at, u.username, u.display_name, u.avatar_url, a.username as added_by_username FROM channel_moderators cm LEFT JOIN ctx_users u ON cm.user_id = u.id LEFT JOIN ctx_users a ON cm.added_by = a.id WHERE cm.channel_id = $1 ORDER BY cm.created_at ASC, cm.id ASC",
++ "SELECT conversation_id AS g FROM dm_participants WHERE user_id = $1",
++ "SELECT created_at FROM ctx_users WHERE id = $1",
++ "SELECT created_at FROM room_messages WHERE room_id = $1 AND user_id = $2 ORDER BY id DESC LIMIT 1",
++ "SELECT delivered FROM chat_ingress_applied WHERE principal = $1 AND family = $2 AND key = $3",
++ "SELECT e.*, u.username FROM emotes e LEFT JOIN ctx_users u ON e.user_id = u.id WHERE e.id = $1",
"SELECT e.*, u.username FROM emotes e LEFT JOIN ctx_users u ON e.user_id = u.id WHERE e.is_global = 1 AND e.is_approved = 1 ORDER BY code",
-- "SELECT e.*, u.username, up.username AS uploader_username, up.display_name AS uploader_display_name FROM emotes e LEFT JOIN ctx_users u ON e.user_id = u.id LEFT JOIN ctx_users up ON e.user_id = up.id WHERE ((e.channel_owner_id = ?) OR (e.channel_owner_id IS NULL AND e.user_id = ?)) AND e.is_approved = 1 ORDER BY code",
-- "SELECT id FROM bridge_refs WHERE boot = ? AND ref = ?",
-- "SELECT id FROM calls WHERE channel_id = ? AND state = 'active'",
-- "SELECT id FROM calls WHERE kind = 'direct' AND channel_id = ? AND state IN ('pending', 'ringing')",
-- "SELECT id FROM calls WHERE kind = 'direct' AND channel_id = ? AND target_user_id = ? AND state = 'ringing'",
-- "SELECT id FROM channel_moderators WHERE channel_id = ? AND user_id = ?",
-- "SELECT id FROM chat_messages WHERE anon_id = ? AND stream_id = ? AND is_deleted = 0",
-- "SELECT id FROM chat_messages WHERE user_id = ? AND stream_id = ? AND is_deleted = 0",
++ "SELECT e.*, u.username, up.username AS uploader_username, up.display_name AS uploader_display_name FROM emotes e LEFT JOIN ctx_users u ON e.user_id = u.id LEFT JOIN ctx_users up ON e.user_id = up.id WHERE ((e.channel_owner_id = $1) OR (e.channel_owner_id IS NULL AND e.user_id = $2)) AND e.is_approved = 1 ORDER BY code",
++ "SELECT h.*, u.username AS created_by_username FROM hidden_relay_users h LEFT JOIN ctx_users u ON u.id = h.created_by",
++ "SELECT id FROM calls WHERE channel_id = $1 AND state = 'active'",
++ "SELECT id FROM calls WHERE kind = 'direct' AND channel_id = $1 AND state IN ('pending', 'ringing')",
++ "SELECT id FROM calls WHERE kind = 'direct' AND channel_id = $1 AND target_user_id = $2 AND state = 'ringing'",
++ "SELECT id FROM channel_moderators WHERE channel_id = $1 AND user_id = $2",
++ "SELECT id FROM chat_messages WHERE anon_id = $1 AND stream_id = $2 AND is_deleted = 0 ORDER BY id",
++ "SELECT id FROM chat_messages WHERE user_id = $1 AND stream_id = $2 AND is_deleted = 0 ORDER BY id",
"SELECT id FROM ctx_streams WHERE is_live = 1",
-- "SELECT id FROM ctx_streams WHERE user_id = ? ORDER BY id DESC LIMIT 1",
-- "SELECT id FROM ctx_users WHERE subject_id = ?",
-- "SELECT id FROM ctx_users WHERE subject_id = ? ORDER BY id LIMIT 1",
-- "SELECT id FROM emotes WHERE id = ?",
-- "SELECT id FROM emotes WHERE user_id = ? AND code = ? AND channel_owner_id IS NULL",
-- "SELECT id FROM room_messages WHERE room_id = ? AND is_deleted = 0 ORDER BY id",
-- "SELECT id FROM user_tags WHERE user_id = ? AND tag_id = ?",
++ "SELECT id FROM ctx_streams WHERE user_id = $1 ORDER BY id DESC LIMIT 1",
++ "SELECT id FROM ctx_users WHERE id = ANY($1)",
++ "SELECT id FROM ctx_users WHERE subject_id = $1",
++ "SELECT id FROM ctx_users WHERE subject_id = $1 ORDER BY id LIMIT 1",
++ "SELECT id FROM ctx_users WHERE subject_id = ANY($1)",
++ "SELECT id FROM emotes WHERE id = $1",
++ "SELECT id FROM emotes WHERE user_id = $1 AND code = $2 AND channel_owner_id IS NULL",
++ "SELECT id FROM room_messages WHERE room_id = $1 AND is_deleted = 0 ORDER BY id",
++ "SELECT id FROM rooms WHERE owner_subject = ANY($1) OR owner_id = ANY($2)",
++ "SELECT id FROM user_tags WHERE user_id = $1 AND tag_id = $2",
++ "SELECT id, blocked_id AS o FROM dm_blocks WHERE blocker_id = $1",
++ "SELECT id, blocker_id AS o FROM dm_blocks WHERE blocked_id = $1",
"SELECT id, message_type, metadata FROM chat_messages WHERE is_deleted = 0 ORDER BY id DESC LIMIT 1",
"SELECT id, state FROM calls WHERE state IN ('pending', 'ringing', 'active')",
-- "SELECT id, stream_id FROM chat_messages WHERE is_deleted = 0 AND auto_delete_at IS NOT NULL AND auto_delete_at <= CURRENT_TIMESTAMP ORDER BY auto_delete_at ASC LIMIT ?",
-- "SELECT id, username, display_name, avatar_url, profile_color FROM ctx_users WHERE id != ? AND is_banned = 0 AND id NOT IN (SELECT blocked_id FROM dm_blocks WHERE blocker_id = ?) AND id NOT IN (SELECT blocker_id FROM dm_blocks WHERE blocked_id = ?) AND COALESCE(subject_id, '') NOT IN (SELECT blocked_subject FROM network_blocks WHERE blocker_subject = ? AND active = 1) AND COALESCE(subject_id, '') NOT IN (SELECT blocker_subject FROM network_blocks WHERE blocked_subject = ? AND active = 1) AND (username LIKE ? COLLATE NOCASE OR display_name LIKE ? COLLATE NOCASE) LIMIT ?",
-- "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = ? AND m.id < ? ORDER BY m.created_at DESC LIMIT ?",
-- "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = ? AND m.id > ? ORDER BY m.created_at ASC LIMIT ?",
-- "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = ? ORDER BY m.created_at DESC LIMIT ?",
++ "SELECT id, stream_id FROM chat_messages WHERE is_deleted = 0 AND auto_delete_at IS NOT NULL AND auto_delete_at <= ov_now() ORDER BY auto_delete_at ASC LIMIT $1",
++ "SELECT id, stream_id, channel_user_id FROM chat_messages WHERE id = $1 AND is_deleted = 0",
++ "SELECT id, username, display_name, avatar_url, profile_color FROM ctx_users WHERE id != $1 AND is_banned = 0 AND id NOT IN (SELECT blocked_id FROM dm_blocks WHERE blocker_id = $2) AND id NOT IN (SELECT blocker_id FROM dm_blocks WHERE blocked_id = $3) AND COALESCE(subject_id, '') NOT IN (SELECT blocked_subject FROM network_blocks WHERE blocker_subject = $4 AND active = 1) AND COALESCE(subject_id, '') NOT IN (SELECT blocker_subject FROM network_blocks WHERE blocked_subject = $5 AND active = 1) AND (username ILIKE $6 OR display_name ILIKE $7) LIMIT $8",
++ "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = $1 AND m.id < $2 ORDER BY m.created_at DESC LIMIT $3",
++ "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = $1 AND m.id > $2 ORDER BY m.created_at ASC LIMIT $3",
++ "SELECT m.*, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_messages m JOIN ctx_users u ON u.id = m.sender_id WHERE m.conversation_id = $1 ORDER BY m.created_at DESC LIMIT $2",
"SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id",
-- "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.id = ?",
-- "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.room_id = ? AND m.id > ? AND m.is_deleted = 0 ORDER BY m.id ASC LIMIT ?",
-- "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.room_id = ? AND m.is_deleted = 0 ORDER BY m.id DESC LIMIT ?",
-- "SELECT message_id FROM deploy_releases WHERE head = ?",
-- "SELECT metadata FROM chat_messages WHERE id = ?",
-- "SELECT ms.*, (SELECT s.is_live FROM ctx_streams s WHERE s.managed_stream_id = ms.id AND s.is_live = 1 LIMIT 1) AS is_currently_live, (SELECT s.id FROM ctx_streams s WHERE s.managed_stream_id = ms.id AND s.is_live = 1 LIMIT 1) AS live_session_id FROM ctx_managed_streams ms WHERE ms.user_id = ? ORDER BY ms.sort_order ASC, ms.created_at ASC",
-- "SELECT r.*, m.role, m.last_read_id, m.last_seen_at, (SELECT COUNT(*) FROM room_messages x WHERE x.room_id = r.id AND x.id > m.last_read_id AND x.is_deleted = 0 AND x.user_id != ?) AS unread FROM room_members m JOIN rooms r ON r.id = m.room_id WHERE m.user_id = ? AND m.role != 'blocked' AND r.archived_at IS NULL ORDER BY COALESCE(r.last_message_at, r.created_at) DESC",
-- "SELECT r.rowid AS id, r.platform, r.username, r.display_name, r.message_count, r.last_seen, cs.updated_at AS last_update FROM relay_users r LEFT JOIN chat_ai_summaries cs ON cs.scope = 'relay' AND cs.window = 'rolling' AND cs.subject_id = r.rowid WHERE r.last_seen >= ? AND r.message_count >= ? AND (cs.updated_at IS NULL OR cs.updated_at < r.last_seen) ORDER BY (cs.updated_at IS NULL) DESC, r.last_seen DESC LIMIT ?",
-- "SELECT result FROM bridge_applied WHERE key = ?",
-- "SELECT role FROM room_members WHERE room_id = ? AND user_id = ?",
-- "SELECT rowid AS id, * FROM relay_users WHERE platform = ? AND username = ?",
++ "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.id = $1",
++ "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.room_id = $1 AND m.id > $2 AND m.is_deleted = 0 ORDER BY m.id ASC LIMIT $3",
++ "SELECT m.id, m.room_id, m.user_id, m.message, m.created_at, m.is_deleted, u.username, u.display_name, u.profile_color, u.role AS user_role FROM room_messages m LEFT JOIN ctx_users u ON u.id = m.user_id WHERE m.room_id = $1 AND m.is_deleted = 0 ORDER BY m.id DESC LIMIT $2",
++ "SELECT message_id FROM deploy_releases WHERE head = $1",
++ "SELECT metadata FROM chat_messages WHERE id = $1",
++ "SELECT ms.*, (SELECT s.is_live FROM ctx_streams s WHERE s.managed_stream_id = ms.id AND s.is_live = 1 LIMIT 1) AS is_currently_live, (SELECT s.id FROM ctx_streams s WHERE s.managed_stream_id = ms.id AND s.is_live = 1 LIMIT 1) AS live_session_id FROM ctx_managed_streams ms WHERE ms.user_id = $1 ORDER BY ms.sort_order ASC, ms.created_at ASC",
++ "SELECT pg_advisory_xact_lock($1, hashtext($2)) AS locked",
++ "SELECT r.*, m.role, m.last_read_id, m.last_seen_at, (SELECT COUNT(*) FROM room_messages x WHERE x.room_id = r.id AND x.id > m.last_read_id AND x.is_deleted = 0 AND x.user_id != $1) AS unread FROM room_members m JOIN rooms r ON r.id = m.room_id WHERE m.user_id = $2 AND m.role != 'blocked' AND r.archived_at IS NULL ORDER BY COALESCE(r.last_message_at, r.created_at) DESC",
++ "SELECT r.id, r.platform, r.username, r.display_name, r.message_count, r.last_seen, cs.updated_at AS last_update FROM relay_users r LEFT JOIN chat_ai_summaries cs ON cs.scope = 'relay' AND cs.\"window\" = 'rolling' AND cs.subject_id = r.id WHERE r.last_seen >= $1 AND r.message_count >= $2 AND (cs.updated_at IS NULL OR cs.updated_at < r.last_seen) ORDER BY (cs.updated_at IS NULL) DESC, r.last_seen DESC LIMIT $3",
++ "SELECT role FROM room_members WHERE room_id = $1 AND user_id = $2",
++ "SELECT room FROM audio_requests WHERE id = $1 AND state = 'queued'",
++ "SELECT room_id AS g FROM room_members WHERE user_id = $1",
"SELECT s.*, u.username, u.display_name, u.avatar_url, u.profile_color, ms.slug AS managed_stream_slug, ms.title AS managed_stream_title FROM ctx_streams s LEFT JOIN ctx_users u ON s.user_id = u.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id",
-- "SELECT s.*, u.username, u.display_name, u.avatar_url, u.profile_color, ms.slug AS managed_stream_slug, ms.title AS managed_stream_title FROM ctx_streams s LEFT JOIN ctx_users u ON s.user_id = u.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE s.id = ?",
-- "SELECT s.*, u.username, u.display_name, u.avatar_url, u.profile_color, ms.slug AS managed_stream_slug, ms.title AS managed_stream_title FROM ctx_streams s LEFT JOIN ctx_users u ON s.user_id = u.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE s.user_id = ? ORDER BY s.created_at DESC LIMIT ?",
-- "SELECT sent_at FROM account_data_events WHERE id = ?",
++ "SELECT s.*, u.username, u.display_name, u.avatar_url, u.profile_color, ms.slug AS managed_stream_slug, ms.title AS managed_stream_title FROM ctx_streams s LEFT JOIN ctx_users u ON s.user_id = u.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE s.id = $1",
++ "SELECT s.*, u.username, u.display_name, u.avatar_url, u.profile_color, ms.slug AS managed_stream_slug, ms.title AS managed_stream_title FROM ctx_streams s LEFT JOIN ctx_users u ON s.user_id = u.id LEFT JOIN ctx_managed_streams ms ON s.managed_stream_id = ms.id WHERE s.user_id = $1 ORDER BY s.created_at DESC LIMIT $2",
++ "SELECT sent_at FROM account_data_events WHERE id = $1",
"SELECT seq, event_id, event FROM events_outbox WHERE sent_at IS NULL ORDER BY seq LIMIT 100",
-- "SELECT seq, tbl, op, pk FROM live_mirror_outbox ORDER BY seq LIMIT ?",
-- "SELECT subject_id FROM ctx_users WHERE id = ?",
-- "SELECT u.id FROM network_blocks b JOIN ctx_users u ON u.subject_id = b.blocked_subject WHERE b.blocker_subject = ? AND b.active = 1",
-- "SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_participants p JOIN ctx_users u ON u.id = p.user_id WHERE p.conversation_id = ?",
-- "SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color, b.created_at as blocked_at FROM dm_blocks b JOIN ctx_users u ON u.id = b.blocked_id WHERE b.blocker_id = ? ORDER BY b.created_at DESC",
-- "SELECT user_id FROM channel_moderators WHERE channel_id = ? ORDER BY created_at ASC, id ASC",
-- "SELECT user_id, subject_id FROM dm_participants WHERE conversation_id = ?",
-- "SELECT valid_after_ms FROM token_revocations WHERE subject_id = ?",
-- "SELECT value FROM chat_meta WHERE key = ?",
-- "SELECT voice, pitch, speed, gap FROM tts_voice_overrides WHERE identity_key = ?",
-- "UPDATE account_data_events SET sent_at = ? WHERE id = ?",
-- "UPDATE audio_requests SET attempts = attempts + 1 WHERE id = ?",
-- "UPDATE audio_requests SET state = 'failed', finished_at = ?, error = 'expired', actor = 'system' WHERE state = 'queued' AND created_at < ?",
-- "UPDATE audio_requests SET state = 'played', finished_at = ?, error = 'delivered before a restart' WHERE state = 'playing'",
-- "UPDATE audio_requests SET state = ?, error = COALESCE(?, error), actor = COALESCE(?, actor), duration_ms = COALESCE(?, duration_ms), started_at = CASE WHEN ? = 'playing' THEN ? ELSE started_at END, finished_at = CASE WHEN ? THEN ? ELSE finished_at END WHERE id = ? AND state IN (?,?)",
-- "UPDATE channel_moderation_settings SET sub_only = ? WHERE channel_id = ?",
-- "UPDATE channel_moderation_settings SET tts_max_length = ? WHERE channel_id = ?",
-- "UPDATE channel_sounds SET command = ? WHERE channel_owner_id = ? AND command = ?",
-- "UPDATE channel_sounds SET emote_code = ? WHERE channel_owner_id = ? AND command = ?",
-- "UPDATE channel_sounds SET emote_code = ? WHERE channel_owner_id = ? AND emote_code = ?",
-- "UPDATE chat_messages SET is_deleted = 1, deleted_by = ?, deleted_at = CURRENT_TIMESTAMP WHERE id = ?",
-- "UPDATE chat_messages SET is_deleted = 1, deleted_by = ?, deleted_at = CURRENT_TIMESTAMP WHERE id IN (?)",
-- "UPDATE chat_messages SET message = ?, metadata = ? WHERE id = ?",
-- "UPDATE chat_messages SET metadata = ? WHERE id = ?",
-- "UPDATE chat_messages SET username = ? WHERE user_id = ?",
-- "UPDATE ctx_channels SET emote_sources = ? WHERE user_id = ?",
-- "UPDATE ctx_streams SET is_live = 0 WHERE id = ?",
-- "UPDATE ctx_users SET profile_color = ? WHERE id = ?",
-- "UPDATE dm_conversations SET is_group = 1 WHERE id = ?",
-- "UPDATE dm_conversations SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
-- "UPDATE dm_conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?",
-- "UPDATE dm_participants SET last_read_at = CURRENT_TIMESTAMP WHERE conversation_id = ? AND user_id = ?",
-- "UPDATE emotes SET media_url = ?, media_asset_id = ? WHERE id = ?",
-- "UPDATE events_outbox SET attempts = attempts + 1, last_error = ? WHERE seq = ?",
-- "UPDATE events_outbox SET sent_at = ?, attempts = attempts + 1, last_error = NULL WHERE seq = ?",
-- "UPDATE pending_ip_messages SET status = 'approved', reviewed_by = ? WHERE channel_id = ? AND ip_address = ? AND status = 'pending'",
-- "UPDATE pending_ip_messages SET status = 'denied', reviewed_by = ? WHERE channel_id = ? AND ip_address = ? AND status = 'pending'",
-- "UPDATE pending_ip_messages SET status = ?, reviewed_by = ? WHERE id = ?",
-- "UPDATE pending_ip_messages SET status = ?, reviewed_by = ? WHERE id = ? AND channel_id = ?",
-- "UPDATE room_attachments SET title = ? WHERE room_id = ? AND service = ? AND resource = ?",
-- "UPDATE room_members SET last_read_id = ?, last_seen_at = CURRENT_TIMESTAMP WHERE room_id = ? AND user_id = ?",
-- "UPDATE room_members SET last_read_id = MAX(last_read_id, ?), last_seen_at = CURRENT_TIMESTAMP WHERE room_id = ? AND user_id = ?",
-- "UPDATE room_members SET role = ? WHERE room_id = ? AND user_id = ?",
-- "UPDATE room_messages SET is_deleted = 1, deleted_by = ? WHERE id = ?",
-- "UPDATE rooms SET message_count = message_count + 1, last_message_at = CURRENT_TIMESTAMP WHERE id = ?",
-- "UPDATE rooms SET name = ?, topic = ?, visibility = ?, slow_seconds = ?, join_role = ? WHERE id = ?",
-- "UPDATE rooms SET owner_subject = NULL WHERE id = ?"
++ "SELECT seq, tbl, op, pk FROM live_mirror_outbox ORDER BY seq LIMIT $1",
++ "SELECT subject_id FROM ctx_users WHERE id = $1",
++ "SELECT table_name FROM information_schema.columns WHERE table_schema = current_schema() AND column_name = 'id'",
++ "SELECT u.id FROM network_blocks b JOIN ctx_users u ON u.subject_id = b.blocked_subject WHERE b.blocker_subject = $1 AND b.active = 1",
++ "SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color FROM dm_participants p JOIN ctx_users u ON u.id = p.user_id WHERE p.conversation_id = $1",
++ "SELECT u.id, u.username, u.display_name, u.avatar_url, u.profile_color, b.created_at as blocked_at FROM dm_blocks b JOIN ctx_users u ON u.id = b.blocked_id WHERE b.blocker_id = $1 ORDER BY b.created_at DESC",
++ "SELECT user_id FROM channel_moderators WHERE channel_id = $1 ORDER BY created_at ASC, id ASC",
++ "SELECT user_id, subject_id FROM dm_participants WHERE conversation_id = $1",
++ "SELECT valid_after_ms FROM token_revocations WHERE subject_id = $1",
++ "SELECT value FROM chat_meta WHERE key = $1",
++ "SELECT voice, pitch, speed, gap FROM tts_voice_overrides WHERE identity_key = $1",
++ "UPDATE account_data_events SET sent_at = $1 WHERE id = $2",
++ "UPDATE audio_requests SET lease_until = $1 WHERE state = 'playing' AND claimed_by = $2",
++ "UPDATE audio_requests SET started_at = $1, duration_ms = $2 WHERE id = $3 AND state = 'playing'",
++ "UPDATE audio_requests SET state = $1, error = COALESCE($2, error), actor = COALESCE($3, actor), duration_ms = COALESCE($4, duration_ms), started_at = CASE WHEN $5 = 'playing' THEN $6 ELSE started_at END, finished_at = CASE WHEN $7 THEN $8 ELSE finished_at END WHERE id = $9 AND state IN ($10,$11)",
++ "UPDATE audio_requests SET state = 'failed', finished_at = $1, error = 'expired', actor = 'system' WHERE state = 'queued' AND created_at < $2",
++ "UPDATE audio_requests SET state = 'failed', finished_at = $1, error = 'not delivered: its owner stopped twice', actor = 'system' WHERE state = 'playing' AND (lease_until IS NULL OR lease_until < $2 OR claimed_by = $3) AND started_at IS NULL AND attempts >= $4 RETURNING room",
++ "UPDATE audio_requests SET state = 'played', finished_at = $1, error = 'delivered before a restart' WHERE state = 'playing' AND (lease_until IS NULL OR lease_until < $2 OR claimed_by = $3) AND started_at IS NOT NULL RETURNING room",
++ "UPDATE audio_requests SET state = 'playing', attempts = attempts + 1, claimed_by = $1, lease_until = $2 WHERE id = $3 AND state = 'queued' RETURNING *",
++ "UPDATE audio_requests SET state = 'queued', claimed_by = NULL, lease_until = NULL, duration_ms = NULL WHERE state = 'playing' AND (lease_until IS NULL OR lease_until < $1 OR claimed_by = $2) AND started_at IS NULL AND attempts < $3 RETURNING room",
++ "UPDATE calls SET created_by_subject = $1 WHERE created_by_subject = $2",
++ "UPDATE calls SET target_subject = $1 WHERE target_subject = $2",
++ "UPDATE channel_moderation_settings SET sub_only = $1 WHERE channel_id = $2",
++ "UPDATE channel_moderation_settings SET tts_max_length = $1 WHERE channel_id = $2",
++ "UPDATE channel_sounds SET command = $1 WHERE channel_owner_id = $2 AND command = $3",
++ "UPDATE channel_sounds SET emote_code = $1 WHERE channel_owner_id = $2 AND command = $3",
++ "UPDATE channel_sounds SET emote_code = $1 WHERE channel_owner_id = $2 AND emote_code = $3",
++ "UPDATE channel_sounds SET media_url = $1, media_asset_id = $2 WHERE id = $3",
++ "UPDATE chat_ingress_applied SET delivered = 0 WHERE principal = $1 AND family = $2 AND key = $3 AND delivered = 2",
++ "UPDATE chat_ingress_applied SET delivered = 1 WHERE principal = $1 AND family = $2 AND key = $3",
++ "UPDATE chat_ingress_applied SET delivered = 2, delivery_claimed_at = $1 WHERE principal = $2 AND family = $3 AND key = $4 AND (delivered = 0 OR (delivered = 2 AND delivery_claimed_at < $5))",
++ "UPDATE chat_ingress_applied SET result = $1 WHERE principal = $2 AND family = $3 AND key = $4",
++ "UPDATE chat_messages SET channel_user_id = $1 WHERE channel_user_id = $2",
++ "UPDATE chat_messages SET is_deleted = 1, deleted_by = $1, deleted_at = ov_now() WHERE id = $2",
++ "UPDATE chat_messages SET is_deleted = 1, deleted_by = $1, deleted_at = ov_now() WHERE id IN ($2)",
++ "UPDATE chat_messages SET message = $1, metadata = $2 WHERE id = $3",
++ "UPDATE chat_messages SET metadata = $1 WHERE id = $2",
++ "UPDATE chat_messages SET subject_id = $1 WHERE subject_id = $2",
++ "UPDATE chat_messages SET user_id = $1 WHERE user_id = $2",
++ "UPDATE chat_messages SET username = $1 WHERE user_id = $2",
++ "UPDATE ctx_channels SET emote_sources = $1 WHERE user_id = $2",
++ "UPDATE ctx_streams SET is_live = 0 WHERE id = $1",
++ "UPDATE ctx_users SET profile_color = $1 WHERE id = $2",
++ "UPDATE ctx_users SET username = 'deleted-' || id, display_name = NULL, avatar_url = NULL, profile_color = NULL, ban_reason = NULL WHERE id = ANY($1)",
++ "UPDATE dm_blocks SET blocked_id = $1 WHERE id = $2",
++ "UPDATE dm_blocks SET blocker_id = $1 WHERE id = $2",
++ "UPDATE dm_blocks SET blocker_subject_id = $1 WHERE blocker_subject_id = $2",
++ "UPDATE dm_conversations SET created_by = $1 WHERE created_by = $2",
++ "UPDATE dm_conversations SET is_group = 1 WHERE id = $1",
++ "UPDATE dm_conversations SET name = $1, updated_at = ov_now() WHERE id = $2",
++ "UPDATE dm_conversations SET updated_at = ov_now() WHERE id = $1",
++ "UPDATE dm_messages SET sender_id = $1 WHERE sender_id = $2",
++ "UPDATE dm_messages SET sender_subject_id = $1 WHERE sender_subject_id = $2",
++ "UPDATE dm_participants SET last_read_at = ov_now() WHERE conversation_id = $1 AND user_id = $2",
++ "UPDATE dm_participants SET subject_id = $1 WHERE subject_id = $2",
++ "UPDATE dm_participants SET user_id = $1 WHERE conversation_id = $2 AND user_id = $3",
++ "UPDATE emotes SET media_url = $1, media_asset_id = $2 WHERE id = $3",
++ "UPDATE events_outbox SET attempts = attempts + 1, last_error = $1 WHERE seq = $2",
++ "UPDATE events_outbox SET sent_at = $1, attempts = attempts + 1, last_error = NULL WHERE seq = $2",
++ "UPDATE network_blocks SET blocked_subject = $1 WHERE blocked_subject = $2 AND blocker_subject = $3",
++ "UPDATE network_blocks SET blocker_subject = $1 WHERE blocker_subject = $2 AND blocked_subject = $3",
++ "UPDATE pending_ip_messages SET status = $1, reviewed_by = $2 WHERE id = $3",
++ "UPDATE pending_ip_messages SET status = $1, reviewed_by = $2 WHERE id = $3 AND channel_id = $4",
++ "UPDATE pending_ip_messages SET status = 'approved', reviewed_by = $1 WHERE channel_id = $2 AND ip_address = $3 AND status = 'pending'",
++ "UPDATE pending_ip_messages SET status = 'denied', reviewed_by = $1 WHERE channel_id = $2 AND ip_address = $3 AND status = 'pending'",
++ "UPDATE room_attachments SET attached_by_subject = $1 WHERE attached_by_subject = $2",
++ "UPDATE room_attachments SET title = $1 WHERE room_id = $2 AND service = $3 AND resource = $4",
++ "UPDATE room_members SET last_read_id = $1, last_seen_at = ov_now() WHERE room_id = $2 AND user_id = $3",
++ "UPDATE room_members SET last_read_id = GREATEST(last_read_id, $1), last_seen_at = ov_now() WHERE room_id = $2 AND user_id = $3",
++ "UPDATE room_members SET role = $1 WHERE room_id = $2 AND user_id = $3",
++ "UPDATE room_members SET user_id = $1 WHERE room_id = $2 AND user_id = $3",
++ "UPDATE room_messages SET is_deleted = 1, deleted_by = $1 WHERE id = $2",
++ "UPDATE room_messages SET subject_id = $1 WHERE subject_id = $2",
++ "UPDATE room_messages SET user_id = $1 WHERE user_id = $2",
++ "UPDATE rooms SET message_count = message_count + 1, last_message_at = ov_now() WHERE id = $1",
++ "UPDATE rooms SET name = $1, topic = $2, visibility = $3, slow_seconds = $4, join_role = $5 WHERE id = $6",
++ "UPDATE rooms SET owner_subject = $1 WHERE owner_subject = $2",
++ "UPDATE rooms SET owner_subject = NULL WHERE id = $1"
]
}
ddiff --git a/test/pg-schema.test.js b/test/pg-schema.test.js
iindex 0acc648..bbb607d 100644
---- a/test/pg-schema.test.js
++++ b/test/pg-schema.test.js
@@@ -9,10 +9,12 @@
const assert = require('assert');
const path = require('path');
const { createTestDb } = require('openvibe-sdk/testing');
++const { createDb } = require('openvibe-sdk/db');
const { suite } = require('./helpers');
const t = suite('pg-schema');
const MIGRATIONS = path.join(__dirname, '..', 'migrations');
++const quiet = { log() {}, info() {}, warn() {}, error() {} };
let db, close;
async function all(sql, params) { return await db.prepare(sql).all(...(params || [])); }
@@@ -24,7 +26,7 @@ const TABLES = [
'channel_sounds', 'relay_users', 'hidden_relay_users', 'pending_ip_messages', 'stream_first_chats', 'moderation_actions',
'channel_moderators', 'channel_moderation_settings', 'emotes', 'user_tags', 'chat_ai_summaries', 'chat_timeline_events',
'ctx_users', 'ctx_streams', 'ctx_managed_streams', 'ctx_channels', 'ctx_sync', 'events_outbox', 'live_mirror_outbox',
-- 'bridge_applied', 'chat_ingress_applied', 'audio_requests', 'bridge_refs', 'import_hold', 'import_runs', 'chat_meta', 'deploy_releases',
++ 'chat_ingress_applied', 'audio_requests', 'import_hold', 'import_runs', 'chat_meta', 'deploy_releases',
'chat_event_inbox', 'calls', 'rooms', 'room_members', 'room_messages', 'room_attachments', 'token_revocations',
'network_blocks', 'account_data_events',
];
@@@ -39,6 +41,12 @@ const MIRRORED = ['chat_messages', 'dm_conversations', 'dm_participants', 'dm_me
t('migrate', async () => {
const tst = await createTestDb({ migrations: MIGRATIONS, service: 'chat' });
db = tst.db; close = tst.close;
++ // createTestDb migrates the production way, so a contract migration (ADR-028) stays held for its
++ // N-1 window. This test wants the final schema: apply it now, windowDays 0, on the owner under
++ // store 'pg' (the handle createTestDb returns is DML only) or on the handle itself on PGlite.
++ const owner = tst.directUrl ? createDb({ url: tst.directUrl, service: 'chat-test-migrate', max: 1, log: quiet }) : db;
++ try { await owner.migrate({ dir: MIGRATIONS, windowDays: 0, log: quiet }); }
++ finally { if (owner !== db) await owner.close(); }
assert.ok(db, 'a database handle');
});
@@@ -49,6 +57,14 @@ t('every table exists', async () => {
assert.strictEqual(have.size, TABLES.length + 1, 'exactly the Chat tables plus ov_migrations');
});
++// 0005 (contract) drops the retired Live chat bridge: the tables exist on an N-1 database, but
++// this release migrates them away (ADR-028).
++t('the retired bridge tables are gone', async () => {
++ for (const name of ['bridge_applied', 'bridge_refs']) {
++ assert.strictEqual((await get(`SELECT to_regclass('${name}') AS t`)).t, null, `${name} is dropped`);
++ }
++});
++
t('every index exists', async () => {
const rows = await all(`SELECT indexname FROM pg_indexes WHERE schemaname = current_schema()`);
const have = new Set(rows.map((r) => r.indexname));
ddiff --git a/test/rehearsal.test.js b/test/rehearsal.test.js
iindex 9634e5d..ffa4307 100644
---- a/test/rehearsal.test.js
++++ b/test/rehearsal.test.js
@@@ -41,7 +41,8 @@ t('the seed applies after 0001–0002 and before 0003', async () => {
assert.strictEqual(row.n, n, `${table} has ${n} seeded row(s)`);
}
assert.strictEqual((await db.prepare(`SELECT to_regclass('chat_ingress_applied') AS t`).get()).t, null, '0003 has not run yet');
-- const second = await db.migrate({ dir: MIGRATIONS, log: { log() {} } });
++ // windowDays 0: a test wants the final schema, not a contract held for its N-1 window (ADR-028).
++ const second = await db.migrate({ dir: MIGRATIONS, log: { log() {} }, windowDays: 0 });
const later = fs.readdirSync(MIGRATIONS).filter((f) => /^\d{4}_.*\.sql$/.test(f)).map((f) => f.slice(0, 4))
.filter((id) => id > '0002').sort();
assert.ok(later.includes('0003'), 'migrations/0003 exists');