openvibers/openvibe.network Commits

Space's builder as the person's one agent: the grants between the two services (#634)

OP OpenVibers committed 6 days ago · 40aad2e5af · 5 files, +55 −7

* Space's builder as the person's one agent: the grants between the two services

Plan 2026-10-22 section Z, stage Z1. Space asks OpenVibe.Actor for a task
on the person's behalf (X-OV-Subject: the task is theirs, their allowance and
their list, Space recorded as the service that started it) and reads that task
back to follow it: actor.task.create and actor.task.read on openvibe.actor.

The other way round: the agent works on the person's own spaces through
Space's builder API (space.build.read, space.build.create, space.build.write
and space.build.publish on openvibe.space). Space reads only that person's
spaces, and publishing waits for the person's answer.

* Merge remote-tracking branch 'origin/main' into feature/z1-space-tools

* Merge remote-tracking branch 'origin/main' into feature/z1-space-tools

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex 6c4eb557..e8c4dc83 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.320.0",
++ "contracts": "openvibe-contracts v0.324.0",
"sdk": "openvibe-sdk v0.94.0",
"shared": "openvibe-shared v4.133.0",
"capabilities": [
@@@ -50,7 +50,8 @@
"per-actor limits (openvibe-sdk/limits)",
"universal telemetry: per-route aggregated platform.telemetry-sample@1 plus per-flush HTTP autoscaling gauges (openvibe-sdk/telemetry)",
"the Dock's state per person: GET and PUT /api/v1/me/dock (network.dock-state@1, network.dock-result@1)",
-- "the machines hub (/api/v1/machines, plan L34): a person's machines, their roles and what they do right now, with Bot as the registry underneath (Network calls it as the person) and network.machine.online/offline/paired/roles_changed/revoked published through OpenVibe.Events"
++ "the machines hub (/api/v1/machines, plan L34): a person's machines, their roles and what they do right now, with Bot as the registry underneath (Network calls it as the person) and network.machine.online/offline/paired/roles_changed/revoked published through OpenVibe.Events",
++ "Space's builder as a task of the person's one agent (plan 2026-10-22 §Z stage Z1): the grants that let Space start a task of the person's agent on OpenVibe.Actor and let that agent work on the person's own spaces through OpenVibe.Space's builder API"
],
"updated": "2026-11-07"
}
ddiff --git a/package-lock.json b/package-lock.json
iindex 5402a35d..0d550dce 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.320.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.324.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0",
"pg": "^8.23.0",
@@@ -2303,9 +2303,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.320.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.320.0",
-- "integrity": "sha512-ECShhtflTLicQosxVSEPlBFQn9eSnOUyO//Z2oS2NPscvb5RtZusrVq3xYWcJIs9XiJ7ws2Q9pAWPaRZIpEvwQ==",
++ "version": "0.324.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.324.0",
++ "integrity": "sha512-fSeabmHcX74SxVKEfGXMgWhEDugYQh5KWB+5Vj86CTvr2q50e/7AfHBDtVVw2WYQ06wiH1wN0hctDYoc2DQi9Q==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 6bba7b62..59122e14 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.320.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.324.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0",
"pg": "^8.23.0",
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex 0ab82af8..bd49c149 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -314,6 +314,14 @@ const DEFAULT_GRANTS = [
// Account export and deletion again (Space#59): spaces hold personal data, so Space subscribes to the two account
// events (its export part and deletion confirmation grants follow once its subscriptions answer).
['space', 'events.subscription.manage', 'openvibe.events', []],
++ // Plan 2026-10-22 section Z, stage Z1: Space's builder becomes a task of the person's one agent. Space asks
++ // OpenVibe.Actor for a task on the person's behalf (X-OV-Subject: the task is theirs, their allowance and their
++ // list, Space recorded as the service that started it) and reads that task back to follow it.
++ ['space', 'actor.task.create', 'openvibe.actor', []],
++ ['space', 'actor.task.read', 'openvibe.actor', []],
++ // The other way round: the agent works on the person's own spaces through Space's builder API (X-OV-Subject names
++ // the person; Space reads and writes only that person's spaces, and publishing waits for the person's answer).
++ ...['space.build.read', 'space.build.create', 'space.build.write', 'space.build.publish'].map((c) => ['actor', c, 'openvibe.space', []]),
// The hosting catalog (plan T20 step 3) reads OpenVibe.Sources items in category hosting (Host#69).
['host', 'sources.item.read', 'openvibe.sources', []],
// The Steam section of openvibe.games (the owner's valve.openvibe.games) reads OpenVibe.Sources items in category
ddiff --git a/test/space-builder-grants.test.js b/test/space-builder-grants.test.js
nnew file mode 100644
iindex 00000000..e623be3c
---- /dev/null
++++ b/test/space-builder-grants.test.js
@@@ -0,0 +1,39 @@
++/**
++ * Space's builder as the person's one agent (plan 2026-10-22 §Z stage Z1): the grants that let the two services
++ * work for the person.
++ *
++ * Space → OpenVibe.Actor actor.task.create, actor.task.read the task is the person's (X-OV-Subject)
++ * Actor → OpenVibe.Space space.build.read|create|write|publish the agent works on the person's own spaces
++ *
++ * Each capability is one the pinned contracts offer, and it is active: a grant for a capability nobody knows
++ * would be a grant that cannot be checked.
++ *
++ * node test/space-builder-grants.test.js
++ */
++'use strict';
++const assert = require('assert');
++const contracts = require('openvibe-contracts');
++const principals = require('../server/identity/principals');
++
++const grants = principals.DEFAULT_GRANTS;
++
++// Space asks Actor for a task on the person's behalf, and reads it back to follow it.
++for (const cap of ['actor.task.create', 'actor.task.read']) {
++ const row = grants.find((g) => g[0] === 'space' && g[1] === cap && g[2] === 'openvibe.actor');
++ assert.ok(row, `space holds ${cap} on openvibe.actor`);
++ assert.deepStrictEqual(row[3], [], 'no namespace: the task is the person\'s, not a project\'s');
++ const known = contracts.capabilities.get(cap);
++ assert.ok(known && known.status === 'active', `${cap} is an active capability`);
++}
++
++// The agent works on the person's own spaces through Space's builder API.
++for (const cap of ['space.build.read', 'space.build.create', 'space.build.write', 'space.build.publish']) {
++ const row = grants.find((g) => g[0] === 'actor' && g[1] === cap && g[2] === 'openvibe.space');
++ assert.ok(row, `actor holds ${cap} on openvibe.space`);
++ assert.deepStrictEqual(row[3], [], 'no namespace: the spaces are the person\'s own');
++ const known = contracts.capabilities.get(cap);
++ assert.ok(known && known.status === 'active', `${cap} is an active capability`);
++ assert.strictEqual(known.owner, 'space', `${cap} belongs to space`);
++}
++
++console.log('space builder grants: the task is the person\'s, the spaces are the person\'s own');