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,88 @@
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fc from 'fast-check';
|
||||
import { Arbiter } from '../../src/core/Arbiter.js';
|
||||
|
||||
function hasPathWithin(adj, src, dst, maxDepth) {
|
||||
const visited = new Set([src]);
|
||||
let frontier = [src];
|
||||
let depth = 0;
|
||||
while (frontier.length && depth < maxDepth) {
|
||||
const next = [];
|
||||
for (const node of frontier) {
|
||||
const neighbors = adj.get(node) || [];
|
||||
for (const n of neighbors) {
|
||||
if (n === dst) return true;
|
||||
if (!visited.has(n)) {
|
||||
visited.add(n);
|
||||
next.push(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
frontier = next;
|
||||
depth++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
describe('Fast-check: multi-hop invariants', () => {
|
||||
test('zero-hop is denied unless explicitly enabled', () => {
|
||||
const arbiter = new Arbiter();
|
||||
arbiter.addNode('node:0', 'node');
|
||||
arbiter.setRelationConfig('link', { type: 'direct' });
|
||||
|
||||
arbiter.setRelationConfig('reachable', { type: 'multi_hop', relation: 'link', maxDepth: 2 });
|
||||
const defaultResult = arbiter.check('node:0', 'reachable', 'node:0');
|
||||
assert.strictEqual(defaultResult.possibility > 0, false);
|
||||
|
||||
arbiter.setRelationConfig('reachable_allow', {
|
||||
type: 'multi_hop',
|
||||
relation: 'link',
|
||||
maxDepth: 2,
|
||||
allowZeroHop: true
|
||||
});
|
||||
const allowedResult = arbiter.check('node:0', 'reachable_allow', 'node:0');
|
||||
assert.strictEqual(allowedResult.possibility > 0, true);
|
||||
});
|
||||
|
||||
test('multi-hop reachability matches bounded BFS', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.integer({ min: 2, max: 6 }),
|
||||
fc.integer({ min: 1, max: 4 }),
|
||||
fc.array(
|
||||
fc.record({
|
||||
src: fc.integer({ min: 0, max: 5 }),
|
||||
dst: fc.integer({ min: 0, max: 5 })
|
||||
}),
|
||||
{ minLength: 1, maxLength: 20 }
|
||||
),
|
||||
(nodeCount, maxDepth, edges) => {
|
||||
const arbiter = new Arbiter();
|
||||
for (let i = 0; i < nodeCount; i++) arbiter.addNode(`node:${i}`, 'node');
|
||||
arbiter.setRelationConfig('link', { type: 'direct' });
|
||||
arbiter.setRelationConfig('reachable', { type: 'multi_hop', relation: 'link', maxDepth, allowZeroHop: false });
|
||||
|
||||
const adj = new Map();
|
||||
for (const edge of edges) {
|
||||
const src = edge.src % nodeCount;
|
||||
const dst = edge.dst % nodeCount;
|
||||
arbiter.addRelation(`node:${src}`, 'link', `node:${dst}`, 1.0);
|
||||
const list = adj.get(src) || [];
|
||||
list.push(dst);
|
||||
adj.set(src, list);
|
||||
}
|
||||
|
||||
for (let i = 0; i < nodeCount; i++) {
|
||||
for (let j = 0; j < nodeCount; j++) {
|
||||
const expected = hasPathWithin(adj, i, j, maxDepth);
|
||||
const result = arbiter.check(`node:${i}`, 'reachable', `node:${j}`);
|
||||
assert.strictEqual(result.possibility > 0, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
{ numRuns: 30 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user