openvibers/openvibe.network Commits
Vibes Pay: Billing accepts sandbox app tokens and reads a project's name (#560)
Changes
@@@ -50,9 +50,10 @@ function grantableCatalog() {* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,* Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5), AI with* sandbox runs on free and local capacity only (ai.app.run, plan T6), Play with sandbox- titles of guests only,-- * never listed or counted (OpenVibe.Play's store). openvibe.network is deliberately not one of them.++ * never listed or counted (OpenVibe.Play's store), Billing with sandbox Vibes and sandbox Balance only (Vibes Pay++ * test charges, D12; real Vibes are never touched). openvibe.network is deliberately not one of them.*/--const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.services', 'openvibe.ai', 'openvibe.quest', 'openvibe.play']);++const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.services', 'openvibe.ai', 'openvibe.quest', 'openvibe.play', 'openvibe.billing']);/*** Capabilities every project's SANDBOX apps may hold without a staff decision when@@@ -548,6 +548,8 @@ const DEFAULT_GRANTS = [// D12: a person's Balance lists the projects they administer and checks the role before a transfer. Billing acts// for the signed-in person with its own service token, never with theirs.['billing', 'network.project.member.read', SELF_AUDIENCE, []],++ // D12 Vibes Pay: the pay sheet names the app's project (its name and icon) from GET /internal/projects/:id.++ ['billing', 'network.project.read', SELF_AUDIENCE, []],['chat', 'identity.subject.resolve', SELF_AUDIENCE, []],['live', 'identity.subject.resolve', SELF_AUDIENCE, []],// Live's avatar picker reports to Network, and Live reads the URL registry and@@@ -67,7 +67,7 @@ async function withoutCatalog(hidden, fn) {(async () => {// ── Settings: defaults, env overrides, and the public-only rule ──let s = policy.settings({});-- assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.events', 'openvibe.media', 'openvibe.play', 'openvibe.quest', 'openvibe.services', 'openvibe.tools']);++ assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.billing', 'openvibe.events', 'openvibe.media', 'openvibe.play', 'openvibe.quest', 'openvibe.services', 'openvibe.tools']);assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');assert.deepStrictEqual(s.sandboxAllowance, ['ai.app.run', 'events.app.publish', 'events.app.read', 'events.app.subscribe','media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'play.player.read', 'play.result.write', 'play.title.manage', 'quest.app_quest.manage', 'services.release.manage', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],@@@ -61,11 +61,12 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };return { status: r.status, type: r.headers.get('content-type') || '', cache: r.headers.get('cache-control'), text, body: text ? JSON.parse(text) : null };};try {-- // 0. The grant: Host (tenancy) and AI (the service that spends for a project, plan G-P3) hold it by default,++ // 0. The grant: Host (tenancy), AI (the service that spends for a project, plan G-P3) and Billing (the Vibes Pay++ // sheet names the app's project, D12) hold it by default,// and it is never grantable to apps.assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.read').map(g => [g[0], g[2]]).sort(),-- [['ai', principals.SELF_AUDIENCE], ['host', principals.SELF_AUDIENCE]],-- 'Host and AI hold network.project.read by default');++ [['ai', principals.SELF_AUDIENCE], ['billing', principals.SELF_AUDIENCE], ['host', principals.SELF_AUDIENCE]],++ 'Host, AI and Billing hold network.project.read by default');assert.strictEqual(policy.grantability('network.project.read').grantable, false, 'network.project.read is never grantable to apps');// A project made through the members' API: allowance, a quota, a second member, two apps with grants, one revoked.
@@ -50,9 +50,10 @@ function grantableCatalog() {
@@ -50,9 +50,10 @@ function grantableCatalog() {
* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,
* sandbox traffic apart from real data: Media in a sandbox tenant, Events with env-marked events,
* Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5), AI with
* Tools with sandbox jobs, Codes with sandbox releases (an app managing its own, WS-N task 5), AI with
* sandbox runs on free and local capacity only (ai.app.run, plan T6), Play with sandbox- titles of guests only,
* sandbox runs on free and local capacity only (ai.app.run, plan T6), Play with sandbox- titles of guests only,
- * never listed or counted (OpenVibe.Play's store). openvibe.network is deliberately not one of them.
+ * never listed or counted (OpenVibe.Play's store), Billing with sandbox Vibes and sandbox Balance only (Vibes Pay
+ * test charges, D12; real Vibes are never touched). openvibe.network is deliberately not one of them.
*/
*/
-const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.services', 'openvibe.ai', 'openvibe.quest', 'openvibe.play']);
+const DEFAULT_SANDBOX_AUDIENCES = Object.freeze(['openvibe.media', 'openvibe.events', 'openvibe.tools', 'openvibe.services', 'openvibe.ai', 'openvibe.quest', 'openvibe.play', 'openvibe.billing']);
/**
/**
* Capabilities every project's SANDBOX apps may hold without a staff decision when
* Capabilities every project's SANDBOX apps may hold without a staff decision when
@@ -548,6 +548,8 @@ const DEFAULT_GRANTS = [
@@ -548,6 +548,8 @@ const DEFAULT_GRANTS = [
// D12: a person's Balance lists the projects they administer and checks the role before a transfer. Billing acts
// D12: a person's Balance lists the projects they administer and checks the role before a transfer. Billing acts
// for the signed-in person with its own service token, never with theirs.
// for the signed-in person with its own service token, never with theirs.
['billing', 'network.project.member.read', SELF_AUDIENCE, []],
['billing', 'network.project.member.read', SELF_AUDIENCE, []],
+ // D12 Vibes Pay: the pay sheet names the app's project (its name and icon) from GET /internal/projects/:id.
+ ['billing', 'network.project.read', SELF_AUDIENCE, []],
['chat', 'identity.subject.resolve', SELF_AUDIENCE, []],
['chat', 'identity.subject.resolve', SELF_AUDIENCE, []],
['live', 'identity.subject.resolve', SELF_AUDIENCE, []],
['live', 'identity.subject.resolve', SELF_AUDIENCE, []],
// Live's avatar picker reports to Network, and Live reads the URL registry and
// Live's avatar picker reports to Network, and Live reads the URL registry and
@@ -67,7 +67,7 @@ async function withoutCatalog(hidden, fn) {
@@ -67,7 +67,7 @@ async function withoutCatalog(hidden, fn) {
(async () => {
(async () => {
// ── Settings: defaults, env overrides, and the public-only rule ──
// ── Settings: defaults, env overrides, and the public-only rule ──
let s = policy.settings({});
let s = policy.settings({});
- assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.events', 'openvibe.media', 'openvibe.play', 'openvibe.quest', 'openvibe.services', 'openvibe.tools']);
+ assert.deepStrictEqual([...s.sandboxAudiences].sort(), ['openvibe.ai', 'openvibe.billing', 'openvibe.events', 'openvibe.media', 'openvibe.play', 'openvibe.quest', 'openvibe.services', 'openvibe.tools']);
assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');
assert.ok(!s.sandboxAudiences.has('openvibe.network'), 'Network itself never defaults to accepting sandbox tokens');
assert.deepStrictEqual(s.sandboxAllowance, ['ai.app.run', 'events.app.publish', 'events.app.read', 'events.app.subscribe',
assert.deepStrictEqual(s.sandboxAllowance, ['ai.app.run', 'events.app.publish', 'events.app.read', 'events.app.subscribe',
'media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'play.player.read', 'play.result.write', 'play.title.manage', 'quest.app_quest.manage', 'services.release.manage', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],
'media.object.delete', 'media.object.list', 'media.object.read', 'media.object.upload', 'play.player.read', 'play.result.write', 'play.title.manage', 'quest.app_quest.manage', 'services.release.manage', 'tools.job.cancel', 'tools.job.create', 'tools.job.read', 'tools.tool.read', 'tools.tool.run'],
@@ -61,11 +61,12 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };
@@ -61,11 +61,12 @@ const U = { owner: userToken(10), dev: userToken(11), staff: userToken(14) };
return { status: r.status, type: r.headers.get('content-type') || '', cache: r.headers.get('cache-control'), text, body: text ? JSON.parse(text) : null };
return { status: r.status, type: r.headers.get('content-type') || '', cache: r.headers.get('cache-control'), text, body: text ? JSON.parse(text) : null };
};
};
try {
try {
- // 0. The grant: Host (tenancy) and AI (the service that spends for a project, plan G-P3) hold it by default,
+ // 0. The grant: Host (tenancy), AI (the service that spends for a project, plan G-P3) and Billing (the Vibes Pay
+ // sheet names the app's project, D12) hold it by default,
// and it is never grantable to apps.
// and it is never grantable to apps.
assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.read').map(g => [g[0], g[2]]).sort(),
assert.deepStrictEqual(principals.DEFAULT_GRANTS.filter(g => g[1] === 'network.project.read').map(g => [g[0], g[2]]).sort(),
- [['ai', principals.SELF_AUDIENCE], ['host', principals.SELF_AUDIENCE]],
- 'Host and AI hold network.project.read by default');
+ [['ai', principals.SELF_AUDIENCE], ['billing', principals.SELF_AUDIENCE], ['host', principals.SELF_AUDIENCE]],
+ 'Host, AI and Billing hold network.project.read by default');
assert.strictEqual(policy.grantability('network.project.read').grantable, false, 'network.project.read is never grantable to apps');
assert.strictEqual(policy.grantability('network.project.read').grantable, false, 'network.project.read is never grantable to apps');
// A project made through the members' API: allowance, a quota, a second member, two apps with grants, one revoked.
// A project made through the members' API: allowance, a quota, a second member, two apps with grants, one revoked.