openvibers/openvibe.network Commits
Network: the product at openre.stream is OpenRestream (owner, 2026-10-09) (#108)
Changes
@@@ -66,7 +66,7 @@ helpers.## 4. What the two old plans got right, and what changedKept from the *domain map*: the split between identity (network) and rooms; one job per domain;--Community as the home of pastes/posts/comments; OpenRe.Stream as the ingest/restream layer++Community as the home of pastes/posts/comments; OpenRestream as the ingest/restream layerbehind Live; Chat as the communication layer; content rooms (news/reviews/deals/coupons/trade)being source-backed and honest. Kept from the *runtime prompt*: capability-style sharedcontracts, real tests over route-existence, readiness that says yellow when it is yellow.@@@ -83,7 +83,7 @@ cookies and CORS and buys nothing at this size. `my.openvibe.network` already re1. **Community phase 2** — spaces, threads, posts, comments as reusable primitives(`/api/comments?target=live:vod:123`) used by Live VOD/clip pages, Media, Blog and Wiki.Discord relay tagging. Moderation shared with Live's tools.--2. **OpenRe.Stream** — move the restream engine's *config UI and status API* to the domain++2. **OpenRestream** — move the restream engine's *config UI and status API* to the domain(Live keeps the ingest processes); a public "what's live where" status page.3. **Chat** — `openvibe.chat` embeds Live's chat server with rooms not bound to a stream(global rooms, DMs) and the voice channels; Live embeds it back. One `/ws/chat`, one moderation.@@@ -75,6 +75,10 @@ async function setDb(db) { database = db; await attachHelpers(db); }* concurrent writers: every write is an upsert, and the redirect-URI merge takes the row with* SELECT … FOR UPDATE inside a transaction (decision 1).*/++// A site's former display names: the seed renames a client still carrying one. The product at openre.stream is++// OpenRestream (owner, 2026-10-09), never "OpenRe.Stream".++const FORMER_NAMES = { openre: ['OpenRe.Stream', 'OpenRe'] };++async function seedDb(db, { log = console } = {}) {const warn = (m) => log.warn(`[DB] ${m}`);@@@ -95,7 +99,7 @@ async function seedDb(db, { log = console } = {}) {{ client_id: 'vip', name: 'OpenVibe.VIP', redirect_uris: ['https://openvibe.vip/auth/callback'] },{ client_id: 'wiki', name: 'OpenVibe.Wiki', redirect_uris: ['https://openvibe.wiki/auth/callback'] },{ client_id: 'blog', name: 'OpenVibe.Blog', redirect_uris: ['https://openvibe.blog/auth/callback'] },-- { client_id: 'openre', name: 'OpenRe.Stream', redirect_uris: ['https://openre.stream/auth/callback'] },++ { client_id: 'openre', name: 'OpenRestream', redirect_uris: ['https://openre.stream/auth/callback'] },{ client_id: 'reviews', name: 'OpenVibe.Reviews', redirect_uris: ['https://openvibe.reviews/auth/callback'] },{ client_id: 'news', name: 'OpenVibe.News', redirect_uris: ['https://openvibe.news/auth/callback'] },{ client_id: 'trade', name: 'OpenVibe.Trade', redirect_uris: ['https://openvibe.trade/auth/callback'] },@@@ -136,8 +140,8 @@ async function seedDb(db, { log = console } = {}) {await mergeRedirectUris(db, c.client_id, c.redirect_uris, { log });// A client created first as a service principal carries the placeholder name "OpenVibe.<id> (service)",// which the sign-in page shows people ("Sign in to OpenVibe.food (service)"). Give it the site's name; a-- // name someone chose stays.-- if (existing.name === `OpenVibe.${c.client_id} (service)`) {++ // name someone chose stays. A site's own former name is renamed too (FORMER_NAMES).++ if (existing.name === `OpenVibe.${c.client_id} (service)` || (FORMER_NAMES[c.client_id] || []).includes(existing.name)) {await db.prepare('UPDATE oauth_clients SET name = ? WHERE client_id = ?').run(c.name, c.client_id);log.log(`[DB] OAuth2 client ${c.client_id} renamed to ${c.name}`);}@@@ -12,7 +12,7 @@ const { exposureOf } = require('../registry/exposure');// (state: internal = runs on loopback only, placeholder = planned). The two lists cannot disagree.// Since openvibe-contracts 0.42.0 (WS-C task 1) the list is the manifests' `site` blocks, in `position` order:// name, icon, tagline, what, the legal profile, and a host only while the manifest has no publicOrigin. A--// site's id is its icon name (live, tools, …, stream for OpenRe.Stream).++// site's id is its icon name (live, tools, …, stream for OpenRestream).const RAW_SITES = contracts.services.manifests.filter((m) => m.site).sort((a, b) => (a.site.position ?? 999) - (b.site.position ?? 999)).map((m) => ({id: m.site.icon, name: m.site.name, icon: m.site.icon, service: m.id, profile: m.site.legalProfile, tagline: m.site.tagline, what: m.site.what,...(m.site.host ? { host: m.site.host } : {}),@@@ -18,7 +18,7 @@ const TYPES = new Set(['tool', 'stream', 'vod', 'clip', 'paste', 'game', 'page',const SERVICE_LABELS = {live: 'Live', tools: 'Tools', games: 'Games', media: 'Media', network: 'Network', community: 'Community', space: 'Space',chat: 'Chat', codes: 'Codes', blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', tips: 'Tips',-- vip: 'VIP', trade: 'Trade', host: 'Host', deals: 'Deals', coupons: 'Coupons', openre: 'OpenRe.Stream',++ vip: 'VIP', trade: 'Trade', host: 'Host', deals: 'Deals', coupons: 'Coupons', openre: 'OpenRestream',};async function ensureSchema(db) { /* the schema is migrations/NNNN_*.sql (plan T2); nothing is created at runtime */ }@@@ -64,7 +64,7 @@ function intents(catalog) {}// The network as a constellation: open sites on the inner ring, the ones opening next faint on the outer ring.--const SHORT = (site) => (/^OpenRe\./.test(site.name) ? 'OpenRe' : site.name.replace(/^OpenVibe\./, ''));++const SHORT = (site) => site.name.replace(/^OpenVibe\./, '');function constellation() {const ring = (list, r, start) => list.map((site, i) => {const a = start + (i / list.length) * Math.PI * 2;@@@ -78,8 +78,8 @@ const DEFAULT_GRANTS = [['host', 'media.object.read', 'openvibe.media', ['host', 'host.*']],['host', 'media.object.list', 'openvibe.media', ['host', 'host.*']],['host', 'media.object.delete', 'openvibe.media', ['host', 'host.*']],-- // Plan T4 (Media cleanup): Live and OpenRe reach Media with their own tokens instead of API keys. Live keeps-- // its objects under the live namespace (VOD/clip/thumbnail uploads, reads, lists, deletes); OpenRe uploads the++ // Plan T4 (Media cleanup): Live and OpenRestream reach Media with their own tokens instead of API keys. Live keeps++ // its objects under the live namespace (VOD/clip/thumbnail uploads, reads, lists, deletes); OpenRestream uploads the// recordings and thumbnails it produces there too....['media.object.read', 'media.object.list', 'media.object.upload', 'media.object.delete'].map(cap => ['live', cap, 'openvibe.media', ['live']]),['openre', 'media.object.upload', 'openvibe.media', ['live']],@@@ -118,7 +118,7 @@ const DEFAULT_GRANTS = [['tips', 'chat.message.send', 'openvibe.chat', []],['tips', 'chat.event.publish', 'openvibe.chat', []],['live', 'tips.interaction.record', 'openvibe.tips', []],-- // Wave 7: Live manages its slots' streams, keys and sessions on OpenRe.Stream; OpenRe publishes++ // Wave 7: Live manages its slots' streams, keys and sessions on OpenRestream; OpenRestream publishes// session/output events (Live consumes openre.session.* by webhook)....['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read'].map(c => ['live', c, 'openvibe.openre', []]),['openre', 'events.event.publish', 'openvibe.events', []],@@@ -194,7 +194,7 @@ const DEFAULT_GRANTS = [['bot', 'network.node.manage', SELF_AUDIENCE, []],// Plan T15: an owner adds an operator by @username on the robot's panel; Bot resolves it to the subject here.['bot', 'identity.subject.resolve', SELF_AUDIENCE, []],-- // Plan T15: Bot runs each robot's OpenRe stream for its owner (find/create/archive, key rotation), plays the live++ // Plan T15: Bot runs each robot's OpenRestream stream for its owner (find/create/archive, key rotation), plays the live// session in the panel (session.read), restreams out (output.*) and sets the owner's streaming and recording// toggles on the stream, minting its own token with its Network client instead of a hand-set BOT_OPENRE_TOKEN....['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read', 'openre.output.read', 'openre.output.write'].map(c => ['bot', c, 'openvibe.openre', []]),@@@ -273,7 +273,7 @@ const DEFAULT_GRANTS = [['live', 'community.paste.create', 'openvibe.community', []],['live', 'community.paste.write', 'openvibe.community', []],['live', 'community.paste.moderate', 'openvibe.community', []],-- // One canonical channel/owner resolver (roadmap §10.5/§15.10, D20-R1): OpenRe, Media and Community (Pulse)++ // One canonical channel/owner resolver (roadmap §10.5/§15.10, D20-R1): OpenRestream, Media and Community (Pulse)// resolve channels, streams, VODs and clips through Live's /internal/lineage/resolve instead of their own// channel mappings....['openre', 'media', 'community'].map(c => [c, 'live.lineage.resolve', 'openvibe.live', []]),@@@ -70,7 +70,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl}const SITES = ['live', 'network', 'tools', 'media', 'community', 'space', 'chat', 'games', 'blog', 'wiki', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'services', 'host', 'ai', 'search', 'sources', 'events', 'billing', 'tips', 'vip', 'openre', 'bot', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'media-hub', 'inventory', 'sites'];--const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };++const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRestream' };const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');/** A registry id from an id or a hostname (openvibe.wiki → wiki, pdf.openvibe.tools → tools). */@@@ -95,7 +95,7 @@ const server = http.createServer(app);await db.prepare("UPDATE oauth_clients SET client_secret = 'openre-secret' WHERE client_id = 'openre'").run();t = await token({ client_id: 'openre', client_secret: 'openre-secret', audience: 'openvibe.network' });assert.strictEqual(t.status, 400); assert.strictEqual(t.body.error, 'invalid_scope', 'a client with no grants gets no token');-- // The canonical channel/owner resolver on Live (D20-R1): OpenRe, Media and Community hold it, nobody else.++ // The canonical channel/owner resolver on Live (D20-R1): OpenRestream, Media and Community hold it, nobody else.await db.prepare("UPDATE oauth_clients SET client_secret = 'community-secret' WHERE client_id = 'community'").run();for (const [client, secret] of [['openre', 'openre-secret'], ['media', 'media-secret'], ['community', 'community-secret']]) {t = await token({ client_id: client, client_secret: secret, audience: 'openvibe.live' });@@@ -212,14 +212,14 @@ const server = http.createServer(app);assert.strictEqual(ub.status, 200, `${client}: ${JSON.stringify(ub.body)}`);assert.strictEqual(ub.body.scope, 'billing.usage.record', `${client} records usage on Billing and holds no money capability`);}-- // Plan T15: Bot mints its own OpenRe token for the robots' streams, sessions, outputs and toggles; nothing beyond OpenRe's++ // Plan T15: Bot mints its own OpenRestream token for the robots' streams, sessions, outputs and toggles; nothing beyond OpenRestream's// stream, key, session-read and output capabilities (never openre.session.end).const bn = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.network', scope: 'identity.subject.resolve' });assert.strictEqual(bn.status, 200, JSON.stringify(bn.body));assert.strictEqual(bn.body.scope, 'identity.subject.resolve', 'Bot resolves an operator\'s @username');const bo = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.openre' });assert.strictEqual(bo.status, 200, JSON.stringify(bo.body));-- assert.deepStrictEqual(bo.body.scope.split(' ').sort(), ['openre.key.rotate', 'openre.output.read', 'openre.output.write', 'openre.session.read', 'openre.stream.read', 'openre.stream.write'], "Bot runs its robots' OpenRe streams");++ assert.deepStrictEqual(bo.body.scope.split(' ').sort(), ['openre.key.rotate', 'openre.output.read', 'openre.output.write', 'openre.session.read', 'openre.stream.read', 'openre.stream.write'], "Bot runs its robots' OpenRestream streams");// Coupons subscribes to its Events sources with its own token (OpenVibe.Coupons scripts/subscribe.js).await db.prepare("UPDATE oauth_clients SET client_secret = 'coupons-secret' WHERE client_id = 'coupons'").run();const cps = await token({ client_id: 'coupons', client_secret: 'coupons-secret', audience: 'openvibe.events', scope: 'events.subscription.manage' });
@@ -66,7 +66,7 @@ helpers.
@@ -66,7 +66,7 @@ helpers.
## 4. What the two old plans got right, and what changed
## 4. What the two old plans got right, and what changed
Kept from the *domain map*: the split between identity (network) and rooms; one job per domain;
Kept from the *domain map*: the split between identity (network) and rooms; one job per domain;
-Community as the home of pastes/posts/comments; OpenRe.Stream as the ingest/restream layer
+Community as the home of pastes/posts/comments; OpenRestream as the ingest/restream layer
behind Live; Chat as the communication layer; content rooms (news/reviews/deals/coupons/trade)
behind Live; Chat as the communication layer; content rooms (news/reviews/deals/coupons/trade)
being source-backed and honest. Kept from the *runtime prompt*: capability-style shared
being source-backed and honest. Kept from the *runtime prompt*: capability-style shared
contracts, real tests over route-existence, readiness that says yellow when it is yellow.
contracts, real tests over route-existence, readiness that says yellow when it is yellow.
@@ -83,7 +83,7 @@ cookies and CORS and buys nothing at this size. `my.openvibe.network` already re
@@ -83,7 +83,7 @@ cookies and CORS and buys nothing at this size. `my.openvibe.network` already re
1. **Community phase 2** — spaces, threads, posts, comments as reusable primitives
1. **Community phase 2** — spaces, threads, posts, comments as reusable primitives
(`/api/comments?target=live:vod:123`) used by Live VOD/clip pages, Media, Blog and Wiki.
(`/api/comments?target=live:vod:123`) used by Live VOD/clip pages, Media, Blog and Wiki.
Discord relay tagging. Moderation shared with Live's tools.
Discord relay tagging. Moderation shared with Live's tools.
-2. **OpenRe.Stream** — move the restream engine's *config UI and status API* to the domain
+2. **OpenRestream** — move the restream engine's *config UI and status API* to the domain
(Live keeps the ingest processes); a public "what's live where" status page.
(Live keeps the ingest processes); a public "what's live where" status page.
3. **Chat** — `openvibe.chat` embeds Live's chat server with rooms not bound to a stream
3. **Chat** — `openvibe.chat` embeds Live's chat server with rooms not bound to a stream
(global rooms, DMs) and the voice channels; Live embeds it back. One `/ws/chat`, one moderation.
(global rooms, DMs) and the voice channels; Live embeds it back. One `/ws/chat`, one moderation.
@@ -75,6 +75,10 @@ async function setDb(db) { database = db; await attachHelpers(db); }
@@ -75,6 +75,10 @@ async function setDb(db) { database = db; await attachHelpers(db); }
* concurrent writers: every write is an upsert, and the redirect-URI merge takes the row with
* concurrent writers: every write is an upsert, and the redirect-URI merge takes the row with
* SELECT … FOR UPDATE inside a transaction (decision 1).
* SELECT … FOR UPDATE inside a transaction (decision 1).
*/
*/
+// A site's former display names: the seed renames a client still carrying one. The product at openre.stream is
+// OpenRestream (owner, 2026-10-09), never "OpenRe.Stream".
+const FORMER_NAMES = { openre: ['OpenRe.Stream', 'OpenRe'] };
+
async function seedDb(db, { log = console } = {}) {
async function seedDb(db, { log = console } = {}) {
const warn = (m) => log.warn(`[DB] ${m}`);
const warn = (m) => log.warn(`[DB] ${m}`);
@@ -95,7 +99,7 @@ async function seedDb(db, { log = console } = {}) {
@@ -95,7 +99,7 @@ async function seedDb(db, { log = console } = {}) {
{ client_id: 'vip', name: 'OpenVibe.VIP', redirect_uris: ['https://openvibe.vip/auth/callback'] },
{ client_id: 'vip', name: 'OpenVibe.VIP', redirect_uris: ['https://openvibe.vip/auth/callback'] },
{ client_id: 'wiki', name: 'OpenVibe.Wiki', redirect_uris: ['https://openvibe.wiki/auth/callback'] },
{ client_id: 'wiki', name: 'OpenVibe.Wiki', redirect_uris: ['https://openvibe.wiki/auth/callback'] },
{ client_id: 'blog', name: 'OpenVibe.Blog', redirect_uris: ['https://openvibe.blog/auth/callback'] },
{ client_id: 'blog', name: 'OpenVibe.Blog', redirect_uris: ['https://openvibe.blog/auth/callback'] },
- { client_id: 'openre', name: 'OpenRe.Stream', redirect_uris: ['https://openre.stream/auth/callback'] },
+ { client_id: 'openre', name: 'OpenRestream', redirect_uris: ['https://openre.stream/auth/callback'] },
{ client_id: 'reviews', name: 'OpenVibe.Reviews', redirect_uris: ['https://openvibe.reviews/auth/callback'] },
{ client_id: 'reviews', name: 'OpenVibe.Reviews', redirect_uris: ['https://openvibe.reviews/auth/callback'] },
{ client_id: 'news', name: 'OpenVibe.News', redirect_uris: ['https://openvibe.news/auth/callback'] },
{ client_id: 'news', name: 'OpenVibe.News', redirect_uris: ['https://openvibe.news/auth/callback'] },
{ client_id: 'trade', name: 'OpenVibe.Trade', redirect_uris: ['https://openvibe.trade/auth/callback'] },
{ client_id: 'trade', name: 'OpenVibe.Trade', redirect_uris: ['https://openvibe.trade/auth/callback'] },
@@ -136,8 +140,8 @@ async function seedDb(db, { log = console } = {}) {
@@ -136,8 +140,8 @@ async function seedDb(db, { log = console } = {}) {
await mergeRedirectUris(db, c.client_id, c.redirect_uris, { log });
await mergeRedirectUris(db, c.client_id, c.redirect_uris, { log });
// A client created first as a service principal carries the placeholder name "OpenVibe.<id> (service)",
// A client created first as a service principal carries the placeholder name "OpenVibe.<id> (service)",
// which the sign-in page shows people ("Sign in to OpenVibe.food (service)"). Give it the site's name; a
// which the sign-in page shows people ("Sign in to OpenVibe.food (service)"). Give it the site's name; a
- // name someone chose stays.
- if (existing.name === `OpenVibe.${c.client_id} (service)`) {
+ // name someone chose stays. A site's own former name is renamed too (FORMER_NAMES).
+ if (existing.name === `OpenVibe.${c.client_id} (service)` || (FORMER_NAMES[c.client_id] || []).includes(existing.name)) {
await db.prepare('UPDATE oauth_clients SET name = ? WHERE client_id = ?').run(c.name, c.client_id);
await db.prepare('UPDATE oauth_clients SET name = ? WHERE client_id = ?').run(c.name, c.client_id);
log.log(`[DB] OAuth2 client ${c.client_id} renamed to ${c.name}`);
log.log(`[DB] OAuth2 client ${c.client_id} renamed to ${c.name}`);
}
}
@@ -12,7 +12,7 @@ const { exposureOf } = require('../registry/exposure');
@@ -12,7 +12,7 @@ const { exposureOf } = require('../registry/exposure');
// (state: internal = runs on loopback only, placeholder = planned). The two lists cannot disagree.
// (state: internal = runs on loopback only, placeholder = planned). The two lists cannot disagree.
// Since openvibe-contracts 0.42.0 (WS-C task 1) the list is the manifests' `site` blocks, in `position` order:
// Since openvibe-contracts 0.42.0 (WS-C task 1) the list is the manifests' `site` blocks, in `position` order:
// name, icon, tagline, what, the legal profile, and a host only while the manifest has no publicOrigin. A
// name, icon, tagline, what, the legal profile, and a host only while the manifest has no publicOrigin. A
-// site's id is its icon name (live, tools, …, stream for OpenRe.Stream).
+// site's id is its icon name (live, tools, …, stream for OpenRestream).
const RAW_SITES = contracts.services.manifests.filter((m) => m.site).sort((a, b) => (a.site.position ?? 999) - (b.site.position ?? 999)).map((m) => ({
const RAW_SITES = contracts.services.manifests.filter((m) => m.site).sort((a, b) => (a.site.position ?? 999) - (b.site.position ?? 999)).map((m) => ({
id: m.site.icon, name: m.site.name, icon: m.site.icon, service: m.id, profile: m.site.legalProfile, tagline: m.site.tagline, what: m.site.what,
id: m.site.icon, name: m.site.name, icon: m.site.icon, service: m.id, profile: m.site.legalProfile, tagline: m.site.tagline, what: m.site.what,
...(m.site.host ? { host: m.site.host } : {}),
...(m.site.host ? { host: m.site.host } : {}),
@@ -18,7 +18,7 @@ const TYPES = new Set(['tool', 'stream', 'vod', 'clip', 'paste', 'game', 'page',
@@ -18,7 +18,7 @@ const TYPES = new Set(['tool', 'stream', 'vod', 'clip', 'paste', 'game', 'page',
const SERVICE_LABELS = {
const SERVICE_LABELS = {
live: 'Live', tools: 'Tools', games: 'Games', media: 'Media', network: 'Network', community: 'Community', space: 'Space',
live: 'Live', tools: 'Tools', games: 'Games', media: 'Media', network: 'Network', community: 'Community', space: 'Space',
chat: 'Chat', codes: 'Codes', blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', tips: 'Tips',
chat: 'Chat', codes: 'Codes', blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', tips: 'Tips',
- vip: 'VIP', trade: 'Trade', host: 'Host', deals: 'Deals', coupons: 'Coupons', openre: 'OpenRe.Stream',
+ vip: 'VIP', trade: 'Trade', host: 'Host', deals: 'Deals', coupons: 'Coupons', openre: 'OpenRestream',
};
};
async function ensureSchema(db) { /* the schema is migrations/NNNN_*.sql (plan T2); nothing is created at runtime */ }
async function ensureSchema(db) { /* the schema is migrations/NNNN_*.sql (plan T2); nothing is created at runtime */ }
@@ -64,7 +64,7 @@ function intents(catalog) {
@@ -64,7 +64,7 @@ function intents(catalog) {
}
}
// The network as a constellation: open sites on the inner ring, the ones opening next faint on the outer ring.
// The network as a constellation: open sites on the inner ring, the ones opening next faint on the outer ring.
-const SHORT = (site) => (/^OpenRe\./.test(site.name) ? 'OpenRe' : site.name.replace(/^OpenVibe\./, ''));
+const SHORT = (site) => site.name.replace(/^OpenVibe\./, '');
function constellation() {
function constellation() {
const ring = (list, r, start) => list.map((site, i) => {
const ring = (list, r, start) => list.map((site, i) => {
const a = start + (i / list.length) * Math.PI * 2;
const a = start + (i / list.length) * Math.PI * 2;
@@ -78,8 +78,8 @@ const DEFAULT_GRANTS = [
@@ -78,8 +78,8 @@ const DEFAULT_GRANTS = [
['host', 'media.object.read', 'openvibe.media', ['host', 'host.*']],
['host', 'media.object.read', 'openvibe.media', ['host', 'host.*']],
['host', 'media.object.list', 'openvibe.media', ['host', 'host.*']],
['host', 'media.object.list', 'openvibe.media', ['host', 'host.*']],
['host', 'media.object.delete', 'openvibe.media', ['host', 'host.*']],
['host', 'media.object.delete', 'openvibe.media', ['host', 'host.*']],
- // Plan T4 (Media cleanup): Live and OpenRe reach Media with their own tokens instead of API keys. Live keeps
- // its objects under the live namespace (VOD/clip/thumbnail uploads, reads, lists, deletes); OpenRe uploads the
+ // Plan T4 (Media cleanup): Live and OpenRestream reach Media with their own tokens instead of API keys. Live keeps
+ // its objects under the live namespace (VOD/clip/thumbnail uploads, reads, lists, deletes); OpenRestream uploads the
// recordings and thumbnails it produces there too.
// recordings and thumbnails it produces there too.
...['media.object.read', 'media.object.list', 'media.object.upload', 'media.object.delete'].map(cap => ['live', cap, 'openvibe.media', ['live']]),
...['media.object.read', 'media.object.list', 'media.object.upload', 'media.object.delete'].map(cap => ['live', cap, 'openvibe.media', ['live']]),
['openre', 'media.object.upload', 'openvibe.media', ['live']],
['openre', 'media.object.upload', 'openvibe.media', ['live']],
@@ -118,7 +118,7 @@ const DEFAULT_GRANTS = [
@@ -118,7 +118,7 @@ const DEFAULT_GRANTS = [
['tips', 'chat.message.send', 'openvibe.chat', []],
['tips', 'chat.message.send', 'openvibe.chat', []],
['tips', 'chat.event.publish', 'openvibe.chat', []],
['tips', 'chat.event.publish', 'openvibe.chat', []],
['live', 'tips.interaction.record', 'openvibe.tips', []],
['live', 'tips.interaction.record', 'openvibe.tips', []],
- // Wave 7: Live manages its slots' streams, keys and sessions on OpenRe.Stream; OpenRe publishes
+ // Wave 7: Live manages its slots' streams, keys and sessions on OpenRestream; OpenRestream publishes
// session/output events (Live consumes openre.session.* by webhook).
// session/output events (Live consumes openre.session.* by webhook).
...['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read'].map(c => ['live', c, 'openvibe.openre', []]),
...['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read'].map(c => ['live', c, 'openvibe.openre', []]),
['openre', 'events.event.publish', 'openvibe.events', []],
['openre', 'events.event.publish', 'openvibe.events', []],
@@ -194,7 +194,7 @@ const DEFAULT_GRANTS = [
@@ -194,7 +194,7 @@ const DEFAULT_GRANTS = [
['bot', 'network.node.manage', SELF_AUDIENCE, []],
['bot', 'network.node.manage', SELF_AUDIENCE, []],
// Plan T15: an owner adds an operator by @username on the robot's panel; Bot resolves it to the subject here.
// Plan T15: an owner adds an operator by @username on the robot's panel; Bot resolves it to the subject here.
['bot', 'identity.subject.resolve', SELF_AUDIENCE, []],
['bot', 'identity.subject.resolve', SELF_AUDIENCE, []],
- // Plan T15: Bot runs each robot's OpenRe stream for its owner (find/create/archive, key rotation), plays the live
+ // Plan T15: Bot runs each robot's OpenRestream stream for its owner (find/create/archive, key rotation), plays the live
// session in the panel (session.read), restreams out (output.*) and sets the owner's streaming and recording
// session in the panel (session.read), restreams out (output.*) and sets the owner's streaming and recording
// toggles on the stream, minting its own token with its Network client instead of a hand-set BOT_OPENRE_TOKEN.
// toggles on the stream, minting its own token with its Network client instead of a hand-set BOT_OPENRE_TOKEN.
...['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read', 'openre.output.read', 'openre.output.write'].map(c => ['bot', c, 'openvibe.openre', []]),
...['openre.stream.read', 'openre.stream.write', 'openre.key.rotate', 'openre.session.read', 'openre.output.read', 'openre.output.write'].map(c => ['bot', c, 'openvibe.openre', []]),
@@ -273,7 +273,7 @@ const DEFAULT_GRANTS = [
@@ -273,7 +273,7 @@ const DEFAULT_GRANTS = [
['live', 'community.paste.create', 'openvibe.community', []],
['live', 'community.paste.create', 'openvibe.community', []],
['live', 'community.paste.write', 'openvibe.community', []],
['live', 'community.paste.write', 'openvibe.community', []],
['live', 'community.paste.moderate', 'openvibe.community', []],
['live', 'community.paste.moderate', 'openvibe.community', []],
- // One canonical channel/owner resolver (roadmap §10.5/§15.10, D20-R1): OpenRe, Media and Community (Pulse)
+ // One canonical channel/owner resolver (roadmap §10.5/§15.10, D20-R1): OpenRestream, Media and Community (Pulse)
// resolve channels, streams, VODs and clips through Live's /internal/lineage/resolve instead of their own
// resolve channels, streams, VODs and clips through Live's /internal/lineage/resolve instead of their own
// channel mappings.
// channel mappings.
...['openre', 'media', 'community'].map(c => [c, 'live.lineage.resolve', 'openvibe.live', []]),
...['openre', 'media', 'community'].map(c => [c, 'live.lineage.resolve', 'openvibe.live', []]),
@@ -70,7 +70,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl
@@ -70,7 +70,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl
}
}
const SITES = ['live', 'network', 'tools', 'media', 'community', 'space', 'chat', 'games', 'blog', 'wiki', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'services', 'host', 'ai', 'search', 'sources', 'events', 'billing', 'tips', 'vip', 'openre', 'bot', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'media-hub', 'inventory', 'sites'];
const SITES = ['live', 'network', 'tools', 'media', 'community', 'space', 'chat', 'games', 'blog', 'wiki', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes', 'services', 'host', 'ai', 'search', 'sources', 'events', 'billing', 'tips', 'vip', 'openre', 'bot', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'media-hub', 'inventory', 'sites'];
-const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };
+const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRestream' };
const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');
const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');
/** A registry id from an id or a hostname (openvibe.wiki → wiki, pdf.openvibe.tools → tools). */
/** A registry id from an id or a hostname (openvibe.wiki → wiki, pdf.openvibe.tools → tools). */
@@ -95,7 +95,7 @@ const server = http.createServer(app);
@@ -95,7 +95,7 @@ const server = http.createServer(app);
await db.prepare("UPDATE oauth_clients SET client_secret = 'openre-secret' WHERE client_id = 'openre'").run();
await db.prepare("UPDATE oauth_clients SET client_secret = 'openre-secret' WHERE client_id = 'openre'").run();
t = await token({ client_id: 'openre', client_secret: 'openre-secret', audience: 'openvibe.network' });
t = await token({ client_id: 'openre', client_secret: 'openre-secret', audience: 'openvibe.network' });
assert.strictEqual(t.status, 400); assert.strictEqual(t.body.error, 'invalid_scope', 'a client with no grants gets no token');
assert.strictEqual(t.status, 400); assert.strictEqual(t.body.error, 'invalid_scope', 'a client with no grants gets no token');
- // The canonical channel/owner resolver on Live (D20-R1): OpenRe, Media and Community hold it, nobody else.
+ // The canonical channel/owner resolver on Live (D20-R1): OpenRestream, Media and Community hold it, nobody else.
await db.prepare("UPDATE oauth_clients SET client_secret = 'community-secret' WHERE client_id = 'community'").run();
await db.prepare("UPDATE oauth_clients SET client_secret = 'community-secret' WHERE client_id = 'community'").run();
for (const [client, secret] of [['openre', 'openre-secret'], ['media', 'media-secret'], ['community', 'community-secret']]) {
for (const [client, secret] of [['openre', 'openre-secret'], ['media', 'media-secret'], ['community', 'community-secret']]) {
t = await token({ client_id: client, client_secret: secret, audience: 'openvibe.live' });
t = await token({ client_id: client, client_secret: secret, audience: 'openvibe.live' });
@@ -212,14 +212,14 @@ const server = http.createServer(app);
@@ -212,14 +212,14 @@ const server = http.createServer(app);
assert.strictEqual(ub.status, 200, `${client}: ${JSON.stringify(ub.body)}`);
assert.strictEqual(ub.status, 200, `${client}: ${JSON.stringify(ub.body)}`);
assert.strictEqual(ub.body.scope, 'billing.usage.record', `${client} records usage on Billing and holds no money capability`);
assert.strictEqual(ub.body.scope, 'billing.usage.record', `${client} records usage on Billing and holds no money capability`);
}
}
- // Plan T15: Bot mints its own OpenRe token for the robots' streams, sessions, outputs and toggles; nothing beyond OpenRe's
+ // Plan T15: Bot mints its own OpenRestream token for the robots' streams, sessions, outputs and toggles; nothing beyond OpenRestream's
// stream, key, session-read and output capabilities (never openre.session.end).
// stream, key, session-read and output capabilities (never openre.session.end).
const bn = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.network', scope: 'identity.subject.resolve' });
const bn = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.network', scope: 'identity.subject.resolve' });
assert.strictEqual(bn.status, 200, JSON.stringify(bn.body));
assert.strictEqual(bn.status, 200, JSON.stringify(bn.body));
assert.strictEqual(bn.body.scope, 'identity.subject.resolve', 'Bot resolves an operator\'s @username');
assert.strictEqual(bn.body.scope, 'identity.subject.resolve', 'Bot resolves an operator\'s @username');
const bo = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.openre' });
const bo = await token({ client_id: 'bot', client_secret: 'bot-secret', audience: 'openvibe.openre' });
assert.strictEqual(bo.status, 200, JSON.stringify(bo.body));
assert.strictEqual(bo.status, 200, JSON.stringify(bo.body));
- assert.deepStrictEqual(bo.body.scope.split(' ').sort(), ['openre.key.rotate', 'openre.output.read', 'openre.output.write', 'openre.session.read', 'openre.stream.read', 'openre.stream.write'], "Bot runs its robots' OpenRe streams");
+ assert.deepStrictEqual(bo.body.scope.split(' ').sort(), ['openre.key.rotate', 'openre.output.read', 'openre.output.write', 'openre.session.read', 'openre.stream.read', 'openre.stream.write'], "Bot runs its robots' OpenRestream streams");
// Coupons subscribes to its Events sources with its own token (OpenVibe.Coupons scripts/subscribe.js).
// Coupons subscribes to its Events sources with its own token (OpenVibe.Coupons scripts/subscribe.js).
await db.prepare("UPDATE oauth_clients SET client_secret = 'coupons-secret' WHERE client_id = 'coupons'").run();
await db.prepare("UPDATE oauth_clients SET client_secret = 'coupons-secret' WHERE client_id = 'coupons'").run();
const cps = await token({ client_id: 'coupons', client_secret: 'coupons-secret', audience: 'openvibe.events', scope: 'events.subscription.manage' });
const cps = await token({ client_id: 'coupons', client_secret: 'coupons-secret', audience: 'openvibe.events', scope: 'events.subscription.manage' });