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)

OP OpenVibers committed 30 days ago · 1210e33f51 · 5 files, +17 −8

* Network: Services may stop an Actor task and delete a Media object; Actor and Media read project roles (contracts 0.169)

Plan T13 step 10 (contracts 0.169.0): Actor and Media join Watch and Events behind the Services control route.

- OpenVibe.Services gets `actor.resource.control` on openvibe.actor (stop a task) and `media.resource.control` on openvibe.media (delete an object, the soft delete).
- Actor and Media get `network.project.member.read`, as Watch and Run have it. Each authority acts for the resource's own person, or for an admin or owner of its project (asked here: `GET /internal/projects/:id/members/:subject`). That is how a project's admins stop or delete what its apps made.
- Pins contracts v0.169.0.

Tests: `principals`, `service-principal` and `mod-principals` pass. The pinned Media scope string in `principals.test.js` now includes the member read.

* Network: internal-projects test names Actor and Media as member-read holders

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 9b805672..b36a4b11 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.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",
ddiff --git a/package.json b/package.json
iindex b2771925..b1fa5718 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.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",
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex c94b6604..08719522 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -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', []],
ddiff --git a/test/internal-projects.test.js b/test/internal-projects.test.js
iindex 4ebcbd85..e5665433 100644
---- a/test/internal-projects.test.js
++++ b/test/internal-projects.test.js
@@@ -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 = {}) => {
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex 87ecca04..cf8c8571 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -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));