101 lines
4.8 KiB
JavaScript
101 lines
4.8 KiB
JavaScript
|
|
import { Arbiter } from '../../src/core/Arbiter.js';
|
||
|
|
import { describe, it, beforeEach } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
describe('Cross-Tenant Transfer — accountIsPlatform root bridge', () => {
|
||
|
|
let arbiter;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
arbiter = new Arbiter({ embeddingDimensions: 256 });
|
||
|
|
|
||
|
|
['account:tenant_a:main', 'account:tenant_a:savings',
|
||
|
|
'account:tenant_b:main', 'account:root:platform'].forEach((a) => arbiter.addNode(a, 'account'));
|
||
|
|
|
||
|
|
arbiter.addNode('session:s_test', 'session');
|
||
|
|
arbiter.addNode('user:admin', 'admin');
|
||
|
|
arbiter.addNode('user:admin_b', 'admin');
|
||
|
|
});
|
||
|
|
|
||
|
|
function addRelation(src, rel, dst, p = 1.0) {
|
||
|
|
arbiter.addRelation(src, rel, dst, { possibility: p });
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkRelation(actor, rel, object) {
|
||
|
|
const cfg = { type: 'direct', relation: rel };
|
||
|
|
if (!arbiter._relationConfigs) arbiter._relationConfigs = {};
|
||
|
|
arbiter._relationConfigs = arbiter._relationConfigs || {};
|
||
|
|
arbiter.setRelationConfig(rel, cfg);
|
||
|
|
return arbiter.authChecker.check(actor, rel, object);
|
||
|
|
}
|
||
|
|
|
||
|
|
it('controls relations grant admin access to accounts', () => {
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_b:main');
|
||
|
|
addRelation('user:admin', 'controls', 'account:root:platform');
|
||
|
|
|
||
|
|
const r1 = checkRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
const r2 = checkRelation('user:admin', 'controls', 'account:tenant_b:main');
|
||
|
|
const rp = checkRelation('user:admin', 'controls', 'account:root:platform');
|
||
|
|
|
||
|
|
assert.ok(r1.possibility > 0.5, `tenant_a controls=${r1.possibility}`);
|
||
|
|
assert.ok(r2.possibility > 0.5, `tenant_b controls=${r2.possibility}`);
|
||
|
|
assert.ok(rp.possibility > 0.5, `root controls=${rp.possibility}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('sameTenantAccount exists for same-tenant pair, absent for cross-tenant', () => {
|
||
|
|
addRelation('account:tenant_a:main', 'sameTenantAccount', 'account:tenant_a:savings');
|
||
|
|
|
||
|
|
const same = checkRelation('account:tenant_a:main', 'sameTenantAccount', 'account:tenant_a:savings');
|
||
|
|
const cross = checkRelation('account:tenant_a:main', 'sameTenantAccount', 'account:tenant_b:main');
|
||
|
|
|
||
|
|
assert.ok(same.possibility > 0.5, `same-tenant possibility=${same.possibility}`);
|
||
|
|
assert.ok(cross.possibility < 0.5, `cross-tenant possibility=${cross.possibility}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('accountIsPlatform only applies to root platform account (code 9999)', () => {
|
||
|
|
addRelation('session:s_test', 'accountIsPlatform', 'account:root:platform');
|
||
|
|
|
||
|
|
const isPlatform = checkRelation('session:s_test', 'accountIsPlatform', 'account:root:platform');
|
||
|
|
const notPlatform = checkRelation('session:s_test', 'accountIsPlatform', 'account:tenant_a:main');
|
||
|
|
|
||
|
|
assert.ok(isPlatform.possibility > 0.5, 'root IS platform');
|
||
|
|
assert.ok(notPlatform.possibility < 0.5, 'regular account is NOT platform');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('within-tenant transfer path: controls + sameTenantAccount', () => {
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_a:savings');
|
||
|
|
addRelation('account:tenant_a:main', 'sameTenantAccount', 'account:tenant_a:savings');
|
||
|
|
addRelation('session:s_test', 'authenticated_as', 'user:admin');
|
||
|
|
|
||
|
|
const hasControls = checkRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
const sameTenant = checkRelation('account:tenant_a:main', 'sameTenantAccount', 'account:tenant_a:savings');
|
||
|
|
|
||
|
|
assert.ok(hasControls.possibility > 0.5);
|
||
|
|
assert.ok(sameTenant.possibility > 0.5);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('cross-tenant transfer path: controls + platform bridge (accountIsPlatform)', () => {
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
addRelation('user:admin', 'controls', 'account:root:platform');
|
||
|
|
addRelation('session:s_test', 'accountIsPlatform', 'account:root:platform');
|
||
|
|
addRelation('session:s_test', 'authenticated_as', 'user:admin');
|
||
|
|
|
||
|
|
const fromTenantA = checkRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
const toRoot = checkRelation('user:admin', 'controls', 'account:root:platform');
|
||
|
|
const isPlatform = checkRelation('session:s_test', 'accountIsPlatform', 'account:root:platform');
|
||
|
|
|
||
|
|
assert.ok(fromTenantA.possibility > 0.5, 'tenant A debitable');
|
||
|
|
assert.ok(toRoot.possibility > 0.5, 'root creditable');
|
||
|
|
assert.ok(isPlatform.possibility > 0.5, 'root is platform bridge');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('accountIsSettled blocks transfers (NEVER gate on debit)', () => {
|
||
|
|
addRelation('user:admin', 'controls', 'account:tenant_a:main');
|
||
|
|
addRelation('account:tenant_a:main', 'accountIsSettled', 'account:tenant_a:main');
|
||
|
|
|
||
|
|
const isSettled = checkRelation('account:tenant_a:main', 'accountIsSettled', 'account:tenant_a:main');
|
||
|
|
assert.ok(isSettled.possibility > 0.5, 'account should be flagged as settled');
|
||
|
|
});
|
||
|
|
});
|