openvibers/openvibe.network Commits
Network: Services may stop an Actor task and delete a Media object; Actor and Media read project roles (contracts 0.169) (#187)
Changes
@@@ -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.168.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.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.1.3","pg": "^8.23.0",@@@ -1320,9 +1320,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.168.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.168.0",-- "integrity": "sha512-v59y6iRgSSHozx98grx5CRwPHN8UCj0L88HHJpJVBw/Cnzd2QzvcQQZIPlOMqH9K1QEEYeFUIJMiCIMJez0b5A==",++ "version": "0.169.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.0",++ "integrity": "sha512-Py2z8UaKGvLzG3atuIAnJKnRe74Te9cJRTaxqFq6m4fov6gaMXeS6rn7lH0tSIo0qBGD5trrT618ARXnklkp+A==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -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.168.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.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.1.3","pg": "^8.23.0",@@@ -241,6 +241,9 @@ const DEFAULT_GRANTS = [['watch', 'run.job.submit', 'openvibe.run', []],['watch', 'network.project.member.read', SELF_AUDIENCE, []],['run', 'network.project.member.read', SELF_AUDIENCE, []],++ // Actor and Media read a person's project role before a project admin's control request (contracts 0.169.0).++ ['actor', 'network.project.member.read', SELF_AUDIENCE, []],++ ['media', 'network.project.member.read', SELF_AUDIENCE, []],// Work's agent pillar: "Ask my Actor agent" on a tracked application gives Actor a task for that person (X-OV-Subject).['work', 'actor.task.create', 'openvibe.actor', []],// Food's recipes pillar: food.suggest_recipes on OpenVibe.AI for the signed-in person (AI#57).@@@ -258,6 +261,10 @@ const DEFAULT_GRANTS = [['services', 'watch.resource.control', 'openvibe.watch', []],// Events is the second (events.resource.control, contracts 0.168.0): suspend and resume a person's subscription.['services', 'events.resource.control', 'openvibe.events', []],++ // Actor and Media (contracts 0.169.0): stop a task; delete an object (the soft delete). Each acts for the resource's++ // own person or an admin of its project.++ ['services', 'actor.resource.control', 'openvibe.actor', []],++ ['services', 'media.resource.control', 'openvibe.media', []],// 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', []],@@@ -143,9 +143,11 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };}// 6. One person's membership (project delegation, ADR-003 amendment 2026-10-12): network.project.member.read,-- // Run and Watch only, never grantable; members only, and a non-member reads exactly like an unknown project.++ // Run, Watch, and (contracts 0.169.0) Actor and Media for a project admin's control request; never grantable;++ // members only, and a non-member reads exactly like an unknown project.assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.member.read').map(g => [g[0], g[2]]).sort(),-- [['run', principals.SELF_AUDIENCE], ['watch', principals.SELF_AUDIENCE]], 'Run and Watch hold network.project.member.read');++ [['actor', principals.SELF_AUDIENCE], ['media', principals.SELF_AUDIENCE], ['run', principals.SELF_AUDIENCE], ['watch', principals.SELF_AUDIENCE]],++ 'Run, Watch, Actor and Media hold network.project.member.read');assert.strictEqual(policy.grantability('network.project.member.read').grantable, false, 'never grantable to apps');assert.ok(principals.DEFAULT_GRANTS.some(g => g[0] === 'watch' && g[1] === 'run.job.submit' && g[2] === 'openvibe.run'), 'Watch may submit Run jobs');const memberRead = async (project, subject, headers = {}) => {@@@ -113,7 +113,7 @@ const server = http.createServer(app);assert.deepStrictEqual(binders, ['openre']);t = await token({ client_id: 'media', client_secret: 'media-secret', audience: 'openvibe.network' });assert.strictEqual(t.status, 200, JSON.stringify(t.body));-- assert.strictEqual(t.body.scope, 'identity.subject.resolve network.account.deletion.confirm network.account.export.contribute', 'Media resolves object owners to subjects and takes part in account export and deletion (ADR-033), nothing else here');++ assert.strictEqual(t.body.scope, 'identity.subject.resolve network.account.deletion.confirm network.account.export.contribute network.project.member.read', 'Media resolves object owners to subjects, takes part in account export and deletion (ADR-033) and reads a person\'s project role before a control request (contracts 0.169.0), nothing else here');const mediaResolve = t.body.access_token;t = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.media' });assert.strictEqual(t.status, 200, JSON.stringify(t.body));
@@ -20,7 +20,7 @@
@@ -20,7 +20,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.168.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.44.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.1.3",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -1320,9 +1320,9 @@
@@ -1320,9 +1320,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.168.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.168.0",
- "integrity": "sha512-v59y6iRgSSHozx98grx5CRwPHN8UCj0L88HHJpJVBw/Cnzd2QzvcQQZIPlOMqH9K1QEEYeFUIJMiCIMJez0b5A==",
+ "version": "0.169.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.0",
+ "integrity": "sha512-Py2z8UaKGvLzG3atuIAnJKnRe74Te9cJRTaxqFq6m4fov6gaMXeS6rn7lH0tSIo0qBGD5trrT618ARXnklkp+A==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -24,7 +24,7 @@
@@ -24,7 +24,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.168.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.169.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.44.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.1.3",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.1.3",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -241,6 +241,9 @@ const DEFAULT_GRANTS = [
@@ -241,6 +241,9 @@ const DEFAULT_GRANTS = [
['watch', 'run.job.submit', 'openvibe.run', []],
['watch', 'run.job.submit', 'openvibe.run', []],
['watch', 'network.project.member.read', SELF_AUDIENCE, []],
['watch', 'network.project.member.read', SELF_AUDIENCE, []],
['run', 'network.project.member.read', SELF_AUDIENCE, []],
['run', 'network.project.member.read', SELF_AUDIENCE, []],
+ // Actor and Media read a person's project role before a project admin's control request (contracts 0.169.0).
+ ['actor', 'network.project.member.read', SELF_AUDIENCE, []],
+ ['media', 'network.project.member.read', SELF_AUDIENCE, []],
// Work's agent pillar: "Ask my Actor agent" on a tracked application gives Actor a task for that person (X-OV-Subject).
// Work's agent pillar: "Ask my Actor agent" on a tracked application gives Actor a task for that person (X-OV-Subject).
['work', 'actor.task.create', 'openvibe.actor', []],
['work', 'actor.task.create', 'openvibe.actor', []],
// Food's recipes pillar: food.suggest_recipes on OpenVibe.AI for the signed-in person (AI#57).
// Food's recipes pillar: food.suggest_recipes on OpenVibe.AI for the signed-in person (AI#57).
@@ -258,6 +261,10 @@ const DEFAULT_GRANTS = [
@@ -258,6 +261,10 @@ const DEFAULT_GRANTS = [
['services', 'watch.resource.control', 'openvibe.watch', []],
['services', 'watch.resource.control', 'openvibe.watch', []],
// Events is the second (events.resource.control, contracts 0.168.0): suspend and resume a person's subscription.
// Events is the second (events.resource.control, contracts 0.168.0): suspend and resume a person's subscription.
['services', 'events.resource.control', 'openvibe.events', []],
['services', 'events.resource.control', 'openvibe.events', []],
+ // Actor and Media (contracts 0.169.0): stop a task; delete an object (the soft delete). Each acts for the resource's
+ // own person or an admin of its project.
+ ['services', 'actor.resource.control', 'openvibe.actor', []],
+ ['services', 'media.resource.control', 'openvibe.media', []],
// Plan T14: OpenVibe.Run publishes run.job.* and hands each job to a paired Node through Bot's internal jobs API
// 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).
// (POST /api/v1/jobs, /jobs/:id/cancel, GET /jobs/:id, capability bot.job.dispatch, service-to-service only).
['run', 'events.event.publish', 'openvibe.events', []],
['run', 'events.event.publish', 'openvibe.events', []],
@@ -143,9 +143,11 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };
@@ -143,9 +143,11 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };
}
}
// 6. One person's membership (project delegation, ADR-003 amendment 2026-10-12): network.project.member.read,
// 6. One person's membership (project delegation, ADR-003 amendment 2026-10-12): network.project.member.read,
- // Run and Watch only, never grantable; members only, and a non-member reads exactly like an unknown project.
+ // Run, Watch, and (contracts 0.169.0) Actor and Media for a project admin's control request; never grantable;
+ // members only, and a non-member reads exactly like an unknown project.
assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.member.read').map(g => [g[0], g[2]]).sort(),
assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.member.read').map(g => [g[0], g[2]]).sort(),
- [['run', principals.SELF_AUDIENCE], ['watch', principals.SELF_AUDIENCE]], 'Run and Watch hold network.project.member.read');
+ [['actor', principals.SELF_AUDIENCE], ['media', principals.SELF_AUDIENCE], ['run', principals.SELF_AUDIENCE], ['watch', principals.SELF_AUDIENCE]],
+ 'Run, Watch, Actor and Media hold network.project.member.read');
assert.strictEqual(policy.grantability('network.project.member.read').grantable, false, 'never grantable to apps');
assert.strictEqual(policy.grantability('network.project.member.read').grantable, false, 'never grantable to apps');
assert.ok(principals.DEFAULT_GRANTS.some(g => g[0] === 'watch' && g[1] === 'run.job.submit' && g[2] === 'openvibe.run'), 'Watch may submit Run jobs');
assert.ok(principals.DEFAULT_GRANTS.some(g => g[0] === 'watch' && g[1] === 'run.job.submit' && g[2] === 'openvibe.run'), 'Watch may submit Run jobs');
const memberRead = async (project, subject, headers = {}) => {
const memberRead = async (project, subject, headers = {}) => {
@@ -113,7 +113,7 @@ const server = http.createServer(app);
@@ -113,7 +113,7 @@ const server = http.createServer(app);
assert.deepStrictEqual(binders, ['openre']);
assert.deepStrictEqual(binders, ['openre']);
t = await token({ client_id: 'media', client_secret: 'media-secret', audience: 'openvibe.network' });
t = await token({ client_id: 'media', client_secret: 'media-secret', audience: 'openvibe.network' });
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
- assert.strictEqual(t.body.scope, 'identity.subject.resolve network.account.deletion.confirm network.account.export.contribute', 'Media resolves object owners to subjects and takes part in account export and deletion (ADR-033), nothing else here');
+ assert.strictEqual(t.body.scope, 'identity.subject.resolve network.account.deletion.confirm network.account.export.contribute network.project.member.read', 'Media resolves object owners to subjects, takes part in account export and deletion (ADR-033) and reads a person\'s project role before a control request (contracts 0.169.0), nothing else here');
const mediaResolve = t.body.access_token;
const mediaResolve = t.body.access_token;
t = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.media' });
t = await token({ client_id: 'live', client_secret: 'live-secret', audience: 'openvibe.media' });
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
assert.strictEqual(t.status, 200, JSON.stringify(t.body));