openvibers/openvibe.chat Commits

README and STATUS.json are current (roadmap WS-U task 3): the README gains Security (SECURITY.md, auth, private data, network exposure, egress, secrets by name) and Deploy (sudo ovhost deploy chat, unit, port, env file, /ready, both nginx files, N-1 recording, rollback) sections. Capabilities and events now lists every event Chat produces and consumes and the grants it uses at Live, Events, Network, VIP and Tools. The status, domain and launch-rule lines say openvibe.chat launched on 2026-09-24 (it left OpenVibe.Sites that day), not that it is a placeholder, and drop a stale release sha. STATUS.json names openvibe-contracts v0.71.0, gains features, corrects the domain, events, capabilities used and the in-code-only notes, and is updated 2026-09-28.

OP OpenVibers committed 28 Sept 2026 · d87c8d3c90 · 2 files, +96 −19


Changes

ddiff --git a/README.md b/README.md
iindex c703cc8..45e6315 100644
---- a/README.md
++++ b/README.md
@@@ -4,10 +4,12 @@
**Status:** alpha — deployed. Since the cutover on 2026-09-23 at 02:03 UTC (`docs/cutover.md`),
Chat is the authority for openvibe.live's chat: the service runs on `openvibe-ovh` (unit
--`openvibe-chat`, 127.0.0.1:4400, release `45f2b1f`), Live runs with `CHAT_AUTHORITY=chat` and keeps
--a read mirror, and chat events go to OpenVibe.Events. `openvibe.chat` still shows its placeholder.
--**Domain:** `openvibe.chat` (placeholder); Chat is served on Live's origin:
--`https://openvibe.live/ws/chat`, `/api/chat/`, `/api/dm/`, `/api/tts/`, `/api/sounds` via nginx.
++`openvibe-chat`, 127.0.0.1:4400), Live runs with `CHAT_AUTHORITY=chat` and keeps a read mirror, and
++chat events go to OpenVibe.Events. `openvibe.chat` is Chat's own site (global chat, messages, rooms,
++settings; the service manifest records it live since 2026-09-24).
++**Domain:** `openvibe.chat` ([deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf)); Chat
++is also served on Live's origin: `https://openvibe.live/ws/chat`, `/api/chat/`, `/api/dm/`,
++`/api/tts/`, `/api/sounds` via nginx.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §9 and §9.5.
**License:** AGPL-3.0 (same as every OpenVibe service).
@@@ -430,10 +432,23 @@ v0.30.2 (so is the `chat.preferences` user module since v0.32.0); the check name
the contracts check enforces it. Planned families:
`chat.room.*`, `chat.message.*`, `chat.dm.*`, `chat.moderation.*`, `chat.tts.*`, `chat.call.*`.
--Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat.moderation.action`
--(produced); planned `chat.room.updated`, `chat.call.*`, `chat.tts.queued|played|failed`. Consumed:
--`live.release.deployed`, `network.module.updated` (Chat's own subscriptions: Network grant
--`chat events.subscription.manage openvibe.events`).
++Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat.moderation.action`,
++`chat.room.message.created`, `chat.room.message.deleted` (produced); planned `chat.room.updated`,
++`chat.call.*`, `chat.tts.queued|played|failed`. Consumed at `POST /internal/events` (signature v2,
++`CHAT_EVENTS_SECRET`, openvibe-sdk inbox): `live.release.deployed`, `network.module.updated`,
++`network.user.token_valid_after`, `vip.membership.changed`, `network.block.changed`,
++`network.subject.merged`, `network.account.export_requested` and `network.account.deleted` (Chat's own
++subscriptions, created at boot when missing).
++
++Called elsewhere, as the service principal `chat` ([server/net/service-auth.js](server/net/service-auth.js)):
++
++| Service | Grant | Why |
++|---|---|---|
++| OpenVibe.Live | `live.chat_context.read`, `live.chat_effects.write`, `live.chat_mirror.write` | the chat context Live still owns (bans, channel settings), effects, the read mirror |
++| OpenVibe.Events | `events.event.publish`, `events.subscription.manage` | the outbox relay; the subscriptions above |
++| OpenVibe.Network | `network.modules.read`, `network.modules.write` | chat preferences and the other chat user modules |
++| OpenVibe.VIP | `vip.entitlement.check` | subscriber badges (fails closed) |
++| OpenVibe.Tools | `tools.tool.run`, `tools.job.read` | sound uploads to MP3 (local ffmpeg is the fallback) |
## Depends on
@@@ -452,6 +467,41 @@ Events: `chat.message.created`, `chat.message.deleted`, `chat.dm.created`, `chat
- a paid TTS request is never duplicated by a retry — *forwarded writes are applied once per idempotency key; paid TTS does not exist yet*
- after a reconnect, deleted and blocked state converge; browser parity (join, send, DM, `/tts`, moderation, popout) — *`scripts/parity.js` and `test/parity.test.js` (`docs/parity.md`): a reader that was away converges on what a connected reader saw (cursor reads carry `deleted_ids`); gaps: sub-only mode does not exist, public chat does not apply blocks*
++## Security
++
++Reporting a vulnerability: [SECURITY.md](SECURITY.md). The rules the code keeps:
++
++- **Auth.** People sign in with a Network session JWT, verified here with the Network's key (tokens
++ issued before a subject's `token_valid_after` are refused), or an `hbt_` API token resolved through
++ Live; `?token=` on `/ws/chat` is deprecated. Services use client-credentials tokens for audience
++ `openvibe.chat`, checked per capability (`chat.live_bridge.write`, `chat.message.send`,
++ `chat.presence.read`). Staff gates ask the contracts staff map.
++- **Private data.** DMs are delivered only to participants, and private rooms only to members
++ (`test/security-crawl.js` walks every route); network blocks count like DM blocks; a staff member
++ reading someone else's chat logs is recorded as a moderation action; account export and deletion
++ follow Network's events.
++- **Network exposure.** `/internal/*` and `/metrics` answer 404 through nginx, which sets the client
++ address from the connection only; WS upgrades check the origin and IP bans.
++- **Egress.** Chat calls its configured Network, Live, Events, VIP and Tools hosts; the soundboard
++ import fetches only 101soundboards' own addresses, checked on every DNS answer.
++- **Secrets.** `OV_OAUTH_CLIENT_SECRET` and `CHAT_EVENTS_SECRET` live in `/etc/openvibe/chat.env` (0600),
++ by name only.
++
++## Deploy
++
++Production deploys with `sudo ovhost deploy chat` on the host (strategy `git-checkout`: fetch,
++fast-forward `/opt/openvibe.chat`, install on a lockfile change, restart, wait for `/ready`).
++The unit is `openvibe-chat.service` on `127.0.0.1:4400`, the env file `/etc/openvibe/chat.env`. Readiness is
++`/ready` (not `/api/ready`). nginx: [deploy/nginx/openvibe.chat.conf](deploy/nginx/openvibe.chat.conf)
++for openvibe.chat and [deploy/nginx/openvibe.live-chat.locations.conf](deploy/nginx/openvibe.live-chat.locations.conf),
++included in openvibe.live's vhost, for Live's chat paths. After a deploy, record the N-1 fixtures
++(`npm run n-1:record`). Switching chat back to Live is in `docs/cutover.md` (with
++`scripts/mirror-flush.js`).
++
++Rollback: ovhost puts the previous sha back by itself when `/ready` does not answer 2xx after the
++restart; afterwards `sudo ovhost rollback chat --to <sha>`. Nothing blocks a rollback: the schema
++code only adds tables and columns.
++
## Launch rule
This repository does not make the product real, and the domain keeps its placeholder page on
@@@ -468,7 +518,8 @@ following exist here (plan §12.12):
The launch release removes the domain from `OpenVibe.Sites/sites.json`, switches routing and
registers maturity in the ecosystem registry atomically. A placeholder is never counted as an
--implemented service.
++implemented service. `openvibe.chat` launched on 2026-09-24 (global chat, messages, settings): it
++left OpenVibe.Sites, and this repository's vhost serves it.
---
ddiff --git a/STATUS.json b/STATUS.json
iindex 325a232..1933e04 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -6,18 +6,44 @@
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"runtime": "node 22, express, ws, better-sqlite3",
"port": 4400,
-- "domain": "openvibe.live (/ws/chat, /api/chat/, /api/dm/, /api/tts/, /api/sounds via an nginx include in openvibe.live's vhost); openvibe.chat is still the OpenVibe.Sites placeholder",
-- "contracts": "openvibe-contracts v0.33.0",
++ "domain": "openvibe.chat (Chat's own site: global chat, messages, rooms, settings; the service manifest records it live since 2026-09-24) and openvibe.live (/ws/chat, /api/chat/, /api/dm/, /api/tts/, /api/sounds via an nginx include in openvibe.live's vhost)",
++ "contracts": "openvibe-contracts v0.71.0",
"capabilities": ["chat.live_bridge.write", "chat.message.send", "chat.presence.read"],
-- "capabilitiesUsed": ["live.chat_context.read", "live.chat_effects.write", "live.chat_mirror.write", "events.event.publish", "events.subscription.manage", "vip.entitlement.check", "network.modules.read", "network.modules.write"],
++ "capabilitiesUsed": [
++ "live.chat_context.read",
++ "live.chat_effects.write",
++ "live.chat_mirror.write",
++ "events.event.publish",
++ "events.subscription.manage",
++ "vip.entitlement.check",
++ "network.modules.read",
++ "network.modules.write",
++ "tools.tool.run",
++ "tools.job.read"
++ ],
"capabilitiesRegistered": true,
-- "events": ["chat.message.created", "chat.message.deleted", "chat.dm.created", "chat.moderation.action"],
-- "eventsConsumed": "live.release.deployed (the deploy card, folded by subject.id with the bridge's deployNotice: one card per head whichever comes first; register C-84) and network.module.updated (drops a cached chat.preferences copy), POST /internal/events, signature v2, openvibe-sdk inbox; subscriptions created at boot. In code only, not deployed; needs CHAT_EVENTS_SECRET in chat.env and the Network grant chat events.subscription.manage openvibe.events",
++ "events": ["chat.message.created", "chat.message.deleted", "chat.dm.created", "chat.moderation.action", "chat.room.message.created", "chat.room.message.deleted"],
++ "eventsConsumed": "live.release.deployed, network.module.updated, network.user.token_valid_after, vip.membership.changed, network.block.changed, network.subject.merged, network.account.export_requested and network.account.deleted at POST /internal/events (signature v2, openvibe-sdk inbox); subscriptions created at boot when missing (CHAT_EVENTS_SECRET and the Network grant chat events.subscription.manage openvibe.events)",
"liveAuthorityFlag": "CHAT_AUTHORITY=chat is set in production live.env since the cutover (2026-09-23 02:03 UTC); Live's chat tables are a read mirror Chat keeps current",
-- "vip": "Member badges from OpenVibe.VIP (server/vip/badges.js: entitlements/check with product chat, cached 60 s, never blocks sending, fails closed). In code only, not deployed; needs the Network grant chat vip.entitlement.check openvibe.vip and Live's chat UI to render vip_badge / chat_vip_badge",
-- "preferences": "Chat preferences in the Network user module chat.preferences (server/prefs/: GET/PUT /api/chat/preferences with Chat's service token, cached 60 s, If-Match on every write; scripts/migrate-chat-preferences.js moves the choices in Live's user_preferences). In code only, not deployed; needs Network with the chat.preferences handoff and the grants chat network.modules.read/write on chat.preferences, then the migration, then Live's chat.js calling /api/chat/preferences",
-- "calls": "Live's call server, call routes and stream hooks ported to server/calls/ with a calls lifecycle table (WS-I task 1). In code only, off (CHAT_CALLS unset); the switch is CHAT_CALLS=1, Live CALLS_AUTHORITY=chat and the nginx include deploy/nginx/calls/openvibe.live-calls.locations.conf (docs/calls-cutover.md)",
++ "vip": "Member badges from OpenVibe.VIP (server/vip/badges.js: entitlements/check with product chat, cached 60 s, never blocks sending, fails closed; vip.membership.changed drops the cache). Needs the Network grant chat vip.entitlement.check openvibe.vip",
++ "preferences": "Chat preferences in the Network user module chat.preferences, plus chat.tts_defaults, chat.dm_settings and chat.presence_prefs (server/prefs/: /api/chat/preferences and siblings with Chat's service token, cached 60 s, If-Match on every write; scripts/migrate-chat-preferences.js moves the choices in Live's user_preferences). Needs the grants chat network.modules.read/write",
++ "calls": "Live's call server, call routes and stream hooks ported to server/calls/ with a calls lifecycle table (WS-I task 1). Off unless CHAT_CALLS=1; the switch is CHAT_CALLS=1, Live CALLS_AUTHORITY=chat and the nginx include deploy/nginx/calls/openvibe.live-calls.locations.conf (docs/calls-cutover.md)",
"deployed": true,
-- "deployment": "openvibe-ovh /opt/openvibe.chat, unit openvibe-chat, 127.0.0.1:4400 (loopback); public only through openvibe.live's nginx include for the chat paths; release 45f2b1f; EVENTS_URL set (chat.message.created reaches OpenVibe.Events); restore drill passed 2026-09-23",
-- "updated": "2026-09-23"
++ "deployment": "Deployed with `sudo ovhost deploy chat`: /opt/openvibe.chat, unit openvibe-chat, 127.0.0.1:4400 (loopback); public through the openvibe.chat vhost and openvibe.live's nginx include for the chat paths; EVENTS_URL set (chat.message.created reaches OpenVibe.Events); restore drill passed 2026-09-23",
++ "updated": "2026-09-28",
++ "features": [
++ "Live's global, stream and channel chat (authority since 2026-09-23, Live keeps a read mirror)",
++ "DMs and group DMs, participants only",
++ "rooms: public and private, roles, invitations, slow mode, unread counts",
++ "moderation: bans, timeouts, purges, filters, slow, sub-only and member-only modes, audited log access",
++ "TTS, sounds and soundboard with persisted queues",
++ "calls ported from Live (off until the calls cutover)",
++ "openvibe.chat site: global chat, messages, rooms, settings, history download",
++ "chat preferences in Network user modules",
++ "VIP subscriber badges",
++ "network blocks, revocation, account merge, export and deletion from Network events",
++ "outbox events and chat.room.message.* on the realtime plane",
++ "N-1 compatibility tests",
++ "per-actor limits (openvibe-sdk/limits)"
++ ]
}