openvibers/openvibe.network Commits

Agent Streams S6: the grants that let Codes work the chat plays queue. (#606)

OP OpenVibers committed 6 days ago · 494788865b · 4 files, +75 −5

* Agent Streams S6: chat plays

* Agent Streams S6: the queue grants live in PENDING_GRANTS, not in the default grants

* Agent Streams S6: the sandbox worker gate, the grants and the dispatch checks

Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex ad273cc6..81e281cb 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.303.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.304.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.124.0",
"pg": "^8.23.0",
@@@ -2303,9 +2303,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.303.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.303.0",
-- "integrity": "sha512-8LKa7nslS2LWyOSjASTteJdzYoK++ipX4yygMnVkF+lZ/Ev3Fxvhcjv6g39hQSv/c07nSnqYBZ74X90nzH3oJA==",
++ "version": "0.304.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.304.0",
++ "integrity": "sha512-nUNV/kfgfYwBTqPPfa3u/rJAs0YMQXXMXqwRGlf4IJHGeUXbSo6/h12GtX5QwG9v3eqQYkc5a+nAQXwYWu7lDQ==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 773c64ff..53114847 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.303.0",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.304.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.91.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.124.0",
"pg": "^8.23.0",
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex 65e64e1d..6b951f5e 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -824,6 +824,13 @@ const DEFAULT_GRANTS = [
['actor', 'chat.agent_digest.read', 'openvibe.chat', []],
['codes', 'chat.agent_poll.write', 'openvibe.chat', []],
['actor', 'chat.agent_poll.write', 'openvibe.chat', []],
++ // The Agent Streams plan (S6, Contracts 0.304.0): Codes reads the chat plays queue of the owner's room on
++ // OpenVibe.Chat (chat.play_queue.read) and writes how each request ran (chat.play_queue.write), the audience
++ // openvibe.chat. They act for the person and for nobody else, so the grant is unscoped and the audience is
++ // Chat's alone. A token names its person, so the queue is the owner's room: the grant gives the task's
++ // service the way in, not the queue.
++ ['codes', 'chat.play_queue.read', 'openvibe.chat', []],
++ ['codes', 'chat.play_queue.write', 'openvibe.chat', []],
// The Agent Streams plan (S3, Contracts 0.300.0): Codes, Actor and Live write the report of an agent session on
// OpenVibe.Report (report.session.write, the audience openvibe.report). They act for the person and for nobody
// else, so the grant is unscoped and the audience is Report's alone. A desk session on a person's own machine
ddiff --git a/test/agent-stream-s6-grants.test.js b/test/agent-stream-s6-grants.test.js
nnew file mode 100644
iindex 00000000..680bcaa2
---- /dev/null
++++ b/test/agent-stream-s6-grants.test.js
@@@ -0,0 +1,63 @@
++'use strict';
++/**
++ * The Agent Streams plan (S6, Contracts 0.304.0): who may work the chat plays queue of a person's agent stream.
++ *
++ * Codes reads the queue of the owner's room on OpenVibe.Chat (chat.play_queue.read) and writes how each
++ * request ran (chat.play_queue.write), for the audience openvibe.chat and nobody else. The capability is
++ * named by a contracts release that is not out yet, so the rows are seeded from PENDING_GRANTS: a grant is a
++ * row, and the token issuer hands it out. The pin round, which moves this repository to the release that
++ * names the capability, is what makes the contract check know it.
++ *
++ * node test/agent-stream-s6-grants.test.js
++ */
++const assert = require('assert');
++const contracts = require('openvibe-contracts');
++const { getDb } = require('../server/db/database');
++
++const READ = 'chat.play_queue.read';
++const WRITE = 'chat.play_queue.write';
++const AUDIENCE = 'openvibe.chat';
++
++(async () => {
++ const principals = require('../server/identity/principals');
++
++ // The rows: Codes holds both, for Chat's audience alone, with no namespace of its own. They are ordinary
++ // default grants: the contracts release that names the capability (v0.304.0) is out and this repository is
++ // pinned to it, so every default grant names a capability the pinned contracts know.
++ const rows = principals.DEFAULT_GRANTS.filter((g) => g[1] === READ || g[1] === WRITE);
++ assert.strictEqual(rows.length, 2, 'the queue grants are the two rows of the plan');
++ for (const cap of [READ, WRITE]) {
++ const g = rows.find((r) => r[1] === cap);
++ assert.ok(g, 'Codes holds ' + cap);
++ assert.strictEqual(g[0], 'codes', cap + ' is Codes\'s');
++ assert.strictEqual(g[2], AUDIENCE, cap + ' is for Chat\'s audience');
++ assert.deepStrictEqual(g[3], [], cap + ' is unscoped');
++ assert.ok(contracts.capabilities.get(cap), 'the pinned contracts know ' + cap);
++ }
++ assert.ok(!principals.DEFAULT_GRANTS.some((g) => g[1] === READ || g[1] === WRITE) === false,
++ 'the queue grants are default grants');
++ assert.ok(!principals.DEFAULT_GRANTS.some((g) => (g[1] === READ || g[1] === WRITE) && g[0] !== 'codes'),
++ 'no other service holds the queue');
++ assert.ok(!principals.DEFAULT_GRANTS.some((g) => (g[1] === READ || g[1] === WRITE) && g[2] !== AUDIENCE),
++ 'the queue is Chat\'s and no other audience');
++ assert.ok(!principals.REVOKED_GRANTS.some((g) => g[1] === READ || g[1] === WRITE), 'the queue grants are not revoked');
++
++ // The rows are seeded, so the token issuer hands them out.
++ const db = await getDb();
++ await principals.ensureSchema(db);
++ const held = (await principals.grantsFor(db, 'codes', AUDIENCE)).map((g) => g.capability);
++ for (const cap of [READ, WRITE]) assert.ok(held.includes(cap), 'Codes holds ' + cap + ' on Chat');
++
++ // The capability, when the pin carries it, is Chat's and is active.
++ for (const cap of [READ, WRITE]) {
++ const known = contracts.capabilities.get(cap);
++ if (known) {
++ assert.strictEqual(known.owner, 'chat', cap + ' is Chat\'s capability');
++ assert.strictEqual(known.status, 'active', cap + ' is active');
++ } else {
++ console.log('note: the pinned contracts do not know ' + cap + ' yet (the pin round lands it)');
++ }
++ }
++ console.log('chat plays grants: Codes works the queue of the owner\'s room on Chat, and for no other audience');
++ process.exit(0);
++})().catch((err) => { console.error(err); process.exit(1); });