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,265 @@
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
|
||||
import { parse } from '../../src/ast/parser/GeneratedParser.js';
|
||||
import { RuleGenerator } from '../../src/ast/generator/RuleGenerator.js';
|
||||
|
||||
function createMockArbiter() {
|
||||
const relationConfigs = new Map();
|
||||
return {
|
||||
relationConfigs,
|
||||
setRelationConfig(relation, config) {
|
||||
relationConfigs.set(relation, config);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('DSL Compiler', () => {
|
||||
const arbiter = createMockArbiter();
|
||||
const compiler = new DSLCompiler(arbiter);
|
||||
|
||||
test('Basic parsing', () => {
|
||||
const dsl = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compiler.compile(dsl, 'test-basic');
|
||||
assert.ok(result.success, 'Basic parsing should succeed');
|
||||
assert.ok(result.program !== null, 'Program should be created');
|
||||
assert.ok(result.generatedRules.size > 0, 'Rules should be generated');
|
||||
});
|
||||
|
||||
test('Basic compilation', () => {
|
||||
const dsl = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string) CACHE eager
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compiler.compile(dsl, 'test-compilation');
|
||||
assert.ok(result.success, 'Basic compilation should succeed');
|
||||
assert.ok(result.generatedRules.has('canRead'), 'canRead rule should be generated');
|
||||
|
||||
const canReadConfig = result.generatedRules.get('canRead');
|
||||
assert.ok(canReadConfig.type === 'direct', 'canRead should be direct rule');
|
||||
assert.ok(canReadConfig.relation === 'hasRole', 'canRead should use hasRole relation');
|
||||
});
|
||||
|
||||
test('Complex DSL compilation', () => {
|
||||
const dsl = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
clearance: string BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
} CACHE eager
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string) CACHE eager
|
||||
fact isMember(user: Employee, group: Device) transitive CACHE lazy
|
||||
fact owns(user: Employee, doc: Account) CACHE eager
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
owns(user, doc)
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
|
||||
evidence canAccessCritical(user: Employee, resource: AuthSession) {
|
||||
fusion min {
|
||||
hasRole(user, 'admin'),
|
||||
hasRole(user, 'superadmin')
|
||||
}
|
||||
|
||||
fusion max {
|
||||
hasRole(user, 'admin'),
|
||||
hasRole(user, 'secret')
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compiler.compile(dsl, 'test-complex');
|
||||
assert.ok(result.success, 'Complex DSL compilation should succeed');
|
||||
assert.ok(result.generatedRules.has('canRead'), 'canRead rule should be generated');
|
||||
assert.ok(result.generatedRules.has('canAccessCritical'), 'canAccessCritical rule should be generated');
|
||||
|
||||
const canReadConfig = result.generatedRules.get('canRead');
|
||||
assert.ok(canReadConfig.type === 'logical', 'canRead should be logical rule');
|
||||
});
|
||||
|
||||
test('Error handling', () => {
|
||||
const invalidDSL = `
|
||||
definition Employee {
|
||||
role: string
|
||||
// Missing closing brace
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
// Missing semicolon
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
// Invalid syntax
|
||||
invalid syntax here
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compiler.compile(invalidDSL, 'test-error');
|
||||
assert.ok(!result.success, 'Invalid DSL should fail');
|
||||
assert.ok(result.errors.length > 0, 'Should have error messages');
|
||||
});
|
||||
|
||||
test('Program management', () => {
|
||||
const dsl1 = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
const dsl2 = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasBalance(user: Employee, amount: number)
|
||||
evidence canWithdraw(user: Employee, amount: number) {
|
||||
hasBalance(user, amount)
|
||||
}
|
||||
`;
|
||||
|
||||
const result1 = compiler.compile(dsl1, 'test-auth');
|
||||
assert.ok(result1.success, 'First program should compile');
|
||||
|
||||
const programs = { 'auth': dsl1, 'finance': dsl2 };
|
||||
const result2 = compiler.compileMultiple(programs);
|
||||
assert.ok(result2.success, 'Multiple programs should compile');
|
||||
|
||||
const authProgram = compiler.getCompiledProgram('test-auth');
|
||||
assert.ok(authProgram !== null, 'Should retrieve compiled program');
|
||||
|
||||
const removed = compiler.removeCompiledProgram('test-auth');
|
||||
assert.ok(removed, 'Should remove program');
|
||||
|
||||
compiler.clearCompiledPrograms();
|
||||
const allPrograms = compiler.getAllCompiledPrograms();
|
||||
assert.ok(allPrograms.size === 0, 'Should clear all programs');
|
||||
});
|
||||
|
||||
test('Validation', () => {
|
||||
const validDSL = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
const invalidDSL = `
|
||||
definition Employee {
|
||||
role: string
|
||||
// Missing closing brace
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
// Missing semicolon
|
||||
`;
|
||||
|
||||
const validResult = compiler.validate(validDSL);
|
||||
assert.ok(validResult.success, 'Valid DSL should pass validation');
|
||||
|
||||
const invalidResult = compiler.validate(invalidDSL);
|
||||
assert.ok(!invalidResult.success, 'Invalid DSL should fail validation');
|
||||
assert.ok(invalidResult.errors.length > 0, 'Should have validation errors');
|
||||
});
|
||||
|
||||
test('Rule generation', () => {
|
||||
const dsl = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string) CACHE eager
|
||||
fact isMember(user: Employee, group: Device) transitive CACHE lazy
|
||||
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
|
||||
evidence canAccess(user: Employee, doc: Account) {
|
||||
hasRole(user, 'reader')
|
||||
}
|
||||
`;
|
||||
|
||||
const result = compiler.compile(dsl, 'test-rules');
|
||||
assert.ok(result.success, 'Rule generation should succeed');
|
||||
|
||||
const canReadConfig = result.generatedRules.get('canRead');
|
||||
assert.ok(canReadConfig.type === 'direct', 'canRead should be direct rule');
|
||||
|
||||
const canAccessConfig = result.generatedRules.get('canAccess');
|
||||
assert.ok(canAccessConfig.type === 'direct', 'canAccess should be direct rule');
|
||||
});
|
||||
|
||||
test('Multiple programs', () => {
|
||||
const programs = {
|
||||
'auth': `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: Employee, role: string)
|
||||
evidence canRead(user: Employee, doc: Account) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`,
|
||||
'finance': `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasBalance(user: Employee, amount: number)
|
||||
evidence canWithdraw(user: Employee, amount: number) {
|
||||
hasBalance(user, amount)
|
||||
}
|
||||
`,
|
||||
'invalid': `
|
||||
// Invalid syntax
|
||||
invalid syntax here
|
||||
`
|
||||
};
|
||||
|
||||
const result = compiler.compileMultiple(programs);
|
||||
assert.ok(!result.success, 'Should fail due to invalid program');
|
||||
assert.ok(result.errors.length > 0, 'Should have errors');
|
||||
assert.ok(result.results.auth.success, 'Auth program should succeed');
|
||||
assert.ok(result.results.finance.success, 'Finance program should succeed');
|
||||
assert.ok(!result.results.invalid.success, 'Invalid program should fail');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user