openvibers/openvibe.network Commits
openvibe-contracts v0.26.0 (developer-project claims, sandbox refusal); grants and clients for deals, coupons and host; tests follow services that left placeholder
Changes
@@@ -20,7 +20,7 @@"helmet": "^8.1.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.13.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0","uuid": "^11.1.0","web-push": "^3.6.7"@@@ -1464,9 +1464,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.13.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",-- "integrity": "sha512-MeIGJYHQsbr6ISeEErduUw/PD52X5IBclH2ht/G/t4lSX5X4o2AwcZYLEzOZrcbaXQyplgC7r86RCGFvi1ifUA==",++ "version": "0.26.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0",++ "integrity": "sha512-yigHjNzLIyK578V0Weqv2zL6z8PO8ce3HRXPa/61n1XK5Y7pTi28qHMMiO6stboQjTziN1+8wMeByoCF1sGCnw==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -23,7 +23,7 @@"helmet": "^8.1.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.13.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0","uuid": "^11.1.0","web-push": "^3.6.7"@@@ -591,6 +591,9 @@ function initDb(dbPath) {{ 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'] },++ { client_id: 'deals', name: 'OpenVibe.Deals', redirect_uris: ['https://openvibe.deals/auth/callback'] },++ { client_id: 'coupons', name: 'OpenVibe.Coupons', redirect_uris: ['https://openvibe.coupons/auth/callback'] },++ { client_id: 'host', name: 'OpenVibe.Host', redirect_uris: ['https://openvibe.host/auth/callback'] },// Billing's staff console (Wave 8): Network admins listed in BILLING_STAFF_SUBJECTS.{ client_id: 'billing', name: 'OpenVibe.Billing', redirect_uris: ['https://billing.openvibe.network/auth/callback'] },];@@@ -83,6 +83,16 @@ const DEFAULT_GRANTS = [['trade', 'events.event.publish', 'openvibe.events', []],['trade', 'sources.item.read', 'openvibe.sources', []],['trade', 'sources.source.read', 'openvibe.sources', []],++ // Wave 18: Deals and Coupons; Wave 21 Stage B: the Host API publishes deploy/domain events.++ ['deals', 'identity.subject.resolve', SELF_AUDIENCE, []],++ ['deals', 'events.event.publish', 'openvibe.events', []],++ ['deals', 'events.subscription.manage', 'openvibe.events', []],++ ['deals', 'sources.item.read', 'openvibe.sources', []],++ ['deals', 'community.comment.write', 'openvibe.community', []],++ ['deals', 'community.comment.moderate', 'openvibe.community', []],++ ['coupons', 'events.event.publish', 'openvibe.events', []],++ ['coupons', 'sources.item.read', 'openvibe.sources', []],++ ['host', 'events.event.publish', 'openvibe.events', []],// Wave 3: producers publish to OpenVibe.Events (their own source only, enforced by Events)....['live', 'media', 'network', 'community', 'billing', 'chat', 'tools', 'games', 'search', 'sources', 'tips'].map(c => [c, 'events.event.publish', 'openvibe.events', []]),// Wave 14: Search subscribes to <owner>.index_document.* deliveries.@@@ -61,7 +61,8 @@ const secretsSeen = [];const token = (form) => fetch(`${base}/oauth/token`, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(form) }).then(async r => ({ status: r.status, cache: r.headers.get('cache-control'), body: await r.json() }));const cc = (appId, secret, audience = 'openvibe.media', extra = {}) => token({ grant_type: 'client_credentials', client_id: appId, client_secret: secret, audience, ...extra });-- const verify = (t, audience = 'openvibe.media') => serviceAuth.verifyServiceToken(t, { publicKey: keys.publicKey, issuer: ISSUER, audience });++ // Inspects token contents, so it opts in to sandbox tokens (contracts v0.26.0 refuses them by default).++ const verify = (t, audience = 'openvibe.media') => serviceAuth.verifyServiceToken(t, { publicKey: keys.publicKey, issuer: ISSUER, audience, acceptSandbox: true });// ── Authentication: Bearer user tokens only; never X-Internal-Key ──let r = await api(null, 'GET', '', null, { 'x-internal-key': 'legacy-key' });@@@ -30,10 +30,10 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');assert.strictEqual(byId.network.runtime.status, 'up');assert.ok(byId.network.runtime.checked_at, 'every health value says when it was checked');assert.strictEqual(byId.live.runtime.status, 'down', 'unreachable is reported, not hidden');-- assert.strictEqual(byId.wiki.runtime.status, 'not-running', 'placeholders are never shown as running');++ assert.strictEqual(byId.codes.runtime.status, 'not-running', 'placeholders are never shown as running');assert.ok(r.body.services.every(s => contracts.validate('registry.service-manifest@1', Object.fromEntries(Object.entries(s).filter(([k]) => k !== 'runtime'))).valid));r = await get('/api/v1/registry/services?status=placeholder');-- assert.ok(r.body.services.length >= 15 && r.body.services.every(s => s.status === 'placeholder'));++ assert.ok(r.body.services.length >= 2 && r.body.services.every(s => s.status === 'placeholder'));r = await get('/api/v1/registry/services/network');assert.ok(r.body.capability_details.some(c => c.id === 'network.coins.credit'));@@@ -72,8 +72,8 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22assert.ok(by.tools.release_error);assert.strictEqual(by.events.basis, 'ready-legacy');assert.strictEqual(by.live.status, 'down', 'unreachable is down');-- assert.strictEqual(by.wiki.status, 'not-running');-- assert.strictEqual(by.wiki.label, 'not running (placeholder)');++ assert.strictEqual(by.codes.status, 'not-running');++ assert.strictEqual(by.codes.label, 'not running (placeholder)');assert.strictEqual(by.contracts.status, 'not-running');assert.strictEqual(by.contracts.label, 'not running (no runtime)');for (const s of r.body.services) assert.ok(s.checked_at, `${s.id} says when it was checked`);@@@ -106,7 +106,7 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22assert.ok(html.includes('<meta name="robots" content="noindex, nofollow">'));assert.ok(html.includes('id="svc-media"') && /id="svc-media"[\s\S]*?Degraded/.test(html));assert.ok(/id="svc-community"[\s\S]*?Down[\s\S]*?SQLITE_CANTOPEN/.test(html));-- assert.ok(/id="svc-wiki"[\s\S]*?not running \(placeholder\)/.test(html));++ assert.ok(/id="svc-codes"[\s\S]*?not running \(placeholder\)/.test(html));assert.ok(/id="svc-tools"[\s\S]*?liveness only/.test(html));assert.ok(html.includes('<noscript>'), 'navigation without JavaScript');assert.ok(html.includes('SLO categories (proposals)'));
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.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.13.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
"uuid": "^11.1.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
"web-push": "^3.6.7"
@@ -1464,9 +1464,9 @@
@@ -1464,9 +1464,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.13.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.13.0",
- "integrity": "sha512-MeIGJYHQsbr6ISeEErduUw/PD52X5IBclH2ht/G/t4lSX5X4o2AwcZYLEzOZrcbaXQyplgC7r86RCGFvi1ifUA==",
+ "version": "0.26.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0",
+ "integrity": "sha512-yigHjNzLIyK578V0Weqv2zL6z8PO8ce3HRXPa/61n1XK5Y7pTi28qHMMiO6stboQjTziN1+8wMeByoCF1sGCnw==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -23,7 +23,7 @@
@@ -23,7 +23,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.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.13.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.26.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
"uuid": "^11.1.0",
"uuid": "^11.1.0",
"web-push": "^3.6.7"
"web-push": "^3.6.7"
@@ -591,6 +591,9 @@ function initDb(dbPath) {
@@ -591,6 +591,9 @@ function initDb(dbPath) {
{ 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'] },
+ { client_id: 'deals', name: 'OpenVibe.Deals', redirect_uris: ['https://openvibe.deals/auth/callback'] },
+ { client_id: 'coupons', name: 'OpenVibe.Coupons', redirect_uris: ['https://openvibe.coupons/auth/callback'] },
+ { client_id: 'host', name: 'OpenVibe.Host', redirect_uris: ['https://openvibe.host/auth/callback'] },
// Billing's staff console (Wave 8): Network admins listed in BILLING_STAFF_SUBJECTS.
// Billing's staff console (Wave 8): Network admins listed in BILLING_STAFF_SUBJECTS.
{ client_id: 'billing', name: 'OpenVibe.Billing', redirect_uris: ['https://billing.openvibe.network/auth/callback'] },
{ client_id: 'billing', name: 'OpenVibe.Billing', redirect_uris: ['https://billing.openvibe.network/auth/callback'] },
];
];
@@ -83,6 +83,16 @@ const DEFAULT_GRANTS = [
@@ -83,6 +83,16 @@ const DEFAULT_GRANTS = [
['trade', 'events.event.publish', 'openvibe.events', []],
['trade', 'events.event.publish', 'openvibe.events', []],
['trade', 'sources.item.read', 'openvibe.sources', []],
['trade', 'sources.item.read', 'openvibe.sources', []],
['trade', 'sources.source.read', 'openvibe.sources', []],
['trade', 'sources.source.read', 'openvibe.sources', []],
+ // Wave 18: Deals and Coupons; Wave 21 Stage B: the Host API publishes deploy/domain events.
+ ['deals', 'identity.subject.resolve', SELF_AUDIENCE, []],
+ ['deals', 'events.event.publish', 'openvibe.events', []],
+ ['deals', 'events.subscription.manage', 'openvibe.events', []],
+ ['deals', 'sources.item.read', 'openvibe.sources', []],
+ ['deals', 'community.comment.write', 'openvibe.community', []],
+ ['deals', 'community.comment.moderate', 'openvibe.community', []],
+ ['coupons', 'events.event.publish', 'openvibe.events', []],
+ ['coupons', 'sources.item.read', 'openvibe.sources', []],
+ ['host', 'events.event.publish', 'openvibe.events', []],
// Wave 3: producers publish to OpenVibe.Events (their own source only, enforced by Events).
// Wave 3: producers publish to OpenVibe.Events (their own source only, enforced by Events).
...['live', 'media', 'network', 'community', 'billing', 'chat', 'tools', 'games', 'search', 'sources', 'tips'].map(c => [c, 'events.event.publish', 'openvibe.events', []]),
...['live', 'media', 'network', 'community', 'billing', 'chat', 'tools', 'games', 'search', 'sources', 'tips'].map(c => [c, 'events.event.publish', 'openvibe.events', []]),
// Wave 14: Search subscribes to <owner>.index_document.* deliveries.
// Wave 14: Search subscribes to <owner>.index_document.* deliveries.
@@ -61,7 +61,8 @@ const secretsSeen = [];
@@ -61,7 +61,8 @@ const secretsSeen = [];
const token = (form) => fetch(`${base}/oauth/token`, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(form) })
const token = (form) => fetch(`${base}/oauth/token`, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(form) })
.then(async r => ({ status: r.status, cache: r.headers.get('cache-control'), body: await r.json() }));
.then(async r => ({ status: r.status, cache: r.headers.get('cache-control'), body: await r.json() }));
const cc = (appId, secret, audience = 'openvibe.media', extra = {}) => token({ grant_type: 'client_credentials', client_id: appId, client_secret: secret, audience, ...extra });
const cc = (appId, secret, audience = 'openvibe.media', extra = {}) => token({ grant_type: 'client_credentials', client_id: appId, client_secret: secret, audience, ...extra });
- const verify = (t, audience = 'openvibe.media') => serviceAuth.verifyServiceToken(t, { publicKey: keys.publicKey, issuer: ISSUER, audience });
+ // Inspects token contents, so it opts in to sandbox tokens (contracts v0.26.0 refuses them by default).
+ const verify = (t, audience = 'openvibe.media') => serviceAuth.verifyServiceToken(t, { publicKey: keys.publicKey, issuer: ISSUER, audience, acceptSandbox: true });
// ── Authentication: Bearer user tokens only; never X-Internal-Key ──
// ── Authentication: Bearer user tokens only; never X-Internal-Key ──
let r = await api(null, 'GET', '', null, { 'x-internal-key': 'legacy-key' });
let r = await api(null, 'GET', '', null, { 'x-internal-key': 'legacy-key' });
@@ -30,10 +30,10 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');
@@ -30,10 +30,10 @@ const { createEcosystemRegistry } = require('../server/registry/ecosystem');
assert.strictEqual(byId.network.runtime.status, 'up');
assert.strictEqual(byId.network.runtime.status, 'up');
assert.ok(byId.network.runtime.checked_at, 'every health value says when it was checked');
assert.ok(byId.network.runtime.checked_at, 'every health value says when it was checked');
assert.strictEqual(byId.live.runtime.status, 'down', 'unreachable is reported, not hidden');
assert.strictEqual(byId.live.runtime.status, 'down', 'unreachable is reported, not hidden');
- assert.strictEqual(byId.wiki.runtime.status, 'not-running', 'placeholders are never shown as running');
+ assert.strictEqual(byId.codes.runtime.status, 'not-running', 'placeholders are never shown as running');
assert.ok(r.body.services.every(s => contracts.validate('registry.service-manifest@1', Object.fromEntries(Object.entries(s).filter(([k]) => k !== 'runtime'))).valid));
assert.ok(r.body.services.every(s => contracts.validate('registry.service-manifest@1', Object.fromEntries(Object.entries(s).filter(([k]) => k !== 'runtime'))).valid));
r = await get('/api/v1/registry/services?status=placeholder');
r = await get('/api/v1/registry/services?status=placeholder');
- assert.ok(r.body.services.length >= 15 && r.body.services.every(s => s.status === 'placeholder'));
+ assert.ok(r.body.services.length >= 2 && r.body.services.every(s => s.status === 'placeholder'));
r = await get('/api/v1/registry/services/network');
r = await get('/api/v1/registry/services/network');
assert.ok(r.body.capability_details.some(c => c.id === 'network.coins.credit'));
assert.ok(r.body.capability_details.some(c => c.id === 'network.coins.credit'));
@@ -72,8 +72,8 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
@@ -72,8 +72,8 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
assert.ok(by.tools.release_error);
assert.ok(by.tools.release_error);
assert.strictEqual(by.events.basis, 'ready-legacy');
assert.strictEqual(by.events.basis, 'ready-legacy');
assert.strictEqual(by.live.status, 'down', 'unreachable is down');
assert.strictEqual(by.live.status, 'down', 'unreachable is down');
- assert.strictEqual(by.wiki.status, 'not-running');
- assert.strictEqual(by.wiki.label, 'not running (placeholder)');
+ assert.strictEqual(by.codes.status, 'not-running');
+ assert.strictEqual(by.codes.label, 'not running (placeholder)');
assert.strictEqual(by.contracts.status, 'not-running');
assert.strictEqual(by.contracts.status, 'not-running');
assert.strictEqual(by.contracts.label, 'not running (no runtime)');
assert.strictEqual(by.contracts.label, 'not running (no runtime)');
for (const s of r.body.services) assert.ok(s.checked_at, `${s.id} says when it was checked`);
for (const s of r.body.services) assert.ok(s.checked_at, `${s.id} says when it was checked`);
@@ -106,7 +106,7 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
@@ -106,7 +106,7 @@ const rel = (release) => [200, { service: 'x', release, released_at: '2026-09-22
assert.ok(html.includes('<meta name="robots" content="noindex, nofollow">'));
assert.ok(html.includes('<meta name="robots" content="noindex, nofollow">'));
assert.ok(html.includes('id="svc-media"') && /id="svc-media"[\s\S]*?Degraded/.test(html));
assert.ok(html.includes('id="svc-media"') && /id="svc-media"[\s\S]*?Degraded/.test(html));
assert.ok(/id="svc-community"[\s\S]*?Down[\s\S]*?SQLITE_CANTOPEN/.test(html));
assert.ok(/id="svc-community"[\s\S]*?Down[\s\S]*?SQLITE_CANTOPEN/.test(html));
- assert.ok(/id="svc-wiki"[\s\S]*?not running \(placeholder\)/.test(html));
+ assert.ok(/id="svc-codes"[\s\S]*?not running \(placeholder\)/.test(html));
assert.ok(/id="svc-tools"[\s\S]*?liveness only/.test(html));
assert.ok(/id="svc-tools"[\s\S]*?liveness only/.test(html));
assert.ok(html.includes('<noscript>'), 'navigation without JavaScript');
assert.ok(html.includes('<noscript>'), 'navigation without JavaScript');
assert.ok(html.includes('SLO categories (proposals)'));
assert.ok(html.includes('SLO categories (proposals)'));