openvibers/openvibe.network Commits

Network: OpenVibe.Play joins (grants, sign-in client, home intent; contracts 0.172.1, shared 4.1.4) (#189)

OP OpenVibers committed 30 days ago · 791067e555 · 9 files, +26 −16

OpenVibe.Play (contracts 0.172.0/0.172.1, Games ADR-0007 decision 10) joins the network.

- **Grants:** `games` → `play.title.manage`, `play.result.write`, `play.player.read` on `openvibe.play` (Games registers Scraplandia and Salvage Rush and posts their scores and unlocks); `play` → `events.subscription.manage`. Play's account grants (export.contribute, deletion.confirm) follow once it is subscribed, as Codes' did.
- **Sign-in client** `play` (https://play.openvibe.network/auth/callback; local :5040).
- **Home:** a "Climb the leaderboards" intent; updates feed knows `play`.
- **Pins:** contracts v0.172.1 (Play's site icon is `play`), shared v4.1.4 (the `play` glyph).
- **Tests:** frame order and registry exposure count Play; the home page budget grows with the network (134 KB raw, 26.1 KB br measured; budgets 140/28).

`npm test`: 121/121.

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 68fc3129..5c21b3fc 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -20,9 +20,9 @@
"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.170.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.172.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.44.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.4",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
@@@ -1320,9 +1320,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.170.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.170.0",
-- "integrity": "sha512-EpOUiqFSYBZ1p7aQTek1IYyHcbD9QT0pdr492+teWvZf425xXSMu7+DQsnzgMP8JdTmdS2VMxoirrBwMUGxjrw==",
++ "version": "0.172.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.172.1",
++ "integrity": "sha512-1nzYXTf/j4Iu+8NkGFb9xWtSNB/UgAS8ZxICngAGh4XPlc9NJzOGImuYY8b2eFlReDOAcq9nIA1GV+wDZxH94w==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1361,9 +1361,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "4.1.3",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
-- "integrity": "sha512-mXLBDTfoFWqnpHQdgpMvSx02v6m9vFy7k3RpKvltK7q4SSM4WQxa2jXzRGpHk0yVfIcoxDH6QqDWx0wNKobulA==",
++ "version": "4.1.4",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.4",
++ "integrity": "sha512-tw1/uDkSj/js+mQjrabMGcZ6mfKRIBQLMd8SvCac2E2RAKiralwDKR+qNZL6oB8qB5ILEx6s8fJylQwVZr6poA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 504ed50e..4f759bba 100644
---- a/package.json
++++ b/package.json
@@@ -24,9 +24,9 @@
"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.170.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.172.1",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.44.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.4",
"pg": "^8.23.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
ddiff --git a/server/db/database.js b/server/db/database.js
iindex 5bfcdc2c..7c94a8a7 100644
---- a/server/db/database.js
++++ b/server/db/database.js
@@@ -126,6 +126,8 @@ async function seedDb(db, { log = console } = {}) {
{ client_id: 'media-hub', name: 'OpenVibe.Download', redirect_uris: ['https://openvibe.download/auth/callback', 'https://openvibe.video/auth/callback'] },
// The network-wide inventory (ADR-054, plan T21).
{ client_id: 'inventory', name: 'OpenVibe.Inventory', redirect_uris: ['https://inventory.openvibe.network/auth/callback'] },
++ // Achievements and leaderboards for every game (Games ADR-0007 decision 10).
++ { client_id: 'play', name: 'OpenVibe.Play', redirect_uris: ['https://play.openvibe.network/auth/callback'] },
];
let seededAny = false;
for (const c of contractClients) {
@@@ -167,6 +169,7 @@ async function seedDb(db, { log = console } = {}) {
{ clientId: 'watch', extraUris: ['http://localhost:4730/auth/callback'] },
{ clientId: 'media-hub', extraUris: ['http://localhost:4990/auth/callback'] },
{ clientId: 'inventory', extraUris: ['http://localhost:5030/auth/callback'] },
++ { clientId: 'play', extraUris: ['http://localhost:5040/auth/callback'] },
];
for (const { clientId, extraUris } of localClients) {
try { await mergeRedirectUris(db, clientId, extraUris, { log, local: true }); }
ddiff --git a/server/home/render.js b/server/home/render.js
iindex e3d268c4..c7595474 100644
---- a/server/home/render.js
++++ b/server/home/render.js
@@@ -55,6 +55,7 @@ const INTENTS = [
['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'],
['video', 'fa-film', 'Watch past streams', 'Recordings and clips from OpenVibe.Live, with AI chapters, captions and the chat.', 'https://openvibe.video/', 'watch video videos vod vods recording recordings replay replays clip clips past stream streams highlights vlog'],
['media-hub', 'fa-cloud-arrow-up', 'Keep and share files', 'A private drive with share links people sign in to open.', 'https://openvibe.download/', 'download files drive upload share link storage backup cloud send'],
++ ['play', 'fa-trophy', 'Climb the leaderboards', 'This week\'s leaders and the achievements you have unlocked in every OpenVibe game.', 'https://play.openvibe.network/', 'leaderboard leaderboards achievement achievements score scores ranking rank high score trophy games play'],
['inventory', 'fa-shirt', 'Wear what you earn', 'One inventory for the items you earn across OpenVibe, worn on every site.', 'https://inventory.openvibe.network/', 'inventory items cosmetics name effect particles hat voice badge collect equip wear profile'],
['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'],
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex 38dda3d0..53529b8e 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -53,6 +53,11 @@ const DEFAULT_GRANTS = [
// OpenVibe.Games gives six of Live's items for achievements (Inventory's live-grantors.json names Games on them),
// granted to signed-in players once each (origin earned, games:<game>:<achievement>:<subject> as the key).
['games', 'inventory.item.grant', 'openvibe.inventory', []],
++ // OpenVibe.Play (contracts 0.172.0, Games ADR-0007 decision 10): Games registers Scraplandia and Salvage Rush as Play
++ // titles and posts their scores and unlocks there. Play subscribes to the account events at boot; its account grants
++ // (export.contribute, deletion.confirm) follow once it is subscribed, as Codes' did.
++ ...['play.title.manage', 'play.result.write', 'play.player.read'].map((cap) => ['games', cap, 'openvibe.play', []]),
++ ['play', '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).
ddiff --git a/server/updates/routes.js b/server/updates/routes.js
iindex 00b8347e..3efe0ea9 100644
---- a/server/updates/routes.js
++++ b/server/updates/routes.js
@@@ -70,7 +70,7 @@ function createUpdatesRoutes({ blogUrl = 'http://127.0.0.1:4810', release = null
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', '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', 'play', 'sites'];
const NAMES = { ai: 'AI', vip: 'VIP', openre: 'OpenRestream' };
const nameOf = (id) => NAMES[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '');
ddiff --git a/test/frame.test.js b/test/frame.test.js
iindex f9fcaf6a..373cdec3 100644
---- a/test/frame.test.js
++++ b/test/frame.test.js
@@@ -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', 'news', 'reviews', 'trade', 'host', 'deals', 'coupons', 'stream', 'media-hub', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'inventory', 'events', 'ai', 'search', 'run'], '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', 'news', 'reviews', 'trade', 'host', 'deals', 'coupons', 'stream', 'media-hub', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'inventory', 'events', 'ai', 'search', 'run', 'play'], 'cold start follows the base order');
assert.strictEqual(p.nav.find(n => n.id === 'space').url, 'https://openvibe.space/', 'Space keeps its own home (the forum returned to Community, 0.118.0)');
assert.strictEqual(p.nav.find(n => n.id === 'community').url, 'https://openvibe.community/', 'Community: the forum, pastes and Pulse');
assert.equal(p.footer.legal.dmca, 'https://openvibe.media/dmca', 'legal links stay on the site\'s own domain');
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex 64fabf8f..6394a09d 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -12,8 +12,8 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
-- htmlRawKB: 132, // measured 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
-- htmlBrotliKB: 26, // 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)
++ htmlRawKB: 140, // measured 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
++ htmlBrotliKB: 28, // 26.1 with Play; 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)
jsFiles: 8, // 7 with boost.js, was 6 (2026-10-13, plan T11 step 3: one more script on every boosted page; it loads route-transition.js on the first move only)
jsRawKB: 266, // 249.8 with boost.js (13.1); 228.3
jsBrotliKB: 64, // 60.3 with boost.js (3.8); 53.9
ddiff --git a/test/registry-exposure.test.js b/test/registry-exposure.test.js
iindex eb5826cd..9fefbfeb 100644
---- a/test/registry-exposure.test.js
++++ b/test/registry-exposure.test.js
@@@ -26,6 +26,7 @@ const PINNED = {
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
inventory: 'live', // the network-wide inventory at inventory.openvibe.network since contracts 0.122.0 (ADR-054)
++ play: 'live', // achievements and leaderboards at play.openvibe.network since contracts 0.172.0 (Games ADR-0007)
run: 'live', // OpenVibe.Run's own site at openvibe.run since 2026-10-11 (contracts 0.141.0; loopback service since 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).
@@@ -117,7 +118,7 @@ const ready = [200, { ready: true, status: 'ready', failed: [], degraded: [], ch
// 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;
// Inventory in v0.122.0; none is retired
// News, Reviews, Coupons, Trade and OpenRestream in v0.125.0; Run in v0.141.0
-- assert.deepStrictEqual(st.exposure_summary, { live: 35, internal: 3, library: 4, repository: 1, placeholder: 1, retired: 0 });
++ assert.deepStrictEqual(st.exposure_summary, { live: 36, internal: 3, library: 4, repository: 1, placeholder: 1, 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());
@@@ -133,7 +134,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', 'news', 'reviews', 'trade', 'host', 'deals', 'coupons', 'stream', 'space', 'media-hub', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'inventory', 'events', 'ai', 'search', 'run']); // run: a site since 0.141.0; events, ai and search: sites since 0.135.0; news, reviews, trade, coupons and stream (OpenRestream): open since 0.125.0; // 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', 'news', 'reviews', 'trade', 'host', 'deals', 'coupons', 'stream', 'space', 'media-hub', 'bot', 'codes', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'watch', 'inventory', 'events', 'ai', 'search', 'run', 'play']); // play: a site since 0.172.0; run: a site since 0.141.0; events, ai and search: sites since 0.135.0; news, reviews, trade, coupons and stream (OpenRestream): open since 0.125.0; // 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`);