import assert from 'node:assert/strict'; import { describe, test } from 'node:test'; import { Arbiter } from '../../src/core/Arbiter.js'; describe('Chain Rules and Reachability', () => { test('simple chain rule enables transitive access', () => { const arbiter = new Arbiter(); arbiter.addNode('user:1', 'user'); arbiter.addNode('user:2', 'user'); arbiter.addNode('resource:1', 'resource'); arbiter.setRelationConfig('friend', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('friend_owner', { type: 'chain', steps: [ { relation: 'friend', direction: 'out' }, { relation: 'owner', direction: 'out' } ] }); arbiter.addRelation('user:1', 'friend', 'user:2', 1.0); arbiter.addRelation('user:2', 'owner', 'resource:1', 1.0); const result = arbiter.check('user:1', 'friend_owner', 'resource:1'); assert.strictEqual(result.possibility, 1.0, 'chain rule grants access'); }); test('multi-hop chain rules work correctly', () => { const arbiter = new Arbiter(); arbiter.addNode('user:1', 'user'); arbiter.addNode('user:2', 'user'); arbiter.addNode('user:3', 'user'); arbiter.addNode('user:4', 'user'); arbiter.addNode('resource:1', 'resource'); arbiter.setRelationConfig('friend', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('friend_owner', { type: 'chain', steps: [ { relation: 'friend', direction: 'out' }, { relation: 'friend', direction: 'out' }, { relation: 'friend', direction: 'out' }, { relation: 'owner', direction: 'out' } ] }); arbiter.addRelation('user:1', 'friend', 'user:2', 1.0); arbiter.addRelation('user:2', 'friend', 'user:3', 1.0); arbiter.addRelation('user:3', 'friend', 'user:4', 1.0); arbiter.addRelation('user:4', 'owner', 'resource:1', 1.0); const result = arbiter.check('user:1', 'friend_owner', 'resource:1'); assert.strictEqual(result.possibility, 1.0, '4-hop chain rule works'); }); test('chain rule respects distance limit', () => { const arbiter = new Arbiter(); arbiter.addNode('user:1', 'user'); arbiter.addNode('user:2', 'user'); arbiter.addNode('user:3', 'user'); arbiter.addNode('user:4', 'user'); arbiter.addNode('resource:1', 'resource'); arbiter.setRelationConfig('friend', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('friend_owner', { type: 'chain', steps: [ { relation: 'friend', direction: 'out' }, { relation: 'owner', direction: 'out' } ] }); arbiter.addRelation('user:1', 'friend', 'user:2', 1.0); arbiter.addRelation('user:2', 'friend', 'user:3', 1.0); arbiter.addRelation('user:3', 'friend', 'user:4', 1.0); arbiter.addRelation('user:4', 'owner', 'resource:1', 1.0); const result = arbiter.check('user:1', 'friend_owner', 'resource:1'); assert.strictEqual(result.possibility, 0.0, 'distance limit prevents access'); }); test('get reachable nodes returns correct set', () => { const arbiter = new Arbiter(); arbiter.addNode('node:1', 'node'); arbiter.addNode('node:2', 'node'); arbiter.addNode('node:3', 'node'); arbiter.addNode('node:4', 'node'); arbiter.setRelationConfig('connect', { type: 'direct' }); arbiter.addRelation('node:1', 'connect', 'node:2', 1.0); arbiter.addRelation('node:2', 'connect', 'node:3', 1.0); const reachable = arbiter.getReachableNodes('node:1', 10); assert.ok(reachable.includes('node:1'), 'source node included'); assert.ok(reachable.includes('node:2'), 'node:2 is reachable'); assert.ok(reachable.includes('node:3'), 'node:3 is reachable'); assert.ok(!reachable.includes('node:4'), 'node:4 is not reachable'); }); test('chain rule does not grant access through missing middle relation', () => { const arbiter = new Arbiter(); arbiter.addNode('user:1', 'user'); arbiter.addNode('user:2', 'user'); arbiter.addNode('resource:1', 'resource'); arbiter.setRelationConfig('friend', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('friend_owner', { type: 'chain', from: { relation: 'friend' }, to: { relation: 'owner' }, resultRelation: 'friend_owner', distance: 2 }); arbiter.addRelation('user:2', 'owner', 'resource:1', 1.0); const result = arbiter.check('user:1', 'friend_owner', 'resource:1'); assert.strictEqual(result.possibility, 0.0, 'no friend relation, no access'); }); });