openvibers/openvibe.network Commits
Network: OpenVibe.Quest, OpenVibe.Rent and OpenVibe.Watch sign in; Quest may credit OpenCoins, resolve subjects and subscribe to events; contracts v0.117.0 (#89)
Changes
@@@ -21,7 +21,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0","pg": "^8.23.0",@@@ -1556,9 +1556,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.116.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",-- "integrity": "sha512-mDqQJ6m8YSJf3UV+kVpqeMrgNDMMzBQdYmj/3o5JqGegj52ymiDH1NerVW6c+0ebsV3zX7RbRlknIy3vr634yw==",++ "version": "0.117.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",++ "integrity": "sha512-LOtF5neI7iUp50wrymf7gVkigZHTmzri6NJcrZ4PN3aPjp0CFg4oIY+2vyPRSP4j1A+vZ+YQK78OytXeRr1s4Q==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -30,7 +30,7 @@"iovalkey": "^0.4.0","jsonwebtoken": "^9.0.0","multer": "^1.4.5-lts.1",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0","pg": "^8.23.0",@@@ -114,6 +114,10 @@ async function seedDb(db, { log = console } = {}) {{ client_id: 'food', name: 'OpenVibe.Food', redirect_uris: ['https://openvibe.food/auth/callback'] },{ client_id: 'help', name: 'OpenVibe.Help', redirect_uris: ['https://openvibe.help/auth/callback'] },{ client_id: 'work', name: 'OpenVibe.Work', redirect_uris: ['https://openvibe.work/auth/callback'] },++ // The shared quest log, listings people post, and Watch's public site (plan T18 step 8, T19).++ { client_id: 'quest', name: 'OpenVibe.Quest', redirect_uris: ['https://openvibe.quest/auth/callback'] },++ { client_id: 'rent', name: 'OpenVibe.Rent', redirect_uris: ['https://openvibe.rent/auth/callback'] },++ { client_id: 'watch', name: 'OpenVibe.Watch', redirect_uris: ['https://openvibe.watch/auth/callback'] },];let seededAny = false;for (const c of contractClients) {@@@ -150,6 +154,9 @@ async function seedDb(db, { log = console } = {}) {{ clientId: 'work', extraUris: ['http://localhost:4960/auth/callback'] },{ clientId: 'food', extraUris: ['http://localhost:4970/auth/callback'] },{ clientId: 'help', extraUris: ['http://localhost:5020/auth/callback'] },++ { clientId: 'quest', extraUris: ['http://localhost:4980/auth/callback'] },++ { clientId: 'rent', extraUris: ['http://localhost:5010/auth/callback'] },++ { clientId: 'watch', extraUris: ['http://localhost:4730/auth/callback'] },];for (const { clientId, extraUris } of localClients) {try { await mergeRedirectUris(db, clientId, extraUris, { log, local: true }); }@@@ -50,6 +50,9 @@ const INTENTS = [['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate research router openrouter do it for me'],['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],['food', 'fa-utensils', 'Eat well for less', 'Food banks and budget grocers near you, meal plans for your budget.', 'https://openvibe.food/', 'food eat meal meals plan grocery groceries cheap budget recipes food bank pantry'],++ ['quest', 'fa-medal', 'Earn badges', 'Quests across the network for the things you already do there.', 'https://openvibe.quest/', 'quest quests badge badges achievement achievements rewards opencoins earn'],++ ['rent', 'fa-house', 'Rent a place', 'Rooms, homes, workspace, parking and gear, posted by people.', 'https://openvibe.rent/', 'rent rental rentals apartment room house flat lease parking storage equipment'],++ ['watch', 'fa-bell', 'Get told when it changes', 'Watch a page, a feed or an API and hear when your value moves.', 'https://openvibe.watch/', 'watch monitor alert alerts notify change changes price drop feed rss tracker uptime'],['help', 'fa-life-ring', 'Get help', 'Every OpenVibe site\'s questions answered, and a person when they are not.', 'https://openvibe.help/', 'help support faq question questions ticket contact problem how'],];@@@ -21,6 +21,11 @@ const CHAT_NAMESPACES = ['chat.preferences', 'chat.tts_defaults', 'chat.dm_setti// Initial grants: what each service does against Network today.const DEFAULT_GRANTS = [['live', 'network.coins.credit', SELF_AUDIENCE, ['live']],++ // OpenVibe.Quest pays a completed quest's OpenCoins (app_id quest, idempotency_key = the completion id), resolving++ // the person's subject to the wallet first, and subscribes to the events it counts (plan T19).++ ['quest', 'network.coins.credit', SELF_AUDIENCE, ['quest']],++ ['quest', 'identity.subject.resolve', SELF_AUDIENCE, []],++ ['quest', 'events.subscription.manage', 'openvibe.events', []],['live', 'network.coins.debit', SELF_AUDIENCE, ['live']],['live', 'network.notifications.push', SELF_AUDIENCE, ['live']],// User modules: each service reads and writes the namespaces it owns (openvibe-contracts manifests/namespaces).@@@ -69,7 +69,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = glreturn { router: r, feed, queryOf };}--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', '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', 'sites'];const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');@@@ -14,7 +14,7 @@ assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');assert.equal(siteForHost('evil.example'), null);assert.strictEqual(svc.ranking().at, null, 'cold start: no use counted yet (the registry\'s featured list says so)');let p = svc.payloadFor('openvibe.media');--assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'space', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'bot', 'codes', 'actor', 'food', 'help', 'work'], 'cold start follows the base order');++assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'space', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch'], 'cold start follows the base order');assert.strictEqual(p.nav.find(n => n.id === 'space').url, 'https://openvibe.space/', 'the forum goes to Space');assert.strictEqual(p.nav.find(n => n.id === 'community').url, 'https://openvibe.community/', 'Community remains for pastes and Pulse');assert.equal(p.footer.legal.dmca, 'https://openvibe.media/dmca', 'legal links stay on the site\'s own domain');@@@ -24,10 +24,11 @@ const PINNED = {bot: 'live', zone: 'placeholder',actor: 'live', // OpenVibe's own agent and the router for agent work at openvibe.actor since 2026-10-08 (contracts 0.115.0)food: 'live', help: 'live', work: 'live', // services since contracts 0.116.0 (plan T19, 2026-10-08)++ quest: 'live', rent: 'live', // services since contracts 0.117.0run: 'internal', // OpenVibe.Run runs on loopback since 2026-10-07 (contracts 0.109.0)services: 'live', // OpenVibe.Services: the developer platform at openvibe.services since 2026-10-08 (contracts 0.113.0; the portal moved from Codes)// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).-- watch: 'internal', // OpenVibe.Watch runs on loopback since 2026-10-07 (contracts 0.108.0)++ watch: 'live', // OpenVibe.Watch's public site since contracts 0.117.0 (loopback service since 0.108.0)sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',examples: 'repository',};@@@ -111,8 +112,8 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], chassert.strictEqual(sBy.live.origin, 'https://openvibe.live');assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),-- // v0.95.0 Services; Space and AI went live in v0.110.0; Food, Help and Work in v0.116.0; none is retired-- assert.deepStrictEqual(st.exposure_summary, { live: 23, internal: 11, library: 4, repository: 1, placeholder: 2, retired: 0 });++ // v0.95.0 Services; Space and AI went live in v0.110.0; Food, Help and Work in v0.116.0; Quest, Rent and Watch in v0.117.0; none is retired++ assert.deepStrictEqual(st.exposure_summary, { live: 26, internal: 10, library: 4, repository: 1, placeholder: 2, retired: 0 });// The page: no row for a service whose domain serves a placeholder shows a bare "Up".const html = await fetch(base + '/status').then(x => x.text());@@@ -128,7 +129,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch// 3. The Frame: the nav lists only sites whose domain serves the service; the rest are soon with a reason.const open = SITES.filter(s => s.status === 'open').map(s => s.id);-- assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'space', 'bot', 'codes', 'actor', 'food', 'help', 'work']); // by site position; bot: a site since openvibe-contracts 0.106.0; host: live since 0.107.0; space: live since 0.110.0 (the forum, 2026-10-07)++ assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'space', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch']); // by site position; bot: a site since openvibe-contracts 0.106.0; host: live since 0.107.0; space: live since 0.110.0 (the forum, 2026-10-07)for (const s of SITES) {assert.ok(s.service && PINNED[s.service], `${s.id} names its service`);assert.strictEqual(s.status === 'open', PINNED[s.service] === 'live', `${s.id} nav status follows exposure`);
@@ -21,7 +21,7 @@
@@ -21,7 +21,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -1556,9 +1556,9 @@
@@ -1556,9 +1556,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.116.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",
- "integrity": "sha512-mDqQJ6m8YSJf3UV+kVpqeMrgNDMMzBQdYmj/3o5JqGegj52ymiDH1NerVW6c+0ebsV3zX7RbRlknIy3vr634yw==",
+ "version": "0.117.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
+ "integrity": "sha512-LOtF5neI7iUp50wrymf7gVkigZHTmzri6NJcrZ4PN3aPjp0CFg4oIY+2vyPRSP4j1A+vZ+YQK78OytXeRr1s4Q==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.116.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.1",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -114,6 +114,10 @@ async function seedDb(db, { log = console } = {}) {
@@ -114,6 +114,10 @@ async function seedDb(db, { log = console } = {}) {
{ client_id: 'food', name: 'OpenVibe.Food', redirect_uris: ['https://openvibe.food/auth/callback'] },
{ client_id: 'food', name: 'OpenVibe.Food', redirect_uris: ['https://openvibe.food/auth/callback'] },
{ client_id: 'help', name: 'OpenVibe.Help', redirect_uris: ['https://openvibe.help/auth/callback'] },
{ client_id: 'help', name: 'OpenVibe.Help', redirect_uris: ['https://openvibe.help/auth/callback'] },
{ client_id: 'work', name: 'OpenVibe.Work', redirect_uris: ['https://openvibe.work/auth/callback'] },
{ client_id: 'work', name: 'OpenVibe.Work', redirect_uris: ['https://openvibe.work/auth/callback'] },
+ // The shared quest log, listings people post, and Watch's public site (plan T18 step 8, T19).
+ { client_id: 'quest', name: 'OpenVibe.Quest', redirect_uris: ['https://openvibe.quest/auth/callback'] },
+ { client_id: 'rent', name: 'OpenVibe.Rent', redirect_uris: ['https://openvibe.rent/auth/callback'] },
+ { client_id: 'watch', name: 'OpenVibe.Watch', redirect_uris: ['https://openvibe.watch/auth/callback'] },
];
];
let seededAny = false;
let seededAny = false;
for (const c of contractClients) {
for (const c of contractClients) {
@@ -150,6 +154,9 @@ async function seedDb(db, { log = console } = {}) {
@@ -150,6 +154,9 @@ async function seedDb(db, { log = console } = {}) {
{ clientId: 'work', extraUris: ['http://localhost:4960/auth/callback'] },
{ clientId: 'work', extraUris: ['http://localhost:4960/auth/callback'] },
{ clientId: 'food', extraUris: ['http://localhost:4970/auth/callback'] },
{ clientId: 'food', extraUris: ['http://localhost:4970/auth/callback'] },
{ clientId: 'help', extraUris: ['http://localhost:5020/auth/callback'] },
{ clientId: 'help', extraUris: ['http://localhost:5020/auth/callback'] },
+ { clientId: 'quest', extraUris: ['http://localhost:4980/auth/callback'] },
+ { clientId: 'rent', extraUris: ['http://localhost:5010/auth/callback'] },
+ { clientId: 'watch', extraUris: ['http://localhost:4730/auth/callback'] },
];
];
for (const { clientId, extraUris } of localClients) {
for (const { clientId, extraUris } of localClients) {
try { await mergeRedirectUris(db, clientId, extraUris, { log, local: true }); }
try { await mergeRedirectUris(db, clientId, extraUris, { log, local: true }); }
@@ -50,6 +50,9 @@ const INTENTS = [
@@ -50,6 +50,9 @@ const INTENTS = [
['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate research router openrouter do it for me'],
['actor', 'fa-user-astronaut', 'Give a task to an agent', 'Actor picks the best agent for it, checks the answer and shows the cost and why.', 'https://openvibe.actor/', 'agent agents ai assistant task tasks automate research router openrouter do it for me'],
['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],
['work', 'fa-briefcase', 'Find work', 'Jobs from open boards in one search, each linked to where you apply.', 'https://openvibe.work/', 'job jobs work career hiring remote employment apply listings'],
['food', 'fa-utensils', 'Eat well for less', 'Food banks and budget grocers near you, meal plans for your budget.', 'https://openvibe.food/', 'food eat meal meals plan grocery groceries cheap budget recipes food bank pantry'],
['food', 'fa-utensils', 'Eat well for less', 'Food banks and budget grocers near you, meal plans for your budget.', 'https://openvibe.food/', 'food eat meal meals plan grocery groceries cheap budget recipes food bank pantry'],
+ ['quest', 'fa-medal', 'Earn badges', 'Quests across the network for the things you already do there.', 'https://openvibe.quest/', 'quest quests badge badges achievement achievements rewards opencoins earn'],
+ ['rent', 'fa-house', 'Rent a place', 'Rooms, homes, workspace, parking and gear, posted by people.', 'https://openvibe.rent/', 'rent rental rentals apartment room house flat lease parking storage equipment'],
+ ['watch', 'fa-bell', 'Get told when it changes', 'Watch a page, a feed or an API and hear when your value moves.', 'https://openvibe.watch/', 'watch monitor alert alerts notify change changes price drop feed rss tracker uptime'],
['help', 'fa-life-ring', 'Get help', 'Every OpenVibe site\'s questions answered, and a person when they are not.', 'https://openvibe.help/', 'help support faq question questions ticket contact problem how'],
['help', 'fa-life-ring', 'Get help', 'Every OpenVibe site\'s questions answered, and a person when they are not.', 'https://openvibe.help/', 'help support faq question questions ticket contact problem how'],
];
];
@@ -21,6 +21,11 @@ const CHAT_NAMESPACES = ['chat.preferences', 'chat.tts_defaults', 'chat.dm_setti
@@ -21,6 +21,11 @@ const CHAT_NAMESPACES = ['chat.preferences', 'chat.tts_defaults', 'chat.dm_setti
// Initial grants: what each service does against Network today.
// Initial grants: what each service does against Network today.
const DEFAULT_GRANTS = [
const DEFAULT_GRANTS = [
['live', 'network.coins.credit', SELF_AUDIENCE, ['live']],
['live', 'network.coins.credit', SELF_AUDIENCE, ['live']],
+ // OpenVibe.Quest pays a completed quest's OpenCoins (app_id quest, idempotency_key = the completion id), resolving
+ // the person's subject to the wallet first, and subscribes to the events it counts (plan T19).
+ ['quest', 'network.coins.credit', SELF_AUDIENCE, ['quest']],
+ ['quest', 'identity.subject.resolve', SELF_AUDIENCE, []],
+ ['quest', 'events.subscription.manage', 'openvibe.events', []],
['live', 'network.coins.debit', SELF_AUDIENCE, ['live']],
['live', 'network.coins.debit', SELF_AUDIENCE, ['live']],
['live', 'network.notifications.push', SELF_AUDIENCE, ['live']],
['live', 'network.notifications.push', SELF_AUDIENCE, ['live']],
// User modules: each service reads and writes the namespaces it owns (openvibe-contracts manifests/namespaces).
// User modules: each service reads and writes the namespaces it owns (openvibe-contracts manifests/namespaces).
@@ -69,7 +69,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl
@@ -69,7 +69,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', fetchImpl = gl
return { router: r, feed, queryOf };
return { router: r, feed, queryOf };
}
}
-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', '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', 'sites'];
const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };
const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRe' };
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) : '');
@@ -14,7 +14,7 @@ assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');
@@ -14,7 +14,7 @@ assert.equal(siteForHost('yt.openvibe.tools').id, 'tools');
assert.equal(siteForHost('evil.example'), null);
assert.equal(siteForHost('evil.example'), null);
assert.strictEqual(svc.ranking().at, null, 'cold start: no use counted yet (the registry\'s featured list says so)');
assert.strictEqual(svc.ranking().at, null, 'cold start: no use counted yet (the registry\'s featured list says so)');
let p = svc.payloadFor('openvibe.media');
let p = svc.payloadFor('openvibe.media');
-assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'space', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'bot', 'codes', 'actor', 'food', 'help', 'work'], 'cold start follows the base order');
+assert.deepEqual(p.nav.map(n => n.id), ['live', 'tools', 'space', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch'], 'cold start follows the base order');
assert.strictEqual(p.nav.find(n => n.id === 'space').url, 'https://openvibe.space/', 'the forum goes to Space');
assert.strictEqual(p.nav.find(n => n.id === 'space').url, 'https://openvibe.space/', 'the forum goes to Space');
assert.strictEqual(p.nav.find(n => n.id === 'community').url, 'https://openvibe.community/', 'Community remains for pastes and Pulse');
assert.strictEqual(p.nav.find(n => n.id === 'community').url, 'https://openvibe.community/', 'Community remains for pastes and Pulse');
assert.equal(p.footer.legal.dmca, 'https://openvibe.media/dmca', 'legal links stay on the site\'s own domain');
assert.equal(p.footer.legal.dmca, 'https://openvibe.media/dmca', 'legal links stay on the site\'s own domain');
@@ -24,10 +24,11 @@ const PINNED = {
@@ -24,10 +24,11 @@ const PINNED = {
bot: 'live', zone: 'placeholder',
bot: 'live', zone: 'placeholder',
actor: 'live', // OpenVibe's own agent and the router for agent work at openvibe.actor since 2026-10-08 (contracts 0.115.0)
actor: 'live', // OpenVibe's own agent and the router for agent work at openvibe.actor since 2026-10-08 (contracts 0.115.0)
food: 'live', help: 'live', work: 'live', // services since contracts 0.116.0 (plan T19, 2026-10-08)
food: 'live', help: 'live', work: 'live', // services since contracts 0.116.0 (plan T19, 2026-10-08)
+ quest: 'live', rent: 'live', // services since contracts 0.117.0
run: 'internal', // OpenVibe.Run runs on loopback since 2026-10-07 (contracts 0.109.0)
run: 'internal', // OpenVibe.Run runs on loopback since 2026-10-07 (contracts 0.109.0)
services: 'live', // OpenVibe.Services: the developer platform at openvibe.services since 2026-10-08 (contracts 0.113.0; the portal moved from Codes)
services: 'live', // OpenVibe.Services: the developer platform at openvibe.services since 2026-10-08 (contracts 0.113.0; the portal moved from Codes)
// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).
// openvibe-contracts v0.102.0: OpenVibe.Watch is a charter only (plan T18).
- watch: 'internal', // OpenVibe.Watch runs on loopback since 2026-10-07 (contracts 0.108.0)
+ watch: 'live', // OpenVibe.Watch's public site since contracts 0.117.0 (loopback service since 0.108.0)
sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',
sdk: 'library', shared: 'library', contracts: 'library', publishing: 'library',
examples: 'repository',
examples: 'repository',
};
};
@@ -111,8 +112,8 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
@@ -111,8 +112,8 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
assert.strictEqual(sBy.live.origin, 'https://openvibe.live');
assert.strictEqual(sBy.live.origin, 'https://openvibe.live');
assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);
assert.strictEqual(sBy.shared.label, `not running (library, released v${require('openvibe-shared/package.json').version})`);
// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),
// Contracts 0.83.0 added two placeholder services (media-hub, space), v0.86-v0.90 Bot (live) and two more (zone, run),
- // v0.95.0 Services; Space and AI went live in v0.110.0; Food, Help and Work in v0.116.0; none is retired
- assert.deepStrictEqual(st.exposure_summary, { live: 23, internal: 11, library: 4, repository: 1, placeholder: 2, retired: 0 });
+ // v0.95.0 Services; Space and AI went live in v0.110.0; Food, Help and Work in v0.116.0; Quest, Rent and Watch in v0.117.0; none is retired
+ assert.deepStrictEqual(st.exposure_summary, { live: 26, internal: 10, library: 4, repository: 1, placeholder: 2, retired: 0 });
// The page: no row for a service whose domain serves a placeholder shows a bare "Up".
// The page: no row for a service whose domain serves a placeholder shows a bare "Up".
const html = await fetch(base + '/status').then(x => x.text());
const html = await fetch(base + '/status').then(x => x.text());
@@ -128,7 +129,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
@@ -128,7 +129,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
// 3. The Frame: the nav lists only sites whose domain serves the service; the rest are soon with a reason.
// 3. The Frame: the nav lists only sites whose domain serves the service; the rest are soon with a reason.
const open = SITES.filter(s => s.status === 'open').map(s => s.id);
const open = SITES.filter(s => s.status === 'open').map(s => s.id);
- assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'space', 'bot', 'codes', 'actor', 'food', 'help', 'work']); // by site position; bot: a site since openvibe-contracts 0.106.0; host: live since 0.107.0; space: live since 0.110.0 (the forum, 2026-10-07)
+ assert.deepStrictEqual(open, ['live', 'tools', 'community', 'games', 'media', 'network', 'chat', 'services', 'blog', 'wiki', 'host', 'space', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch']); // by site position; bot: a site since openvibe-contracts 0.106.0; host: live since 0.107.0; space: live since 0.110.0 (the forum, 2026-10-07)
for (const s of SITES) {
for (const s of SITES) {
assert.ok(s.service && PINNED[s.service], `${s.id} names its service`);
assert.ok(s.service && PINNED[s.service], `${s.id} names its service`);
assert.strictEqual(s.status === 'open', PINNED[s.service] === 'live', `${s.id} nav status follows exposure`);
assert.strictEqual(s.status === 'open', PINNED[s.service] === 'live', `${s.id} nav status follows exposure`);