openvibers/openvibe.live AGENTS.md

AGENTS.md

main 14.7 KB · 97 lines · markdown

1# AGENTS.md — OpenVibe.Live
2
3## Project Overview
4
5Self-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.
6
7**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.
8
9## Commands
10
11```bash
12npm run dev # Start dev server (NODE_ENV=development)
13npm start # Start production server (node server/index.js)
14# The schema is migrations/NNNN_*.sql, applied at boot (DATABASE_DIRECT_URL, owner); without DATABASE_URL, development uses PGlite under DATA_DIR
15node --check <file.js> # Syntax check (no linter configured)
16npm test # All unit/security/migration/deploy tests + size budgets (test/run.js), on PGlite
17npm run test:pg # The same on the PostgreSQL + PgBouncer containers (openvibe-sdk scripts/test-services.sh up)
18node test/<file>.test.js # One test
19BASE=http://127.0.0.1:3000 npm run test:browser # Browser smoke (running server + Chrome); add -- --a11y for axe (WCAG 2.1 AA)
20```
21
22**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**.
23
24**Deploy:** production runs the release layout (`/opt/openvibe.live/current` → `releases/<time>-<sha>`; deploy with `cd /opt/openvibe.live/current && sudo deploy/scripts/deploy.sh`), env `/etc/openvibe/live.env`, unit `openvibe-live.service`. Static-only changes deploy without a restart. See [docs/deploy.md](docs/deploy.md).
25
26## Architecture at a Glance
27
28- **Entry:** [server/index.js](server/index.js) — Express app, middleware, route mounting, WS upgrade handler, sub-service init
29- **Config:** [server/config.js](server/config.js) reads `.env` ([.env.example](.env.example))
30- **Database:** [server/db/database.js](server/db/database.js) — all queries (async, `openvibe-sdk/db`), schema in [migrations/](migrations/) (`0001_analytics.sql`, `0002_live.sql`)
31- **Auth:** [server/auth/auth.js](server/auth/auth.js) — RS256 JWT from openvibe.tools SSO + `hbt_` API tokens
32- **Permissions:** [server/auth/permissions.js](server/auth/permissions.js) — role hierarchy: `user < streamer < global_mod < admin`
33- **Frontend shell:** [public/index.html](public/index.html) — navbar and `<section id="page-*">` shells (page markup, the home page's included, is in `public/fragments/`, inlined on its own route); routing via `history.pushState` in [public/js/app.js](public/js/app.js)
34- **Route loading:** [public/features.json](public/features.json) maps routes → features (fragment in `public/fragments/`, CSS in `public/css/features/`, scripts, deps, stubs); [public/js/ov-loader.js](public/js/ov-loader.js) loads them. New page code goes in a feature, not in `index.html`/core `app.js`. See [docs/architecture.md](docs/architecture.md#frontend-loading).
35
36Each feature lives in its own `server/<feature>/` directory with `routes.js` + service files. Frontend: one JS file per feature in `public/js/`, registered in `public/features.json`.
37
38## Conventions
39
40- **CommonJS** (`require`/`module.exports`) everywhere. No ES modules except dynamic `import()` for mediasoup-client.
41- **Style:** 4-space indent, single quotes, semicolons. No linter/formatter configured.
42- **Naming:** `camelCase` for JS, `snake_case` for PostgreSQL columns/tables.
43- **DB access:** `database.js` helpers, every one async (`await db.getUserById()`, `await db.run()`, `await db.get()`, `await db.all()`); an INSERT that needs its id says `RETURNING id`. Never leave a write un-awaited when anything after it depends on it: the pool runs un-awaited work out of order (PGlite does not, so only `npm run test:pg` shows it).
44- **Auth middleware:** `requireAuth` from `auth.js`. Permission checks via `permissions.js`.
45- **DB migrations:** a new `migrations/NNNN_name.sql` with its `-- phase: expand|migrate|contract` header (a contract names its expand with `-- after: NNNN` and waits out the N-1 window); never edit one that has run. No table is created at runtime.
46- **Public responses:** serialize through [server/web/serializers.js](server/web/serializers.js) — never return raw `managed_streams`/`users` rows.
47- **TURN:** ICE lists come from [server/net/turn.js](server/net/turn.js); set `TURN_AUTH_SECRET` (coturn `use-auth-secret`) for short-lived credentials.
48- **Outbound fetches of user-chosen URLs:** [server/net/egress.js](server/net/egress.js) only. Background loops: `server/utils/jobs.js`.
49- **Files and restore drills:** every file location comes from [server/paths.js](server/paths.js) (`DATA_DIR`), never a literal `./data`; the database is `DATABASE_URL` (drill mode refuses production's `ov_live`). Anything started at boot or at module load (timers, listeners, sockets, jobs, outbound calls) must stay off under `LIVE_DRILL` ([server/drill.js](server/drill.js)); `test/drill-mode.test.js` fails on any new one.
50- **WebSocket servers:** Each has `init(server)` and `handleUpgrade(req, socket, head)` methods.
51- **Frontend globals:** `currentUser`, `api()`, `navigate()`, `handleLinkClick()`. Cross-component sync via `CustomEvent` (e.g., `openvibe-auth-changed`).
52
53## Key Pitfalls
54
55- **No build step:** Changes to `public/` take effect on deploy without a restart; caching follows content hashes. `npm test` fails if the home page's size budgets grow (scripts/perf/check-budgets.js).
56- **First-paint CSS:** public routes block only on `/css/style.core.css`, a subset of `style.css` generated from `public/css/critical-selectors.json` (`server/web/critical-css.js`); the full `style.css` follows without blocking. After changing how a public page looks, re-run `node scripts/perf/critical-css.js` (docs/performance-audit.md).
57- **Inline handlers on lazy features:** a function called from `onclick=` in markup that exists before its feature loads must be listed in that feature's `stubs`.
58- **New page routes:** the SPA fallback answers 404 for any path [server/web/page-status.js](server/web/page-status.js) does not know. A new top-level route goes in both `routeFromURL()` and that file's page list.
59- **innerHTML usage:** Frontend has heavy `innerHTML` — prefer DOM node creation for new code to avoid XSS.
60- **Public AI text may describe people, and nudity is fine** (owner, 2026-10-31: the old no-looks rule is gone, `openvibe-sdk/no-looks` is deprecated; "dick is art… titties is art… stop being against art on my platform"). What stays: the AI never picks, posts or auto-clips a moment whose text says it shows sex acts or porn (`server/ai/explicit.js`; porn would need an adults-only section with age verification), and the home tagline never pairs a child with bare skin (`server/ai/hero-copy.js`). Anything sexual involving a minor is illegal and always blocked.
61- **WebSocket auth lifecycle:** WS connections can start anonymous and upgrade via `join` message. On account switch, the socket must be rebuilt (not just re-joined) — see `openvibe-auth-changed` handling in `chat.js`.
62- **openvibe-shared:** Pinned release of OpenVibers/OpenVibe.Shared (`"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/vX.Y.Z"`), served at `/shared/*` from `node_modules`. Change it there and bump the tag; never edit `node_modules`.
63- **DM delivery:** Server verifies `dm.isParticipant()` before delivering — always maintain this check.
64- **Schema:** every table is in `migrations/`; `ensureTables()` is gone. The frozen and Chat-owned tables are not in the PostgreSQL schema at all.
65
66**Robots:** OpenVibe.Bot owns them (plan T15). Live has no control socket, hardware API or ONVIF code: a channel bound to a robot (`channels.bot_robot_id`, `PUT /api/streams/channel/:username/bot`) embeds the robot's panel (`server/bot/embed.js`), and chat's `!forward`/`!say` … reach Live at `POST /internal/chat-effects/hardware`, which forwards them to Bot's `POST /api/v1/robots/:id/commands` as the viewer (`server/bot/bot-client.js`, Network grant `bot.robot.control`). Live's old controls tables (`control_configs`, its buttons and whitelist, `stream_controls`, the ONVIF camera tables, `api_keys`) and the columns that pointed at them are dropped by migration `0011_drop_live_controls` (`test/controls-tables-gone.test.js`).
67
68## WebSocket Endpoints
69
70`/ws/broadcast`, `/ws/call`, `/ws/robotstreamer-publish` — upgraded via the handler in `server/index.js` with origin checks and IP bans. `/ws/chat` is OpenVibe.Chat's (nginx routes it to 127.0.0.1:4400); Live runs no chat WebSocket server. Any other path gets a plain HTTP answer, not a dropped socket (nginx logs a drop as a 502): 404, or 410 naming OpenVibe.Bot for the retired robot control socket `/ws/control` ([server/net/ws-paths.js](server/net/ws-paths.js)); a new WebSocket server adds its path there.
71
72## Testing
73
74Standalone Node scripts in `test/` using `assert`, run together by `npm test`. They run on one migrated PGlite database per run (`test/helpers/pg-preload.mjs`; `npm run test:pg` uses the containers). Browser smoke: `test/browser/smoke.js` (routes × widths, console errors, overflow, duplicate scripts, resource growth). Always `node --check` modified files before committing.
75
76## Documentation
77
78Every file below is also served on the site at `/docs/<name>` (rendered by `server/docs/routes.js`, GitHub-compatible heading anchors) — link to `https://openvibe.live/docs/whip#…` rather than to GitHub when pointing users at a doc.
79
80- [docs/architecture.md](docs/architecture.md) — System design, data flows, module map
81- [docs/broadcasting.md](docs/broadcasting.md) — Streaming protocols (WebRTC/RTMP/JSMPEG/WHIP)
82- [docs/go-live-in-your-browser.md](docs/go-live-in-your-browser.md) — Going live from a browser with no OBS and no follower minimum (the headline feature: keep it on the home page, in /llms.txt and in search metadata)
83- [docs/whip.md](docs/whip.md) — WHIP ingest API reference (auth forms, CORS, browser-only publishing, error codes)
84- [docs/agent-streams.md](docs/agent-streams.md) — Agent streams: the `ags_` resource, publish and view tickets, the curtain, the owner page and the embed
85- [docs/streams-api.md](docs/streams-api.md) — Stream slots API: everything the Go live screen does, as calls (slots, settings, key, title ideas, history, going live and ending)
86- [docs/rundown.md](docs/rundown.md) — The Rundown (`/rundown`, was the Arena): what streamers talked about, written up from their speech — topics, takes, episodes, crossovers
87- [docs/chat-system.md](docs/chat-system.md) — Chat features and moderation
88- [docs/api-tokens.md](docs/api-tokens.md) — Bot/integration token system
89- [docs/vods-and-clips.md](docs/vods-and-clips.md) — VOD/clip pipeline (pre-split; storage/cutting now in OpenVibe.Media)
90- [docs/dashboard.md](docs/dashboard.md) — Streamer dashboard
91- [docs/onboarding.md](docs/onboarding.md) — New user flow
92- [docs/deploy.md](docs/deploy.md) — Deploy kinds, release layout, caching, nginx
93- [docs/performance-audit.md](docs/performance-audit.md) — Measurements and tools
94- [SETUP.md](SETUP.md) — Full deployment guide
95- [SECURITY_AUDIT.md](SECURITY_AUDIT.md) — Security audit findings
96- [hardware/README.md](hardware/README.md) — Raspberry Pi integration (moved to OpenVibe.Extensions `hardware/`; this is a redirect)
97