openvibers/openvibe.network Commits
Network: Space answers account export and deletion again (spaces hold personal data; its subscriptions were verified first) (#251)
Changes
@@@ -125,9 +125,10 @@ const DEFAULT_GRANTS = [// Since 2026-10-08 the services built from the skeleton answer too, through openvibe-sdk/account-data (SDK 0.36.0),// and since 2026-10-09 the older services that hold personal rows (SDK 0.37.0; each subscribed before this grant).// Codes since 2026-10-14 (its run_tasks, plan T16; subscribed first through Network#178). Play since 2026-10-14 (its-- // scores and unlocks; subscribed at its first boot, Network#189).++ // scores and unlocks; subscribed at its first boot, Network#189). Space again since 2026-10-16 (its spaces, Space#59;++ // subscribed first through Network#250)....['live', 'chat', 'community', 'media', 'games', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'media-hub',-- 'deals', 'trade', 'tips', 'vip', 'watch', 'services', 'wiki', 'blog', 'bot', 'inventory', 'codes', 'play'].flatMap((svc) => [++ 'deals', 'trade', 'tips', 'vip', 'watch', 'services', 'wiki', 'blog', 'bot', 'inventory', 'codes', 'play', 'space'].flatMap((svc) => [[svc, 'network.account.export.contribute', SELF_AUDIENCE, []],[svc, 'network.account.deletion.confirm', SELF_AUDIENCE, []],]),@@@ -495,15 +496,13 @@ const REVOKED_GRANTS = [['services', 'codes.resource.read', 'openvibe.codes'],['codes', 'events.event.publish', 'openvibe.events'],// The forum returned to Community (owner decision 2026-10-08, contracts 0.118.0): Space keeps only its sign-in-- // client. Its forum grants go, and Network stops waiting for its account export part and deletion confirmation-- // (the forum's account data is Community's).++ // client. Its forum grants go (the forum's account data is Community's). Its account and subscription grants came++ // back with spaces (RESTORED_GRANTS, 2026-10-16).['space', 'identity.subject.resolve', SELF_AUDIENCE],['space', 'media.object.upload', 'openvibe.media'],['space', 'vip.resource.policy.evaluate', 'openvibe.vip'],['space', 'events.event.read', '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'],@@@ -524,6 +523,9 @@ const RESTORED_GRANTS = [['space', 'events.event.publish', 'openvibe.events'],// Spaces (Space#58, #59): Space subscribes to the account events again (revoked with the forum, 10-08).['space', 'events.subscription.manage', 'openvibe.events'],++ // ...and answers account export and deletion for them again (Space#59, subscriptions verified 2026-10-16 20:20 UTC).++ ['space', 'network.account.export.contribute', SELF_AUDIENCE],++ ['space', 'network.account.deletion.confirm', SELF_AUDIENCE],];const CHANGED_DEFAULT_NAMESPACES = [@@@ -93,11 +93,11 @@ const server = http.createServer(app);// ── Export ──const seeded = await accountData.expectedServices(db, 'network.account.export.contribute');-- assert.deepStrictEqual(seeded, ['actor', 'blog', 'bot', 'chat', 'codes', 'community', 'deals', 'food', 'games', 'help', 'inventory', 'live', 'media', 'media-hub', 'play', 'quest', 'rent', 'services', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work'],-- 'the holders of the contribute grant: the skeleton services answer too since 2026-10-08 and the older ones since 2026-10-09 (openvibe-sdk/account-data); Space left with the forum; Codes and Play since 2026-10-14 (run tasks; scores and unlocks)');++ assert.deepStrictEqual(seeded, ['actor', 'blog', 'bot', 'chat', 'codes', 'community', 'deals', 'food', 'games', 'help', 'inventory', 'live', 'media', 'media-hub', 'play', 'quest', 'rent', 'services', 'space', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work'],++ 'the holders of the contribute grant: the skeleton services answer too since 2026-10-08 and the older ones since 2026-10-09 (openvibe-sdk/account-data); Codes and Play since 2026-10-14 (run tasks; scores and unlocks); Space again since 2026-10-16 (its spaces)');// The scenarios below follow five services; the newer ones are set aside here, exactly as an operator would// revoke a service's grant (the same rows, the same revoked_at).-- for (const svc of ['actor', 'blog', 'bot', 'codes', 'deals', 'food', 'help', 'inventory', 'media-hub', 'play', 'quest', 'rent', 'services', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work']) {++ for (const svc of ['actor', 'blog', 'bot', 'codes', 'deals', 'food', 'help', 'inventory', 'media-hub', 'play', 'quest', 'rent', 'services', 'space', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work']) {for (const cap of ['network.account.export.contribute', 'network.account.deletion.confirm']) {await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND revoked_at IS NULL').run(svc, cap);}@@@ -171,9 +171,11 @@ const server = http.createServer(app);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) 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.++ // Space (T20 step 2, contracts 0.149.0) Space publishes its hosting events; since spaces (2026-10-16) it answers account++ // export and deletion again, and holds nothing else on Network.const spaceNetwork = await token({ client_id: 'space', client_secret: 'space-secret', audience: 'openvibe.network' });-- assert.strictEqual(spaceNetwork.status, 400, 'Space holds no Network grant');++ assert.strictEqual(spaceNetwork.status, 200, JSON.stringify(spaceNetwork.body));++ assert.deepStrictEqual(String(spaceNetwork.body.scope).split(' ').sort(), ['network.account.deletion.confirm', 'network.account.export.contribute'], 'Space\'s only Network grants are the account ones');r = await post('/internal/identity/resolve-batch', { system: 'network', ids: ['7'] }, { authorization: `Bearer ${com.body.access_token}` });assert.strictEqual(r.status, 200); assert.strictEqual(r.body.results['7'].username, 'payee');// Bot adds an operator by @username: the same projection, any case, a leading @ ignored; an unknown name is a 404.
@@ -125,9 +125,10 @@ const DEFAULT_GRANTS = [
@@ -125,9 +125,10 @@ const DEFAULT_GRANTS = [
// Since 2026-10-08 the services built from the skeleton answer too, through openvibe-sdk/account-data (SDK 0.36.0),
// Since 2026-10-08 the services built from the skeleton answer too, through openvibe-sdk/account-data (SDK 0.36.0),
// and since 2026-10-09 the older services that hold personal rows (SDK 0.37.0; each subscribed before this grant).
// and since 2026-10-09 the older services that hold personal rows (SDK 0.37.0; each subscribed before this grant).
// Codes since 2026-10-14 (its run_tasks, plan T16; subscribed first through Network#178). Play since 2026-10-14 (its
// Codes since 2026-10-14 (its run_tasks, plan T16; subscribed first through Network#178). Play since 2026-10-14 (its
- // scores and unlocks; subscribed at its first boot, Network#189).
+ // scores and unlocks; subscribed at its first boot, Network#189). Space again since 2026-10-16 (its spaces, Space#59;
+ // subscribed first through Network#250).
...['live', 'chat', 'community', 'media', 'games', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'media-hub',
...['live', 'chat', 'community', 'media', 'games', 'actor', 'food', 'help', 'work', 'quest', 'rent', 'media-hub',
- 'deals', 'trade', 'tips', 'vip', 'watch', 'services', 'wiki', 'blog', 'bot', 'inventory', 'codes', 'play'].flatMap((svc) => [
+ 'deals', 'trade', 'tips', 'vip', 'watch', 'services', 'wiki', 'blog', 'bot', 'inventory', 'codes', 'play', 'space'].flatMap((svc) => [
[svc, 'network.account.export.contribute', SELF_AUDIENCE, []],
[svc, 'network.account.export.contribute', SELF_AUDIENCE, []],
[svc, 'network.account.deletion.confirm', SELF_AUDIENCE, []],
[svc, 'network.account.deletion.confirm', SELF_AUDIENCE, []],
]),
]),
@@ -495,15 +496,13 @@ const REVOKED_GRANTS = [
@@ -495,15 +496,13 @@ const REVOKED_GRANTS = [
['services', 'codes.resource.read', 'openvibe.codes'],
['services', 'codes.resource.read', 'openvibe.codes'],
['codes', 'events.event.publish', 'openvibe.events'],
['codes', 'events.event.publish', 'openvibe.events'],
// The forum returned to Community (owner decision 2026-10-08, contracts 0.118.0): Space keeps only its sign-in
// The forum returned to Community (owner decision 2026-10-08, contracts 0.118.0): Space keeps only its sign-in
- // client. Its forum grants go, and Network stops waiting for its account export part and deletion confirmation
- // (the forum's account data is Community's).
+ // client. Its forum grants go (the forum's account data is Community's). Its account and subscription grants came
+ // back with spaces (RESTORED_GRANTS, 2026-10-16).
['space', 'identity.subject.resolve', SELF_AUDIENCE],
['space', 'identity.subject.resolve', SELF_AUDIENCE],
['space', 'media.object.upload', 'openvibe.media'],
['space', 'media.object.upload', 'openvibe.media'],
['space', 'vip.resource.policy.evaluate', 'openvibe.vip'],
['space', 'vip.resource.policy.evaluate', 'openvibe.vip'],
['space', 'events.event.read', 'openvibe.events'],
['space', 'events.event.read', 'openvibe.events'],
['space', 'network.blocks.read', SELF_AUDIENCE],
['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
// 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.
// its resource index go. Space serves the engine.
['host', 'media.object.upload', 'openvibe.media'],
['host', 'media.object.upload', 'openvibe.media'],
@@ -524,6 +523,9 @@ const RESTORED_GRANTS = [
@@ -524,6 +523,9 @@ const RESTORED_GRANTS = [
['space', 'events.event.publish', 'openvibe.events'],
['space', 'events.event.publish', 'openvibe.events'],
// Spaces (Space#58, #59): Space subscribes to the account events again (revoked with the forum, 10-08).
// Spaces (Space#58, #59): Space subscribes to the account events again (revoked with the forum, 10-08).
['space', 'events.subscription.manage', 'openvibe.events'],
['space', 'events.subscription.manage', 'openvibe.events'],
+ // ...and answers account export and deletion for them again (Space#59, subscriptions verified 2026-10-16 20:20 UTC).
+ ['space', 'network.account.export.contribute', SELF_AUDIENCE],
+ ['space', 'network.account.deletion.confirm', SELF_AUDIENCE],
];
];
const CHANGED_DEFAULT_NAMESPACES = [
const CHANGED_DEFAULT_NAMESPACES = [
@@ -93,11 +93,11 @@ const server = http.createServer(app);
@@ -93,11 +93,11 @@ const server = http.createServer(app);
// ── Export ──
// ── Export ──
const seeded = await accountData.expectedServices(db, 'network.account.export.contribute');
const seeded = await accountData.expectedServices(db, 'network.account.export.contribute');
- assert.deepStrictEqual(seeded, ['actor', 'blog', 'bot', 'chat', 'codes', 'community', 'deals', 'food', 'games', 'help', 'inventory', 'live', 'media', 'media-hub', 'play', 'quest', 'rent', 'services', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work'],
- 'the holders of the contribute grant: the skeleton services answer too since 2026-10-08 and the older ones since 2026-10-09 (openvibe-sdk/account-data); Space left with the forum; Codes and Play since 2026-10-14 (run tasks; scores and unlocks)');
+ assert.deepStrictEqual(seeded, ['actor', 'blog', 'bot', 'chat', 'codes', 'community', 'deals', 'food', 'games', 'help', 'inventory', 'live', 'media', 'media-hub', 'play', 'quest', 'rent', 'services', 'space', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work'],
+ 'the holders of the contribute grant: the skeleton services answer too since 2026-10-08 and the older ones since 2026-10-09 (openvibe-sdk/account-data); Codes and Play since 2026-10-14 (run tasks; scores and unlocks); Space again since 2026-10-16 (its spaces)');
// The scenarios below follow five services; the newer ones are set aside here, exactly as an operator would
// The scenarios below follow five services; the newer ones are set aside here, exactly as an operator would
// revoke a service's grant (the same rows, the same revoked_at).
// revoke a service's grant (the same rows, the same revoked_at).
- for (const svc of ['actor', 'blog', 'bot', 'codes', 'deals', 'food', 'help', 'inventory', 'media-hub', 'play', 'quest', 'rent', 'services', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work']) {
+ for (const svc of ['actor', 'blog', 'bot', 'codes', 'deals', 'food', 'help', 'inventory', 'media-hub', 'play', 'quest', 'rent', 'services', 'space', 'tips', 'trade', 'vip', 'watch', 'wiki', 'work']) {
for (const cap of ['network.account.export.contribute', 'network.account.deletion.confirm']) {
for (const cap of ['network.account.export.contribute', 'network.account.deletion.confirm']) {
await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND revoked_at IS NULL').run(svc, cap);
await db.prepare('UPDATE principal_grants SET revoked_at = ov_now() WHERE client_id = ? AND capability = ? AND revoked_at IS NULL').run(svc, cap);
}
}
@@ -171,9 +171,11 @@ const server = http.createServer(app);
@@ -171,9 +171,11 @@ const server = http.createServer(app);
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');
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();
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) and its grants went; since the static hosting engine moved to
// 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.
+ // Space (T20 step 2, contracts 0.149.0) Space publishes its hosting events; since spaces (2026-10-16) it answers account
+ // export and deletion again, and holds nothing else on Network.
const spaceNetwork = await token({ client_id: 'space', client_secret: 'space-secret', audience: 'openvibe.network' });
const spaceNetwork = await token({ client_id: 'space', client_secret: 'space-secret', audience: 'openvibe.network' });
- assert.strictEqual(spaceNetwork.status, 400, 'Space holds no Network grant');
+ assert.strictEqual(spaceNetwork.status, 200, JSON.stringify(spaceNetwork.body));
+ assert.deepStrictEqual(String(spaceNetwork.body.scope).split(' ').sort(), ['network.account.deletion.confirm', 'network.account.export.contribute'], 'Space\'s only Network grants are the account ones');
r = await post('/internal/identity/resolve-batch', { system: 'network', ids: ['7'] }, { authorization: `Bearer ${com.body.access_token}` });
r = await post('/internal/identity/resolve-batch', { system: 'network', ids: ['7'] }, { authorization: `Bearer ${com.body.access_token}` });
assert.strictEqual(r.status, 200); assert.strictEqual(r.body.results['7'].username, 'payee');
assert.strictEqual(r.status, 200); assert.strictEqual(r.body.results['7'].username, 'payee');
// Bot adds an operator by @username: the same projection, any case, a leading @ ignored; an unknown name is a 404.
// Bot adds an operator by @username: the same projection, any case, a leading @ ignored; an unknown name is a 404.