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:
John Dvorak
2026-07-31 13:44:06 -07:00
commit 717ae1031e
373 changed files with 654131 additions and 0 deletions
@@ -0,0 +1,209 @@
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { Arbiter } from '../../src/index.js';
import { OWAFusion } from '../../src/utils/OWAFusion.js';
const EPS = 1e-6;
function approxEqual(actual, expected, epsilon = EPS) {
assert.ok(Math.abs(actual - expected) <= epsilon, `expected ${expected} but got ${actual}`);
}
function addValueRelation(arbiter, src, relation, dst, value, possibility, reliability) {
arbiter.addRelation(src, relation, dst, { value, possibility, reliability });
arbiter.setRelationConfig(relation, { type: 'direct' });
}
function buildComparatorRule(leftRelations, rightRelations, weights) {
return {
type: 'relational_comparator',
comparator: '>=',
fallbackBehavior: 'deny',
left: {
rule: {
union: {
rules: leftRelations.map((relation) => ({ type: 'direct', relation })),
aggregator: 'owa',
owaWeights: weights
}
},
extractValue: true,
aggregator: 'owa',
owaWeights: weights
},
right: {
rule: {
union: {
rules: rightRelations.map((relation) => ({ type: 'direct', relation })),
aggregator: 'owa',
owaWeights: weights
}
},
extractValue: true,
aggregator: 'owa',
owaWeights: weights,
evaluateFrom: 'object'
}
};
}
function fuseTriples(values, possibilities, reliabilities, weights) {
const metas = values.map(() => ({}));
return OWAFusion.fuseTriplesWithMeta(values, possibilities, reliabilities, metas, weights, 'owa');
}
function computeComparatorOutcome(left, right, weights) {
const fusedLeft = fuseTriples(left.values, left.possibilities, left.reliabilities, weights);
const fusedRight = fuseTriples(right.values, right.possibilities, right.reliabilities, weights);
const comparison = fusedLeft.value >= fusedRight.value ? 1 : 0;
const averageOperandPossibility = (fusedLeft.possibility + fusedRight.possibility) / 2;
const confidenceWeight = Math.min(averageOperandPossibility * 2, 1.0);
return {
possibility: comparison * confidenceWeight,
reliability: Math.min(fusedLeft.reliability, fusedRight.reliability)
};
}
describe('Relational comparator uncertainty', () => {
test('OWA value aggregation drives comparator possibility', () => {
const arbiter = new Arbiter();
arbiter.addNode('user:alice', 'user');
arbiter.addNode('doc:1', 'doc');
const weights = [0.6, 0.3, 0.1];
const left = {
relations: ['risk_low', 'risk_mid', 'risk_high'],
values: [10, 20, 40],
possibilities: [0.9, 0.6, 0.4],
reliabilities: [0.8, 0.9, 0.7]
};
const right = {
relations: ['limit_low', 'limit_mid', 'limit_high'],
values: [15, 25, 30],
possibilities: [0.8, 0.5, 0.9],
reliabilities: [0.9, 0.8, 0.95]
};
left.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'user:alice', relation, 'doc:1', left.values[idx], left.possibilities[idx], left.reliabilities[idx]);
});
right.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'doc:1', relation, 'doc:1', right.values[idx], right.possibilities[idx], right.reliabilities[idx]);
});
arbiter.setRelationConfig('risk_ok', buildComparatorRule(left.relations, right.relations, weights));
const expected = computeComparatorOutcome(left, right, weights);
const result = arbiter.check('user:alice', 'risk_ok', 'doc:1', { fastPath: false, explain: true });
approxEqual(result.possibility, expected.possibility);
approxEqual(result.reliability, expected.reliability);
});
test('low operand confidence scales comparator output', () => {
const arbiter = new Arbiter();
arbiter.addNode('user:alice', 'user');
arbiter.addNode('doc:1', 'doc');
const weights = [0.5, 0.3, 0.2];
const left = {
relations: ['risk_a', 'risk_b', 'risk_c'],
values: [50, 30, 10],
possibilities: [0.1, 0.1, 0.1],
reliabilities: [0.9, 0.9, 0.9]
};
const right = {
relations: ['limit_a', 'limit_b', 'limit_c'],
values: [20, 15, 5],
possibilities: [0.1, 0.1, 0.1],
reliabilities: [0.9, 0.9, 0.9]
};
left.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'user:alice', relation, 'doc:1', left.values[idx], left.possibilities[idx], left.reliabilities[idx]);
});
right.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'doc:1', relation, 'doc:1', right.values[idx], right.possibilities[idx], right.reliabilities[idx]);
});
arbiter.setRelationConfig('risk_ok', buildComparatorRule(left.relations, right.relations, weights));
const expected = computeComparatorOutcome(left, right, weights);
const result = arbiter.check('user:alice', 'risk_ok', 'doc:1', { fastPath: false, explain: true });
approxEqual(result.possibility, expected.possibility);
approxEqual(result.reliability, expected.reliability);
assert.ok(result.possibility < 1, 'expected confidence scaling to reduce possibility');
});
test('OWA union aggregates comparator results with uncertainty', () => {
const arbiter = new Arbiter();
arbiter.addNode('user:alice', 'user');
arbiter.addNode('doc:1', 'doc');
const weights = [0.6, 0.3, 0.1];
const unionWeights = [0.7, 0.3];
const leftA = {
relations: ['risk_a1', 'risk_a2', 'risk_a3'],
values: [12, 18, 25],
possibilities: [0.9, 0.7, 0.5],
reliabilities: [0.9, 0.8, 0.7]
};
const rightA = {
relations: ['limit_a1', 'limit_a2', 'limit_a3'],
values: [10, 15, 20],
possibilities: [0.8, 0.6, 0.7],
reliabilities: [0.9, 0.9, 0.8]
};
const leftB = {
relations: ['risk_b1', 'risk_b2', 'risk_b3'],
values: [8, 9, 11],
possibilities: [0.4, 0.5, 0.6],
reliabilities: [0.8, 0.8, 0.8]
};
const rightB = {
relations: ['limit_b1', 'limit_b2', 'limit_b3'],
values: [9, 10, 12],
possibilities: [0.7, 0.6, 0.5],
reliabilities: [0.9, 0.9, 0.9]
};
leftA.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'user:alice', relation, 'doc:1', leftA.values[idx], leftA.possibilities[idx], leftA.reliabilities[idx]);
});
rightA.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'doc:1', relation, 'doc:1', rightA.values[idx], rightA.possibilities[idx], rightA.reliabilities[idx]);
});
leftB.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'user:alice', relation, 'doc:1', leftB.values[idx], leftB.possibilities[idx], leftB.reliabilities[idx]);
});
rightB.relations.forEach((relation, idx) => {
addValueRelation(arbiter, 'doc:1', relation, 'doc:1', rightB.values[idx], rightB.possibilities[idx], rightB.reliabilities[idx]);
});
const comparatorA = buildComparatorRule(leftA.relations, rightA.relations, weights);
const comparatorB = buildComparatorRule(leftB.relations, rightB.relations, weights);
arbiter.setRelationConfig('risk_union', {
union: {
rules: [comparatorA, comparatorB],
aggregator: 'owa',
owaWeights: unionWeights
}
});
const outcomeA = computeComparatorOutcome(leftA, rightA, weights);
const outcomeB = computeComparatorOutcome(leftB, rightB, weights);
const expectedUnion = OWAFusion.fuseWithMeta(
[outcomeA.possibility, outcomeB.possibility],
[{}, {}],
unionWeights,
'owa',
true
).value;
const result = arbiter.check('user:alice', 'risk_union', 'doc:1', { fastPath: false });
approxEqual(result.possibility, expectedUnion);
});
});