openvibers/openvibe.network Commits

Network: grants follow static hosting to Space (events publish, Services' resource fan-out); Host's engine grants revoked (#160)

OP OpenVibers committed 12 Oct 2026 · f53004a60f · 7 files, +26 −17

* openvibe-contracts v0.150.0 (was v0.148.0)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v0.150.0 release.

* Network: grants follow static hosting to Space (events publish, Services' resource fan-out); Host's engine grants revoked

The static hosting engine moved from Host to Space today (Space#36 and Host#84 live; contracts 0.149.0/0.150.0). The grants follow it:

- **Space gets `events.event.publish` back.** It was revoked on 10-08 when the forum returned to Community, and Space's hosting outbox publishes `space.deploy.*` and `space.domain.verified`. Without it, the first deploy event would be refused. Its other forum-era grants stay revoked.
- **Services' resource fan-out reads `space.resource.read`** at openvibe.space instead of `host.resource.read`. Host's index is gone, and Services discovers authorities from the contracts manifests, where 0.150.0 makes Space the authority.
- **Revoked from Host:** its engine-only grants, `media.object.upload|read|list|delete` on its tenant files, and Services' `host.resource.read`. Host keeps its catalog grants and `network.project.read`, which `test/internal-projects.test.js` uses as the endpoint's example caller.

Tests: `service-principal`, `internal-projects`.

* Network tests: Space publishes its hosting events; Space, not Host, is the hosting resource authority

Changes

ddiff --git a/README.md b/README.md
iindex cff9d2a2..813dde32 100644
---- a/README.md
++++ b/README.md
@@@ -732,7 +732,7 @@ Reporting a vulnerability: [SECURITY.md](SECURITY.md). The rules the code keeps:
Same as the parent [OpenVibe](../LICENSE) project.
<!-- versions:start -->
--- openvibe-contracts: v0.148.0
++- openvibe-contracts: v0.150.0
- openvibe-sdk: v0.44.0
- openvibe-shared: v4.0.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 0eac2a09..39b7a0e9 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -6,7 +6,7 @@
"runtime": "node 22, express, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4000,
"domain": "openvibe.network (with auth., api., admin., my. and themes.openvibe.network in its manifest)",
-- "contracts": "openvibe-contracts v0.148.0",
++ "contracts": "openvibe-contracts v0.150.0",
"sdk": "openvibe-sdk v0.44.0",
"shared": "openvibe-shared v4.0.0",
"capabilities": [
ddiff --git a/package-lock.json b/package-lock.json
iindex 6839829f..7914af80 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -20,7 +20,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.148.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.150.0",
"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.0.0",
"pg": "^8.23.0",
@@@ -1320,9 +1320,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.148.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.148.0",
-- "integrity": "sha512-IojUJ27+/SngR6QWJysym0J+LjcWDuXQOQ/zdz+toSmw/dmLrsYpmUp+GD75Qt2Ak6LjnKLUtGcjTH2GAYO2mw==",
++ "version": "0.150.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.150.0",
++ "integrity": "sha512-CNKm1UpEynr56yIK9ppnpSAM2JXT+YLuPbLGzw4tB9A3pX/rfSIKlo8Pj3agEkoNwwlavXDdXv9az1S0Gg2H7g==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 8be72887..e9713d33 100644
---- a/package.json
++++ b/package.json
@@@ -24,7 +24,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.148.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.150.0",
"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.0.0",
"pg": "^8.23.0",
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex 186a0957..00760e42 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -77,10 +77,6 @@ const DEFAULT_GRANTS = [
['games', 'media.object.upload', 'openvibe.media', ['games']],
// Plan T12: Host keeps tenant deploy files in Media (Host#24, HOST_OBJECT_STORE=media), its own namespace only:
// write-through on deploy, re-fetch on a cache miss, list and delete when its GC finds a blob unreferenced.
-- ['host', 'media.object.upload', 'openvibe.media', ['host', 'host.*']],
-- ['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 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.
@@@ -191,6 +187,9 @@ const DEFAULT_GRANTS = [
['coupons', 'events.subscription.manage', 'openvibe.events', []],
['coupons', 'sources.item.read', 'openvibe.sources', []],
['host', 'events.event.publish', 'openvibe.events', []],
++ // T20 step 2 (2026-10-12, Space#36): the static hosting engine moved to Space, which publishes space.deploy.* and
++ // space.domain.verified through its outbox. (Its forum-era grants stay revoked below.)
++ ['space', 'events.event.publish', 'openvibe.events', []],
// The hosting catalog (plan T20 step 3) reads OpenVibe.Sources items in category hosting (Host#69).
['host', 'sources.item.read', 'openvibe.sources', []],
// The hosting catalog's provider and plan discussion threads on OpenVibe.Community (Host#74).
@@@ -239,7 +238,8 @@ const DEFAULT_GRANTS = [
['services', 'network.resource.read', SELF_AUDIENCE, []],
// Phase 2 (contracts 0.129.0-0.134.0): watches, Actor tasks, robots, rooms, OpenRestream's streams and Community's
// pastes and spaces.
-- ...['media', 'events', 'host', 'watch', 'actor', 'bot', 'chat', 'openre', 'community'].map(id => ['services', `${id}.resource.read`, `openvibe.${id}`, []]),
++ // Since contracts 0.150.0 the static hosting index is Space's (space.resource.read); Host's is retired.
++ ...['media', 'events', 'space', 'watch', 'actor', 'bot', 'chat', 'openre', 'community'].map(id => ['services', `${id}.resource.read`, `openvibe.${id}`, []]),
// Plan T14: OpenVibe.Run publishes run.job.* and hands each job to a paired Node through Bot's internal jobs API
// (POST /api/v1/jobs, /jobs/:id/cancel, GET /jobs/:id, capability bot.job.dispatch, service-to-service only).
['run', 'events.event.publish', 'openvibe.events', []],
@@@ -378,10 +378,16 @@ const REVOKED_GRANTS = [
['space', 'vip.resource.policy.evaluate', 'openvibe.vip'],
['space', 'events.subscription.manage', 'openvibe.events'],
['space', 'events.event.read', 'openvibe.events'],
-- ['space', 'events.event.publish', 'openvibe.events'],
['space', 'network.blocks.read', SELF_AUDIENCE],
['space', 'network.account.export.contribute', SELF_AUDIENCE],
['space', 'network.account.deletion.confirm', SELF_AUDIENCE],
++ // The static hosting engine left Host (T20, Host#84, contracts 0.150.0): its Media objects and Services' fan-out to
++ // its resource index go. Space serves the engine.
++ ['host', 'media.object.upload', 'openvibe.media'],
++ ['host', 'media.object.read', 'openvibe.media'],
++ ['host', 'media.object.list', 'openvibe.media'],
++ ['host', 'media.object.delete', 'openvibe.media'],
++ ['services', 'host.resource.read', 'openvibe.host'],
];
// Default grants whose namespaces changed: a row still exactly as the old default seeded it is moved to
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex d8424762..87ecca04 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -170,8 +170,8 @@ const server = http.createServer(app);
assert.strictEqual(com.status, 200, JSON.stringify(com.body));
assert.deepStrictEqual(com.body.scope.split(' '), ['identity.subject.resolve', 'network.account.deletion.confirm', 'network.account.export.contribute', 'network.blocks.read', 'network.modules.read', 'network.modules.write'], 'resolve, account export and deletion, platform blocks, plus its community.profile module');
await db.prepare("UPDATE oauth_clients SET client_secret = 'space-secret' WHERE client_id = 'space'").run();
-- // The forum returned to Community (contracts 0.118.0): Space keeps only its sign-in client, so its service token
-- // gets no grant on any audience.
++ // The forum returned to Community (contracts 0.118.0) and its grants went; since the static hosting engine moved to
++ // Space (T20 step 2, contracts 0.149.0) Space publishes its hosting events, and nothing else.
const spaceNetwork = await token({ client_id: 'space', client_secret: 'space-secret', audience: 'openvibe.network' });
assert.strictEqual(spaceNetwork.status, 400, 'Space holds no Network grant');
r = await post('/internal/identity/resolve-batch', { system: 'network', ids: ['7'] }, { authorization: `Bearer ${com.body.access_token}` });
@@@ -195,10 +195,13 @@ const server = http.createServer(app);
assert.strictEqual(r.status, 403, 'Media holds no coin capability');
const media = await token({ client_id: 'community', client_secret: 'community-secret', audience: 'openvibe.media' });
assert.strictEqual(media.body.scope, 'media.object.upload', 'community may upload screenshot bytes to Media');
-- for (const audience of ['openvibe.media', 'openvibe.vip', 'openvibe.events']) {
++ for (const audience of ['openvibe.media', 'openvibe.vip']) {
const response = await token({ client_id: 'space', client_secret: 'space-secret', audience });
assert.strictEqual(response.status, 400, `Space holds no ${audience} grant`);
}
++ const spaceEvents = await token({ client_id: 'space', client_secret: 'space-secret', audience: 'openvibe.events' });
++ assert.strictEqual(spaceEvents.status, 200, JSON.stringify(spaceEvents.body));
++ assert.strictEqual(spaceEvents.body.scope, 'events.event.publish', 'Space publishes its hosting events and nothing more');
const cm = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.community' });
assert.deepStrictEqual(cm.body.scope.split(' '), ['community.comment.moderate', 'community.comment.write', 'community.paste.create', 'community.paste.moderate', 'community.paste.write', 'community.pulse.write']);
const tl = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.tools' });
ddiff --git a/test/services-resource-grants.test.js b/test/services-resource-grants.test.js
iindex be32857d..1e35c38b 100644
---- a/test/services-resource-grants.test.js
++++ b/test/services-resource-grants.test.js
@@@ -33,5 +33,5 @@ for (const [id] of granted) {
assert.ok(cap && cap.status !== 'retired', `services holds ${id}, which the contracts do not offer`);
assert.ok(!revoked.has(id), `${id} is both granted and revoked`);
}
--assert.ok(checked >= 4, `at least Network, Media, Events and Host are active authorities (${checked})`);
++assert.ok(checked >= 4, `at least Network, Media, Events and Space are active authorities (${checked})`);
console.log(`services resource grants: ${checked} active authorities granted, ${granted.size} grants in all`);