# AGENTS.md — OpenVibe.Live ## Project Overview Self-hosted live streaming platform — Node.js/Express monolith, vanilla JS SPA frontend, PostgreSQL (`openvibe-sdk/db`; embedded PGlite in development and tests; on PostgreSQL since 2026-10-08). Part of the **OpenVibe network** with SSO via openvibe.network. See [README.md](README.md) for features and [docs/architecture.md](docs/architecture.md) for system design. **Network context:** SSO/OAuth2 + the OpenCoins wallet come from **OpenVibe.Network** (`server/monetization/wallet-client.js`). The media subsystem (VODs/clips/pastes/thumbnails/files) lives in **OpenVibe.Media** — Live talks to it via [server/media-client.js](server/media-client.js), the thin proxy routers in [server/media-proxy/](server/media-proxy/), and the Media-backed recorder [server/streaming/recorder.js](server/streaming/recorder.js). Media completion events arrive over OpenVibe.Events at `POST /internal/media-events` (the only path; the direct webhook was retired 2026-10-10). The legacy local `vods`/`clips`/`pastes`/`paste_likes`/`paste_comments` tables are FROZEN: never read or write them (`test/frozen-tables.test.js` enforces it; ask Media or Community through `server/media-proxy/lookups.js`, `media-client.js`, `pastes-client.js`); VOD/clip comments are OpenVibe.Community threads ([server/comments-client.js](server/comments-client.js)) and the old local `comments` table is not in the PostgreSQL database either; Chat owns every chat table — the staged set (`channel_moderators`, `channel_moderation_settings`, `emotes`, `user_tags`, `chat_ai_summaries`, `chat_timeline_events`; Live reads those only through `server/chat/moderation-client.js`, capability `chat.moderation.read`, cached 30 s, and the summaries through `server/chat/insight-client.js`, cached 60 s) and the twelve the chat cutover imports covered (`chat_messages`, the DM tables, `tts_voice_overrides`, `channel_sounds`, `relay_users`, `hidden_relay_users`, `pending_ip_messages`, `stream_first_chats`, `moderation_actions`); Live keeps no copy of any of them since 2026-10-05 — chat stats, queues, history, first-chat and DM-block state all come through `server/chat/chat-reads.js`, and the twelve tables were never created on PostgreSQL (`migrations/0002_live.sql` leaves them out; the SQLite-era boot migration 007 dropped them before the move) — `test/chat-tables-dropped.test.js` guards that nothing under `server/` names them and no migration creates one; Live-owned AI/transcript state for Media-hosted content lives in `vod_ai_state`/`clip_ai_state`. See ../CONTRACTS.md for the binding inter-service contracts. **Money:** OpenVibe.Billing is the only money ledger since the cutover of 2026-10-17 ([billing-client.js](server/monetization/billing-client.js), [billing-actions.js](server/monetization/billing-actions.js)): every checkout, donation, cashout, recycle, subscription, balance and entitlement is a Billing call, provider and PowerChat webhooks go to Billing (Live's answer 410), and Live's old `openvibe_bucks_*`/`openvibe_coins_balance` columns and `transactions`/`payment_orders`/`subscriptions` tables are gone (contract migration `0016_drop_old_money_ledger`; `test/money-tables-gone.test.js`). The owner-only `money_writes_frozen` flag ([server/monetization/money-authority.js](server/monetization/money-authority.js), `/api/admin/money`) refuses money actions. **Cosmetics:** OpenVibe.Inventory owns them since 2026-10-09 (ADR-054: `live.*` items issued by `service:live`, Live's item ids as aliases); [server/monetization/cosmetics.js](server/monetization/cosmetics.js) reads and writes through [inventory-client.js](server/monetization/inventory-client.js) (Chat's decor lookups batch 100 people per read) and Live keeps only the catalog's renderers. Live's `user_cosmetics`/`user_equipped`/`user_equipped_tag` are read by nothing (`test/cosmetics-tables-gone.test.js`) and are dropped by a contract migration. ## Commands ```bash npm run dev # Start dev server (NODE_ENV=development) npm start # Start production server (node server/index.js) # The schema is migrations/NNNN_*.sql, applied at boot (DATABASE_DIRECT_URL, owner); without DATABASE_URL, development uses PGlite under DATA_DIR node --check # Syntax check (no linter configured) npm test # All unit/security/migration/deploy tests + size budgets (test/run.js), on PGlite npm run test:pg # The same on the PostgreSQL + PgBouncer containers (openvibe-sdk scripts/test-services.sh up) node test/.test.js # One test BASE=http://127.0.0.1:3000 npm run test:browser # Browser smoke (running server + Chrome); add -- --a11y for axe (WCAG 2.1 AA) ``` **No build step.** Frontend is plain JS served directly — no bundler, no transpiler. Asset URLs are content-hashed at serve time (`server/web/assets.js`) — **never add `?v=` by hand**. **Deploy:** production runs the release layout (`/opt/openvibe.live/current` → `releases/