Files
core/tests/rules/defeasible-logic.test.js
T
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
2026-07-31 13:44:06 -07:00

417 lines
14 KiB
JavaScript

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}`);
});
});
});