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:
John Dvorak
2026-07-31 13:44:06 -07:00
commit 717ae1031e
373 changed files with 654131 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
import { Arbiter } from '../src/core/Arbiter.js';
function parseArgs(argv) {
const args = new Map();
for (let i = 2; i < argv.length; i++) {
const value = argv[i];
if (!value.startsWith('--')) continue;
const [key, inline] = value.slice(2).split('=');
if (inline !== undefined) {
args.set(key, inline);
continue;
}
const next = argv[i + 1];
if (next && !next.startsWith('--')) {
args.set(key, next);
i++;
} else {
args.set(key, true);
}
}
return args;
}
function createRng(seed) {
let state = seed >>> 0;
return () => {
state = (1664525 * state + 1013904223) >>> 0;
return state / 0x100000000;
};
}
function parseSizes(value) {
if (!value) return null;
return value.split(',').map(item => Number(item.trim())).filter(Number.isFinite);
}
function formatBytes(bytes) {
if (bytes === 0) return '0B';
const units = ['B', 'KB', 'MB', 'GB'];
const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
const scaled = bytes / Math.pow(1024, index);
return `${scaled.toFixed(2)}${units[index]}`;
}
function buildGraph({ nodes, edgesPerNode, rng, enableReachability, authMode, maxDepth }) {
const arbiter = new Arbiter();
for (let i = 0; i < nodes; i++) {
arbiter.addNode(`node:${i}`, 'node');
}
const totalEdges = Math.max(1, Math.round(nodes * edgesPerNode));
for (let i = 0; i < totalEdges; i++) {
const src = Math.floor(rng() * nodes);
const dst = Math.floor(rng() * nodes);
if (src === dst) continue;
arbiter.addRelation(`node:${src}`, 'link', `node:${dst}`, 1.0);
}
arbiter.setRelationConfig('link', { type: 'direct' });
arbiter.setRelationConfig('can_reach', {
type: 'multi_hop',
relation: 'link',
maxDepth,
skipReachabilityCheck: true,
collectValues: false,
trackPaths: false,
fallbackToBasicPaths: false
});
if (enableReachability) {
arbiter.graphManager.initializeReachabilityChecker({ enableBackwardIndex: true });
}
return { arbiter, totalEdges };
}
function runAuthChecks({ arbiter, nodes, rng, samples, authMode }) {
const start = process.hrtime.bigint();
for (let i = 0; i < samples; i++) {
const src = Math.floor(rng() * nodes);
const dst = Math.floor(rng() * nodes);
const relation = authMode === 'direct' ? 'link' : 'can_reach';
arbiter.check(`node:${src}`, relation, `node:${dst}`, { fastPath: true });
}
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
const qps = elapsedMs > 0 ? Math.round((samples / elapsedMs) * 1000) : 0;
return { elapsedMs, qps };
}
function runMutationBench({ arbiter, nodes, rng, samples }) {
const keys = [];
const insertStart = process.hrtime.bigint();
for (let i = 0; i < samples; i++) {
const src = Math.floor(rng() * nodes);
const dst = Math.floor(rng() * nodes);
const srcKey = `node:${src}`;
const dstKey = `node:${dst}`;
keys.push([srcKey, dstKey]);
arbiter.addRelation(srcKey, 'link', dstKey, 1.0);
}
const insertMs = Number(process.hrtime.bigint() - insertStart) / 1e6;
const insertQps = insertMs > 0 ? Math.round((samples / insertMs) * 1000) : 0;
const updateStart = process.hrtime.bigint();
for (const [srcKey, dstKey] of keys) {
arbiter.addRelation(srcKey, 'link', dstKey, { possibility: 0.7 });
}
const updateMs = Number(process.hrtime.bigint() - updateStart) / 1e6;
const updateQps = updateMs > 0 ? Math.round((samples / updateMs) * 1000) : 0;
const deleteStart = process.hrtime.bigint();
for (const [srcKey, dstKey] of keys) {
arbiter.removeRelation(srcKey, 'link', dstKey);
}
const deleteMs = Number(process.hrtime.bigint() - deleteStart) / 1e6;
const deleteQps = deleteMs > 0 ? Math.round((samples / deleteMs) * 1000) : 0;
return { insertMs, insertQps, updateMs, updateQps, deleteMs, deleteQps };
}
function captureMemory() {
if (global.gc) {
global.gc();
}
const memory = process.memoryUsage();
return {
rss: memory.rss,
heapUsed: memory.heapUsed,
heapTotal: memory.heapTotal
};
}
const args = parseArgs(process.argv);
const config = {
sizes: parseSizes(args.get('sizes')) || [5000, 10000, 25000, 50000, 100000, 200000],
edgesPerNode: Number(args.get('edges-per-node') || 4),
seed: Number(args.get('seed') || 42),
maxSeconds: Number(args.get('max-seconds') || 60),
reachability: args.get('reachability') === 'true',
authSamples: Number(args.get('auth-samples') || 20000),
mutationSamples: Number(args.get('mutation-samples') || 5000),
authMode: args.get('auth-mode') || 'multi-hop',
maxDepth: Number(args.get('max-depth') || 4)
};
const rng = createRng(config.seed);
const start = Date.now();
console.log('arbiter_scaling');
console.log(`edges_per_node=${config.edgesPerNode} reachability=${config.reachability}`);
console.log(`auth_mode=${config.authMode} max_depth=${config.maxDepth}`);
console.log('nodes,edges,build_ms,reachability_ms,auth_ms,auth_qps,insert_ms,insert_qps,update_ms,update_qps,delete_ms,delete_qps,rss,heap_used,heap_total');
for (const nodes of config.sizes) {
if ((Date.now() - start) / 1000 > config.maxSeconds) break;
const beforeMemory = captureMemory();
const buildStart = Date.now();
const { arbiter, totalEdges } = buildGraph({
nodes,
edgesPerNode: config.edgesPerNode,
rng,
enableReachability: false,
authMode: config.authMode,
maxDepth: config.maxDepth
});
const buildMs = Date.now() - buildStart;
let reachabilityMs = 0;
if (config.reachability) {
const reachStart = Date.now();
arbiter.graphManager.initializeReachabilityChecker({ enableBackwardIndex: true });
reachabilityMs = Date.now() - reachStart;
}
const auth = runAuthChecks({ arbiter, nodes, rng, samples: config.authSamples, authMode: config.authMode });
const mutations = runMutationBench({ arbiter, nodes, rng, samples: config.mutationSamples });
const afterMemory = captureMemory();
console.log([
nodes,
totalEdges,
buildMs,
reachabilityMs,
Math.round(auth.elapsedMs),
auth.qps,
Math.round(mutations.insertMs),
mutations.insertQps,
Math.round(mutations.updateMs),
mutations.updateQps,
Math.round(mutations.deleteMs),
mutations.deleteQps,
formatBytes(afterMemory.rss - beforeMemory.rss),
formatBytes(afterMemory.heapUsed - beforeMemory.heapUsed),
formatBytes(afterMemory.heapTotal - beforeMemory.heapTotal)
].join(','));
}