717ae1031e
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.
89 lines
2.9 KiB
JavaScript
89 lines
2.9 KiB
JavaScript
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 }
|
|
);
|
|
});
|
|
});
|