import { Arbiter } from '../../src/core/Arbiter.js'; import { RuleEvaluator } from '../../src/authorization/RuleEvaluator.js'; import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; describe('Defeasible Logic - Advanced Authorization Patterns', () => { let arbiter; let ruleEvaluator; beforeEach(() => { arbiter = new Arbiter({ embeddingDimensions: 256 }); ruleEvaluator = new RuleEvaluator(arbiter); setupDefeasibleEntities(); }); function setupDefeasibleEntities() { // Users with different roles and permissions ['alice', 'bob', 'charlie', 'diana', 'eve'].forEach(name => { arbiter.addNode(`user:${name}`, 'user'); }); // Resources and documents ['doc:public', 'doc:internal', 'doc:confidential', 'doc:secret'].forEach(doc => { arbiter.addNode(doc, 'document'); }); // Projects and teams ['project:alpha', 'project:beta', 'project:classified'].forEach(project => { arbiter.addNode(project, 'project'); }); ['team:engineering'].forEach(team => { arbiter.addNode(team, 'team'); }); // Security levels and clearance ['clearance:public', 'clearance:internal', 'clearance:confidential', 'clearance:secret'].forEach(clearance => { arbiter.addNode(clearance, 'clearance'); }); // Set up basic relations arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('has_clearance', { type: 'direct' }); arbiter.setRelationConfig('can_access', { type: 'direct' }); arbiter.setRelationConfig('is_blocked', { type: 'direct' }); arbiter.setRelationConfig('is_emergency', { type: 'direct' }); arbiter.setRelationConfig('requires_approval', { type: 'direct' }); arbiter.setRelationConfig('is_weekend', { type: 'direct' }); arbiter.setRelationConfig('is_business_hours', { type: 'direct' }); } describe('Basic Defeasible Logic Patterns', () => { it('handles unless (defeater) logic - access denied when blocked', () => { // Set up user access arbiter.addRelation('user:alice', 'can_access', 'doc:internal', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Set up defeater - Alice is blocked arbiter.addRelation('user:alice', 'is_blocked', 'doc:internal', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure defeasible rule: can access UNLESS blocked arbiter.setRelationConfig('can_access_unless_blocked', { when: { intersection: [ { type: 'direct', relation: 'can_access' } ] }, unless: { union: [ { type: 'direct', relation: 'is_blocked' } ] } }); const result = arbiter.authChecker.check('user:alice', 'can_access_unless_blocked', 'doc:internal'); // Should be denied due to defeater assert.strictEqual(result.possibility, 0, 'Expected denial due to defeater'); }); it('handles always (strict) logic - emergency access always allowed', () => { // Set up emergency access arbiter.addRelation('user:alice', 'is_emergency', 'doc:secret', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure strict rule: emergency access ALWAYS allowed arbiter.setRelationConfig('emergency_access', { always: { type: 'direct', relation: 'is_emergency' } }); const result = arbiter.authChecker.check('user:alice', 'emergency_access', 'doc:secret'); // Should be allowed due to strict rule assert.ok(result.possibility > 0.8, `Expected high possibility for emergency access, got ${result.possibility}`); }); it('handles when (defeasible) logic - conditional access', () => { // Set up conditional access arbiter.addRelation('user:bob', 'member_of', 'team:engineering', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('team:engineering', 'can_access', 'project:alpha', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure defeasible rule: can access WHEN team member arbiter.setRelationConfig('team_access', { when: { intersection: [ { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'can_access', direction: 'out' } ] } ] } }); const result = arbiter.authChecker.check('user:bob', 'team_access', 'project:alpha'); // Should be allowed due to defeasible rule assert.ok(result.possibility > 0.7, `Expected high possibility for team access, got ${result.possibility}`); }); it('handles requires (inverse defeater) logic - access requires approval', () => { // Set up user access arbiter.addRelation('user:charlie', 'can_access', 'doc:confidential', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Set up requirement - Charlie has approval arbiter.addRelation('user:charlie', 'requires_approval', 'doc:confidential', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure inverse defeater rule: can access REQUIRES approval arbiter.setRelationConfig('approved_access', { when: { intersection: [ { type: 'direct', relation: 'can_access' } ] }, requires: { union: [ { type: 'direct', relation: 'requires_approval' } ] } }); const result = arbiter.authChecker.check('user:charlie', 'approved_access', 'doc:confidential'); // Should be allowed due to requirement being met assert.ok(result.possibility > 0.7, `Expected high possibility for approved access, got ${result.possibility}`); }); }); describe('Complex Defeasible Logic Combinations', () => { it('handles multiple defeaters with priority', () => { // Set up user with multiple potential defeaters arbiter.addRelation('user:diana', 'can_access', 'doc:secret', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:diana', 'is_blocked', 'doc:secret', { value: 0.8, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:diana', 'is_weekend', 'doc:secret', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure complex defeasible rule with multiple defeaters arbiter.setRelationConfig('complex_access', { when: { intersection: [ { type: 'direct', relation: 'can_access' } ] }, unless: { union: [ { type: 'direct', relation: 'is_blocked' }, { type: 'direct', relation: 'is_weekend' } ], aggregator: 'max' // Either defeater can block access } }); const result = arbiter.authChecker.check('user:diana', 'complex_access', 'doc:secret'); // Should be denied due to weekend defeater assert.strictEqual(result.possibility, 0, 'Expected denial due to weekend defeater'); }); it('handles strict rules overriding defeaters', () => { // Set up user with both defeater and strict rule arbiter.addRelation('user:eve', 'is_blocked', 'doc:secret', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:eve', 'is_emergency', 'doc:secret', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure rule with both defeater and strict rule arbiter.setRelationConfig('emergency_override', { always: { type: 'direct', relation: 'is_emergency' }, unless: { union: [ { type: 'direct', relation: 'is_blocked' } ] } }); const result = arbiter.authChecker.check('user:eve', 'emergency_override', 'doc:secret'); // Should be allowed due to strict rule overriding defeater assert.strictEqual(result.possibility, 0, `Expected defeater to override strict rule, got ${result.possibility}`); }); it('handles defeasible rules with requirements', () => { // Set up user with access, clearance, and approval arbiter.addRelation('user:alice', 'can_access', 'doc:confidential', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:alice', 'has_clearance', 'doc:confidential', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:alice', 'requires_approval', 'doc:confidential', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); // Configure complex rule: access WHEN cleared AND approved, UNLESS blocked arbiter.setRelationConfig('secure_access', { when: { intersection: [ { type: 'direct', relation: 'can_access' }, { type: 'direct', relation: 'has_clearance' } ] }, requires: { union: [ { type: 'direct', relation: 'requires_approval' } ] }, unless: { union: [ { type: 'direct', relation: 'is_blocked' } ] } }); const result = arbiter.authChecker.check('user:alice', 'secure_access', 'doc:confidential'); // Should be allowed due to all conditions being met assert.ok(result.possibility > 0.7, `Expected high possibility for secure access, got ${result.possibility}`); }); }); describe('Defeasible Logic with OWA Aggregation', () => { it('handles majority-based defeasible logic', () => { // Set up multiple approval sources arbiter.addRelation('user:bob', 'manager_approval', 'project:alpha', { value: 0.8, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:bob', 'peer_approval', 'project:alpha', { value: 0.9, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:bob', 'system_approval', 'project:alpha', { value: 0.7, possibility: 1.0, changed_last_at: Date.now() }); // Configure majority-based defeasible rule arbiter.setRelationConfig('consensus_access', { when: { intersection: [ { type: 'direct', relation: 'manager_approval' }, { type: 'direct', relation: 'peer_approval' }, { type: 'direct', relation: 'system_approval' } ], aggregator: 'majority' // Require consensus } }); const result = arbiter.authChecker.check('user:bob', 'consensus_access', 'project:alpha'); // Should be allowed due to majority consensus assert.ok(result.possibility > 0.6, `Expected reasonable possibility for consensus access, got ${result.possibility}`); }); it('handles priority-weighted defeasible logic', () => { // Set up rules with different priorities arbiter.addRelation('user:charlie', 'high_priority_rule', 'project:beta', { value: 0.8, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:charlie', 'medium_priority_rule', 'project:beta', { value: 0.6, possibility: 1.0, changed_last_at: Date.now() }); arbiter.addRelation('user:charlie', 'low_priority_rule', 'project:beta', { value: 0.9, possibility: 1.0, changed_last_at: Date.now() }); // Configure priority-weighted defeasible rule arbiter.setRelationConfig('priority_access', { when: { intersection: [ { type: 'direct', relation: 'high_priority_rule', priority: 10 }, { type: 'direct', relation: 'medium_priority_rule', priority: 5 }, { type: 'direct', relation: 'low_priority_rule', priority: 1 } ], aggregator: 'priority' // Weight by priority } }); const result = arbiter.authChecker.check('user:charlie', 'priority_access', 'project:beta'); // Should be allowed with priority weighting assert.ok(result.possibility > 0.5, `Expected reasonable possibility for priority access, got ${result.possibility}`); }); }); describe('Defeasible Logic Evaluation Modes', () => { it('handles binary mode defeasible logic', () => { // Set up simple defeasible rule arbiter.addRelation('user:diana', 'can_access', 'doc:internal', { value: 1.0, possibility: 1.0, changed_last_at: Date.now() }); arbiter.setRelationConfig('binary_defeasible', { when: { intersection: [ { type: 'direct', relation: 'can_access' } ] }, mode: 'binary' }); const result = arbiter.authChecker.check('user:diana', 'binary_defeasible', 'doc:internal'); // Should work in binary mode assert.ok(result.possibility > 0.8, `Expected high possibility in binary mode, got ${result.possibility}`); }); it('handles threshold mode defeasible logic', () => { // Set up threshold-based defeasible rule arbiter.addRelation('user:eve', 'can_access', 'doc:confidential', { value: 0.7, possibility: 1.0, changed_last_at: Date.now() }); arbiter.setRelationConfig('threshold_defeasible', { when: { intersection: [ { type: 'direct', relation: 'can_access' } ] }, mode: 'threshold' }); const result = arbiter.authChecker.check('user:eve', 'threshold_defeasible', 'doc:confidential', { fastPath: true, minAllowPossibility: 0.8 }); // Should handle threshold mode appropriately assert.ok(result.possibility >= 0, `Expected valid possibility in threshold mode, got ${result.possibility}`); }); }); });