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
+121
View File
@@ -0,0 +1,121 @@
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { Arbiter } from '../../src/core/Arbiter.js';
describe('Rule result cache stats', () => {
test('records cache hits on repeated aggregate checks', () => {
const arbiter = new Arbiter({ enableRuleResultCache: true, ruleResultCacheTTL: 60000 });
arbiter.addNode('user:1', 'user');
arbiter.addNode('resource:1', 'resource');
arbiter.setRelationConfig('member', { type: 'direct' });
arbiter.setRelationConfig('allow', {
union: {
rules: [{ type: 'direct', relation: 'member' }],
aggregator: 'max'
}
});
arbiter.registerDependencyIndex(new Map([['member', {
all: new Set(['allow']),
byLevel: {
never: new Set(),
always: new Set(),
requires: new Set(),
when: new Set(),
unless: new Set(),
ordinary: new Set(['allow'])
}
}]]));
arbiter.addRelation('user:1', 'member', 'resource:1', 1.0);
const userId = arbiter.resolveNodeId('user:1');
const objectId = arbiter.resolveNodeId('resource:1');
const config = arbiter.relationConfigs.get('allow');
arbiter.authChecker.ruleEvaluator.evaluateRule(
userId,
'user:1',
objectId,
'resource:1',
config,
new Set(),
'allow',
{ includeMeta: false, collectValues: false, cacheRuleResult: true }
);
const baseKey = arbiter.keyManager.createCompositeKey(userId, 'allow', objectId);
const cacheKey = `${baseKey}|logical`;
assert.ok(arbiter.ruleResultCache.get(cacheKey), 'cache entry created');
arbiter.authChecker.ruleEvaluator.evaluateRule(
userId,
'user:1',
objectId,
'resource:1',
config,
new Set(),
'allow',
{ includeMeta: false, collectValues: false, cacheRuleResult: true }
);
assert.ok(arbiter.ruleResultCacheStats.misses >= 1, 'cache miss recorded');
assert.ok(arbiter.ruleResultCacheStats.hits >= 1, 'cache hit recorded');
});
test('does not prepopulate cache on config set', () => {
const arbiter = new Arbiter({ enableRuleResultCache: true, ruleResultCacheTTL: 60000 });
arbiter.addNode('user:1', 'user');
arbiter.addNode('resource:1', 'resource');
arbiter.setRelationConfig('risk_score', { type: 'direct' });
arbiter.setRelationConfig('risk_limit', { type: 'direct' });
arbiter.addRelation('user:1', 'risk_score', 'resource:1', 1.0, { value: 10 });
arbiter.addRelation('resource:1', 'risk_limit', 'resource:1', 1.0, { value: 20 });
arbiter.setRelationConfig('risk_ok_owa', {
type: 'relational_comparator',
comparator: '<=',
left: {
rule: { type: 'direct', relation: 'risk_score' },
extractValue: true,
valueRelation: 'risk_score',
aggregator: 'owa',
owaWeights: [1]
},
right: {
rule: { type: 'direct', relation: 'risk_limit' },
extractValue: true,
valueRelation: 'risk_limit',
evaluateFrom: 'object'
}
});
const userId = arbiter.resolveNodeId('user:1');
const objectId = arbiter.resolveNodeId('resource:1');
const config = arbiter.relationConfigs.get('risk_ok_owa');
const cacheKey = arbiter.authChecker.ruleEvaluator._getRuleResultCacheKey(
userId,
'risk_ok_owa',
objectId,
config
);
assert.ok(!arbiter.ruleResultCache.get(cacheKey), 'cache not populated on config set');
arbiter.authChecker.ruleEvaluator.evaluateRule(
userId,
'user:1',
objectId,
'resource:1',
config,
new Set(),
'risk_ok_owa',
{ includeMeta: false, collectValues: true, cacheRuleResult: true }
);
assert.ok(arbiter.ruleResultCache.get(cacheKey), 'cache populated after evaluation');
});
});