openvibers/openvibe.network Commits

Design's token may run the deep dive workflow (#851)

OP OpenVibers committed 1 days ago · 008f7e263a · 4 files, +29 −4

* Design's token may run the deep dive workflow

/create's Research first starts OpenVibe.AI's design.deep_dive on the
person's behalf. Design's ai.run.create grant named only ai.generate and
ai.chat, so AI's namespace rule refused the run. The default now names
the workflow, and a changed-default entry moves the production row that
was seeded before it. A static test covers the grant and the move.

* Fix the grant tests for Design's deep dive namespace

principals.test.js now expects design.deep_dive in Design's token. The
changed-namespaces list stays unexported; the new static test reads the
entry from the source instead, so session-report-grants keeps its rule.

* The Design AI grant test now expects design.deep_dive too

A fresh row and the old ai.generate row both end with the deep dive
namespace after ensureSchema; a hand-edited row is still left alone.

Changes

ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex b1e2ec61..b86d8de4 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -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']],
ddiff --git a/test/design-ai-chat-grant.test.js b/test/design-ai-chat-grant.test.js
iindex a108cab1..05a41865 100644
---- a/test/design-ai-chat-grant.test.js
++++ b/test/design-ai-chat-grant.test.js
@@@ -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']));
ddiff --git a/test/design-deep-dive-grant.test.js b/test/design-deep-dive-grant.test.js
nnew file mode 100644
iindex 00000000..fcab1a93
---- /dev/null
++++ b/test/design-deep-dive-grant.test.js
@@@ -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');
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex d0bd3526..fe9af093 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -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));