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,139 @@
|
||||
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 parseSizes(value) {
|
||||
if (!value) return null;
|
||||
return value.split(',').map(item => Number(item.trim())).filter(Number.isFinite);
|
||||
}
|
||||
|
||||
function createRng(seed) {
|
||||
let state = seed >>> 0;
|
||||
return () => {
|
||||
state = (1664525 * state + 1013904223) >>> 0;
|
||||
return state / 0x100000000;
|
||||
};
|
||||
}
|
||||
|
||||
function randInt(rng, max) {
|
||||
return Math.floor(rng() * max);
|
||||
}
|
||||
|
||||
function measure(label, iterations, fn) {
|
||||
const start = process.hrtime.bigint();
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
fn(i);
|
||||
}
|
||||
const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
|
||||
return { label, elapsedMs, qps: elapsedMs > 0 ? Math.round((iterations / elapsedMs) * 1000) : 0 };
|
||||
}
|
||||
|
||||
function buildNodes(arbiter, size, prefix) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
arbiter.addNode(`${prefix}:${i}`, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv);
|
||||
const config = {
|
||||
sizes: parseSizes(args.get('sizes')) || [10000, 40000, 80000],
|
||||
samples: Number(args.get('samples') || 200000),
|
||||
seed: Number(args.get('seed') || 42),
|
||||
maxSeconds: Number(args.get('max-seconds') || 60)
|
||||
};
|
||||
|
||||
const rng = createRng(config.seed);
|
||||
const startTime = Date.now();
|
||||
|
||||
console.log('edge_mutation_bench');
|
||||
console.log('size,edges,metric,elapsed_ms,qps');
|
||||
|
||||
for (const size of config.sizes) {
|
||||
if ((Date.now() - startTime) / 1000 > config.maxSeconds) break;
|
||||
|
||||
const arbiter = new Arbiter();
|
||||
buildNodes(arbiter, size, 'user');
|
||||
buildNodes(arbiter, size, 'resource');
|
||||
|
||||
arbiter.setRelationConfig('link', { type: 'direct' });
|
||||
arbiter.setRelationConfig('link_add', { type: 'direct' });
|
||||
|
||||
const currentDst = new Array(size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
arbiter.addRelation(`user:${i}`, 'link', `resource:${i}`);
|
||||
currentDst[i] = i;
|
||||
}
|
||||
|
||||
const addCount = Math.min(config.samples, size);
|
||||
const addPairs = new Array(addCount);
|
||||
for (let i = 0; i < addCount; i++) {
|
||||
addPairs[i] = {
|
||||
src: i % size,
|
||||
dst: (i * 7) % size
|
||||
};
|
||||
}
|
||||
|
||||
const updateOps = new Array(config.samples);
|
||||
for (let i = 0; i < config.samples; i++) {
|
||||
const src = randInt(rng, size);
|
||||
let dst = randInt(rng, size);
|
||||
if (dst === currentDst[src]) {
|
||||
dst = (dst + 1) % size;
|
||||
}
|
||||
updateOps[i] = { src, dst };
|
||||
}
|
||||
|
||||
const addMetric = measure('add_relations', addCount, index => {
|
||||
const pair = addPairs[index];
|
||||
arbiter.addRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`);
|
||||
});
|
||||
|
||||
const removeMetric = measure('remove_relations', addCount, index => {
|
||||
const pair = addPairs[index];
|
||||
arbiter.removeRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`);
|
||||
});
|
||||
|
||||
const updateMetric = measure('update_relations', updateOps.length, index => {
|
||||
const op = updateOps[index];
|
||||
const srcKey = `user:${op.src}`;
|
||||
const oldDst = currentDst[op.src];
|
||||
arbiter.removeRelation(srcKey, 'link', `resource:${oldDst}`);
|
||||
arbiter.addRelation(srcKey, 'link', `resource:${op.dst}`);
|
||||
currentDst[op.src] = op.dst;
|
||||
});
|
||||
|
||||
const metrics = [
|
||||
{ metric: addMetric, edges: addCount },
|
||||
{ metric: removeMetric, edges: addCount },
|
||||
{ metric: updateMetric, edges: size }
|
||||
];
|
||||
|
||||
for (const entry of metrics) {
|
||||
console.log([
|
||||
size,
|
||||
entry.edges,
|
||||
entry.metric.label,
|
||||
Math.round(entry.metric.elapsedMs),
|
||||
entry.metric.qps
|
||||
].join(','));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user