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.
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// benchmark-interval-inference.js
|
||||
// Benchmark interval-based inference with relational comparisons
|
||||
|
||||
import { performance } from 'perf_hooks';
|
||||
import { Arbiter } from '../src/index.js';
|
||||
import { PACBayesInference } from '../src/inference/PACBayesInference.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Create a realistic financial services graph
|
||||
function createFinancialGraph() {
|
||||
const arbiter = new Arbiter({
|
||||
enableInference: true,
|
||||
useOptimizedInference: true,
|
||||
useANN: true
|
||||
});
|
||||
|
||||
// Account tiers with typical balance ranges
|
||||
const accountTiers = {
|
||||
basic: { min: 1000, max: 25000, count: 100 },
|
||||
silver: { min: 25000, max: 100000, count: 50 },
|
||||
gold: { min: 100000, max: 500000, count: 25 },
|
||||
platinum: { min: 500000, max: 2000000, count: 10 }
|
||||
};
|
||||
|
||||
// Create accounts with realistic balance distributions
|
||||
const accounts = [];
|
||||
for (const [tier, config] of Object.entries(accountTiers)) {
|
||||
for (let i = 0; i < config.count; i++) {
|
||||
const balance = config.min + Math.random() * (config.max - config.min);
|
||||
const accountAge = Math.floor(Math.random() * 120); // months
|
||||
const creditScore = 600 + Math.floor(Math.random() * 250);
|
||||
|
||||
const id = `${tier}_account_${i}`;
|
||||
accounts.push({
|
||||
id,
|
||||
tier,
|
||||
balance: Math.floor(balance),
|
||||
accountAge,
|
||||
creditScore
|
||||
});
|
||||
|
||||
arbiter.addNode(id, 'account', {
|
||||
tier,
|
||||
balance: Math.floor(balance),
|
||||
accountAge,
|
||||
creditScore
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create financial products with requirements
|
||||
const products = [
|
||||
{ id: 'savings_basic', minBalance: 1000, minCredit: 0 },
|
||||
{ id: 'checking_premium', minBalance: 25000, minCredit: 650 },
|
||||
{ id: 'investment_silver', minBalance: 50000, minCredit: 700 },
|
||||
{ id: 'investment_gold', minBalance: 100000, minCredit: 720 },
|
||||
{ id: 'private_banking', minBalance: 500000, minCredit: 750 },
|
||||
{ id: 'wealth_management', minBalance: 1000000, minCredit: 780 }
|
||||
];
|
||||
|
||||
for (const product of products) {
|
||||
arbiter.addNode(product.id, 'product', {
|
||||
minBalance: product.minBalance,
|
||||
minCredit: product.minCredit
|
||||
});
|
||||
}
|
||||
|
||||
// Add access relations based on requirements
|
||||
for (const account of accounts) {
|
||||
for (const product of products) {
|
||||
if (account.balance >= product.minBalance && account.creditScore >= product.minCredit) {
|
||||
arbiter.addRelation(account.id, 'can_access', product.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock relational rules
|
||||
arbiter.getRelationalRules = (relation) => {
|
||||
if (relation === 'has_min_balance_for') {
|
||||
return [{
|
||||
type: 'relational-comparison',
|
||||
attribute: 'balance',
|
||||
operator: '>=',
|
||||
threshold: 'object.minBalance' // Would be resolved dynamically
|
||||
}];
|
||||
}
|
||||
if (relation === 'meets_credit_requirement') {
|
||||
return [{
|
||||
type: 'relational-comparison',
|
||||
attribute: 'creditScore',
|
||||
operator: '>=',
|
||||
threshold: 'object.minCredit'
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
return { arbiter, accounts, products };
|
||||
}
|
||||
|
||||
async function benchmarkIntervalQueries() {
|
||||
console.log('=== Interval Inference Benchmark ===\n');
|
||||
|
||||
const { arbiter, accounts, products } = createFinancialGraph();
|
||||
|
||||
// Create interval inference engine
|
||||
const inference = new PACBayesInference(arbiter, {
|
||||
intervalConfidence: 0.95,
|
||||
minVotersForInterval: 3,
|
||||
delta: 0.05,
|
||||
k: 15 // neighbors for inference
|
||||
});
|
||||
|
||||
arbiter.inferenceEngine = inference;
|
||||
|
||||
// Record observed decisions
|
||||
let recordedCount = 0;
|
||||
const canAccessRelations = arbiter.relationManager.getRelationsByName('can_access');
|
||||
for (const rel of canAccessRelations) {
|
||||
if (Math.random() < 0.7) { // Record 70%
|
||||
const subjectKey = arbiter.nodeManager.getNodeKey(rel.src);
|
||||
const objectKey = arbiter.nodeManager.getNodeKey(rel.dst);
|
||||
if (subjectKey && objectKey) {
|
||||
inference.recordDecision(subjectKey, rel.rel, objectKey, 'allow');
|
||||
recordedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Recorded ${recordedCount} access decisions\n`);
|
||||
|
||||
// Generate embeddings
|
||||
if (arbiter.embeddingManager) {
|
||||
arbiter.embeddingManager.forceRegenerateEmbeddings();
|
||||
if (arbiter.similarityManager) arbiter.similarityManager.ensureIndexReady();
|
||||
}
|
||||
|
||||
// Simulate missing attribute values
|
||||
const testAccounts = accounts.slice(0, 20); // Test with 20 accounts
|
||||
const originalBalances = new Map();
|
||||
|
||||
for (const account of testAccounts) {
|
||||
originalBalances.set(account.id, account.balance);
|
||||
// Remove balance attribute to simulate stale/missing data
|
||||
const node = arbiter.nodeManager.getNodeByKey(account.id);
|
||||
if (node && node.data) {
|
||||
delete node.data.balance;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== Testing Interval Estimation ===\n');
|
||||
|
||||
// Test interval estimation accuracy
|
||||
let totalError = 0;
|
||||
let validEstimates = 0;
|
||||
|
||||
for (const account of testAccounts.slice(0, 5)) { // Show first 5
|
||||
const interval = await inference.estimateAttributeInterval(account.id, 'balance', 10);
|
||||
const actual = originalBalances.get(account.id);
|
||||
|
||||
console.log(`Account: ${account.id} (${account.tier})`);
|
||||
console.log(` Actual balance: $${actual.toLocaleString()}`);
|
||||
console.log(` Estimated interval: [$${interval.lower?.toLocaleString() || 'N/A'}, $${interval.upper?.toLocaleString() || 'N/A'}]`);
|
||||
console.log(` Confidence: ${(interval.confidence * 100).toFixed(1)}%`);
|
||||
console.log(` Voters: ${interval.voters}`);
|
||||
|
||||
if (interval.lower && interval.upper) {
|
||||
const inInterval = actual >= interval.lower && actual <= interval.upper;
|
||||
console.log(` Contains actual: ${inInterval ? 'YES' : 'NO'}`);
|
||||
if (inInterval) validEstimates++;
|
||||
|
||||
const midpoint = (interval.lower + interval.upper) / 2;
|
||||
const error = Math.abs(midpoint - actual) / actual;
|
||||
totalError += error;
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log(`\nInterval Coverage: ${validEstimates}/${testAccounts.slice(0, 5).length}`);
|
||||
console.log(`Average Error: ${(totalError / 5 * 100).toFixed(1)}%\n`);
|
||||
|
||||
console.log('=== Benchmarking Authorization Queries ===\n');
|
||||
|
||||
// Benchmark authorization checks with missing data
|
||||
const queries = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const account = testAccounts[Math.floor(Math.random() * testAccounts.length)];
|
||||
const product = products[Math.floor(Math.random() * products.length)];
|
||||
queries.push({ subject: account.id, object: product.id });
|
||||
}
|
||||
|
||||
let allowCount = 0;
|
||||
let denyCount = 0;
|
||||
let undeterminedCount = 0;
|
||||
let totalTime = 0;
|
||||
|
||||
for (const query of queries) {
|
||||
const start = performance.now();
|
||||
const result = await inference.check(query.subject, 'can_access', query.object);
|
||||
const elapsed = performance.now() - start;
|
||||
totalTime += elapsed;
|
||||
|
||||
if (result.outcome === 'allow') allowCount++;
|
||||
else if (result.outcome === 'deny') denyCount++;
|
||||
else undeterminedCount++;
|
||||
}
|
||||
|
||||
console.log('Query Results:');
|
||||
console.log(` Allow: ${allowCount} (${(allowCount/queries.length*100).toFixed(1)}%)`);
|
||||
console.log(` Deny: ${denyCount} (${(denyCount/queries.length*100).toFixed(1)}%)`);
|
||||
console.log(` Undetermined: ${undeterminedCount} (${(undeterminedCount/queries.length*100).toFixed(1)}%)`);
|
||||
console.log(`\nPerformance:`);
|
||||
console.log(` Total queries: ${queries.length}`);
|
||||
console.log(` Average latency: ${(totalTime / queries.length).toFixed(2)}ms`);
|
||||
console.log(` QPS: ${(1000 / (totalTime / queries.length)).toFixed(0)}`);
|
||||
|
||||
// Show cache stats
|
||||
console.log('\n=== Cache Statistics ===');
|
||||
const cacheStats = inference.getIntervalCacheStats();
|
||||
console.log(`Cache entries: ${cacheStats.size}`);
|
||||
if (cacheStats.entries.length > 0) {
|
||||
console.log('Sample entries:');
|
||||
for (const entry of cacheStats.entries.slice(0, 3)) {
|
||||
console.log(` ${entry.key}: [${entry.interval.lower}, ${entry.interval.upper}] (age: ${(entry.age/1000).toFixed(1)}s)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Show optimization statistics
|
||||
console.log('\n=== Optimization Statistics ===');
|
||||
const perfStats = inference.getPerformanceStats();
|
||||
console.log('Inference Performance:');
|
||||
console.log(` Queries: ${perfStats.inference.queries}`);
|
||||
console.log(` Cache hits: ${perfStats.inference.cacheHits}`);
|
||||
console.log(` Cache hit rate: ${(perfStats.inference.overallCacheHitRate * 100).toFixed(1)}%`);
|
||||
console.log(` Similarity cache hits: ${perfStats.inference.similarityCacheHits}`);
|
||||
|
||||
console.log('\nSimilarity Manager Performance:');
|
||||
console.log(` Total searches: ${perfStats.similarity.searches || 0}`);
|
||||
console.log(` Vector cache hits: ${perfStats.similarity.vectorCacheHits || 0}`);
|
||||
console.log(` Vector cache hit rate: ${((perfStats.similarity.vectorCacheHitRate || 0) * 100).toFixed(1)}%`);
|
||||
console.log(` Average search time: ${(perfStats.similarity.avgSearchTime || 0).toFixed(2)}ms`);
|
||||
|
||||
console.log('\nOptimization Impact:');
|
||||
console.log(` Redundant searches avoided: ${perfStats.optimization.redundantSearchesAvoided}`);
|
||||
console.log(` Redundant conversions avoided: ${perfStats.optimization.redundantConversionsAvoided}`);
|
||||
console.log(` Vector conversion efficiency: ${((perfStats.optimization.vectorCacheHitRate || 0) * 100).toFixed(1)}%`);
|
||||
}
|
||||
|
||||
// Run benchmark
|
||||
(async () => {
|
||||
try {
|
||||
await benchmarkIntervalQueries();
|
||||
} catch (error) {
|
||||
console.error('Benchmark error:', error);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user