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,108 @@
|
||||
/**
|
||||
* relation-cache-extraction.test.js — verifies RF-08 deletion test closure.
|
||||
*
|
||||
* Before this refactor, RelationManager aliased 7 cache fields from
|
||||
* RelationCaches (this._relationLookupCache, this._valueLookupCache, etc.).
|
||||
* Removing RelationCaches.js from disk without removing the aliases
|
||||
* would have left broken references. This test confirms:
|
||||
* - The aliases no longer exist on RelationManager
|
||||
* - All cache state lives under RelationManager._caches
|
||||
* - Functional behavior is unchanged: relations still work, caches still
|
||||
* populate and serve lookups
|
||||
*/
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Arbiter } from '../../src/index.js';
|
||||
|
||||
describe('RF-08: RelationManager cache extraction', () => {
|
||||
it('does not alias cache fields (deletion test)', () => {
|
||||
const arbiter = new Arbiter();
|
||||
const rm = arbiter.relationManager;
|
||||
|
||||
// The seven aliased fields that used to live on RelationManager
|
||||
// are gone. Each must be undefined.
|
||||
assert.equal(rm._maxCacheSize, undefined);
|
||||
assert.equal(rm._relationLookupCache, undefined);
|
||||
assert.equal(rm._valueLookupCache, undefined);
|
||||
assert.equal(rm._relationLookupCacheKeys, undefined);
|
||||
assert.equal(rm._valueLookupCacheKeys, undefined);
|
||||
assert.equal(rm._valueRelationsBySrcCache, undefined);
|
||||
assert.equal(rm._valueRelationsByDstCache, undefined);
|
||||
assert.equal(rm._valueRelationsByNameCache, undefined);
|
||||
|
||||
// The only cache surface is now _caches (the RelationCaches instance).
|
||||
assert.ok(rm._caches);
|
||||
assert.ok(rm._caches.relationLookupCache);
|
||||
assert.ok(rm._caches.valueLookupCache);
|
||||
assert.ok(rm._caches.valueRelationsBySrcCache);
|
||||
assert.ok(rm._caches.valueRelationsByDstCache);
|
||||
assert.ok(rm._caches.valueRelationsByNameCache);
|
||||
});
|
||||
|
||||
it('functional cache behavior is preserved', () => {
|
||||
const arbiter = new Arbiter();
|
||||
arbiter.addNode('user', 'user');
|
||||
arbiter.addNode('doc', 'document');
|
||||
arbiter.addRelation('user', 'can_read', 'doc', 1.0);
|
||||
|
||||
const srcId = arbiter.nodeIdByKey.get('user');
|
||||
const dstId = arbiter.nodeIdByKey.get('doc');
|
||||
|
||||
// First call populates cache
|
||||
const r1 = arbiter.relationManager.getDirectRelation(srcId, 'can_read', dstId);
|
||||
assert.ok(r1);
|
||||
|
||||
const cacheKey = arbiter.relationManager._makeDirectCacheKey(srcId, 'can_read', dstId);
|
||||
assert.ok(arbiter.relationManager._caches.relationLookupCache.has(cacheKey));
|
||||
|
||||
// Second call hits cache
|
||||
const r2 = arbiter.relationManager.getDirectRelation(srcId, 'can_read', dstId);
|
||||
assert.equal(r1, r2);
|
||||
|
||||
// Removal invalidates
|
||||
arbiter.removeRelation('user', 'can_read', 'doc');
|
||||
assert.equal(arbiter.relationManager._caches.relationLookupCache.has(cacheKey), false);
|
||||
});
|
||||
|
||||
it('value relation caches still work end-to-end', () => {
|
||||
const arbiter = new Arbiter();
|
||||
arbiter.addNode('user', 'user');
|
||||
arbiter.addNode('account', 'account');
|
||||
arbiter.addRelation('user', 'has_balance', 'account', 1.0, { value: 100 });
|
||||
|
||||
const srcId = arbiter.nodeIdByKey.get('user');
|
||||
const dstId = arbiter.nodeIdByKey.get('account');
|
||||
|
||||
// Populate
|
||||
const v1 = arbiter.relationManager.getValueRelation(srcId, 'has_balance', dstId);
|
||||
assert.ok(v1);
|
||||
assert.equal(v1.pointValue, 100);
|
||||
|
||||
const cacheKey = arbiter.relationManager._makeValueCacheKey(srcId, 'has_balance', dstId);
|
||||
assert.ok(arbiter.relationManager._caches.valueLookupCache.has(cacheKey));
|
||||
|
||||
// Update — should invalidate
|
||||
arbiter.relationManager._modifyRelation('user', 'has_balance', 'account', { value: 200 });
|
||||
assert.equal(arbiter.relationManager._caches.valueLookupCache.has(cacheKey), false);
|
||||
|
||||
// Re-fetch reflects new value
|
||||
const v2 = arbiter.relationManager.getValueRelation(srcId, 'has_balance', dstId);
|
||||
assert.equal(v2.pointValue, 200);
|
||||
});
|
||||
|
||||
it('cache stats reference _caches.size() correctly', () => {
|
||||
const arbiter = new Arbiter();
|
||||
arbiter.addNode('a', 'x');
|
||||
arbiter.addNode('b', 'x');
|
||||
arbiter.addRelation('a', 'rel', 'b', 1.0);
|
||||
|
||||
const srcId = arbiter.nodeIdByKey.get('a');
|
||||
const dstId = arbiter.nodeIdByKey.get('b');
|
||||
arbiter.relationManager.getDirectRelation(srcId, 'rel', dstId);
|
||||
|
||||
const stats = arbiter.relationManager.getCacheStats();
|
||||
assert.ok(typeof stats.relationCacheSize === 'number');
|
||||
assert.ok(typeof stats.valueCacheSize === 'number');
|
||||
assert.ok(stats.relationCacheSize >= 1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user