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,166 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { CondensedGraph } from '../src/core/CondensedGraph.js';
|
||||
import { ShardedSnapshotBuilder } from '../src/core/shards/ShardedSnapshotBuilder.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 makeRng(seed = 1337) {
|
||||
let state = seed >>> 0;
|
||||
return () => {
|
||||
state = (state * 1664525 + 1013904223) >>> 0;
|
||||
return state / 0xffffffff;
|
||||
};
|
||||
}
|
||||
|
||||
function makeZipfSampler(count, skew, seed = 1337) {
|
||||
const weights = new Float64Array(count);
|
||||
let sum = 0;
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const w = 1 / Math.pow(i, skew);
|
||||
weights[i - 1] = w;
|
||||
sum += w;
|
||||
}
|
||||
const cdf = new Float64Array(count);
|
||||
let acc = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
acc += weights[i] / sum;
|
||||
cdf[i] = acc;
|
||||
}
|
||||
let state = seed >>> 0;
|
||||
const rand = () => {
|
||||
state = (state * 1664525 + 1013904223) >>> 0;
|
||||
return state / 0xffffffff;
|
||||
};
|
||||
return () => {
|
||||
const r = rand();
|
||||
let lo = 0;
|
||||
let hi = cdf.length - 1;
|
||||
while (lo < hi) {
|
||||
const mid = (lo + hi) >> 1;
|
||||
if (r <= cdf[mid]) {
|
||||
hi = mid;
|
||||
} else {
|
||||
lo = mid + 1;
|
||||
}
|
||||
}
|
||||
return lo;
|
||||
};
|
||||
}
|
||||
|
||||
const args = parseArgs(process.argv);
|
||||
const edges = Number(args.get('edges') || 1000000);
|
||||
const users = Number(args.get('users') || 100000);
|
||||
const groups = Number(args.get('groups') || 5000);
|
||||
const docs = Number(args.get('docs') || 500000);
|
||||
const bucketSize = Number(args.get('bucket') || 4096);
|
||||
const output = args.get('output') || 'tmp/shards-complex-1m';
|
||||
const userSkew = Number(args.get('user-skew') || 1.2);
|
||||
const groupSkew = Number(args.get('group-skew') || 1.1);
|
||||
const docSkew = Number(args.get('doc-skew') || 1.15);
|
||||
const directions = args.get('directions')
|
||||
? String(args.get('directions')).split(',')
|
||||
: ['out', 'in'];
|
||||
const shardMode = args.get('shard-mode') || 'range';
|
||||
const componentRelations = args.get('component-relations')
|
||||
? String(args.get('component-relations')).split(',')
|
||||
: null;
|
||||
|
||||
const rng = makeRng(42);
|
||||
|
||||
console.log('Build complex sharded snapshot');
|
||||
console.log(` edges: ${edges}`);
|
||||
console.log(` users: ${users}`);
|
||||
console.log(` groups: ${groups}`);
|
||||
console.log(` docs: ${docs}`);
|
||||
console.log(` bucket: ${bucketSize}`);
|
||||
console.log(` output: ${output}`);
|
||||
console.log(` shard mode: ${shardMode}`);
|
||||
if (componentRelations) {
|
||||
console.log(` component relations: ${componentRelations.join(',')}`);
|
||||
}
|
||||
console.log(` user-skew: ${userSkew}`);
|
||||
console.log(` group-skew: ${groupSkew}`);
|
||||
console.log(` doc-skew: ${docSkew}`);
|
||||
|
||||
const graph = new CondensedGraph();
|
||||
|
||||
const memberEdges = Math.floor(edges * 0.3);
|
||||
const viewerEdges = Math.floor(edges * 0.3);
|
||||
const ownerEdges = Math.floor(edges * 0.1);
|
||||
const riskEdges = Math.floor(edges * 0.2);
|
||||
let riskLimitEdges = edges - memberEdges - viewerEdges - ownerEdges - riskEdges;
|
||||
if (riskLimitEdges < 0) riskLimitEdges = 0;
|
||||
|
||||
const userSampler = makeZipfSampler(users, userSkew, 7);
|
||||
const groupSampler = makeZipfSampler(groups, groupSkew, 11);
|
||||
const docSampler = makeZipfSampler(docs, docSkew, 13);
|
||||
|
||||
for (let i = 0; i < memberEdges; i++) {
|
||||
const userId = userSampler();
|
||||
const groupId = groupSampler();
|
||||
graph.addEdge(`user:${userId}`, 'member', `group:${groupId}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < viewerEdges; i++) {
|
||||
const groupId = groupSampler();
|
||||
const docId = docSampler();
|
||||
graph.addEdge(`group:${groupId}`, 'viewer', `doc:${docId}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < ownerEdges; i++) {
|
||||
const userId = userSampler();
|
||||
const docId = docSampler();
|
||||
graph.addEdge(`user:${userId}`, 'owner', `doc:${docId}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < riskEdges; i++) {
|
||||
const userId = userSampler();
|
||||
const docId = docSampler();
|
||||
const value = Math.min(1, Math.max(0, rng()));
|
||||
graph.addEdge(`user:${userId}`, 'risk', `doc:${docId}`, { value, possibility: 1.0, reliability: 1.0 });
|
||||
}
|
||||
|
||||
for (let i = 0; i < riskLimitEdges; i++) {
|
||||
const docId = docSampler();
|
||||
const value = 0.5;
|
||||
graph.addEdge(`doc:${docId}`, 'risk_limit', `doc:${docId}`, { value, possibility: 1.0, reliability: 1.0 });
|
||||
}
|
||||
|
||||
graph.finalizePerfectHash();
|
||||
graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
|
||||
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
const snapshot = graph.toBinary();
|
||||
const snapshotPath = path.join(output, 'snapshot.bin');
|
||||
fs.writeFileSync(snapshotPath, new Uint8Array(snapshot));
|
||||
console.log(` snapshot MB: ${(snapshot.byteLength / 1024 / 1024).toFixed(2)}`);
|
||||
|
||||
const builder = new ShardedSnapshotBuilder({
|
||||
bucketSize,
|
||||
includeDirections: directions,
|
||||
shardMode,
|
||||
componentRelations
|
||||
});
|
||||
const manifest = builder.build(graph, output);
|
||||
console.log(` shards: ${manifest.shards.length}`);
|
||||
console.log(` manifest: ${path.join(output, 'manifest.json')}`);
|
||||
Reference in New Issue
Block a user