Skip to content
-
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.OpenVibers · 28 Sept 2026
d87c8d3
-
Per-actor rate limits on Chat's REST API (roadmap WS-R task 4, openvibe-sdk v0.12.0 limits). server/net/actor-limits.js is one limiter for the app. It counts a person as user:usr_… (user:<id> before their subject is known) whether they call with their Network token or an hbt_ API token, so a bot counts against its owner, and anyone else by their own address. The caller's token is resolved once per request (auth.js requestUser, now shared by requireAuth, optionalAuth and the ban exemption), so reads count against the person rather than the address. Browsers call these routes themselves; Live's service calls (the bridge at /internal/live and the stream hooks at /internal/calls) carry every viewer's chat, so they are never limited and never count as svc:live. The WebSocket keeps its own flood control. Past a limit a route answers 429 problem+json rate_limited with Retry-After before any work, logs one [Limits] line and counts chat_rate_limited_total{limit,window}.OpenVibers · 28 Sept 2026
27cb1cb
-
N-1 tests (roadmap WS-P task 11): the previous release's clients against this server, and its SQL against this schema. test/n-1.test.js boots this checkout with its own test/helpers.js (stub Network and Live) on a database created from the previous release's schema, so Chat's migrations run over it, and replays test/fixtures/n-1/client.json inside openvibe-shared's mixed-version matrix (release-compat assertMixedVersion): 104 requests (Live's chat widget, messenger and pickers at Live 8caa540 on the paths nginx sends to Chat, Chat's own pages' scripts, links and forms, the openvibe-sdk chat client; every call anonymously, reads signed in), each answered compatibly (status, JSON, every response field the client reads with a type it had), and two /ws/chat sessions (anonymous and signed in) sending the six message types Live's chat and Chat's page send (join, get-users, chat, join_room, room_message, self-delete-history): every reply type the client handles must still come, with the fields it reads. Then every statement in test/fixtures/n-1/worker.json (222: what that release ran, plus its SQL literals) must still prepare on the migrated schema, and no old INSERT may miss a new NOT NULL column without a default. Fixtures recorded from 739d3a1, the release in production; refresh after each deploy with `N1_LIVE_REF=<live sha> npm run n-1:record [ref]` (a temporary worktree of the release, booted, seeded, replayed; Live's files from N1_LIVE_REPO, default ../OpenVibe.Live). The harness is the file Live, Media and Community share (test/n-1/harness.js); test/n-1/service.js is Chat's part. In npm test, so in CI, about 6 s, no git history or network. Checked by breaking it: renaming users-list's users field fails it.OpenVibers · 26 Sept 2026
c4c80d8
-
Rooms: call and system kinds, the full role set, Community attachments; openvibe.chat: call rooms, the DM inbox, working with Live down (roadmap WS-I tasks 4 and 8)OpenVibers · 26 Sept 2026
66724bd
-
Parity gaps closed (roadmap WS-I task 6): sub-only mode, blocks in public chat, slow mode as the saved setting, purges and deletes on every surface, /clear says what it doesOpenVibers · 26 Sept 2026
bcc53f4
-
Chat parity script (roadmap WS-I task 6): scripts/parity.js runs the chat product end to end over the real protocol with two or three people connected and checks what each sees, one scenario per item: join (anonymous and signed in, the user list), send (echo and delivery with one id, history), DM (participants only, no echo, a non-participant refused), /tts (the room's tts line, the request in the channel's queue, the audio frame), soundboard (!sound: announce, audio, queue), ban, timeout, purge (the dashboard's ISO range), slow mode (viewers slowed, moderators exempt), the popout (a live popout and a channel-room popout, as popout-chat.html joins), and reconnect convergence (a reader away while a line is deleted and another written reads ?after_id= and ends with the connected reader's view; a fresh page agrees). Sub-only mode and blocks in public chat do not exist: those two scenarios report the gap and touch nothing. test/parity.test.js runs the same scenarios against Chat with stubs (found by npm test) and adds Live's moderator paths through the bridge (the context-menu ban that disconnects and drops the user from the list, the single-message delete converging in the global feed's cursor read), the DM participant rule on delivery, and the CLI: its dry run prints the plan and opens nothing, and --apply first resolves every account behind the tokens and the stream's owner and runs nothing unless all are named in OV_PARITY_TEST_ACCOUNTS. docs/parity.md: how to run it, the gaps, what was fixed on the way.OpenVibers · 26 Sept 2026
91a36f2
-
Staged tables move to Chat one table at a time (WS-I task 2, register C-04; Live d6480da): table_authority now says who writes channel_moderators, channel_moderation_settings, emotes, user_tags, chat_ai_summaries and chat_timeline_events, and a restart keeps what a table was handed. At 'live' Chat's copy follows Live's captured changes (bridge op stagedApply, rows REPLACEd, never mirrored back) and answers Live's dual read (stagedSlice: count and sha256 of a slice). setTableAuthority is Chat's half of Live's handoff: to 'chat' needs LIVE_MIRROR, back to 'live' sends the table's queued mirror rows first and refuses while any is left. At 'chat' the bridge takes Live's writers under Live's function names (addChannelModerator, removeChannelModerator, upsertChannelModerationSettings with Live's clamps, setChannelAlertSound, createEmote, updateEmote, deleteEmote, setEmoteMedia, grantUserTag, revokeUserTag, upsertChatAiSummary, addChatTimelineEvents), each refusing while its table is Live's and answering Live's return value plus the rows; the mirror triggers copy the table to Live; Chat's chat server reads moderators and settings in place and writes /slow and alert sounds itself. scripts/import-from-live.js is a dry run unless --apply (which backs chat.db up with VACUUM INTO first), makes a staged table at 'live' equal to Live's (refreshed, and pruned of rows Live removed, before inserting) and only compares one at 'chat'; --tables no longer crashes in the id-headroom step. scripts/parity-check.js --tables compares row counts and a content hash per staged table; scripts/table-authority.js shows both sides and sets Chat's side when Live cannot. /ready reports table_authority. Runbook with dates and rollback per step: docs/staged-tables-cutover.mdOpenVibers · 26 Sept 2026
e17dbbb
-
Platform blocks (WS-E task 5, Contracts 0.49.0): Chat consumes network.block.changed into a newest-revision projection, and a network block counts like dm_blocks for new DMs, 1:1 messages, group invites, calls and the DM user search, both ways; scripts/migrate-dm-blocks-to-network.js exports dm_blocks as subject pairs for Network's import (dry run by default)OpenVibers · 25 Sept 2026
ea1770a
-
Calls can run in Chat (WS-I task 1): Live's call server moved to server/calls/ with the same /ws/call protocol and limits, Live's voice-channel and /api/streams/:id/call routes on the same paths and shapes, loopback /internal/calls/stream-channel for Live's stream hooks (bridge service token, chat.live_bridge.write), and a calls table with the pending/ringing/active/ended/missed/declined/failed lifecycle; off until CHAT_CALLS=1 so nothing changes before the cutover in docs/calls-cutover.mdOpenVibers · 25 Sept 2026
603d0b4
-
Chat owns chat.tts_defaults, chat.dm_settings and chat.presence_prefs (Contracts 0.41.0, WS-B task 9): one module store per namespace (server/prefs/module-store.js), /api/chat/tts-settings, /dm-settings and /presence beside /preferences, openvibe.chat Settings edits all four; DMs honour new_conversations nobody (existing conversations stay open) and group_invites; user lists count, never name, someone hidden; network.module.updated refreshes themOpenVibers · 25 Sept 2026
2bdb0cf
-
openvibe-contracts v0.32.0: chat.message.send is chat's own capability, checked with a literal idOpenVibers · 23 Sept 2026
4abf0fa
-
Events consumer (compatibility register C-84): Chat subscribes to live.release.deployed and network.module.updated and receives them at POST /internal/events (server/events/consumer.js). live.release.deployed makes the deploy card exactly as the bridge op deployNotice does (same fold: newest row in any room is a deploy card under 3 hours old; same broadcast passes and late-joiner replay), keyed by subject.id, the head commit. Both paths claim the head in the new deploy_releases table in the transaction that stores or folds the card, so while Live sends both the same head is one card whichever arrives first; the second only records bridge_at / event_at. The bridge's head is its first commit (Live sends them newest first). A retried bridge op, a redelivered event and Live re-announcing after a crash are the same card; a redeploy of an already announced head (a rollback) says nothing; a release event older than 6 hours (an operator replay) is ignored:stale. The bridge call keeps working. network.module.updated for chat.preferences goes to prefs.handleEvent(): a newer revision drops the cached copy at once instead of within CHAT_PREFS_TTL_MS, an older or equal one and other namespaces change nothing (and are not recorded). Exactly once: the openvibe-sdk inbox (chat_event_inbox, pruned after 35 days) claims (chat, event_id) in the same transaction as the change; broadcasts run after the commit; a failure is 500 and Events retries. Signature v2 only (parseDelivery requireV2, ±300 s) under CHAT_EVENTS_SECRET (comma list for rotation, 32+ characters each): bad, stale, v1-only or unsigned is 401, no secret 503, a forwarded request 403. Subscriptions are created at boot when missing (server/events/subscriptions.js: list, then create, 409 = exists; retried in the background at 10 s, 1, 5 and 15 min), an existing one is left as it is even when disabled; CHAT_EVENTS_SUBSCRIBE=0 turns it off, CHAT_EVENTS_ENDPOINT overrides http://127.0.0.1:<PORT>/internal/events. scripts/subscribe-events.js --dry-run | --disable | --enable. Needs the Network grant chat events.subscription.manage openvibe.events. /ready reports the consumer's counters under events.consumer. Adds openvibe-sdk v0.5.0. test/events-consumer.test.js: signature checks, the inbox, bridge-then-event and event-then-bridge for one head, folding and a stream message starting a new card across both paths, release validation and staleness, the preferences cache with older revisions ignored, idempotent boot subscriptions against a stub Events, inbox pruning. README (Events: Consumed), .env.example, STATUS.jsonOpenVibers · 23 Sept 2026
ed90461
-
Chat preferences move to the OpenVibe.Network user module chat.preferences (roadmap Wave 1 item 24; Chat owns the namespace since the Wave 6 cutover). GET /api/chat/preferences and PUT /api/chat/preferences { preferences } (a patch, null removes a field, optional If-Match) read and change the signed-in person's record through Network's /internal/modules/chat.preferences/:subject with Chat's service token (audience openvibe.network, network.modules.read + write; server/prefs/network-modules.js, one retry after a 401). server/prefs/chat-preferences.js: every write names the revision it read, a revision that moved is read again and the patch re-applied (3 tries), a browser If-Match is strict (412), a patch that changes nothing writes nothing, values are checked against the namespace schema (openvibe-contracts modules.validateData) before Network sees them. Cache per person for CHAT_PREFS_TTL_MS (60 s): Chat's own writes land in it, a change made through Network directly shows within the TTL, and handleEvent() drops an entry at once for a newer network.module.updated (for when Events deliveries reach Chat); Network down serves the cached copy (stale: true) or 503. API tokens read only; no Network subject is 409 prefs.subject_unknown. scripts/migrate-chat-preferences.js (server/prefs/from-live.js) moves what Live kept in user_preferences.chat_settings: only choices (showTimestamps -> timestamps, compactMode -> compact, fontSize small/large -> font_scale 0.88/1.18, showBadges false -> show_badges), the subject from Live's linked_accounts (the network link first), Live's database opened read-only, writes through Network only and create-only (If-Match 0) so an existing record is never overwritten and a re-run creates nothing. Dry run by default; --apply requires --backup <new file>, written 0600 and read back before any write, then given each creation's revision; --rollback <file> [--apply] deletes what that run created, keeping records changed since. test/helpers.js: the stub Network serves user modules with Network's revision and If-Match rules. Tests: test/preferences.test.js, test/migrate-preferences.test.js. README (Chat preferences), STATUS.json, .env.example.OpenVibers · 23 Sept 2026
c645a53
-
VIP member badges: a member's messages in a creator's room carry the creator's VIP badge (a perk of their plan version bound to `chat badge`), resolved through OpenVibe.VIP's entitlement check (product chat) behind the shared product cacheOpenVibers · 23 Sept 2026
d02a73c
-
Persisted TTS and sound queue: every TTS utterance, channel !sound and 101soundboards clip is a row of audio_requests (server/chat/audio-queue.js) played one at a time per room, queued -> playing -> played, or skipped / failed. The audio is made when its turn comes and paced by the clip's length (WAV header, MP3 frame bitrate, or the sound's stored duration), so skip and clear take effect for everything not yet delivered. The broadcaster and moderators control it with /skiptts [id] and /cleartts (logged as tts_skip / tts_clear) and GET /api/tts/queue, POST /api/tts/queue/skip, /clear and /:id/report (the playing client's played / failed). Synthesis errors, missing clips and repeated crashes are recorded as failed and the queue moves on. A keyed request (the TTS of message m<id>) is queued once per room, replacing the 30 s in-memory dedupe, and the per-user limit (tts_max_queue_per_user) now holds. A restart keeps the queue: rows that were playing are finished, rows older than 5 minutes expire, and the rest play once the room has listeners again (a second after a socket rejoins, 8 s at most). Clients get the same tts-audio / soundboard-audio frames plus request_id, and two new frames they may ignore: audio-skip and audio-clear. test/audio-queue.test.js covers the state machine, pacing, permissions, failures, dedupe and a real SIGTERM restartOpenVibers · 23 Sept 2026
bf88760
-
Chat restart and cursor resume: test/restart-resume.test.js runs Chat as its own process, restarts it with SIGTERM on the same database, and proves that stream, global and channel readers that reconnect and read after_id=<last cursor> end up with exactly the room's rows: nothing written while they were away (by people or through Live's bridge, before or after the restart) is missing, nothing at or under the cursor comes back, and ids keep increasing. It exposed a duplicate: Live's placeholder-id map lived only in memory, so a broadcast whose insert the old process had acknowledged reached browsers with the negative placeholder instead of the row id, and a reader showed the row twice (frame and history). The map is now also kept in bridge_refs (a day; written in the insert's transaction when keyed), and a lookup falls back to it. Test helpers: h.detach, h.spawnChat, h.stopChatOpenVibers · 23 Sept 2026
49a5011
-
Enforce chat.message.send on the service API: an /internal/live/calls op that sends a chat message (a saveChatMessage write, the deploy notice, or a pushed chat/dm frame through any broadcast op or sendDm) needs chat.message.send in the caller's service or app token beside chat.live_bridge.write. Without it that op alone is refused (capability.denied, nothing persisted, no event, not marked applied) and the rest of the batch runs, so Live's queue is not blocked. Other frames (system notices, call invites), TTS, sounds, moderation writes and invalidations need only the bridge capability. Contracts still lists chat.message.send as owned by live, so the id is held in a constant the contracts check does not scan; it needs re-owning to chat. Network must grant live chat.message.send openvibe.chat before this deploys (docs/cutover.md). Denial test for a service and an app principal in test/bridge.test.jsOpenVibers · 23 Sept 2026
791cd3d
-
chat.message.deleted: every delete of public-room messages (one message by a moderator or through Live's bridge, a user's / anon's / relay user's history from self-delete or /api/mod, a time-range purge, the auto-delete sweep) adds a public chat.message.deleted to the outbox in the same transaction, one per 500 ids, carrying only message_ids (never the text, the author or who deleted it) and payload.redacts { subject_type: chat_message, subject_ids }, so OpenVibe.Events turns the stored chat.message.created into a tombstone on every read path. Return values of the delete functions are unchanged. test/message-deleted.test.js covers every path, chunking and rollback, and, with a sibling OpenVibe.Events checkout, the relay end to end: anonymous, signed-in and service SSE replay and pull return the tombstone at an unchanged seq, and only svc:chat can redact Chat's eventsOpenVibers · 23 Sept 2026
4cc1cbe
-
Docs: STATUS.json and README match production (2026-09-23)OpenVibers · 23 Sept 2026
4dc7b87
-
/ready tells the truth: a real chat.db read (required) and the last successful Live sync (optional)OpenVibers · 23 Sept 2026
45f2b1f
-
Cutover kit: Live patch (CHAT_AUTHORITY=chat), runbook, capability proposals, ops filesOpenVibers · 23 Sept 2026
547e2f9
-
Charter: OpenVibe.Chat as defined in the OpenVibe realignment plan (placeholder, no code yet)OpenVibers · 21 Sept 2026
bfadcf6