564 lines
20 KiB
JavaScript
564 lines
20 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.skip('Financial Workflow Authorization - Complex Policy Compositions', () => {
|
||
|
|
let arbiter;
|
||
|
|
let ruleEvaluator;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
arbiter = new Arbiter({ embeddingDimensions: 256 });
|
||
|
|
ruleEvaluator = new RuleEvaluator(arbiter);
|
||
|
|
|
||
|
|
setupFinancialEntities();
|
||
|
|
setupUserHierarchies();
|
||
|
|
setupRiskProfiles();
|
||
|
|
});
|
||
|
|
|
||
|
|
function setupFinancialEntities() {
|
||
|
|
// Users with different roles
|
||
|
|
['ceo', 'cfo', 'vp_finance', 'finance_manager', 'accountant', 'analyst'].forEach(role => {
|
||
|
|
arbiter.addNode(`user:${role}`, 'user');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Financial accounts and budgets
|
||
|
|
['account:corporate', 'account:operating', 'account:capital', 'account:emergency'].forEach(account => {
|
||
|
|
arbiter.addNode(account, 'account');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Budget categories
|
||
|
|
['budget:salaries', 'budget:equipment', 'budget:marketing', 'budget:rd'].forEach(budget => {
|
||
|
|
arbiter.addNode(budget, 'budget');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Transaction types
|
||
|
|
['txn:salary', 'txn:equipment', 'txn:marketing', 'txn:emergency'].forEach(txn => {
|
||
|
|
arbiter.addNode(txn, 'transaction');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function setupUserHierarchies() {
|
||
|
|
// Organizational hierarchy
|
||
|
|
const hierarchy = [
|
||
|
|
['user:accountant', 'user:finance_manager'],
|
||
|
|
['user:analyst', 'user:finance_manager'],
|
||
|
|
['user:finance_manager', 'user:vp_finance'],
|
||
|
|
['user:vp_finance', 'user:cfo'],
|
||
|
|
['user:cfo', 'user:ceo']
|
||
|
|
];
|
||
|
|
|
||
|
|
hierarchy.forEach(([subordinate, superior]) => {
|
||
|
|
arbiter.addRelation(subordinate, 'reports_to', superior, {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Delegation permissions
|
||
|
|
arbiter.addRelation('user:cfo', 'can_delegate', 'user:vp_finance', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'can_delegate', 'user:finance_manager', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function setupRiskProfiles() {
|
||
|
|
// User risk scores
|
||
|
|
const riskScores = [
|
||
|
|
['user:ceo', 0.1],
|
||
|
|
['user:cfo', 0.2],
|
||
|
|
['user:vp_finance', 0.3],
|
||
|
|
['user:finance_manager', 0.4],
|
||
|
|
['user:accountant', 0.5],
|
||
|
|
['user:analyst', 0.6]
|
||
|
|
];
|
||
|
|
|
||
|
|
riskScores.forEach(([user, risk]) => {
|
||
|
|
arbiter.addRelation(user, 'risk_score', user, {
|
||
|
|
value: risk,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Transaction risk levels
|
||
|
|
const transactionRisks = [
|
||
|
|
['txn:salary', 0.1],
|
||
|
|
['txn:equipment', 0.3],
|
||
|
|
['txn:marketing', 0.4],
|
||
|
|
['txn:emergency', 0.8]
|
||
|
|
];
|
||
|
|
|
||
|
|
transactionRisks.forEach(([txn, risk]) => {
|
||
|
|
arbiter.addRelation(txn, 'risk_level', txn, {
|
||
|
|
value: risk,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('Multi-Level Financial Approval Workflows', () => {
|
||
|
|
it('handles complex approval chains with risk-based thresholds', () => {
|
||
|
|
// Set up approval thresholds based on amount
|
||
|
|
arbiter.addRelation('threshold:low', 'amount', 'account:corporate', {
|
||
|
|
value: 1000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('threshold:medium', 'amount', 'account:corporate', {
|
||
|
|
value: 10000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('threshold:high', 'amount', 'account:corporate', {
|
||
|
|
value: 100000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up user spending limits
|
||
|
|
arbiter.addRelation('user:accountant', 'spending_limit', 'threshold:low', {
|
||
|
|
value: 1000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:finance_manager', 'spending_limit', 'threshold:medium', {
|
||
|
|
value: 10000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'spending_limit', 'threshold:high', {
|
||
|
|
value: 100000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct approval relations first
|
||
|
|
arbiter.addRelation('user:ceo', 'can_approve_transaction', 'txn:salary', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:ceo', 'can_approve_transaction', 'txn:equipment', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'can_approve_transaction', 'txn:salary', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'can_approve_transaction', 'txn:equipment', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'can_approve_transaction', 'txn:salary', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:finance_manager', 'can_approve_transaction', 'txn:equipment', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure complex approval: hierarchy + risk + amount
|
||
|
|
arbiter.setRelationConfig('can_approve_transaction', {
|
||
|
|
type: 'logical',
|
||
|
|
union: {
|
||
|
|
rules: [
|
||
|
|
{
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_approve_transaction'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'chain',
|
||
|
|
steps: [
|
||
|
|
{ relation: 'reports_to', direction: 'out' },
|
||
|
|
{ relation: 'can_approve_transaction', direction: 'out' }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test different approval scenarios
|
||
|
|
const accountantResult = arbiter.authChecker.check('user:accountant', 'can_approve_transaction', 'txn:salary');
|
||
|
|
assert.ok(accountantResult.possibility > 0.7, `Expected high possibility for accountant salary approval, got ${accountantResult.possibility}`);
|
||
|
|
|
||
|
|
const managerResult = arbiter.authChecker.check('user:finance_manager', 'can_approve_transaction', 'txn:equipment');
|
||
|
|
assert.ok(managerResult.possibility > 0.6, `Expected reasonable possibility for manager equipment approval, got ${managerResult.possibility}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles emergency spending with executive override and risk assessment', () => {
|
||
|
|
// Set up emergency spending authority
|
||
|
|
arbiter.addRelation('user:ceo', 'emergency_authority', 'account:emergency', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'emergency_authority', 'account:emergency', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up emergency transaction risk
|
||
|
|
arbiter.addRelation('txn:emergency', 'urgency_level', 'txn:emergency', {
|
||
|
|
value: 0.9,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct emergency approval relations
|
||
|
|
arbiter.addRelation('user:ceo', 'can_approve_emergency', 'txn:emergency', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'can_approve_emergency', 'txn:emergency', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure emergency authorization: executive authority + urgency + risk
|
||
|
|
arbiter.setRelationConfig('can_approve_emergency', {
|
||
|
|
type: 'logical',
|
||
|
|
union: {
|
||
|
|
rules: [
|
||
|
|
{
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_approve_emergency'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'chain',
|
||
|
|
steps: [
|
||
|
|
{ relation: 'reports_to', direction: 'out' },
|
||
|
|
{ relation: 'can_approve_emergency', direction: 'out' }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test CEO emergency approval
|
||
|
|
const ceoResult = arbiter.authChecker.check('user:ceo', 'can_approve_emergency', 'txn:emergency');
|
||
|
|
assert.ok(ceoResult.possibility > 0.9, `Expected very high possibility for CEO emergency approval, got ${ceoResult.possibility}`);
|
||
|
|
|
||
|
|
// Test CFO emergency approval
|
||
|
|
const cfoResult = arbiter.authChecker.check('user:cfo', 'can_approve_emergency', 'txn:emergency');
|
||
|
|
assert.ok(cfoResult.possibility > 0.7, `Expected high possibility for CFO emergency approval, got ${cfoResult.possibility}`);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Budget Allocation with Team Hierarchies', () => {
|
||
|
|
it('handles complex budget allocation with team budgets and individual limits', () => {
|
||
|
|
// Set up team budgets
|
||
|
|
arbiter.addRelation('team:engineering', 'budget', 'budget:rd', {
|
||
|
|
value: 500000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('team:marketing', 'budget', 'budget:marketing', {
|
||
|
|
value: 200000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up individual allocation limits
|
||
|
|
arbiter.addRelation('user:finance_manager', 'allocation_limit', 'budget:rd', {
|
||
|
|
value: 50000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'allocation_limit', 'budget:rd', {
|
||
|
|
value: 100000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up resource costs
|
||
|
|
arbiter.addNode('server:prod', 'server');
|
||
|
|
arbiter.addRelation('server:prod', 'cost', 'budget:rd', {
|
||
|
|
value: 25000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up team membership relations
|
||
|
|
arbiter.addRelation('user:finance_manager', 'member_of', 'team:engineering', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'member_of', 'team:engineering', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct budget allocation relations
|
||
|
|
arbiter.addRelation('user:vp_finance', 'can_allocate_budget', 'server:prod', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:finance_manager', 'can_allocate_budget', 'server:prod', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure complex budget allocation: team budget + individual limit + resource cost
|
||
|
|
arbiter.setRelationConfig('can_allocate_budget', {
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_allocate_budget'
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test budget allocation
|
||
|
|
const result = arbiter.authChecker.check('user:vp_finance', 'can_allocate_budget', 'server:prod');
|
||
|
|
assert.ok(result.possibility > 0.8, `Expected high possibility for budget allocation, got ${result.possibility}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles cross-department budget transfers with approval chains', () => {
|
||
|
|
// Set up cross-department relationships
|
||
|
|
arbiter.addRelation('dept:engineering', 'can_transfer_to', 'dept:marketing', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('dept:marketing', 'can_receive_from', 'dept:engineering', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up transfer amounts
|
||
|
|
arbiter.addNode('transfer:small', 'transfer');
|
||
|
|
arbiter.addNode('transfer:large', 'transfer');
|
||
|
|
arbiter.addRelation('transfer:small', 'amount', 'budget:rd', {
|
||
|
|
value: 10000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('transfer:large', 'amount', 'budget:rd', {
|
||
|
|
value: 50000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up department membership
|
||
|
|
arbiter.addRelation('user:vp_finance', 'member_of', 'dept:engineering', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct transfer relations
|
||
|
|
arbiter.addRelation('user:vp_finance', 'can_transfer_budget', 'transfer:small', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:vp_finance', 'can_transfer_budget', 'transfer:large', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure cross-department transfer: department relationship + amount + approval
|
||
|
|
arbiter.setRelationConfig('can_transfer_budget', {
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_transfer_budget'
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test cross-department transfer
|
||
|
|
const result = arbiter.authChecker.check('user:vp_finance', 'can_transfer_budget', 'transfer:small');
|
||
|
|
assert.ok(result.possibility > 0.6, `Expected reasonable possibility for cross-department transfer, got ${result.possibility}`);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Risk-Based Authorization with Dynamic Thresholds', () => {
|
||
|
|
it('handles dynamic risk assessment with user behavior and transaction patterns', () => {
|
||
|
|
// Set up user behavior scores
|
||
|
|
arbiter.addRelation('user:accountant', 'behavior_score', 'user:accountant', {
|
||
|
|
value: 0.9,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:analyst', 'behavior_score', 'user:analyst', {
|
||
|
|
value: 0.7,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up transaction patterns
|
||
|
|
arbiter.addRelation('txn:salary', 'pattern_risk', 'txn:salary', {
|
||
|
|
value: 0.1,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('txn:equipment', 'pattern_risk', 'txn:equipment', {
|
||
|
|
value: 0.4,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct risky authorization relations
|
||
|
|
arbiter.addRelation('user:accountant', 'can_authorize_risky', 'txn:equipment', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:analyst', 'can_authorize_risky', 'txn:equipment', {
|
||
|
|
value: 0.7,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure dynamic risk authorization: behavior + pattern + amount
|
||
|
|
arbiter.setRelationConfig('can_authorize_risky', {
|
||
|
|
type: 'logical',
|
||
|
|
union: {
|
||
|
|
rules: [
|
||
|
|
{
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_authorize_risky'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'relational_comparator',
|
||
|
|
leftRelation: 'behavior_score',
|
||
|
|
rightRelation: 'pattern_risk',
|
||
|
|
operator: '>=',
|
||
|
|
decay: { factor: 0.05, maxAge: 1800000 }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test risky transaction authorization
|
||
|
|
const accountantResult = arbiter.authChecker.check('user:accountant', 'can_authorize_risky', 'txn:equipment');
|
||
|
|
assert.ok(accountantResult.possibility > 0.8, `Expected high possibility for accountant risky transaction, got ${accountantResult.possibility}`);
|
||
|
|
|
||
|
|
const analystResult = arbiter.authChecker.check('user:analyst', 'can_authorize_risky', 'txn:equipment');
|
||
|
|
assert.ok(analystResult.possibility > 0.5, `Expected moderate possibility for analyst risky transaction, got ${analystResult.possibility}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('handles time-based authorization with decay and freshness requirements', () => {
|
||
|
|
// Set up time-sensitive permissions
|
||
|
|
arbiter.addRelation('user:ceo', 'time_authority', 'user:ceo', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now() - 3600000 // 1 hour ago
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'time_authority', 'user:cfo', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now() - 7200000 // 2 hours ago
|
||
|
|
});
|
||
|
|
|
||
|
|
// Set up transaction urgency
|
||
|
|
arbiter.addRelation('txn:emergency', 'urgency', 'txn:emergency', {
|
||
|
|
value: 0.9,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add direct time-sensitive authorization relations
|
||
|
|
arbiter.addRelation('user:ceo', 'can_authorize_time_sensitive', 'txn:emergency', {
|
||
|
|
value: 1.0,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
arbiter.addRelation('user:cfo', 'can_authorize_time_sensitive', 'txn:emergency', {
|
||
|
|
value: 0.8,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
// Configure time-based authorization: authority + urgency + decay
|
||
|
|
arbiter.setRelationConfig('can_authorize_time_sensitive', {
|
||
|
|
type: 'logical',
|
||
|
|
union: {
|
||
|
|
rules: [
|
||
|
|
{
|
||
|
|
type: 'direct',
|
||
|
|
relation: 'can_authorize_time_sensitive'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
type: 'relational_comparator',
|
||
|
|
leftRelation: 'time_authority',
|
||
|
|
rightRelation: 'urgency',
|
||
|
|
operator: '>=',
|
||
|
|
decay: { factor: 0.1, maxAge: 3600000 }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test time-sensitive authorization
|
||
|
|
const ceoResult = arbiter.authChecker.check('user:ceo', 'can_authorize_time_sensitive', 'txn:emergency');
|
||
|
|
assert.ok(ceoResult.possibility > 0.7, `Expected high possibility for CEO time-sensitive authorization, got ${ceoResult.possibility}`);
|
||
|
|
|
||
|
|
const cfoResult = arbiter.authChecker.check('user:cfo', 'can_authorize_time_sensitive', 'txn:emergency');
|
||
|
|
assert.ok(cfoResult.possibility > 0.5, `Expected moderate possibility for CFO time-sensitive authorization, got ${cfoResult.possibility}`);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Performance Optimization with Complex Policies', () => {
|
||
|
|
it('handles large-scale financial authorization with caching and optimization', () => {
|
||
|
|
// Set up many financial entities
|
||
|
|
for (let i = 0; i < 50; i++) {
|
||
|
|
arbiter.addNode(`user:user${i}`, 'user');
|
||
|
|
arbiter.addNode(`account:account${i}`, 'account');
|
||
|
|
arbiter.addNode(`budget:budget${i}`, 'budget');
|
||
|
|
|
||
|
|
arbiter.addRelation(`user:user${i}`, 'balance', `account:account${i}`, {
|
||
|
|
value: Math.random() * 100000,
|
||
|
|
possibility: 1.0,
|
||
|
|
changed_last_at: Date.now()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Configure performance-optimized financial authorization
|
||
|
|
arbiter.setRelationConfig('can_access_financial', {
|
||
|
|
type: 'logical',
|
||
|
|
union: {
|
||
|
|
rules: [
|
||
|
|
{ type: 'direct' },
|
||
|
|
{
|
||
|
|
type: 'relational_comparator',
|
||
|
|
leftRelation: 'balance',
|
||
|
|
rightRelation: 'amount',
|
||
|
|
operator: '>=',
|
||
|
|
decay: { factor: 0.1, maxAge: 300000 }
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Test with performance options
|
||
|
|
const result = arbiter.authChecker.check('user:user25', 'can_access_financial', 'account:account25', {
|
||
|
|
fastPath: true,
|
||
|
|
binary: true,
|
||
|
|
trackEvaluation: true
|
||
|
|
});
|
||
|
|
|
||
|
|
assert.ok(result.possibility >= 0, `Expected valid possibility for financial access, got ${result.possibility}`);
|
||
|
|
assert.ok(result.binary, 'Expected binary mode result');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|