openvibers/openvibe.network Commits
Design's token may run the deep dive workflow (#851)
Changes
@@@ -850,7 +850,8 @@ const DEFAULT_GRANTS = [// ai.generate for a pinned frame; ai.chat for one with a preference (Design #57, 10-31, L37: the chat passthrough is// what passes `prefer` to the router). Without ai.chat every unpinned frame was refused ("this token may not run// ai.chat") from 10-31 to 11-02.-- ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat']],++ // Design's /create starts a deep dive on the person's behalf (workflow design.deep_dive), beside its frames.++ ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat', 'design.deep_dive']],// The Exchange platform API's icon reference search (contracts 0.407.0, exchange.search.icons): Design asks// OpenVibe.AI for the references beside a glyph draft with its own service token, so no Exchange key is needed,// and reaches only POST /v1/search/icons. This grant was never granted or revoked before, so it needs no@@@ -1042,6 +1043,9 @@ const CHANGED_DEFAULT_NAMESPACES = [// production row, seeded before, kept ['ai.generate'] (the seed never changes an existing row), so frames still// failed until this moved it.['design', 'ai.run.create', 'openvibe.ai', ['ai.generate'], ['ai.generate', 'ai.chat']],++ // Design's /create starts a deep dive on the person's behalf (workflow design.deep_dive). The row above moved++ // production to ai.chat; this moves the same row to name the dive's workflow too.++ ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat'], ['ai.generate', 'ai.chat', 'design.deep_dive']],// The shared image task (rich-content §4/§5, Network #751): the production Blog/Wiki rows were seeded before// image.picture joined their list, so the seed keeps the old namespaces. Move them once, explicitly.['wiki', 'ai.run.create', 'openvibe.ai', ['wiki.*', 'research.*'], ['wiki.*', 'research.*', 'image.picture']],@@@ -14,12 +14,12 @@ const principals = require('../server/identity/principals');const db = getDb();await principals.ensureSchema(db);const row = async () => JSON.parse((await db.prepare("SELECT namespaces FROM principal_grants WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").get()).namespaces);-- assert.deepStrictEqual((await row()).sort(), ['ai.chat', 'ai.generate'], 'a fresh database gets both');++ assert.deepStrictEqual((await row()).sort(), ['ai.chat', 'ai.generate', 'design.deep_dive'], 'a fresh database gets the frames and the deep dive');// Production's row, as the old default seeded it.await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate']));await principals.ensureSchema(db);-- assert.deepStrictEqual(await row(), ['ai.generate', 'ai.chat'], 'the old default row is moved at boot');++ assert.deepStrictEqual(await row(), ['ai.generate', 'ai.chat', 'design.deep_dive'], 'the old default row is moved at boot, through both moves');// A row edited by hand stays as it was.await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate', 'custom.x']));nnew file mode 100644@@@ -0,0 +1,21 @@++/**++ * OpenVibe.Design starts a deep dive (AI workflow design.deep_dive) for /create's Research first, with its own++ * client-credentials token on openvibe.ai. The grant names the workflow, and the changed-default entry moves a++ * production row seeded before it. Exchange keeps its own allowance; this is Design's.++ */++'use strict';++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const principals = require('../server/identity/principals');++++const AUDIENCE = 'openvibe.ai';++const grant = principals.DEFAULT_GRANTS.find(([client, cap, aud]) => client === 'design' && cap === 'ai.run.create' && aud === AUDIENCE);++assert.ok(grant, 'Design holds ai.run.create on openvibe.ai');++assert.ok(grant[3].includes('ai.generate'), 'its frames still run ai.generate');++assert.ok(grant[3].includes('ai.chat'), 'a frame with a preference still runs ai.chat');++assert.ok(grant[3].includes('design.deep_dive'), 'Research first can start a deep dive');++// The list itself is deliberately not exported (session-report-grants.test.js checks that). Read the entry.++const source = fs.readFileSync(path.join(__dirname, '..', 'server', 'identity', 'principals.js'), 'utf8');++assert.match(source, /\['design', 'ai\.run\.create', 'openvibe\.ai', \['ai\.generate', 'ai\.chat'\], \['ai\.generate', 'ai\.chat', 'design\.deep_dive'\]\]/, 'a production row seeded at ai.generate/ai.chat is moved to name the deep dive');++console.log('design deep dive grant: ok');@@@ -214,7 +214,7 @@ const server = http.createServer(app);// Design's frames: ai.generate when pinned, ai.chat with a preference (11-02: without ai.chat every unpinned frame failed).t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.ai' });assert.strictEqual(t.status, 200, JSON.stringify(t.body));-- assert.deepStrictEqual([...JSON.parse(Buffer.from(t.body.access_token.split('.')[1], 'base64url').toString()).ns].sort(), ['ai.chat', 'ai.generate'], 'pinned frames run ai.generate, preferred ones ai.chat');++ assert.deepStrictEqual([...JSON.parse(Buffer.from(t.body.access_token.split('.')[1], 'base64url').toString()).ns].sort(), ['ai.chat', 'ai.generate', 'design.deep_dive'], 'pinned frames run ai.generate, preferred ones ai.chat, Research first starts design.deep_dive');// Open item D8: Design publishes a person's design on Space, acting for them.t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.space' });assert.strictEqual(t.status, 200, JSON.stringify(t.body));
@@ -850,7 +850,8 @@ const DEFAULT_GRANTS = [
@@ -850,7 +850,8 @@ const DEFAULT_GRANTS = [
// ai.generate for a pinned frame; ai.chat for one with a preference (Design #57, 10-31, L37: the chat passthrough is
// ai.generate for a pinned frame; ai.chat for one with a preference (Design #57, 10-31, L37: the chat passthrough is
// what passes `prefer` to the router). Without ai.chat every unpinned frame was refused ("this token may not run
// what passes `prefer` to the router). Without ai.chat every unpinned frame was refused ("this token may not run
// ai.chat") from 10-31 to 11-02.
// ai.chat") from 10-31 to 11-02.
- ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat']],
+ // Design's /create starts a deep dive on the person's behalf (workflow design.deep_dive), beside its frames.
+ ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat', 'design.deep_dive']],
// The Exchange platform API's icon reference search (contracts 0.407.0, exchange.search.icons): Design asks
// The Exchange platform API's icon reference search (contracts 0.407.0, exchange.search.icons): Design asks
// OpenVibe.AI for the references beside a glyph draft with its own service token, so no Exchange key is needed,
// OpenVibe.AI for the references beside a glyph draft with its own service token, so no Exchange key is needed,
// and reaches only POST /v1/search/icons. This grant was never granted or revoked before, so it needs no
// and reaches only POST /v1/search/icons. This grant was never granted or revoked before, so it needs no
@@ -1042,6 +1043,9 @@ const CHANGED_DEFAULT_NAMESPACES = [
@@ -1042,6 +1043,9 @@ const CHANGED_DEFAULT_NAMESPACES = [
// production row, seeded before, kept ['ai.generate'] (the seed never changes an existing row), so frames still
// production row, seeded before, kept ['ai.generate'] (the seed never changes an existing row), so frames still
// failed until this moved it.
// failed until this moved it.
['design', 'ai.run.create', 'openvibe.ai', ['ai.generate'], ['ai.generate', 'ai.chat']],
['design', 'ai.run.create', 'openvibe.ai', ['ai.generate'], ['ai.generate', 'ai.chat']],
+ // Design's /create starts a deep dive on the person's behalf (workflow design.deep_dive). The row above moved
+ // production to ai.chat; this moves the same row to name the dive's workflow too.
+ ['design', 'ai.run.create', 'openvibe.ai', ['ai.generate', 'ai.chat'], ['ai.generate', 'ai.chat', 'design.deep_dive']],
// The shared image task (rich-content §4/§5, Network #751): the production Blog/Wiki rows were seeded before
// The shared image task (rich-content §4/§5, Network #751): the production Blog/Wiki rows were seeded before
// image.picture joined their list, so the seed keeps the old namespaces. Move them once, explicitly.
// image.picture joined their list, so the seed keeps the old namespaces. Move them once, explicitly.
['wiki', 'ai.run.create', 'openvibe.ai', ['wiki.*', 'research.*'], ['wiki.*', 'research.*', 'image.picture']],
['wiki', 'ai.run.create', 'openvibe.ai', ['wiki.*', 'research.*'], ['wiki.*', 'research.*', 'image.picture']],
@@ -14,12 +14,12 @@ const principals = require('../server/identity/principals');
@@ -14,12 +14,12 @@ const principals = require('../server/identity/principals');
const db = getDb();
const db = getDb();
await principals.ensureSchema(db);
await principals.ensureSchema(db);
const row = async () => JSON.parse((await db.prepare("SELECT namespaces FROM principal_grants WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").get()).namespaces);
const row = async () => JSON.parse((await db.prepare("SELECT namespaces FROM principal_grants WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").get()).namespaces);
- assert.deepStrictEqual((await row()).sort(), ['ai.chat', 'ai.generate'], 'a fresh database gets both');
+ assert.deepStrictEqual((await row()).sort(), ['ai.chat', 'ai.generate', 'design.deep_dive'], 'a fresh database gets the frames and the deep dive');
// Production's row, as the old default seeded it.
// Production's row, as the old default seeded it.
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate']));
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate']));
await principals.ensureSchema(db);
await principals.ensureSchema(db);
- assert.deepStrictEqual(await row(), ['ai.generate', 'ai.chat'], 'the old default row is moved at boot');
+ assert.deepStrictEqual(await row(), ['ai.generate', 'ai.chat', 'design.deep_dive'], 'the old default row is moved at boot, through both moves');
// A row edited by hand stays as it was.
// A row edited by hand stays as it was.
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate', 'custom.x']));
await db.prepare("UPDATE principal_grants SET namespaces = ? WHERE client_id = 'design' AND capability = 'ai.run.create' AND audience = 'openvibe.ai'").run(JSON.stringify(['ai.generate', 'custom.x']));
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+/**
+ * OpenVibe.Design starts a deep dive (AI workflow design.deep_dive) for /create's Research first, with its own
+ * client-credentials token on openvibe.ai. The grant names the workflow, and the changed-default entry moves a
+ * production row seeded before it. Exchange keeps its own allowance; this is Design's.
+ */
+'use strict';
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const principals = require('../server/identity/principals');
+
+const AUDIENCE = 'openvibe.ai';
+const grant = principals.DEFAULT_GRANTS.find(([client, cap, aud]) => client === 'design' && cap === 'ai.run.create' && aud === AUDIENCE);
+assert.ok(grant, 'Design holds ai.run.create on openvibe.ai');
+assert.ok(grant[3].includes('ai.generate'), 'its frames still run ai.generate');
+assert.ok(grant[3].includes('ai.chat'), 'a frame with a preference still runs ai.chat');
+assert.ok(grant[3].includes('design.deep_dive'), 'Research first can start a deep dive');
+// The list itself is deliberately not exported (session-report-grants.test.js checks that). Read the entry.
+const source = fs.readFileSync(path.join(__dirname, '..', 'server', 'identity', 'principals.js'), 'utf8');
+assert.match(source, /\['design', 'ai\.run\.create', 'openvibe\.ai', \['ai\.generate', 'ai\.chat'\], \['ai\.generate', 'ai\.chat', 'design\.deep_dive'\]\]/, 'a production row seeded at ai.generate/ai.chat is moved to name the deep dive');
+console.log('design deep dive grant: ok');
@@ -214,7 +214,7 @@ const server = http.createServer(app);
@@ -214,7 +214,7 @@ const server = http.createServer(app);
// Design's frames: ai.generate when pinned, ai.chat with a preference (11-02: without ai.chat every unpinned frame failed).
// Design's frames: ai.generate when pinned, ai.chat with a preference (11-02: without ai.chat every unpinned frame failed).
t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.ai' });
t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.ai' });
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
- assert.deepStrictEqual([...JSON.parse(Buffer.from(t.body.access_token.split('.')[1], 'base64url').toString()).ns].sort(), ['ai.chat', 'ai.generate'], 'pinned frames run ai.generate, preferred ones ai.chat');
+ assert.deepStrictEqual([...JSON.parse(Buffer.from(t.body.access_token.split('.')[1], 'base64url').toString()).ns].sort(), ['ai.chat', 'ai.generate', 'design.deep_dive'], 'pinned frames run ai.generate, preferred ones ai.chat, Research first starts design.deep_dive');
// Open item D8: Design publishes a person's design on Space, acting for them.
// Open item D8: Design publishes a person's design on Space, acting for them.
t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.space' });
t = await token({ client_id: 'design', client_secret: 'design-secret', audience: 'openvibe.space' });
assert.strictEqual(t.status, 200, JSON.stringify(t.body));
assert.strictEqual(t.status, 200, JSON.stringify(t.body));