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,160 @@
|
||||
import { test, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { setupChainRuleTestGraph } from './helpers.js';
|
||||
|
||||
describe('ChainRule Comprehensive Tests', () => {
|
||||
|
||||
describe('Fast Path and Performance Optimization', () => {
|
||||
it('supports early exit with minAllowPossibility threshold', () => {
|
||||
const arbiter = setupChainRuleTestGraph();
|
||||
|
||||
arbiter.setRelationConfig('quick_access_check', {
|
||||
type: 'chain',
|
||||
steps: [
|
||||
{ relation: 'member_of', direction: 'out' },
|
||||
{ relation: 'manages', direction: 'out' }
|
||||
]
|
||||
});
|
||||
|
||||
// Test with fast path enabled and low threshold
|
||||
const result = arbiter.check('user:alice', 'quick_access_check', 'project:web-app', {
|
||||
fastPath: true,
|
||||
minAllowPossibility: 0.5 // Exit early if possibility >= 0.5
|
||||
});
|
||||
|
||||
assert.equal(result.possibility, 1);
|
||||
assert.equal(result.reason, 'allow_threshold_met');
|
||||
});
|
||||
|
||||
it('supports early exit with maxDenyPossibility threshold', () => {
|
||||
const arbiter = setupChainRuleTestGraph();
|
||||
|
||||
arbiter.setRelationConfig('deny_check', {
|
||||
type: 'chain',
|
||||
steps: [
|
||||
{ relation: 'member_of', direction: 'out' },
|
||||
{ relation: 'manages', direction: 'out' }
|
||||
]
|
||||
});
|
||||
|
||||
// Test denial with fast path
|
||||
const result = arbiter.check('user:alice', 'deny_check', 'project:mobile-app', {
|
||||
fastPath: true,
|
||||
maxDenyPossibility: 0.8 // Exit early if denial >= 0.8
|
||||
});
|
||||
|
||||
assert.equal(result.possibility, 0);
|
||||
assert.equal(result.reason, 'no_chain_path_found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Comparison with Other Rules', () => {
|
||||
it('compares ChainRule vs ParentRule performance', () => {
|
||||
const arbiter = setupChainRuleTestGraph();
|
||||
|
||||
// Set up equivalent rules using ChainRule and ParentRule
|
||||
arbiter.setRelationConfig('chain_parent_check', {
|
||||
type: 'chain',
|
||||
steps: [
|
||||
{ relation: 'manages', direction: 'in' }, // project ← group
|
||||
{ relation: 'member_of', direction: 'in' } // group ← user
|
||||
]
|
||||
});
|
||||
|
||||
arbiter.setRelationConfig('traditional_parent_check', {
|
||||
type: 'parent',
|
||||
parentRelation: 'manages',
|
||||
relation: 'member_of',
|
||||
reverse: true
|
||||
});
|
||||
|
||||
const startTime1 = Date.now();
|
||||
const chainResult = arbiter.check('project:web-app', 'chain_parent_check', 'user:alice');
|
||||
const chainTime = Date.now() - startTime1;
|
||||
|
||||
const startTime2 = Date.now();
|
||||
const parentResult = arbiter.check('project:web-app', 'traditional_parent_check', 'user:alice');
|
||||
const parentTime = Date.now() - startTime2;
|
||||
|
||||
if (process.env.TEST_DEBUG === '1') console.log('ChainRule result:', chainResult.possibility);
|
||||
if (process.env.TEST_DEBUG === '1') console.log('ParentRule result:', parentResult.possibility);
|
||||
|
||||
// ChainRule should succeed (Alice is member of engineering, which manages web-app)
|
||||
assert.equal(chainResult.possibility, 1);
|
||||
|
||||
// ParentRule might have different semantics - just verify it returns a valid result
|
||||
assert.ok(typeof parentResult.possibility === 'number');
|
||||
|
||||
if (process.env.TEST_DEBUG === '1') console.log(`ChainRule time: ${chainTime}ms, ParentRule time: ${parentTime}ms`);
|
||||
|
||||
// ChainRule should be reasonably performant
|
||||
assert.ok(chainTime < 100); // Should complete in reasonable time
|
||||
});
|
||||
|
||||
it('compares ChainRule vs MultiHopRule for path finding', () => {
|
||||
const arbiter = setupChainRuleTestGraph();
|
||||
|
||||
// ChainRule: specific path
|
||||
arbiter.setRelationConfig('chain_access', {
|
||||
type: 'chain',
|
||||
steps: [
|
||||
{ relation: 'member_of', direction: 'out' },
|
||||
{ relation: 'belongs_to', direction: 'out' },
|
||||
{ relation: 'has_access_level', direction: 'out' }
|
||||
]
|
||||
});
|
||||
|
||||
// MultiHopRule: flexible path finding
|
||||
arbiter.setRelationConfig('multihop_access', {
|
||||
type: 'multi_hop',
|
||||
relation: 'member_of',
|
||||
maxDepth: 3,
|
||||
pathAggregation: 'max'
|
||||
});
|
||||
|
||||
const chainResult = arbiter.check('user:alice', 'chain_access', 'access:level-5');
|
||||
const multihopResult = arbiter.check('user:alice', 'multihop_access', 'access:level-5');
|
||||
|
||||
// ChainRule should give precise result for defined path
|
||||
assert.equal(chainResult.possibility, 1);
|
||||
|
||||
// MultiHopRule might give different result based on path exploration
|
||||
if (process.env.TEST_DEBUG === '1') console.log('Chain vs MultiHop results:', {
|
||||
chain: chainResult.possibility,
|
||||
multihop: multihopResult.possibility
|
||||
});
|
||||
});
|
||||
|
||||
it('demonstrates ChainRule semantic clarity vs other approaches', () => {
|
||||
const arbiter = setupChainRuleTestGraph();
|
||||
|
||||
// ChainRule: explicit semantic path
|
||||
arbiter.setRelationConfig('semantic_chain', {
|
||||
type: 'chain',
|
||||
steps: [
|
||||
{ relation: 'member_of', direction: 'out' },
|
||||
{ relation: 'manages', direction: 'out' },
|
||||
{ relation: 'has_budget', direction: 'out' }
|
||||
],
|
||||
extractValues: true,
|
||||
extractFrom: 2,
|
||||
extractRelation: 'has_budget',
|
||||
valueAggregation: 'sum'
|
||||
});
|
||||
|
||||
const result = arbiter.check('user:alice', 'semantic_chain', 'budget:tech-2024');
|
||||
|
||||
// Should provide clear semantic meaning:
|
||||
// "user's budget access through group project management"
|
||||
assert.equal(result.possibility, 1);
|
||||
|
||||
// Values are internal only, not exposed in authorization results
|
||||
assert.equal(result.value, undefined);
|
||||
|
||||
if (process.env.TEST_DEBUG === '1') console.log('Semantic chain result:', {
|
||||
access: result.possibility,
|
||||
meaning: 'user → group → project → budget'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user