openvibers/openvibe.network Commits
Agent Streams S6: the grants that let Codes work the chat plays queue. (#606)
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.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",@@@ -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",@@@ -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 machinennew file mode 100644@@@ -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); });
@@ -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.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-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",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.124.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -2303,9 +2303,9 @@
@@ -2303,9 +2303,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"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",
"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.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-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",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.124.0",
"pg": "^8.23.0",
"pg": "^8.23.0",
@@ -824,6 +824,13 @@ const DEFAULT_GRANTS = [
@@ -824,6 +824,13 @@ const DEFAULT_GRANTS = [
['actor', 'chat.agent_digest.read', 'openvibe.chat', []],
['actor', 'chat.agent_digest.read', 'openvibe.chat', []],
['codes', 'chat.agent_poll.write', 'openvibe.chat', []],
['codes', 'chat.agent_poll.write', 'openvibe.chat', []],
['actor', '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
// 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
// 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
// else, so the grant is unscoped and the audience is Report's alone. A desk session on a person's own machine
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -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); });