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
+265
View File
@@ -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');
});
});
+283
View File
@@ -0,0 +1,283 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Type Definitions', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Basic definitions', () => {
const testCases = [
{
input: `definition User { role: string }`,
description: 'Simple definition with one field'
},
{
input: `definition User {
role: string
isActive: boolean
}`,
description: 'Definition with multiple fields'
},
{
input: `definition Group {
name: string
description: string
created: timestamp
}`,
description: 'Definition with different field types'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-basic-def-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.definitions.length > 0, 'Should have definitions');
});
});
test('Field types', () => {
const testCases = [
{ type: 'string', description: 'String field type' },
{ type: 'number', description: 'Number field type' },
{ type: 'boolean', description: 'Boolean field type' },
{ type: 'timestamp', description: 'Timestamp field type' },
{ type: 'User', description: 'Custom type field' },
{ type: 'Permission', description: 'Another custom type field' }
];
testCases.forEach(({ type, description }) => {
const dsl = `definition Test { field: ${type} }`;
const result = compiler.compile(dsl, `test-field-type-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Array types', () => {
const testCases = [
{ type: 'string[]', description: 'String array' },
{ type: 'number[]', description: 'Number array' },
{ type: 'boolean[]', description: 'Boolean array' },
{ type: 'Permission[]', description: 'Custom type array' },
{ type: 'User[]', description: 'User array' }
];
testCases.forEach(({ type, description }) => {
const dsl = `definition Test { items: ${type} }`;
const result = compiler.compile(dsl, `test-array-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Behaviors', () => {
const testCases = [
{
input: `definition User {
balance: number BEHAVES { decaying down hourly }
}`,
description: 'Decay behavior - down hourly'
},
{
input: `definition User {
reputation: number BEHAVES { decaying up daily }
}`,
description: 'Decay behavior - up daily'
},
{
input: `definition User {
score: number BEHAVES { decaying neutral weekly }
}`,
description: 'Decay behavior - neutral weekly'
},
{
input: `definition User {
stability: number BEHAVES { decaying stable monthly }
}`,
description: 'Decay behavior - stable monthly'
},
{
input: `definition User {
confidence: number BEHAVES { blurring fixed }
}`,
description: 'Blur behavior - fixed'
},
{
input: `definition User {
accuracy: number BEHAVES { blurring adaptive }
}`,
description: 'Blur behavior - adaptive'
},
{
input: `definition User {
precision: number BEHAVES { blurring confidence confidence_90 }
}`,
description: 'Blur behavior - confidence with level'
},
{
input: `definition User {
session: string BEHAVES { ttl 1h }
}`,
description: 'TTL behavior - hours'
},
{
input: `definition User {
token: string BEHAVES { ttl 24h }
}`,
description: 'TTL behavior - 24 hours'
},
{
input: `definition User {
cache: string BEHAVES { ttl 7d }
}`,
description: 'TTL behavior - days'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-behavior-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Caching', () => {
const testCases = [
{
input: `definition User {
role: string CACHE eager
}`,
description: 'Eager caching'
},
{
input: `definition User {
score: number CACHE lazy
}`,
description: 'Lazy caching'
},
{
input: `definition User {
balance: number BEHAVES { decaying down hourly } CACHE eager
}`,
description: 'Behavior with eager caching'
},
{
input: `definition User {
reputation: number BEHAVES { blurring adaptive } CACHE lazy
}`,
description: 'Behavior with lazy caching'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-cache-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Complex definitions', () => {
const testCases = [
{
input: `definition User {
role: string
isActive: boolean
lastActive: timestamp BEHAVES {
decaying down hourly
} CACHE lazy
isSuspended: boolean
balance: number BEHAVES {
decaying down hourly
} CACHE eager
score: number BEHAVES {
blurring adaptive confidence_95
} CACHE lazy
session: string BEHAVES {
ttl 24h
} CACHE eager
}`,
description: 'Complex definition with multiple behaviors and caching'
},
{
input: `definition Group {
name: string
permissions: Permission[]
members: User[]
created: timestamp BEHAVES {
decaying stable monthly
} CACHE lazy
isPublic: boolean CACHE eager
}`,
description: 'Definition with arrays and mixed behaviors'
},
{
input: `definition Document {
level: string
owner: User
tags: string[]
content: string BEHAVES {
blurring fixed
} CACHE lazy
accessCount: number BEHAVES {
decaying up daily
} CACHE eager
expiresAt: timestamp BEHAVES {
ttl 30d
} CACHE eager
}`,
description: 'Definition with all behavior types'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-complex-def-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.definitions.length > 0, 'Should have definitions');
});
});
test('Definition error handling', () => {
const testCases = [
{
input: `definition User { role: string`,
description: 'Missing closing brace should fail'
},
{
input: `definition User { role: }`,
description: 'Missing field type should fail'
},
{
input: `definition User { : string }`,
description: 'Missing field name should fail'
},
{
input: `definition User { role: string BEHAVES { }`,
description: 'Incomplete behavior should fail'
},
{
input: `definition User { role: string CACHE }`,
description: 'Incomplete cache directive should fail'
},
{
input: `definition User { role: string BEHAVES { invalid } }`,
description: 'Invalid behavior should fail'
}
];
testCases.forEach(({ input, description }) => {
try {
const result = compiler.compile(input, `test-def-error-${Date.now()}`);
assert.ok(!result.success, `${description} should fail to parse`);
} catch {
// Expected to fail
}
});
});
});
+374
View File
@@ -0,0 +1,374 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Evidence Rules', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Basic evidence', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
hasRole(user, 'admin')
}`,
description: 'Simple evidence with function call'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
user.isActive
}`,
description: 'Evidence with attribute access'
},
{
input: `evidence canModify(user: User, doc: Document) {
user.isActive
hasRole(user, 'admin')
}`,
description: 'Evidence with multiple conditions'
},
{
input: `evidence canDelete(user: User, doc: Document) {
owns(user, doc)
user.isActive
}`,
description: 'Evidence with ownership and status'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-basic-evidence-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.evidence.length > 0, 'Should have evidence');
});
});
test('Defeasible logic', () => {
const testCases = [
{
input: `evidence canAccess(user: User, resource: Resource) {
ALWAYS user.isActive
}`,
description: 'ALWAYS rule - strict requirement'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
WHEN hasRole(user, 'admin')
}`,
description: 'WHEN rule - defeasible condition'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}`,
description: 'WHEN/UNLESS rule - defeasible with defeater'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
REQUIRES hasClearance(user, resource.level)
}`,
description: 'REQUIRES rule - inverse defeater'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
ALWAYS user.isActive
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, resource.level)
}`,
description: 'Complex defeasible logic with all rule types'
},
{
input: `evidence canAccessSensitive(user: User, doc: Document) {
ALWAYS user.isActive
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, doc.level)
fusion majority {
user.isTrusted
user.hasRecentActivity
}
}`,
description: 'Defeasible logic with fusion'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-defeasible-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Pattern matching', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
}
}`,
description: 'Basic pattern matching with wildcard'
},
{
input: `evidence canRead(user: User, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} limit 5
}`,
description: 'Pattern matching with limit'
},
{
input: `evidence canRead(user: User, doc: Document) {
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7
}`,
description: 'Pattern matching with binding and condition'
},
{
input: `evidence canRead(user: User, doc: Document) {
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7 limit 5
}`,
description: 'Pattern matching with binding, condition, and limit'
},
{
input: `evidence canRead(user: User, doc: Document) {
isMember(user, *group) {
isMember(group, *parentGroup) {
canRead(parentGroup, doc)
} limit 2
} limit 3
}`,
description: 'Nested pattern matching'
},
{
input: `evidence canRead(user: User, doc: Document) {
isFriend(user, *friend) {
isMember(friend, *group) {
canRead(group, doc)
} limit 1
} limit 5
}`,
description: 'Multi-hop pattern matching'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-pattern-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Fusion', () => {
const testCases = [
{
input: `evidence canAccess(user: User, resource: Resource) {
fusion min {
hasClearance(user, resource.level)
user.isActive
}
}`,
description: 'Min fusion - all conditions must be true'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
fusion max {
hasRole(user, 'admin')
hasRole(user, 'superuser')
}
}`,
description: 'Max fusion - any condition can be true'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.hasRecentActivity
}
}`,
description: 'Majority fusion - most conditions must be true'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
fusion min {
hasClearance(user, resource.level)
user.isActive
NOT user.isBlacklisted
}
fusion max {
hasRole(user, 'admin')
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.lastActive within 1hr
}
}
}`,
description: 'Nested fusion with different strategies'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
fusion average {
user.reputation
user.activityScore
user.verificationLevel
}
}`,
description: 'Average fusion for numeric values'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-fusion-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Complex evidence', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
owns(user, doc)
isMember(user, *group) {
canRead(group, doc)
} limit 5
parentOf(user, *parent) {
canRead(parent, doc)
} limit 3
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7 limit 5
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}`,
description: 'Complex evidence with all features'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
ALWAYS user.isActive
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, resource.level)
fusion min {
hasClearance(user, resource.level)
user.isActive
NOT user.isBlacklisted
}
fusion max {
hasRole(user, 'admin')
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.lastActive within 1hr
}
}
}`,
description: 'Critical access with all rule types and fusion'
},
{
input: `evidence canModify(user: User, doc: Document) {
owns(user, doc)
isMember(user, *group) {
canModify(group, doc)
} limit 3
similar(doc, *similar) |similarity| {
canModify(user, similar)
similar.isEditable
} with similarity > 0.8 limit 2
fusion majority {
user.isTrusted
user.hasRecentActivity
doc.isPublic
}
}`,
description: 'Modification access with similarity and fusion'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-complex-evidence-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Evidence error handling', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
hasRole(user, 'admin'
}`,
description: 'Missing closing parenthesis should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
WHEN hasRole(user, 'admin') UNLESS
}`,
description: 'Incomplete UNLESS condition should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
fusion min {
hasRole(user, 'admin')
}`,
description: 'Incomplete fusion should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} with
}`,
description: 'Incomplete with clause should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} limit
}`,
description: 'Incomplete limit should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
invalid syntax here
}`,
description: 'Invalid syntax should fail'
}
];
testCases.forEach(({ input, description }) => {
try {
const result = compiler.compile(input, `test-evidence-error-${Date.now()}`);
assert.ok(!result.success, `${description} should fail to parse`);
} catch {
// Expected to fail
}
});
});
});
+233
View File
@@ -0,0 +1,233 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Expression Parsing', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Arithmetic operator precedence', () => {
const testCases = [
{
input: '1 + 2 * 3',
expected: 'Should evaluate as 1 + (2 * 3) = 7',
description: 'Multiplication before addition'
},
{
input: '10 - 3 * 2',
expected: 'Should evaluate as 10 - (3 * 2) = 4',
description: 'Multiplication before subtraction'
},
{
input: '8 / 2 * 4',
expected: 'Should evaluate as (8 / 2) * 4 = 16',
description: 'Left-associative division and multiplication'
},
{
input: '2 + 3 * 4 - 5',
expected: 'Should evaluate as 2 + (3 * 4) - 5 = 9',
description: 'Mixed arithmetic with correct precedence'
},
{
input: '(1 + 2) * 3',
expected: 'Should evaluate as (1 + 2) * 3 = 9',
description: 'Parentheses override precedence'
}
];
testCases.forEach(({ input, expected, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-arithmetic-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Logical operator precedence', () => {
const testCases = [
{
input: 'true && false || true',
expected: 'Should evaluate as (true && false) || true = true',
description: 'AND before OR'
},
{
input: 'false || true && false',
expected: 'Should evaluate as false || (true && false) = false',
description: 'AND before OR (alternative)'
},
{
input: 'NOT true && false',
expected: 'Should evaluate as (NOT true) && false = false',
description: 'NOT before AND'
},
{
input: 'true && NOT false',
expected: 'Should evaluate as true && (NOT false) = true',
description: 'NOT before AND (alternative)'
},
{
input: '(true || false) && true',
expected: 'Should evaluate as (true || false) && true = true',
description: 'Parentheses override logical precedence'
}
];
testCases.forEach(({ input, expected, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-logical-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Comparison operators', () => {
const testCases = [
{ input: '1 == 1', description: 'Equality comparison' },
{ input: '1 != 2', description: 'Inequality comparison' },
{ input: '5 > 3', description: 'Greater than' },
{ input: '3 < 5', description: 'Less than' },
{ input: '4 >= 4', description: 'Greater than or equal' },
{ input: '4 <= 4', description: 'Less than or equal' },
{ input: '1 == 1 && 2 > 1', description: 'Comparison with logical operators' },
{ input: '1 + 2 == 3', description: 'Arithmetic in comparison' }
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-comparison-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Temporal expressions', () => {
const testCases = [
{ input: 'user.lastActive within 1h', description: 'Temporal within expression' },
{ input: 'user.lastLogin within 24h', description: 'Temporal within with hours' },
{ input: 'user.createdAt within 7d', description: 'Temporal within with days' },
{ input: 'user.lastActivity within 1h && user.isActive', description: 'Temporal with logical operators' }
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-temporal-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Unary operators', () => {
const testCases = [
{ input: 'NOT true', description: 'NOT operator' },
{ input: '!false', description: 'Alternative NOT operator' },
{ input: 'NOT (true && false)', description: 'NOT with parenthesized expression' },
{ input: 'NOT user.isSuspended', description: 'NOT with attribute access' }
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-unary-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Attribute access', () => {
const testCases = [
{ input: 'user.role', description: 'Simple attribute access' },
{ input: 'user.profile.name', description: 'Nested attribute access' },
{ input: 'user.permissions[0]', description: 'Array access' },
{ input: 'user.role.permissions[0]', description: 'Nested attribute with array access' },
{ input: 'user.isActive && user.role == "admin"', description: 'Attribute access in logical expression' }
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-attribute-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Function calls', () => {
const testCases = [
{ input: 'hasRole(user, "admin")', description: 'Simple function call' },
{ input: 'isMember(user, group)', description: 'Function call with variables' },
{ input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments' },
{ input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls' },
{ input: 'hasRole(user, user.role)', description: 'Function call with attribute access' }
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-function-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Complex expressions', () => {
const testCases = [
{
input: 'user.isActive && (hasRole(user, "admin") || hasPermission(user, resource, "read"))',
description: 'Complex logical expression with function calls'
},
{
input: 'user.balance > 100 && user.isActive && NOT user.isSuspended',
description: 'Multiple conditions with NOT'
},
{
input: 'user.lastActive within 1h && (user.role == "admin" || user.hasEmergencyAccess)',
description: 'Temporal with logical conditions'
},
{
input: 'hasRole(user, "admin") && user.isActive && NOT (user.isSuspended || user.isBlacklisted)',
description: 'Complex negation with multiple conditions'
},
{
input: 'user.score > 0.8 && user.isTrusted && user.lastActivity within 24h',
description: 'Multiple attribute conditions with temporal'
}
];
testCases.forEach(({ input, description }) => {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-complex-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Expression error handling', () => {
const testCases = [
{
input: 'user.role ==',
description: 'Incomplete comparison should fail'
},
{
input: 'user.role &&',
description: 'Incomplete logical expression should fail'
},
{
input: 'hasRole(user,)',
description: 'Function call with missing argument should fail'
},
{
input: 'user.role == "admin" &&',
description: 'Incomplete logical expression should fail'
}
];
testCases.forEach(({ input, description }) => {
try {
const dsl = `evidence test() { ${input} }`;
const result = compiler.compile(dsl, `test-error-${Date.now()}`);
assert.ok(!result.success, `${description} should fail to parse`);
} catch {
// Expected to fail
}
});
});
});
+232
View File
@@ -0,0 +1,232 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Fact Declarations', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Basic facts', () => {
const testCases = [
{
input: `fact hasRole(user: User, role: string)`,
description: 'Simple fact with two parameters'
},
{
input: `fact isMember(user: User, group: Group)`,
description: 'Fact with custom types'
},
{
input: `fact owns(user: User, doc: Document)`,
description: 'Fact with multiple custom types'
},
{
input: `fact isActive(user: User)`,
description: 'Fact with single parameter'
},
{
input: `fact hasPermission(user: User, resource: Resource, action: string)`,
description: 'Fact with three parameters'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-basic-fact-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.facts.length > 0, 'Should have facts');
});
});
test('Fact properties', () => {
const testCases = [
{
input: `fact isMember(user: User, group: Group) transitive`,
description: 'Transitive fact'
},
{
input: `fact isFriend(user: User, friend: User) symmetrical`,
description: 'Symmetrical fact'
},
{
input: `fact isMember(user: User, group: Group) transitive symmetrical`,
description: 'Fact with multiple properties'
},
{
input: `fact isColleague(user: User, colleague: User) symmetrical`,
description: 'Symmetrical relationship fact'
},
{
input: `fact isParentOf(parent: User, child: User) transitive`,
description: 'Transitive hierarchical fact'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-fact-property-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Fact caching', () => {
const testCases = [
{
input: `fact hasRole(user: User, role: string) CACHE eager`,
description: 'Eager cached fact'
},
{
input: `fact isMember(user: User, group: Group) CACHE lazy`,
description: 'Lazy cached fact'
},
{
input: `fact isMember(user: User, group: Group) transitive CACHE eager`,
description: 'Transitive fact with eager caching'
},
{
input: `fact isFriend(user: User, friend: User) symmetrical CACHE lazy`,
description: 'Symmetrical fact with lazy caching'
},
{
input: `fact hasPermission(user: User, resource: Resource, action: string) CACHE eager`,
description: 'Multi-parameter fact with eager caching'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-fact-cache-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Fact limits', () => {
const testCases = [
{
input: `fact isMember(user: User, group: Group) limit 10`,
description: 'Fact with simple limit'
},
{
input: `fact isFriend(user: User, friend: User) limit 100`,
description: 'Fact with higher limit'
},
{
input: `fact isMember(user: User, group: Group) transitive limit 5`,
description: 'Transitive fact with limit'
},
{
input: `fact isFriend(user: User, friend: User) symmetrical limit 50`,
description: 'Symmetrical fact with limit'
},
{
input: `fact isMember(user: User, group: Group) transitive CACHE lazy limit 3`,
description: 'Fact with properties, caching, and limit'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-fact-limit-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Parameter types', () => {
const testCases = [
{ type: 'string', description: 'String parameter' },
{ type: 'number', description: 'Number parameter' },
{ type: 'boolean', description: 'Boolean parameter' },
{ type: 'timestamp', description: 'Timestamp parameter' },
{ type: 'User', description: 'Custom type parameter' },
{ type: 'Group', description: 'Another custom type parameter' },
{ type: 'Permission[]', description: 'Array type parameter' }
];
testCases.forEach(({ type, description }) => {
const dsl = `fact test(param: ${type})`;
const result = compiler.compile(dsl, `test-param-type-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Complex facts', () => {
const testCases = [
{
input: `fact hasRole(user: User, role: string) CACHE eager
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
fact owns(user: User, doc: Document) CACHE eager
fact isSuspended(user: User) CACHE lazy`,
description: 'Multiple facts with different configurations'
},
{
input: `fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
fact isAdmin(user: User) CACHE eager
fact isOwner(user: User, resource: Resource) CACHE eager
fact hasAccess(user: User, resource: Resource, level: string) CACHE lazy`,
description: 'Permission-related facts'
},
{
input: `fact isMember(user: User, group: Group) transitive CACHE lazy limit 5
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50
fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 20
fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3`,
description: 'Relationship facts with various properties'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-complex-facts-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.facts.length > 0, 'Should have facts');
});
});
test('Fact error handling', () => {
const testCases = [
{
input: `fact hasRole(user: User, role: string`,
description: 'Missing closing parenthesis should fail'
},
{
input: `fact hasRole(user: User, )`,
description: 'Missing parameter name should fail'
},
{
input: `fact hasRole(user: User, role: )`,
description: 'Missing parameter type should fail'
},
{
input: `fact hasRole(, role: string)`,
description: 'Missing parameter name should fail'
},
{
input: `fact hasRole(user: User, role: string) CACHE`,
description: 'Incomplete cache directive should fail'
},
{
input: `fact hasRole(user: User, role: string) limit`,
description: 'Incomplete limit should fail'
},
{
input: `fact hasRole(user: User, role: string) invalid`,
description: 'Invalid property should fail'
}
];
testCases.forEach(({ input, description }) => {
try {
const result = compiler.compile(input, `test-fact-error-${Date.now()}`);
assert.ok(!result.success, `${description} should fail to parse`);
} catch {
// Expected to fail
}
});
});
});
+534
View File
@@ -0,0 +1,534 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Integration Tests', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Complete authorization system', () => {
const completeSystem = `
// Type definitions with complex behaviors
definition User {
role: string
isActive: boolean
lastActive: timestamp BEHAVES {
decaying down hourly
} CACHE lazy
isSuspended: boolean
balance: number BEHAVES {
decaying down hourly
} CACHE eager
score: number BEHAVES {
blurring adaptive confidence_95
} CACHE lazy
session: string BEHAVES {
ttl 24h
} CACHE eager
clearance: string BEHAVES {
blurring fixed
} CACHE eager
reputation: number BEHAVES {
decaying up daily
} CACHE lazy
}
definition Group {
name: string
permissions: Permission[]
level: string
isPublic: boolean CACHE eager
created: timestamp BEHAVES {
decaying stable monthly
} CACHE lazy
}
definition Document {
level: string
owner: User
tags: string[]
content: string BEHAVES {
blurring fixed
} CACHE lazy
accessCount: number BEHAVES {
decaying up daily
} CACHE eager
expiresAt: timestamp BEHAVES {
ttl 30d
} CACHE eager
isPublic: boolean CACHE eager
}
definition Resource {
level: string
owner: User
permissions: Permission[]
isPublic: boolean CACHE eager
accessCount: number BEHAVES {
decaying up daily
} CACHE eager
}
// Facts with various properties and caching
fact hasRole(user: User, role: string) CACHE eager
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
fact owns(user: User, doc: Document) CACHE eager
fact isSuspended(user: User) CACHE lazy
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
fact isAdmin(user: User) CACHE eager
fact isOwner(user: User, resource: Resource) CACHE eager
fact hasAccess(user: User, resource: Resource, level: string) CACHE lazy
fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 50
fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3
// Evidence rules with complex logic
evidence canRead(user: User, doc: Document) {
owns(user, doc)
isMember(user, *group) {
canRead(group, doc)
} limit 5
parentOf(user, *parent) {
canRead(parent, doc)
} limit 3
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7 limit 5
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}
evidence canWrite(user: User, doc: Document) {
owns(user, doc)
isMember(user, *group) {
canWrite(group, doc)
} limit 3
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES user.isActive
}
evidence canDelete(user: User, doc: Document) {
owns(user, doc)
ALWAYS user.isActive
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES user.isActive
}
evidence canAccessCritical(user: User, resource: Resource) {
fusion min {
hasClearance(user, resource.level)
user.isActive
NOT user.isBlacklisted
}
fusion max {
hasRole(user, 'admin')
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.lastActive within 1hr
}
}
}
evidence canAccessSensitive(user: User, doc: Document) {
ALWAYS user.isActive
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, doc.level)
fusion majority {
user.isTrusted
user.hasRecentActivity
}
}
// Measures for computed values
measure userRole(user: User) {
user.role
} PROVIDES string
measure userPermissions(user: User) {
fusion max {
user.role.permissions
user.group.permissions
}
} PROVIDES Permission[]
measure effectiveClearance(user: User) {
fusion majority {
user.clearance
user.role.clearance
user.group.clearance
}
} PROVIDES string
measure userTrustScore(user: User) {
fusion average {
user.reputation
user.activityScore
user.verificationLevel
}
} PROVIDES number
measure userBalance(user: User) {
user.balance
} PROVIDES number
measure userScore(user: User) {
user.score
} PROVIDES number
`;
const result = compiler.compile(completeSystem, 'test-complete-system');
assert.ok(result.success, 'Complete authorization system should compile successfully');
assert.ok(result.program.definitions.length >= 4, 'Should have multiple definitions');
assert.ok(result.program.facts.length >= 10, 'Should have multiple facts');
assert.ok(result.program.evidence.length >= 5, 'Should have multiple evidence rules');
assert.ok(result.program.measures.length >= 6, 'Should have multiple measures');
});
test('Multi-domain system', () => {
const multiDomain = `
// Authentication domain
definition User {
role: string
isActive: boolean
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
session: string BEHAVES { ttl 24h } CACHE eager
}
fact hasRole(user: User, role: string) CACHE eager
fact isActive(user: User) CACHE eager
evidence canAuthenticate(user: User) {
user.isActive
user.session within 24h
}
// Authorization domain
definition Resource {
level: string
owner: User
permissions: Permission[]
}
fact owns(user: User, resource: Resource) CACHE eager
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
evidence canAccess(user: User, resource: Resource) {
owns(user, resource)
hasPermission(user, resource, 'read')
}
// Finance domain
definition Account {
balance: number BEHAVES { decaying down hourly } CACHE eager
owner: User
isActive: boolean CACHE eager
}
fact hasAccount(user: User, account: Account) CACHE eager
fact hasBalance(user: User, amount: number) CACHE eager
evidence canWithdraw(user: User, amount: number) {
hasBalance(user, amount)
user.isActive
}
// Social domain
definition Group {
name: string
members: User[]
isPublic: boolean CACHE eager
}
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
evidence canAccessGroup(user: User, group: Group) {
isMember(user, group)
group.isPublic
}
`;
const result = compiler.compile(multiDomain, 'test-multi-domain');
assert.ok(result.success, 'Multi-domain system should compile successfully');
assert.ok(result.program.definitions.length >= 4, 'Should have multiple domain definitions');
assert.ok(result.program.facts.length >= 8, 'Should have multiple domain facts');
assert.ok(result.program.evidence.length >= 4, 'Should have multiple domain evidence rules');
});
test('Hierarchical access', () => {
const hierarchicalSystem = `
definition User {
role: string
level: string
isActive: boolean
clearance: string
}
definition Organization {
name: string
level: string
parent: Organization
}
fact isMember(user: User, org: Organization) transitive CACHE lazy limit 5
fact isParentOf(parent: Organization, child: Organization) transitive CACHE eager limit 3
fact hasRole(user: User, role: string) CACHE eager
fact hasClearance(user: User, level: string) CACHE eager
evidence canAccessOrg(user: User, org: Organization) {
isMember(user, org)
isParentOf(org, *parentOrg) {
canAccessOrg(user, parentOrg)
} limit 3
WHEN hasRole(user, 'admin') UNLESS user.isSuspended
}
evidence canAccessResource(user: User, resource: Resource) {
isMember(user, *org) {
canAccessResource(org, resource)
} limit 5
parentOf(user, *parent) {
canAccessResource(parent, resource)
} limit 2
}
`;
const result = compiler.compile(hierarchicalSystem, 'test-hierarchical');
assert.ok(result.success, 'Hierarchical access system should compile successfully');
});
test('Similarity-based access', () => {
const similaritySystem = `
definition User {
profile: string
interests: string[]
isActive: boolean
}
definition Document {
content: string
tags: string[]
isPublic: boolean
owner: User
}
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
fact hasInterest(user: User, interest: string) CACHE lazy
fact hasTag(doc: Document, tag: string) CACHE lazy
evidence canRead(user: User, doc: Document) {
owns(user, doc)
similar(doc, *similar) |similarity| {
canRead(user, similar)
similar.isPublic
} with similarity > 0.7 limit 10
isFriend(user, *friend) {
canRead(friend, doc)
} limit 5
fusion majority {
user.interests
doc.tags
}
}
evidence canRecommend(user: User, doc: Document) {
similar(user, *similarUser) |similarity| {
canRead(similarUser, doc)
} with similarity > 0.8 limit 20
fusion average {
user.profile
doc.content
}
}
`;
const result = compiler.compile(similaritySystem, 'test-similarity');
assert.ok(result.success, 'Similarity-based access system should compile successfully');
});
test('Temporal access', () => {
const temporalSystem = `
definition User {
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
session: string BEHAVES { ttl 24h } CACHE eager
isActive: boolean
}
definition Event {
startTime: timestamp
endTime: timestamp
isPublic: boolean
}
fact hasAccess(user: User, event: Event) CACHE lazy
fact isParticipant(user: User, event: Event) CACHE eager
evidence canAccessEvent(user: User, event: Event) {
user.lastActive within 1h
isParticipant(user, event)
WHEN event.isPublic UNLESS user.isSuspended
fusion min {
user.session within 24h
user.isActive
}
}
evidence canAccessHistorical(user: User, event: Event) {
user.lastActive within 24h
fusion majority {
user.isActive
user.session within 24h
event.isPublic
}
}
`;
const result = compiler.compile(temporalSystem, 'test-temporal');
assert.ok(result.success, 'Temporal access system should compile successfully');
});
test('Complex behaviors', () => {
const behaviorSystem = `
definition User {
balance: number BEHAVES { decaying down hourly } CACHE eager
score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy
session: string BEHAVES { ttl 24h } CACHE eager
reputation: number BEHAVES { decaying up daily } CACHE lazy
clearance: string BEHAVES { blurring fixed } CACHE eager
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
}
definition Document {
content: string BEHAVES { blurring fixed } CACHE lazy
accessCount: number BEHAVES { decaying up daily } CACHE eager
expiresAt: timestamp BEHAVES { ttl 30d } CACHE eager
isPublic: boolean CACHE eager
}
fact hasBalance(user: User, amount: number) CACHE eager
fact hasScore(user: User, score: number) CACHE lazy
fact hasReputation(user: User, reputation: number) CACHE lazy
evidence canAccessDocument(user: User, doc: Document) {
user.balance > 0
user.score > 0.5
user.reputation > 0.3
doc.accessCount < 1000
fusion majority {
user.isActive
user.lastActive within 1h
doc.isPublic
}
}
measure userEffectiveScore(user: User) {
fusion average {
user.score
user.reputation
user.balance
}
} PROVIDES number
measure documentPopularity(doc: Document) {
doc.accessCount
} PROVIDES number
`;
const result = compiler.compile(behaviorSystem, 'test-behaviors');
assert.ok(result.success, 'Complex behaviors system should compile successfully');
});
test('Performance scenarios', () => {
const performanceSystem = `
definition User {
role: string
isActive: boolean
permissions: Permission[] CACHE eager
}
definition Resource {
level: string
owner: User
permissions: Permission[] CACHE eager
}
// High-frequency facts with limits
fact isMember(user: User, group: Group) transitive CACHE lazy limit 5
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
fact owns(user: User, resource: Resource) CACHE eager
// Optimized evidence rules
evidence canAccess(user: User, resource: Resource) {
owns(user, resource)
isMember(user, *group) {
canAccess(group, resource)
} limit 3
WHEN hasPermission(user, resource, 'read')
}
evidence canModify(user: User, resource: Resource) {
owns(user, resource)
isMember(user, *group) {
canModify(group, resource)
} limit 2
WHEN hasPermission(user, resource, 'write')
}
// Efficient measures
measure userEffectivePermissions(user: User) {
user.permissions
} PROVIDES Permission[]
measure resourceAccessLevel(resource: Resource) {
resource.level
} PROVIDES string
`;
const result = compiler.compile(performanceSystem, 'test-performance');
assert.ok(result.success, 'Performance scenarios should compile successfully');
});
});
+306
View File
@@ -0,0 +1,306 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
function createMockArbiter() {
const relationConfigs = new Map();
return {
relationConfigs,
setRelationConfig(relation, config) {
relationConfigs.set(relation, config);
}
};
}
describe('Measure Definitions', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
test('Basic measures', () => {
const testCases = [
{
input: `measure userRole(user: User) {
user.role
} PROVIDES string`,
description: 'Simple measure with attribute access'
},
{
input: `measure userBalance(user: User) {
user.balance
} PROVIDES number`,
description: 'Measure accessing numeric attribute'
},
{
input: `measure isUserActive(user: User) {
user.isActive
} PROVIDES boolean`,
description: 'Measure accessing boolean attribute'
},
{
input: `measure userPermissions(user: User) {
user.permissions
} PROVIDES Permission[]`,
description: 'Measure accessing array attribute'
},
{
input: `measure userScore(user: User) {
user.score
} PROVIDES number`,
description: 'Measure with behavior-inherited attribute'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-basic-measure-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.measures.length > 0, 'Should have measures');
});
});
test('Measure return types', () => {
const testCases = [
{ type: 'string', description: 'String return type' },
{ type: 'number', description: 'Number return type' },
{ type: 'boolean', description: 'Boolean return type' },
{ type: 'timestamp', description: 'Timestamp return type' },
{ type: 'Permission[]', description: 'Array return type' },
{ type: 'User', description: 'Custom type return' },
{ type: 'Group[]', description: 'Custom array return type' }
];
testCases.forEach(({ type, description }) => {
const dsl = `measure test() { true } PROVIDES ${type}`;
const result = compiler.compile(dsl, `test-measure-return-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Measure aggregation', () => {
const testCases = [
{
input: `measure userPermissions(user: User) {
aggregate {
user.role.permissions
user.group.permissions
} USING majority
} PROVIDES Permission[]`,
description: 'Aggregation with majority strategy'
},
{
input: `measure userClearance(user: User) {
aggregate {
user.clearance
user.role.clearance
user.group.clearance
} USING max
} PROVIDES string`,
description: 'Aggregation with max strategy'
},
{
input: `measure userScore(user: User) {
aggregate {
user.reputation
user.activityScore
user.verificationLevel
} USING average
} PROVIDES number`,
description: 'Aggregation with average strategy'
},
{
input: `measure userTrust(user: User) {
aggregate {
user.reputation
user.activityScore
user.verificationLevel
user.socialProof
} USING min
} PROVIDES number`,
description: 'Aggregation with min strategy'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-measure-aggregation-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Measure fusion', () => {
const testCases = [
{
input: `measure effectiveClearance(user: User) {
fusion max {
user.clearance
user.role.clearance
user.group.clearance
}
} PROVIDES string`,
description: 'Fusion with max strategy'
},
{
input: `measure userPermissions(user: User) {
fusion min {
user.role.permissions
user.group.permissions
}
} PROVIDES Permission[]`,
description: 'Fusion with min strategy'
},
{
input: `measure userScore(user: User) {
fusion majority {
user.reputation
user.activityScore
user.verificationLevel
}
} PROVIDES number`,
description: 'Fusion with majority strategy'
},
{
input: `measure userTrust(user: User) {
fusion average {
user.reputation
user.activityScore
user.verificationLevel
user.socialProof
}
} PROVIDES number`,
description: 'Fusion with average strategy'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-measure-fusion-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Complex measures', () => {
const testCases = [
{
input: `measure userEffectivePermissions(user: User) {
aggregate {
user.role.permissions
user.group.permissions
user.directPermissions
} USING majority
} PROVIDES Permission[]`,
description: 'Complex aggregation with multiple sources'
},
{
input: `measure userTrustScore(user: User) {
fusion average {
user.reputation
user.activityScore
user.verificationLevel
user.socialProof
user.peerRatings
}
} PROVIDES number`,
description: 'Complex fusion with multiple metrics'
},
{
input: `measure userAccessLevel(user: User) {
fusion max {
user.clearance
user.role.clearance
user.group.clearance
user.temporaryClearance
}
} PROVIDES string`,
description: 'Complex clearance calculation'
},
{
input: `measure userSimilarity(user1: User, user2: User) {
similar(user1, user2) |similarity| {
similarity
} with similarity > 0.5
} PROVIDES number`,
description: 'Similarity measure with pattern matching'
},
{
input: `measure userEffectiveRole(user: User) {
fusion majority {
user.role
user.temporaryRole
user.actingRole
}
} PROVIDES string`,
description: 'Role determination with multiple sources'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-complex-measure-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
test('Measure error handling', () => {
const testCases = [
{
input: `measure userRole(user: User) {
user.role
}`,
description: 'Missing PROVIDES clause should fail',
expectSuccess: false
},
{
input: `measure userRole(user: User) {
user.role
} PROVIDES`,
description: 'Incomplete PROVIDES clause should fail',
expectSuccess: false
},
{
input: `measure userRole(user: User) {
user.role
} PROVIDES string`,
description: 'Valid measure should succeed',
expectSuccess: true
},
{
input: `measure userPermissions(user: User) {
aggregate {
user.role.permissions
user.group.permissions
} USING
} PROVIDES Permission[]`,
description: 'Incomplete USING clause should fail',
expectSuccess: false
},
{
input: `measure userScore(user: User) {
fusion {
user.reputation
user.activityScore
}
} PROVIDES number`,
description: 'Missing fusion strategy should fail',
expectSuccess: false
},
{
input: `measure userRole(user: User) {
invalid syntax here
} PROVIDES string`,
description: 'Invalid syntax should fail',
expectSuccess: false
}
];
testCases.forEach(({ input, description, expectSuccess }) => {
try {
const result = compiler.compile(input, `test-measure-error-${Date.now()}`);
if (expectSuccess) {
assert.ok(result.success, `${description} should parse successfully`);
} else {
assert.ok(!result.success, `${description} should fail to parse`);
}
} catch {
if (!expectSuccess) {
// Expected to fail
}
}
});
});
});
+101
View File
@@ -0,0 +1,101 @@
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { PeggyDSLParser } from '../../src/ast/parser/PeggyDSLParser.js';
describe('Peggy DSL Parser', () => {
const parser = new PeggyDSLParser();
test('Basic parsing', () => {
const dsl = `
definition User {
role: string
isActive: boolean
}
fact hasRole(user: User, role: string)
evidence canRead(user: User, doc: Document) {
hasRole(user, 'admin')
}
`;
const program = parser.parse(dsl);
assert.ok(program !== null, 'Program should be created');
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
assert.ok(program.facts.length === 1, 'Should have 1 fact');
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
});
test('Complex DSL parsing', () => {
const dsl = `
definition User {
role: string
isActive: boolean
clearance: string BEHAVES {
blurring adaptive confidence_95
} CACHE eager
}
fact hasRole(user: User, role: string) CACHE eager
fact isMember(user: User, group: Group) transitive CACHE lazy
evidence canRead(user: User, doc: Document) {
hasRole(user, 'admin')
isMember(user, *group) {
canRead(group, doc)
} limit 5
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}
`;
const program = parser.parse(dsl);
assert.ok(program !== null, 'Program should be created');
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
assert.ok(program.facts.length === 2, 'Should have 2 facts');
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
});
test('Error handling', () => {
const invalidDSL = `
definition User {
role: string
// Missing closing brace
fact hasRole(user: User, role: string)
// Missing semicolon
`;
assert.throws(
() => parser.parse(invalidDSL),
/Parsing failed/,
'Should have parsing error message'
);
});
test('Validation', () => {
const validDSL = `
definition User {
role: string
isActive: boolean
}
fact hasRole(user: User, role: string)
`;
const invalidDSL = `
definition User {
role: string
// Missing closing brace
`;
const validResult = parser.validate(validDSL);
assert.ok(validResult.success, 'Valid DSL should pass validation');
assert.ok(validResult.program !== null, 'Valid DSL should return program');
const invalidResult = parser.validate(invalidDSL);
assert.ok(!invalidResult.success, 'Invalid DSL should fail validation');
assert.ok(invalidResult.errors.length > 0, 'Should have validation errors');
});
});
+249
View File
@@ -0,0 +1,249 @@
# Evidence DSL Test Suite
## Overview
This comprehensive test suite follows a **structural linguistic approach** to validate the Evidence DSL (Domain Specific Language) for authorization policies. The tests are organized incrementally from basic language primitives to complex integration scenarios.
## Test Structure
### 1. Structural Linguistic Tests (`StructuralLinguisticTests.js`)
**Level: Comprehensive**
- **Lexical Primitives**: Identifiers, literals, keywords, whitespace
- **Basic Expressions**: Arithmetic, logical, comparison, temporal
- **Type System**: Definitions, fields, behaviors, caching
- **Fact System**: Declarations, properties, caching, limits
- **Evidence System**: Rules, defeasible logic, pattern matching
- **Measure System**: Aggregation, fusion, return types
- **Complex Integration**: Multi-feature combinations
### 2. Expression Tests (`ExpressionTests.js`)
**Level: Focused**
- Arithmetic operator precedence
- Logical operator precedence
- Comparison operators
- Temporal expressions
- Unary operators
- Attribute access
- Function calls
- Complex expressions
- Error handling
### 3. Definition Tests (`DefinitionTests.js`)
**Level: Focused**
- Basic type definitions
- Field types (string, number, boolean, timestamp, custom)
- Array types
- Behaviors (decay, blur, TTL)
- Caching (eager, lazy)
- Complex definitions
- Error handling
### 4. Fact Tests (`FactTests.js`)
**Level: Focused**
- Basic fact declarations
- Fact properties (transitive, symmetrical)
- Fact caching
- Fact limits
- Parameter types
- Complex facts
- Error handling
### 5. Evidence Tests (`EvidenceTests.js`)
**Level: Focused**
- Basic evidence rules
- Defeasible logic (ALWAYS, WHEN/UNLESS, REQUIRES)
- Pattern matching with wildcards
- Fusion strategies (min, max, majority, average)
- Complex evidence composition
- Error handling
### 6. Measure Tests (`MeasureTests.js`)
**Level: Focused**
- Basic measure definitions
- Return types
- Aggregation with different strategies
- Fusion with different strategies
- Complex measures
- Error handling
### 7. Integration Tests (`IntegrationTests.js`)
**Level: Integration**
- Complete authorization systems
- Multi-domain systems
- Hierarchical access patterns
- Similarity-based access
- Temporal access patterns
- Complex behaviors
- Performance scenarios
## Test Runner (`TestRunner.js`)
The test runner orchestrates all test suites and provides:
- **Comprehensive Testing**: Run all test suites
- **Selective Testing**: Run specific test suites
- **Level-based Testing**: Run tests by complexity level
- **Detailed Reporting**: Summary and detailed results
- **Coverage Analysis**: Language feature coverage
## Usage
### Run All Tests
```javascript
import { runAllTests } from '../../../../../lib/src/ast/tests/tests/TestRunner.js';
const results = runAllTests(arbiter);
console.log(`Tests: ${results.passed}/${results.total} passed`);
```
### Run Specific Test Suites
```javascript
import { runSpecificTests } from '../../../../../lib/src/ast/tests/tests/TestRunner.js';
const results = runSpecificTests(arbiter, [
'Expression Tests',
'Definition Tests'
]);
```
### Run Tests by Level
```javascript
import { runTestsByLevel } from '../../../../../lib/src/ast/tests/tests/TestRunner.js';
// Run only focused tests
const results = runTestsByLevel(arbiter, 'focused');
// Run only integration tests
const results = runTestsByLevel(arbiter, 'integration');
```
## Language Feature Coverage
### ✅ Lexical Primitives
- Identifiers (simple, with underscores, with numbers)
- Literals (string, number, boolean, duration)
- Keywords (reserved words)
- Whitespace and comments
### ✅ Expression System
- Arithmetic operators (+, -, *, /) with precedence
- Logical operators (&&, ||, NOT) with precedence
- Comparison operators (==, !=, >, <, >=, <=)
- Temporal expressions (within)
- Unary operators (NOT, !)
- Attribute access (object.attribute)
- Function calls (predicate(args))
### ✅ Type System
- Type definitions with fields
- Field types (string, number, boolean, timestamp, custom)
- Array types (Type[])
- Behaviors (decay, blur, TTL)
- Caching directives (eager, lazy)
### ✅ Fact System
- Fact declarations with parameters
- Fact properties (transitive, symmetrical)
- Caching directives
- Limits for performance
- Parameter types
### ✅ Evidence System
- Basic evidence rules
- Defeasible logic (ALWAYS, WHEN/UNLESS, REQUIRES)
- Pattern matching with wildcards (*)
- Binding clauses (|variable|)
- With clauses (with condition)
- Limits for pattern matching
- Fusion strategies (min, max, majority, average)
### ✅ Measure System
- Measure definitions
- Return type specifications (PROVIDES)
- Aggregation with strategies (USING)
- Fusion with strategies
- Complex value computation
### ✅ Integration Features
- Multi-domain systems
- Hierarchical access patterns
- Similarity-based access
- Temporal access patterns
- Complex behavior combinations
- Performance optimization scenarios
## Test Philosophy
### Structural Linguistic Approach
The tests follow a structural linguistic methodology:
1. **Phonological Level**: Basic lexical elements (identifiers, literals)
2. **Morphological Level**: Word formation (operators, keywords)
3. **Syntactic Level**: Grammar rules (expressions, statements)
4. **Semantic Level**: Meaning (types, behaviors, logic)
5. **Pragmatic Level**: Usage (integration, real-world scenarios)
### Incremental Complexity
Tests progress from simple to complex:
- **Level 1**: Lexical primitives
- **Level 2**: Basic expressions
- **Level 3**: Type system
- **Level 4**: Fact system
- **Level 5**: Evidence system
- **Level 6**: Measure system
- **Level 7**: Complex integration
### Comprehensive Coverage
Each language feature is tested for:
- **Valid cases**: Correct syntax and semantics
- **Invalid cases**: Error handling and recovery
- **Edge cases**: Boundary conditions
- **Integration**: Multi-feature combinations
## Running Tests
### Prerequisites
- Node.js environment
- Arbiter instance for testing
- All dependencies installed
### Basic Usage
```bash
# Run all tests
npm test
# Run specific test file
node src/ast/tests/StructuralLinguisticTests.js
# Run with specific arbiter
node -e "
import { runAllTests } from '../../../../../lib/src/ast/tests/src/ast/tests/TestRunner.js';
const results = runAllTests(arbiter);
console.log(results);
"
```
### Test Output
The test runner provides:
- **Progress indicators**: Real-time test execution
- **Detailed results**: Pass/fail status for each test
- **Error reporting**: Specific error messages for failures
- **Performance metrics**: Execution time for each suite
- **Coverage analysis**: Language feature coverage
## Contributing
When adding new tests:
1. Follow the structural linguistic approach
2. Test both valid and invalid cases
3. Include error handling tests
4. Document test purpose and expected behavior
5. Maintain incremental complexity
6. Update coverage documentation
## Test Maintenance
- **Regular Updates**: Keep tests current with language changes
- **Performance Monitoring**: Track test execution time
- **Coverage Analysis**: Ensure comprehensive feature coverage
- **Error Handling**: Validate error messages and recovery
- **Integration Testing**: Test real-world scenarios