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,109 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import { CondensedGraph } from '../src/core/CondensedGraph.js';
|
||||
import { ShardedSnapshotBuilder } from '../src/core/shards/ShardedSnapshotBuilder.js';
|
||||
import { ShardedSnapshot } from '../src/core/shards/ShardedSnapshot.js';
|
||||
import { FileShardStorage } from '../src/core/shards/FileShardStorage.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;
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv);
|
||||
const edges = Number(args.get('edges') || 50000);
|
||||
const users = Number(args.get('users') || 5000);
|
||||
const docs = Number(args.get('docs') || 20000);
|
||||
const bucketSize = Number(args.get('bucket') || 4096);
|
||||
const samples = Number(args.get('samples') || 10000);
|
||||
const output = args.get('output') || 'tmp/shards-bench';
|
||||
|
||||
console.log('Sharded snapshot bench');
|
||||
console.log(` edges: ${edges}`);
|
||||
console.log(` users: ${users}`);
|
||||
console.log(` docs: ${docs}`);
|
||||
console.log(` bucket: ${bucketSize}`);
|
||||
console.log(` samples: ${samples}`);
|
||||
|
||||
const graph = new CondensedGraph();
|
||||
const relations = ['owner', 'editor', 'viewer'];
|
||||
const edgePairs = [];
|
||||
const buildStart = performance.now();
|
||||
for (let i = 0; i < edges; i++) {
|
||||
const userIndex = i % users;
|
||||
const docIndex = i % docs;
|
||||
const relId = i % 3;
|
||||
const srcId = graph._ensureNode(`user:${userIndex}`);
|
||||
const dstId = graph._ensureNode(`doc:${docIndex}`);
|
||||
graph.addEdge(srcId, relations[relId], dstId);
|
||||
edgePairs.push({ srcId, dstId, relId });
|
||||
}
|
||||
const buildTime = performance.now() - buildStart;
|
||||
|
||||
graph.finalizePerfectHash();
|
||||
graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
|
||||
const snapshot = graph.toBinary();
|
||||
const snapshotPath = path.join(output, 'snapshot.bin');
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
fs.writeFileSync(snapshotPath, new Uint8Array(snapshot));
|
||||
|
||||
const shardStart = performance.now();
|
||||
const builder = new ShardedSnapshotBuilder({ bucketSize, includeDirections: ['out'] });
|
||||
const manifest = builder.build(graph, output);
|
||||
const shardTime = performance.now() - shardStart;
|
||||
|
||||
const storage = new FileShardStorage(output);
|
||||
const sharded = new ShardedSnapshot(manifest, storage, { cacheLimit: 8 });
|
||||
sharded.initializeSync();
|
||||
|
||||
const srcIds = Array.from({ length: users }, (_, i) => i);
|
||||
const dstIds = Array.from({ length: docs }, (_, i) => i + users);
|
||||
|
||||
const findStart = performance.now();
|
||||
let hits = 0;
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const pair = edgePairs[i % edgePairs.length];
|
||||
const srcId = pair.srcId;
|
||||
const dstId = pair.dstId;
|
||||
const relId = pair.relId;
|
||||
const edge = sharded.findEdgeSync(srcId, relId, dstId);
|
||||
if (edge) hits++;
|
||||
}
|
||||
const findTime = performance.now() - findStart;
|
||||
|
||||
const outStart = performance.now();
|
||||
let outEdges = 0;
|
||||
for (let i = 0; i < samples; i++) {
|
||||
const srcId = srcIds[i % srcIds.length];
|
||||
const relId = i % 3;
|
||||
const edgesOut = sharded.getOutEdgesSync(srcId, relId);
|
||||
outEdges += edgesOut.length;
|
||||
}
|
||||
const outTime = performance.now() - outStart;
|
||||
|
||||
console.log('\nResults');
|
||||
console.log(` build time: ${buildTime.toFixed(2)} ms`);
|
||||
console.log(` shard time: ${shardTime.toFixed(2)} ms`);
|
||||
console.log(` shards: ${manifest.shards.length}`);
|
||||
console.log(` snapshot size: ${(snapshot.byteLength / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` findEdge avg: ${(findTime / samples * 1000).toFixed(3)} µs`);
|
||||
console.log(` getOutEdges avg: ${(outTime / samples * 1000).toFixed(3)} µs`);
|
||||
console.log(` hits: ${hits}`);
|
||||
console.log(` outEdges: ${outEdges}`);
|
||||
Reference in New Issue
Block a user