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,128 @@
import { test, describe, it, beforeEach } from 'node:test';
import assert from 'node:assert';
import { Arbiter } from '../../src/core/Arbiter.js';
import { ChainRule } from '../../src/authorization/rules/ChainRule.js';
describe('Cache Memory Improvements (SimpleLRUCache default, injectable factory)', () => {
let arbiter;
let chainRule;
beforeEach(() => {
arbiter = new Arbiter({
directCheckCacheSize: 1000,
directCheckCacheTTL: 60000
});
chainRule = new ChainRule(arbiter);
});
it('should use SimpleLRUCache (default cacheFactory) for chain caches', () => {
// Verify ChainRule uses the default SimpleLRUCache
assert.ok(chainRule.chainResultCache.constructor.name === 'SimpleLRUCache');
// Verify cache capacity
assert.strictEqual(chainRule.maxCacheSize, 2000);
});
it('should use SimpleLRUCache for direct check cache', () => {
// Verify Arbiter uses the default SimpleLRUCache
assert.ok(arbiter.directCheckCache.constructor.name === 'SimpleLRUCache');
// Verify cache capacity
assert.strictEqual(arbiter.directCheckCacheSize, 1000);
});
it('should use SimpleLRUCache for relation manager caches', () => {
// Cache state lives in RelationCaches (RF-08): reach it via `_caches`.
assert.ok(arbiter.relationManager._caches.relationLookupCache.constructor.name === 'SimpleLRUCache');
assert.ok(arbiter.relationManager._caches.valueLookupCache.constructor.name === 'SimpleLRUCache');
// Verify cache capacity
assert.strictEqual(arbiter.relationManager._caches.maxCacheSize, 10000);
});
it('should use SimpleLRUCache for value manager caches', () => {
// Verify ValueManager uses the default SimpleLRUCache
assert.ok(arbiter.valueManager.blurredValueCache.constructor.name === 'SimpleLRUCache');
assert.ok(arbiter.valueManager.distributionCache.constructor.name === 'SimpleLRUCache');
});
it('should use SimpleLRUCache for similarity manager cache', () => {
// SimilarityManager is only constructed when similarityParams are provided
const simArbiter = new Arbiter({ similarityParams: {} });
assert.ok(simArbiter.similarityManager.nodeKeyToVector.constructor.name === 'SimpleLRUCache');
});
it('should handle cache eviction automatically', () => {
// Test that the default cache handles eviction automatically
const cache = chainRule.chainResultCache;
const initialSize = cache.size();
// Fill cache beyond capacity
for (let i = 0; i < chainRule.maxCacheSize + 100; i++) {
cache.set(`key_${i}`, { result: { possibility: 0.5 }, timestamp: Date.now() });
}
// Cache should not exceed capacity
assert.ok(cache.size() <= chainRule.maxCacheSize);
console.log(`Cache size after overflow: ${cache.size()} (max: ${chainRule.maxCacheSize})`);
});
it('should track eviction statistics', () => {
// Test that eviction statistics are tracked
const cache = chainRule.chainResultCache;
// Fill cache beyond capacity to trigger evictions
for (let i = 0; i < chainRule.maxCacheSize + 50; i++) {
cache.set(`key_${i}`, { result: { possibility: 0.5 }, timestamp: Date.now() });
}
// Check if eviction stats are tracked
if (chainRule.stats) {
console.log(`Chain result cache evictions: ${chainRule.stats.evictedResults || 0}`);
console.log(`Chain path cache evictions: ${chainRule.stats.evictedPaths || 0}`);
}
});
it('should provide better memory management than Map', () => {
// Compare memory usage patterns
const mapCache = new Map();
const hyperbolicCache = chainRule.chainResultCache;
// Fill both caches
for (let i = 0; i < 1000; i++) {
const value = { result: { possibility: 0.5 }, timestamp: Date.now() };
mapCache.set(`key_${i}`, value);
hyperbolicCache.set(`key_${i}`, value);
}
// Map grows indefinitely, the default cache is bounded
assert.ok(mapCache.size === 1000);
assert.ok(hyperbolicCache.size() <= chainRule.maxCacheSize);
console.log(`Map size: ${mapCache.size}`);
console.log(`SimpleLRUCache size: ${hyperbolicCache.size()}`);
});
it('should handle TTL without manual cleanup', () => {
// Test that TTL is handled without manual cleanup
const cache = arbiter.directCheckCache;
// Add entries with different timestamps
const now = Date.now();
cache.set('key1', { result: { possibility: 0.5 }, timestamp: now });
cache.set('key2', { result: { possibility: 0.5 }, timestamp: now - 70000 }); // Expired
// SimpleLRUCache keeps both entries; TTL is enforced at read time
// No manual cleanup needed
assert.ok(cache.has('key1'));
assert.ok(cache.has('key2')); // Still in cache, but will be evicted based on frequency
});
});
console.log('✅ Cache Memory Improvements Test Suite');
console.log('📊 Benefits:');
console.log(' - Automatic eviction based on frequency and recency');
console.log(' - No manual TTL cleanup needed');
console.log(' - Bounded memory usage');
console.log(' - Better cache hit rates through intelligent eviction');
console.log(' - Protection against memory leaks');