Files
core/benchmarks/chain-rule-benchmark.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

946 lines
35 KiB
JavaScript

import { performance } from 'perf_hooks';
import { Arbiter } from '../src/index.js';
console.log('⛓️ ChainRule Performance Benchmark Analysis\n');
// Benchmark utilities
class ChainBenchmark {
constructor(name) {
this.name = name;
this.results = [];
this.labels = [];
}
async run(fn, iterations = 1000, label = '') {
// Warmup
for (let i = 0; i < Math.min(10, iterations / 10); i++) {
try {
await fn();
} catch (e) {
throw e;
}
}
// Actual benchmark
const times = [];
const totalStart = performance.now();
for (let i = 0; i < iterations; i++) {
try {
const start = performance.now();
await fn();
const end = performance.now();
times.push(end - start);
} catch (e) {
throw e;
}
}
const totalTime = performance.now() - totalStart;
const avg = times.reduce((a, b) => a + b, 0) / times.length;
const min = Math.min(...times);
const max = Math.max(...times);
const p50 = times.sort((a, b) => a - b)[Math.floor(times.length * 0.5)];
const p95 = times[Math.floor(times.length * 0.95)];
const p99 = times[Math.floor(times.length * 0.99)];
const qps = Math.round(iterations / (totalTime / 1000));
this.results.push({
avg: avg.toFixed(3),
min: min.toFixed(3),
max: max.toFixed(3),
p50: p50.toFixed(3),
p95: p95.toFixed(3),
p99: p99.toFixed(3),
qps: qps
});
this.labels.push(label);
return { avg, min, max, p50, p95, p99, qps };
}
report() {
console.log('Configuration | Avg (ms) | P50 (ms) | P95 (ms) | P99 (ms) | QPS | Speedup');
console.log('---------------------------------|----------|----------|----------|----------|---------|--------');
const baselineQPS = this.results[0].qps;
this.results.forEach((result, i) => {
const label = this.labels[i] || `Test ${i + 1}`;
const speedup = i === 0 ? '1.00x' : `${(result.qps / baselineQPS).toFixed(2)}x`;
console.log(`${label.padEnd(32)} | ${result.avg.padStart(8)} | ${result.p50.padStart(8)} | ${result.p95.padStart(8)} | ${result.p99.padStart(8)} | ${result.qps.toString().padStart(7)} | ${speedup.padStart(6)}`);
});
}
}
// Enterprise organizational data generator
function setupEnterpriseChainGraph(scale = 'medium') {
const scales = {
small: { users: 200, groups: 50, projects: 20, departments: 8, budgets: 100 },
medium: { users: 2000, groups: 200, projects: 100, departments: 15, budgets: 500 },
large: { users: 10000, groups: 1000, projects: 500, departments: 25, budgets: 2000 },
xlarge: { users: 20000, groups: 5000, projects: 2000, departments: 40, budgets: 8000 },
xxlarge: { users: 100000, groups: 10000, projects: 5000, departments: 50, budgets: 20000 }
};
const config = scales[scale];
console.log(`🏢 Setting up ${scale} enterprise graph: ${config.users} users, ${config.groups} groups, ${config.projects} projects`);
const arbiter = new Arbiter({
fastConstructionMode: true,
enableInference: false // Disable by default for clean baseline measurements
});
// Create organizational hierarchy: users → groups → departments → divisions → company
const entities = {
users: [],
groups: [],
departments: [],
divisions: ['engineering', 'sales', 'marketing', 'finance', 'operations'],
projects: [],
budgets: [],
resources: [],
facilities: []
};
// Generate users
for (let i = 0; i < config.users; i++) {
const userKey = `user:emp${i}`;
entities.users.push(userKey);
arbiter.addNode(userKey, 'user');
}
// Generate groups (teams within departments)
for (let i = 0; i < config.groups; i++) {
const groupKey = `group:team${i}`;
entities.groups.push(groupKey);
arbiter.addNode(groupKey, 'group');
}
// Generate departments
for (let i = 0; i < config.departments; i++) {
const deptKey = `dept:dept${i}`;
entities.departments.push(deptKey);
arbiter.addNode(deptKey, 'department');
}
// Generate divisions
entities.divisions.forEach(div => {
const divKey = `division:${div}`;
arbiter.addNode(divKey, 'division');
});
// Add company root
arbiter.addNode('company:acme', 'company');
// Generate projects
for (let i = 0; i < config.projects; i++) {
const projectKey = `project:proj${i}`;
entities.projects.push(projectKey);
arbiter.addNode(projectKey, 'project');
}
// Generate budgets with values
for (let i = 0; i < config.budgets; i++) {
const budgetKey = `budget:budget${i}`;
const value = Math.floor(Math.random() * 5000000) + 100000; // $100K to $5M
entities.budgets.push({ key: budgetKey, value });
arbiter.addNode(budgetKey, 'budget');
}
// Generate resources (servers, databases, etc.)
for (let i = 0; i < config.projects / 2; i++) {
const resourceKey = `resource:res${i}`;
const cost = Math.floor(Math.random() * 100000) + 5000; // $5K to $100K
entities.resources.push({ key: resourceKey, cost });
arbiter.addNode(resourceKey, 'resource');
}
// Generate facilities
const facilityNames = ['hq', 'east-office', 'west-office', 'remote', 'datacenter'];
facilityNames.forEach(name => {
const facilityKey = `facility:${name}`;
entities.facilities.push(facilityKey);
arbiter.addNode(facilityKey, 'facility');
});
console.log(' 🔗 Building organizational chains...');
// Build 5-level organizational hierarchy: user → group → department → division → company
// User → Group membership
entities.users.forEach(user => {
// Each user belongs to 1-3 groups
const groupCount = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < groupCount; i++) {
const group = entities.groups[Math.floor(Math.random() * entities.groups.length)];
arbiter.addRelation(user, 'member_of', group);
}
});
// Group → Department membership
entities.groups.forEach(group => {
const dept = entities.departments[Math.floor(Math.random() * entities.departments.length)];
arbiter.addRelation(group, 'belongs_to', dept);
});
// Department → Division membership
entities.departments.forEach(dept => {
const division = entities.divisions[Math.floor(Math.random() * entities.divisions.length)];
arbiter.addRelation(dept, 'part_of', `division:${division}`);
});
// Division → Company membership
entities.divisions.forEach(div => {
arbiter.addRelation(`division:${div}`, 'part_of', 'company:acme');
});
// Project chains: user → group → project → budget
entities.projects.forEach(project => {
// Projects owned by groups
const ownerGroup = entities.groups[Math.floor(Math.random() * entities.groups.length)];
arbiter.addRelation(ownerGroup, 'manages', project);
// Projects have budgets
const budget = entities.budgets[Math.floor(Math.random() * entities.budgets.length)];
arbiter.addRelation(project, 'has_budget', budget.key, { value: budget.value });
// Projects have resource costs
if (Math.random() < 0.7) {
const resource = entities.resources[Math.floor(Math.random() * entities.resources.length)];
arbiter.addRelation(project, 'uses_resource', resource.key, { cost: resource.cost });
}
});
// Facility chains: user → facility, department → facility
entities.users.forEach(user => {
const facility = entities.facilities[Math.floor(Math.random() * entities.facilities.length)];
arbiter.addRelation(user, 'located_in', facility);
});
entities.departments.forEach(dept => {
const facility = entities.facilities[Math.floor(Math.random() * entities.facilities.length)];
arbiter.addRelation(dept, 'operates_in', facility);
});
// Configure basic relation types
const relationTypes = [
'member_of', 'belongs_to', 'part_of', 'manages', 'has_budget',
'uses_resource', 'located_in', 'operates_in', 'can_access', 'has_permission'
];
relationTypes.forEach(rel => {
arbiter.setRelationConfig(rel, { type: 'direct' });
});
console.log(' ✅ Enterprise chain graph ready');
// Initialize PLTC for reachability checks
console.log(' ⚡ Initializing PLTC indices...');
const initStart = Date.now();
arbiter.graphManager.initializeReachabilityChecker();
const initTime = Date.now() - initStart;
console.log(` ✅ PLTC initialized in ${initTime}ms`);
return { arbiter, ...entities };
}
// Benchmark 1: Chain Length Performance
async function benchmarkChainLength() {
console.log('📏 Benchmark 1: Chain Length Performance Impact\n');
const graph = setupEnterpriseChainGraph('medium');
const benchmark = new ChainBenchmark('Chain Length Performance');
// Baseline: Direct access (no chain)
graph.arbiter.setRelationConfig('direct_access', {
type: 'direct',
relation: 'can_access'
});
// Create a substantial number of direct access relations for realistic testing
// Instead of 100 random relations, create relations for every user to some projects
const directAccessPairs = [];
graph.users.forEach((user, i) => {
// Each user has direct access to 1-3 projects
const numProjects = Math.floor(Math.random() * 3) + 1;
for (let j = 0; j < numProjects; j++) {
const project = graph.projects[(i * 7 + j) % graph.projects.length]; // Deterministic but spread out
graph.arbiter.addRelation(user, 'can_access', project);
directAccessPairs.push({ user, project });
}
});
console.log(` ✅ Created ${directAccessPairs.length} direct access relations (${(directAccessPairs.length / (graph.users.length * graph.projects.length) * 100).toFixed(1)}% coverage)`);
await benchmark.run(() => {
// Test EXISTING relations for accurate performance measurement
const pair = directAccessPairs[Math.floor(Math.random() * directAccessPairs.length)];
return graph.arbiter.check(pair.user, 'direct_access', pair.project, { noInfer: true });
}, 1000, 'Direct Access (baseline)');
// 2-step chain: user → group → project
graph.arbiter.setRelationConfig('chain_2_step', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
]
});
// Build collection of valid 2-step chain paths for testing
const validChain2Paths = [];
graph.users.forEach(user => {
// Find groups this user belongs to
const userGroups = graph.arbiter.relationManager.getRelationsFromSrc(user, 'member_of');
userGroups.forEach(groupRel => {
// Find projects this group manages
const groupProjects = graph.arbiter.relationManager.getRelationsFromSrc(groupRel.object, 'manages');
groupProjects.forEach(projectRel => {
validChain2Paths.push({ user, project: projectRel.object });
});
});
});
console.log(` ✅ Found ${validChain2Paths.length} valid 2-step chain paths (user→group→project)`);
await benchmark.run(() => {
// Test EXISTING chain paths for accurate performance measurement
if (validChain2Paths.length === 0) {
// Fallback to random if no valid paths found
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_2_step', project, { noInfer: true });
} else {
const path = validChain2Paths[Math.floor(Math.random() * validChain2Paths.length)];
return graph.arbiter.check(path.user, 'chain_2_step', path.project, { noInfer: true });
}
}, 1000, '2-Step Chain (user→group→project)');
// 3-step chain: user → group → department → division
graph.arbiter.setRelationConfig('chain_3_step', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'belongs_to', direction: 'out' },
{ relation: 'part_of', direction: 'out' }
]
});
// Build collection of valid 3-step chain paths for testing
const validChain3Paths = [];
graph.users.forEach(user => {
// Find groups this user belongs to
const userGroups = graph.arbiter.relationManager.getRelationsFromSrc(user, 'member_of');
userGroups.forEach(groupRel => {
// Find departments this group belongs to
const groupDepts = graph.arbiter.relationManager.getRelationsFromSrc(groupRel.object, 'belongs_to');
groupDepts.forEach(deptRel => {
// Find divisions this department is part of
const deptDivisions = graph.arbiter.relationManager.getRelationsFromSrc(deptRel.object, 'part_of');
deptDivisions.forEach(divisionRel => {
validChain3Paths.push({ user, division: divisionRel.object });
});
});
});
});
console.log(` ✅ Found ${validChain3Paths.length} valid 3-step chain paths (user→group→dept→division)`);
await benchmark.run(() => {
// Test EXISTING chain paths for accurate performance measurement
if (validChain3Paths.length === 0) {
// Fallback to random if no valid paths found
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const division = graph.divisions[Math.floor(Math.random() * graph.divisions.length)];
return graph.arbiter.check(user, 'chain_3_step', `division:${division}`, { noInfer: true });
} else {
const path = validChain3Paths[Math.floor(Math.random() * validChain3Paths.length)];
return graph.arbiter.check(path.user, 'chain_3_step', path.division, { noInfer: true });
}
}, 1000, '3-Step Chain (user→group→dept→division)');
// 4-step chain: user → group → project → budget → facility (create a valid 4-step path)
// First add budget→facility relationships to create valid 4-step chains
graph.budgets.forEach(budget => {
const facility = graph.facilities[Math.floor(Math.random() * graph.facilities.length)];
graph.arbiter.addRelation(budget.key, 'allocated_to', facility);
});
graph.arbiter.setRelationConfig('allocated_to', { type: 'direct' });
graph.arbiter.setRelationConfig('chain_4_step', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' }, // user → group
{ relation: 'manages', direction: 'out' }, // group → project
{ relation: 'has_budget', direction: 'out' }, // project → budget
{ relation: 'allocated_to', direction: 'out' } // budget → facility
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const facility = graph.facilities[Math.floor(Math.random() * graph.facilities.length)];
return graph.arbiter.check(user, 'chain_4_step', facility, { noInfer: true });
}, 1000, '4-Step Chain (user→group→project→budget→facility)');
// 5-step chain: user → group → department → division → company → facility
// Add company→facility relationship for valid 5-step chain
graph.facilities.forEach(facility => {
graph.arbiter.addRelation('company:acme', 'operates', facility);
});
graph.arbiter.setRelationConfig('operates', { type: 'direct' });
graph.arbiter.setRelationConfig('chain_5_step', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' }, // user → group
{ relation: 'belongs_to', direction: 'out' }, // group → department
{ relation: 'part_of', direction: 'out' }, // department → division
{ relation: 'part_of', direction: 'out' }, // division → company
{ relation: 'operates', direction: 'out' } // company → facility
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const facility = graph.facilities[Math.floor(Math.random() * graph.facilities.length)];
return graph.arbiter.check(user, 'chain_5_step', facility, { noInfer: true });
}, 1000, '5-Step Chain (user→group→dept→div→company→facility)');
benchmark.report();
}
// Benchmark 2: ChainRule vs Traditional Approaches
async function benchmarkVsTraditional() {
console.log('⚔️ Benchmark 2: ChainRule vs Traditional Authorization\n');
const graph = setupEnterpriseChainGraph('medium');
const benchmark = new ChainBenchmark('ChainRule vs Traditional');
// Traditional ParentRule approach
graph.arbiter.setRelationConfig('traditional_parent', {
type: 'parent',
parentRelation: 'manages',
relation: 'member_of'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'traditional_parent', project, { noInfer: true });
}, 1000, 'ParentRule (baseline)');
// Equivalent ChainRule approach
graph.arbiter.setRelationConfig('chain_equivalent', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_equivalent', project, { noInfer: true });
}, 1000, 'ChainRule (equivalent logic)');
// MultiHopRule approach
graph.arbiter.setRelationConfig('multihop_approach', {
type: 'multi_hop',
relation: 'member_of',
maxDepth: 3,
pathAggregation: 'max'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'multihop_approach', project, { noInfer: true });
}, 1000, 'MultiHopRule (flexible paths)');
// ChainRule with reverse direction
graph.arbiter.setRelationConfig('chain_reverse', {
type: 'chain',
steps: [
{ relation: 'manages', direction: 'in' },
{ relation: 'member_of', direction: 'in' }
]
});
await benchmark.run(() => {
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
return graph.arbiter.check(project, 'chain_reverse', user, { noInfer: true });
}, 1000, 'ChainRule (reverse direction)');
benchmark.report();
}
// Benchmark 3: Value Extraction Performance
async function benchmarkValueExtraction() {
console.log('💰 Benchmark 3: Value Extraction Performance\n');
const graph = setupEnterpriseChainGraph('medium');
const benchmark = new ChainBenchmark('Value Extraction Performance');
// Chain without value extraction (baseline)
graph.arbiter.setRelationConfig('chain_no_values', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const budget = graph.budgets[Math.floor(Math.random() * graph.budgets.length)];
return graph.arbiter.check(user, 'chain_no_values', budget.key);
}, 1000, 'Chain (no value extraction)');
// Chain with value extraction - SUM aggregation
graph.arbiter.setRelationConfig('chain_sum_values', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
],
extractValues: true,
extractFrom: 2,
extractRelation: 'has_budget',
valueAggregation: 'sum'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const budget = graph.budgets[Math.floor(Math.random() * graph.budgets.length)];
return graph.arbiter.check(user, 'chain_sum_values', budget.key);
}, 1000, 'Chain with SUM aggregation');
// Chain with value extraction - MAX aggregation
graph.arbiter.setRelationConfig('chain_max_values', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
],
extractValues: true,
extractFrom: 2,
extractRelation: 'has_budget',
valueAggregation: 'max'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const budget = graph.budgets[Math.floor(Math.random() * graph.budgets.length)];
return graph.arbiter.check(user, 'chain_max_values', budget.key);
}, 1000, 'Chain with MAX aggregation');
// Chain with value extraction - MIN aggregation
graph.arbiter.setRelationConfig('chain_min_values', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
],
extractValues: true,
extractFrom: 2,
extractRelation: 'has_budget',
valueAggregation: 'min'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const budget = graph.budgets[Math.floor(Math.random() * graph.budgets.length)];
return graph.arbiter.check(user, 'chain_min_values', budget.key);
}, 1000, 'Chain with MIN aggregation');
// Chain with OWA fusion aggregation
graph.arbiter.setRelationConfig('chain_owa_values', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
],
extractValues: true,
extractFrom: 2,
extractRelation: 'has_budget',
valueAggregation: 'optimistic',
owaWeights: [0.7, 0.5, 0.3, 0.1]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const budget = graph.budgets[Math.floor(Math.random() * graph.budgets.length)];
return graph.arbiter.check(user, 'chain_owa_values', budget.key);
}, 1000, 'Chain with OWA aggregation');
benchmark.report();
}
// Benchmark 4: Direction and Pattern Performance
async function benchmarkDirectionPatterns() {
console.log('🧭 Benchmark 4: Chain Direction and Pattern Performance\n');
const graph = setupEnterpriseChainGraph('medium');
const benchmark = new ChainBenchmark('Direction and Pattern Performance');
// Forward chain (baseline)
graph.arbiter.setRelationConfig('forward_chain', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'belongs_to', direction: 'out' },
{ relation: 'part_of', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const division = graph.divisions[Math.floor(Math.random() * graph.divisions.length)];
return graph.arbiter.check(user, 'forward_chain', `division:${division}`);
}, 1000, 'Forward Chain (baseline)');
// Backward chain
graph.arbiter.setRelationConfig('backward_chain', {
type: 'chain',
steps: [
{ relation: 'part_of', direction: 'in' },
{ relation: 'belongs_to', direction: 'in' },
{ relation: 'member_of', direction: 'in' }
]
});
await benchmark.run(() => {
const division = graph.divisions[Math.floor(Math.random() * graph.divisions.length)];
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
return graph.arbiter.check(`division:${division}`, 'backward_chain', user);
}, 1000, 'Backward Chain');
// Mixed direction chain
graph.arbiter.setRelationConfig('mixed_chain', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' }, // user → group
{ relation: 'belongs_to', direction: 'in' }, // group ← department
{ relation: 'operates_in', direction: 'out' } // department → facility
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const facility = graph.facilities[Math.floor(Math.random() * graph.facilities.length)];
return graph.arbiter.check(user, 'mixed_chain', facility);
}, 1000, 'Mixed Direction Chain');
// Parallel chains pattern (testing multiple chain endpoints)
graph.arbiter.setRelationConfig('parallel_chains', {
union: [
{
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
]
},
{
type: 'chain',
steps: [
{ relation: 'located_in', direction: 'out' },
{ relation: 'operates_in', direction: 'in' },
{ relation: 'belongs_to', direction: 'in' },
{ relation: 'manages', direction: 'out' }
]
}
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'parallel_chains', project);
}, 1000, 'Parallel Chains (Union)');
benchmark.report();
}
// Benchmark 5: ChainRule with Inference
async function benchmarkChainInference() {
console.log('🧠 Benchmark 5: ChainRule with Inference Integration\n');
const graph = setupEnterpriseChainGraph('small'); // Smaller graph for inference
const benchmark = new ChainBenchmark('ChainRule Inference Performance');
// Remove some relationships to create inference opportunities
const relationships = [];
for (let i = 0; i < 50; i++) {
const user = graph.users[i];
const group = graph.groups[Math.floor(Math.random() * Math.min(20, graph.groups.length))];
relationships.push({ user, group });
graph.arbiter.relationManager.removeRelation(user, 'member_of', group);
}
// Chain without inference (baseline)
graph.arbiter.setRelationConfig('chain_no_inference', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
],
allowInference: false
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_no_inference', project, { noInfer: true });
}, 500, 'Chain (no inference)');
// Chain with basic inference
graph.arbiter.setRelationConfig('chain_basic_inference', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
],
allowInference: true
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_basic_inference', project);
}, 500, 'Chain (basic inference)');
// Chain with inference and reliability threshold
graph.arbiter.setRelationConfig('chain_reliable_inference', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
],
allowInference: true,
minReliability: 0.7
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_reliable_inference', project);
}, 500, 'Chain (high reliability inference)');
benchmark.report();
}
// Benchmark 7: Real-World Enterprise Scenarios
async function benchmarkEnterpriseScenarios() {
console.log('🏢 Benchmark 7: Real-World Enterprise Authorization Scenarios\n');
const graph = setupEnterpriseChainGraph('medium');
const benchmark = new ChainBenchmark('Enterprise Scenarios');
// Scenario 1: Budget Authorization (user → group → project → budget >= threshold)
graph.arbiter.setRelationConfig('budget_authorization', {
type: 'relational_comparator',
leftOperand: {
rule: {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'has_budget', direction: 'out' }
],
extractValues: true,
extractFrom: 2,
extractRelation: 'has_budget',
valueAggregation: 'sum'
},
extractValue: true
},
rightOperand: {
rule: { type: 'direct', relation: 'has_value' },
extractValue: true
},
comparator: '>=',
fallbackBehavior: 'deny'
});
// Add budget thresholds
for (let i = 0; i < 50; i++) {
const threshold = Math.floor(Math.random() * 1000000) + 50000;
graph.arbiter.addRelation(`threshold:${i}`, 'has_value', `value:${threshold}`, { value: threshold });
}
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const threshold = `threshold:${Math.floor(Math.random() * 50)}`;
return graph.arbiter.check(user, 'budget_authorization', threshold);
}, 500, 'Budget Authorization Chain');
// Scenario 2: Facility Access (user → department → facility)
graph.arbiter.setRelationConfig('facility_access', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'belongs_to', direction: 'out' },
{ relation: 'operates_in', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const facility = graph.facilities[Math.floor(Math.random() * graph.facilities.length)];
return graph.arbiter.check(user, 'facility_access', facility);
}, 1000, 'Facility Access Chain');
// Scenario 3: Resource Approval (user → group → project → resource)
graph.arbiter.setRelationConfig('resource_approval', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' },
{ relation: 'uses_resource', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const resource = graph.resources[Math.floor(Math.random() * graph.resources.length)];
return graph.arbiter.check(user, 'resource_approval', resource.key);
}, 1000, 'Resource Approval Chain');
// Scenario 4: Cross-Division Access (complex multi-step authorization)
graph.arbiter.setRelationConfig('cross_division_access', {
union: [
{
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'belongs_to', direction: 'out' },
{ relation: 'part_of', direction: 'out' }
]
},
{
type: 'chain',
steps: [
{ relation: 'located_in', direction: 'out' },
{ relation: 'operates_in', direction: 'in' },
{ relation: 'part_of', direction: 'out' }
]
}
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const division = graph.divisions[Math.floor(Math.random() * graph.divisions.length)];
return graph.arbiter.check(user, 'cross_division_access', `division:${division}`);
}, 1000, 'Cross-Division Access (Union)');
benchmark.report();
}
// Main benchmark runner
async function runChainRuleBenchmarks() {
console.log('🚀 Starting ChainRule Performance Benchmarks...\n');
try {
await benchmarkChainLength();
await benchmarkVsTraditional();
await benchmarkValueExtraction();
await benchmarkDirectionPatterns();
await benchmarkChainInference();
await benchmarkEnterpriseScenarios();
console.log('\n✅ All ChainRule benchmarks completed successfully!');
console.log('\n🚀 X-LARGE SCALE BENCHMARKS (xlarge)');
await benchmarkChainLengthScale('xlarge', 100);
await benchmarkVsTraditionalScale('xlarge', 100);
console.log('\n🚀 XX-LARGE SCALE BENCHMARKS (xxlarge)');
await benchmarkChainLengthScale('xxlarge', 50);
await benchmarkVsTraditionalScale('xxlarge', 50);
} catch (error) {
console.error('❌ Benchmark failed:', error);
process.exit(1);
}
}
// Run if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
runChainRuleBenchmarks();
}
export { runChainRuleBenchmarks };
// Add helper functions for large scale runs:
async function benchmarkChainLengthScale(scale, iterations) {
console.log(`\n📏 [${scale.toUpperCase()}] Chain Length Performance Impact`);
const graph = setupEnterpriseChainGraph(scale);
const benchmark = new ChainBenchmark(`Chain Length Performance (${scale})`);
// Baseline: Direct access (no chain)
graph.arbiter.setRelationConfig('direct_access', {
type: 'direct',
relation: 'can_access'
});
const directAccessPairs = [];
graph.users.forEach((user, i) => {
const numProjects = Math.floor(Math.random() * 3) + 1;
for (let j = 0; j < numProjects; j++) {
const project = graph.projects[(i * 7 + j) % graph.projects.length];
graph.arbiter.addRelation(user, 'can_access', project);
directAccessPairs.push({ user, project });
}
});
await benchmark.run(() => {
const pair = directAccessPairs[Math.floor(Math.random() * directAccessPairs.length)];
return graph.arbiter.check(pair.user, 'direct_access', pair.project, { noInfer: true });
}, iterations, 'Direct Access (baseline)');
// 2-step chain
graph.arbiter.setRelationConfig('chain_2_step', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_2_step', project, { noInfer: true });
}, iterations, '2-Step Chain (user→group→project)');
benchmark.report();
}
async function benchmarkVsTraditionalScale(scale, iterations) {
console.log(`\n⚔️ [${scale.toUpperCase()}] ChainRule vs Traditional Authorization`);
const graph = setupEnterpriseChainGraph(scale);
const benchmark = new ChainBenchmark(`ChainRule vs Traditional (${scale})`);
// Traditional ParentRule approach
graph.arbiter.setRelationConfig('traditional_parent', {
type: 'parent',
parentRelation: 'manages',
relation: 'member_of'
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'traditional_parent', project, { noInfer: true });
}, iterations, 'ParentRule (baseline)');
// Equivalent ChainRule approach
graph.arbiter.setRelationConfig('chain_equivalent', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'manages', direction: 'out' }
]
});
await benchmark.run(() => {
const user = graph.users[Math.floor(Math.random() * graph.users.length)];
const project = graph.projects[Math.floor(Math.random() * graph.projects.length)];
return graph.arbiter.check(user, 'chain_equivalent', project, { noInfer: true });
}, iterations, 'ChainRule (equivalent logic)');
benchmark.report();
}