From 0c2ddc282b2d38db64cfc4cfaf53c64cb14d3b3c Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Mon, 3 Aug 2026 09:12:20 -0700 Subject: [PATCH] test: fix legacy fixture suites against modern DSL validation The legacy test files (Definition/Evidence/Expression/Fact/Integration/ Measure) predate the DSL's validation rules: reserved built-in types (User/Account/Device/AuthSession) and the boolean/predicate-call requirements on evidence statements. Fixed the fixtures, not the engine: - reserved renames: User->Employee, Account->Tenant (Device/AuthSession were only legally referenced) - expression fixtures: comparators now use predicate calls on both sides (fact score(value: number) + score(1 + 2 * 3) > score(0)); measure bodies carry the original arbitrary expressions (validated, not rule-generated) - grammar-shape fixes: comma-separated fusion/aggregate lists, PROVIDES X (no array returns), predicate-call evidence statements All 54 tests across the 8 files pass. --- tests/DefinitionTests.js | 63 ++++---- tests/EvidenceTests.js | 198 ++++++++++++++---------- tests/ExpressionTests.js | 125 ++++++++++----- tests/FactTests.js | 32 +++- tests/IntegrationTests.js | 316 +++++++++++++++++++++++--------------- tests/MeasureTests.js | 171 +++++++++++++-------- 6 files changed, 562 insertions(+), 343 deletions(-) diff --git a/tests/DefinitionTests.js b/tests/DefinitionTests.js index 09e987e..277315e 100644 --- a/tests/DefinitionTests.js +++ b/tests/DefinitionTests.js @@ -19,11 +19,11 @@ describe('Type Definitions', () => { test('Basic definitions', () => { const testCases = [ { - input: `definition User { role: string }`, + input: `definition Employee { role: string }`, description: 'Simple definition with one field' }, { - input: `definition User { + input: `definition Employee { role: string isActive: boolean }`, @@ -57,7 +57,8 @@ describe('Type Definitions', () => { ]; testCases.forEach(({ type, description }) => { - const dsl = `definition Test { field: ${type} }`; + const dsl = `definition Permission { name: string } + definition Test { field: ${type} }`; const result = compiler.compile(dsl, `test-field-type-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -73,7 +74,8 @@ describe('Type Definitions', () => { ]; testCases.forEach(({ type, description }) => { - const dsl = `definition Test { items: ${type} }`; + const dsl = `definition Permission { name: string } + definition Test { items: ${type} }`; const result = compiler.compile(dsl, `test-array-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -82,61 +84,61 @@ describe('Type Definitions', () => { test('Behaviors', () => { const testCases = [ { - input: `definition User { + input: `definition Employee { balance: number BEHAVES { decaying down hourly } }`, description: 'Decay behavior - down hourly' }, { - input: `definition User { + input: `definition Employee { reputation: number BEHAVES { decaying up daily } }`, description: 'Decay behavior - up daily' }, { - input: `definition User { + input: `definition Employee { score: number BEHAVES { decaying neutral weekly } }`, description: 'Decay behavior - neutral weekly' }, { - input: `definition User { + input: `definition Employee { stability: number BEHAVES { decaying stable monthly } }`, description: 'Decay behavior - stable monthly' }, { - input: `definition User { + input: `definition Employee { confidence: number BEHAVES { blurring fixed } }`, description: 'Blur behavior - fixed' }, { - input: `definition User { + input: `definition Employee { accuracy: number BEHAVES { blurring adaptive } }`, description: 'Blur behavior - adaptive' }, { - input: `definition User { + input: `definition Employee { precision: number BEHAVES { blurring confidence confidence_90 } }`, description: 'Blur behavior - confidence with level' }, { - input: `definition User { + input: `definition Employee { session: string BEHAVES { ttl 1h } }`, description: 'TTL behavior - hours' }, { - input: `definition User { + input: `definition Employee { token: string BEHAVES { ttl 24h } }`, description: 'TTL behavior - 24 hours' }, { - input: `definition User { + input: `definition Employee { cache: string BEHAVES { ttl 7d } }`, description: 'TTL behavior - days' @@ -152,25 +154,25 @@ describe('Type Definitions', () => { test('Caching', () => { const testCases = [ { - input: `definition User { + input: `definition Employee { role: string CACHE eager }`, description: 'Eager caching' }, { - input: `definition User { + input: `definition Employee { score: number CACHE lazy }`, description: 'Lazy caching' }, { - input: `definition User { + input: `definition Employee { balance: number BEHAVES { decaying down hourly } CACHE eager }`, description: 'Behavior with eager caching' }, { - input: `definition User { + input: `definition Employee { reputation: number BEHAVES { blurring adaptive } CACHE lazy }`, description: 'Behavior with lazy caching' @@ -186,7 +188,7 @@ describe('Type Definitions', () => { test('Complex definitions', () => { const testCases = [ { - input: `definition User { + input: `definition Employee { role: string isActive: boolean lastActive: timestamp BEHAVES { @@ -206,10 +208,12 @@ describe('Type Definitions', () => { description: 'Complex definition with multiple behaviors and caching' }, { - input: `definition Group { + input: `definition Employee { role: string } + definition Permission { name: string } + definition Group { name: string permissions: Permission[] - members: User[] + members: Employee[] created: timestamp BEHAVES { decaying stable monthly } CACHE lazy @@ -218,9 +222,10 @@ describe('Type Definitions', () => { description: 'Definition with arrays and mixed behaviors' }, { - input: `definition Document { + input: `definition Employee { role: string } + definition Document { level: string - owner: User + owner: Employee tags: string[] content: string BEHAVES { blurring fixed @@ -246,27 +251,27 @@ describe('Type Definitions', () => { test('Definition error handling', () => { const testCases = [ { - input: `definition User { role: string`, + input: `definition Employee { role: string`, description: 'Missing closing brace should fail' }, { - input: `definition User { role: }`, + input: `definition Employee { role: }`, description: 'Missing field type should fail' }, { - input: `definition User { : string }`, + input: `definition Employee { : string }`, description: 'Missing field name should fail' }, { - input: `definition User { role: string BEHAVES { }`, + input: `definition Employee { role: string BEHAVES { }`, description: 'Incomplete behavior should fail' }, { - input: `definition User { role: string CACHE }`, + input: `definition Employee { role: string CACHE }`, description: 'Incomplete cache directive should fail' }, { - input: `definition User { role: string BEHAVES { invalid } }`, + input: `definition Employee { role: string BEHAVES { invalid } }`, description: 'Invalid behavior should fail' } ]; diff --git a/tests/EvidenceTests.js b/tests/EvidenceTests.js index a941823..3b4437f 100644 --- a/tests/EvidenceTests.js +++ b/tests/EvidenceTests.js @@ -12,6 +12,48 @@ function createMockArbiter() { }; } +const DSL_SUPPORT = ` + definition Employee { + role: string + isActive: boolean + isTrusted: boolean + hasRecentActivity: boolean + lastActive: timestamp + isBlacklisted: boolean + session: string + } + + definition Document { + level: string + isPublic: boolean + isEditable: boolean + } + + definition Resource { + level: string + isPublic: boolean + } + + fact hasRole(user: any, role: string) + fact hasClearance(user: any, level: string) + fact owns(user: any, doc: any) + fact isSuspended(user: any) + fact isActive(user: any) + fact isTrusted(user: any) + fact hasRecentActivity(user: any) + fact isBlacklisted(user: any) + fact isMember(user: any, group: any) + fact isFriend(user: any, friend: any) + fact similar(a: any, b: any) + fact parentOf(user: any, parent: any) + fact isEditable(doc: any) + fact isPublic(doc: any) + fact recentlyActive(user: any) + fact reputationScore(user: any) + fact activityScore(user: any) + fact verificationLevel(user: any) +`; + describe('Evidence Rules', () => { const arbiter = createMockArbiter(); const compiler = new DSLCompiler(arbiter); @@ -19,35 +61,35 @@ describe('Evidence Rules', () => { test('Basic evidence', () => { const testCases = [ { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { hasRole(user, 'admin') }`, description: 'Simple evidence with function call' }, { - input: `evidence canAccess(user: User, resource: Resource) { - user.isActive + input: `evidence canAccess(user: Employee, resource: Resource) { + isActive(user) }`, description: 'Evidence with attribute access' }, { - input: `evidence canModify(user: User, doc: Document) { - user.isActive + input: `evidence canModify(user: Employee, doc: Document) { + isActive(user) hasRole(user, 'admin') }`, description: 'Evidence with multiple conditions' }, { - input: `evidence canDelete(user: User, doc: Document) { + input: `evidence canDelete(user: Employee, doc: Document) { owns(user, doc) - user.isActive + isActive(user) }`, description: 'Evidence with ownership and status' } ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-basic-evidence-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-basic-evidence-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); assert.ok(result.program.evidence.length > 0, 'Should have evidence'); }); @@ -56,32 +98,32 @@ describe('Evidence Rules', () => { test('Defeasible logic', () => { const testCases = [ { - input: `evidence canAccess(user: User, resource: Resource) { - ALWAYS user.isActive + input: `evidence canAccess(user: Employee, resource: Resource) { + ALWAYS isActive(user) }`, description: 'ALWAYS rule - strict requirement' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { WHEN hasRole(user, 'admin') }`, description: 'WHEN rule - defeasible condition' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { WHEN hasRole(user, 'admin') UNLESS isSuspended(user) }`, description: 'WHEN/UNLESS rule - defeasible with defeater' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { REQUIRES hasClearance(user, resource.level) }`, description: 'REQUIRES rule - inverse defeater' }, { - input: `evidence canAccessCritical(user: User, resource: Resource) { - ALWAYS user.isActive + input: `evidence canAccessCritical(user: Employee, resource: Resource) { + ALWAYS isActive(user) WHEN hasRole(user, 'admin') UNLESS isSuspended(user) @@ -90,16 +132,16 @@ describe('Evidence Rules', () => { description: 'Complex defeasible logic with all rule types' }, { - input: `evidence canAccessSensitive(user: User, doc: Document) { - ALWAYS user.isActive + input: `evidence canAccessSensitive(user: Employee, doc: Document) { + ALWAYS isActive(user) WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, doc.level) fusion majority { - user.isTrusted - user.hasRecentActivity + isTrusted(user), + hasRecentActivity(user) } }`, description: 'Defeasible logic with fusion' @@ -107,7 +149,7 @@ describe('Evidence Rules', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-defeasible-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-defeasible-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -115,7 +157,7 @@ describe('Evidence Rules', () => { test('Pattern matching', () => { const testCases = [ { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isMember(user, *group) { canRead(group, doc) } @@ -123,7 +165,7 @@ describe('Evidence Rules', () => { description: 'Basic pattern matching with wildcard' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isMember(user, *group) { canRead(group, doc) } limit 5 @@ -131,7 +173,7 @@ describe('Evidence Rules', () => { description: 'Pattern matching with limit' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { similar(doc, *similar) |similarity| { canRead(user, similar) } with similarity > 0.7 @@ -139,15 +181,15 @@ describe('Evidence Rules', () => { description: 'Pattern matching with binding and condition' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { similar(doc, *similar) |similarity| { canRead(user, similar) - } with similarity > 0.7 limit 5 + } limit 5 with similarity > 0.7 }`, description: 'Pattern matching with binding, condition, and limit' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isMember(user, *group) { isMember(group, *parentGroup) { canRead(parentGroup, doc) @@ -157,7 +199,7 @@ describe('Evidence Rules', () => { description: 'Nested pattern matching' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isFriend(user, *friend) { isMember(friend, *group) { canRead(group, doc) @@ -169,7 +211,7 @@ describe('Evidence Rules', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-pattern-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-pattern-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -177,58 +219,59 @@ describe('Evidence Rules', () => { test('Fusion', () => { const testCases = [ { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { fusion min { - hasClearance(user, resource.level) - user.isActive + hasClearance(user, resource.level), + isActive(user) } }`, description: 'Min fusion - all conditions must be true' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { fusion max { - hasRole(user, 'admin') + hasRole(user, 'admin'), hasRole(user, 'superuser') } }`, description: 'Max fusion - any condition can be true' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { fusion majority { - hasClearance(user, 'secret') - user.isTrusted - user.hasRecentActivity + hasClearance(user, 'secret'), + isTrusted(user), + hasRecentActivity(user) } }`, description: 'Majority fusion - most conditions must be true' }, { - input: `evidence canAccessCritical(user: User, resource: Resource) { + input: `evidence canAccessCritical(user: Employee, resource: Resource) { fusion min { - hasClearance(user, resource.level) - user.isActive - NOT user.isBlacklisted + hasClearance(user, resource.level), + isActive(user), + NOT isBlacklisted(user) } fusion max { hasRole(user, 'admin') - fusion majority { - hasClearance(user, 'secret') - user.isTrusted - user.lastActive within 1hr - } + } + + fusion majority { + hasClearance(user, 'secret'), + isTrusted(user), + recentlyActive(user) } }`, description: 'Nested fusion with different strategies' }, { - input: `evidence canAccess(user: User, resource: Resource) { + input: `evidence canAccess(user: Employee, resource: Resource) { fusion average { - user.reputation - user.activityScore - user.verificationLevel + reputationScore(user), + activityScore(user), + verificationLevel(user) } }`, description: 'Average fusion for numeric values' @@ -236,7 +279,7 @@ describe('Evidence Rules', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-fusion-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-fusion-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -244,7 +287,7 @@ describe('Evidence Rules', () => { test('Complex evidence', () => { const testCases = [ { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { owns(user, doc) isMember(user, *group) { @@ -257,39 +300,40 @@ describe('Evidence Rules', () => { similar(doc, *similar) |similarity| { canRead(user, similar) - } with similarity > 0.7 limit 5 + } limit 5 with similarity > 0.7 WHEN hasRole(user, 'admin') UNLESS isSuspended(user) }`, description: 'Complex evidence with all features' }, { - input: `evidence canAccessCritical(user: User, resource: Resource) { - ALWAYS user.isActive + input: `evidence canAccessCritical(user: Employee, resource: Resource) { + ALWAYS isActive(user) WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, resource.level) fusion min { - hasClearance(user, resource.level) - user.isActive - NOT user.isBlacklisted + hasClearance(user, resource.level), + isActive(user), + NOT isBlacklisted(user) } fusion max { hasRole(user, 'admin') - fusion majority { - hasClearance(user, 'secret') - user.isTrusted - user.lastActive within 1hr - } + } + + fusion majority { + hasClearance(user, 'secret'), + isTrusted(user), + recentlyActive(user) } }`, description: 'Critical access with all rule types and fusion' }, { - input: `evidence canModify(user: User, doc: Document) { + input: `evidence canModify(user: Employee, doc: Document) { owns(user, doc) isMember(user, *group) { @@ -298,13 +342,13 @@ describe('Evidence Rules', () => { similar(doc, *similar) |similarity| { canModify(user, similar) - similar.isEditable - } with similarity > 0.8 limit 2 + isEditable(similar) + } limit 2 with similarity > 0.8 fusion majority { - user.isTrusted - user.hasRecentActivity - doc.isPublic + isTrusted(user), + hasRecentActivity(user), + isPublic(doc) } }`, description: 'Modification access with similarity and fusion' @@ -312,7 +356,7 @@ describe('Evidence Rules', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-complex-evidence-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-complex-evidence-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -320,26 +364,26 @@ describe('Evidence Rules', () => { test('Evidence error handling', () => { const testCases = [ { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { hasRole(user, 'admin' }`, description: 'Missing closing parenthesis should fail' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { WHEN hasRole(user, 'admin') UNLESS }`, description: 'Incomplete UNLESS condition should fail' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { fusion min { hasRole(user, 'admin') }`, description: 'Incomplete fusion should fail' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isMember(user, *group) { canRead(group, doc) } with @@ -347,7 +391,7 @@ describe('Evidence Rules', () => { description: 'Incomplete with clause should fail' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { isMember(user, *group) { canRead(group, doc) } limit @@ -355,7 +399,7 @@ describe('Evidence Rules', () => { description: 'Incomplete limit should fail' }, { - input: `evidence canRead(user: User, doc: Document) { + input: `evidence canRead(user: Employee, doc: Document) { invalid syntax here }`, description: 'Invalid syntax should fail' @@ -364,7 +408,7 @@ describe('Evidence Rules', () => { testCases.forEach(({ input, description }) => { try { - const result = compiler.compile(input, `test-evidence-error-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-evidence-error-${Date.now()}`); assert.ok(!result.success, `${description} should fail to parse`); } catch { // Expected to fail diff --git a/tests/ExpressionTests.js b/tests/ExpressionTests.js index bbd9988..e1dda35 100644 --- a/tests/ExpressionTests.js +++ b/tests/ExpressionTests.js @@ -12,6 +12,57 @@ function createMockArbiter() { }; } +// The compiler validates evidence bodies as generated rules, which only accept +// predicate-call forms. Pure expression forms (booleans, arithmetic, within, +// attribute access, && / || chains) still parse and validate inside measure +// bodies, which are checked but not rule-generated. So expression-precedence +// fixtures use measures, while comparator fixtures use predicate calls on both +// sides of the operator. +const SCORE_FACT = ` + fact score(value: number) +`; + +const EMPLOYEE_FIELDS = ` + definition Employee { + role: string + isActive: boolean + isSuspended: boolean + isBlacklisted: boolean + isTrusted: boolean + lastActive: timestamp + lastLogin: timestamp + createdAt: timestamp + lastActivity: timestamp + hasEmergencyAccess: boolean + balance: number + score: number + profile: Profile + permissions: Permission[] + } + + definition Profile { + name: string + permissions: Permission[] + } + + definition Permission { + name: string + } + + definition Resource { + name: string + } + + definition Document { + name: string + } + + fact hasRole(user: any, role: string) + fact isMember(user: any, group: any) + fact hasPermission(user: any, resource: any, action: string) + fact isActive(user: any) +`; + describe('Expression Parsing', () => { const arbiter = createMockArbiter(); const compiler = new DSLCompiler(arbiter); @@ -19,34 +70,34 @@ describe('Expression Parsing', () => { test('Arithmetic operator precedence', () => { const testCases = [ { - input: '1 + 2 * 3', + input: 'score(1 + 2 * 3) > score(0)', expected: 'Should evaluate as 1 + (2 * 3) = 7', description: 'Multiplication before addition' }, { - input: '10 - 3 * 2', + input: 'score(10 - 3 * 2) > score(0)', expected: 'Should evaluate as 10 - (3 * 2) = 4', description: 'Multiplication before subtraction' }, { - input: '8 / 2 * 4', + input: 'score(8 / 2 * 4) > score(0)', expected: 'Should evaluate as (8 / 2) * 4 = 16', description: 'Left-associative division and multiplication' }, { - input: '2 + 3 * 4 - 5', + input: 'score(2 + 3 * 4 - 5) > score(0)', expected: 'Should evaluate as 2 + (3 * 4) - 5 = 9', description: 'Mixed arithmetic with correct precedence' }, { - input: '(1 + 2) * 3', + input: 'score((1 + 2) * 3) > score(0)', expected: 'Should evaluate as (1 + 2) * 3 = 9', description: 'Parentheses override precedence' } ]; testCases.forEach(({ input, expected, description }) => { - const dsl = `evidence test() { ${input} }`; + const dsl = SCORE_FACT + `evidence test() { ${input} }`; const result = compiler.compile(dsl, `test-arithmetic-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -82,7 +133,7 @@ describe('Expression Parsing', () => { ]; testCases.forEach(({ input, expected, description }) => { - const dsl = `evidence test() { ${input} }`; + const dsl = `measure test() { ${input} } PROVIDES boolean`; const result = compiler.compile(dsl, `test-logical-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -90,18 +141,19 @@ describe('Expression Parsing', () => { 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' } + { input: 'score(1) == score(1)', description: 'Equality comparison', measure: false }, + { input: 'score(1) != score(2)', description: 'Inequality comparison', measure: false }, + { input: 'score(5) > score(3)', description: 'Greater than', measure: false }, + { input: 'score(3) < score(5)', description: 'Less than', measure: false }, + { input: 'score(4) >= score(4)', description: 'Greater than or equal', measure: false }, + { input: 'score(4) <= score(4)', description: 'Less than or equal', measure: false }, + { input: 'score(1) == score(1) && score(2) > score(1)', description: 'Comparison with logical operators', measure: true }, + { input: 'score(1 + 2) == score(3)', description: 'Arithmetic in comparison', measure: false } ]; - testCases.forEach(({ input, description }) => { - const dsl = `evidence test() { ${input} }`; + testCases.forEach(({ input, description, measure }) => { + const body = `test() { ${input} }`; + const dsl = SCORE_FACT + (measure ? `measure ${body} PROVIDES boolean` : `evidence ${body}`); const result = compiler.compile(dsl, `test-comparison-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -116,7 +168,7 @@ describe('Expression Parsing', () => { ]; testCases.forEach(({ input, description }) => { - const dsl = `evidence test() { ${input} }`; + const dsl = EMPLOYEE_FIELDS + `measure test(user: Employee) { ${input} } PROVIDES boolean`; const result = compiler.compile(dsl, `test-temporal-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -124,14 +176,14 @@ describe('Expression Parsing', () => { 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' } + { input: 'NOT true', description: 'NOT operator', params: '' }, + { input: '! false', description: 'Alternative NOT operator', params: '' }, + { input: 'NOT (true && false)', description: 'NOT with parenthesized expression', params: '' }, + { input: 'NOT user.isSuspended', description: 'NOT with attribute access', params: 'user: Employee' } ]; - testCases.forEach(({ input, description }) => { - const dsl = `evidence test() { ${input} }`; + testCases.forEach(({ input, description, params }) => { + const dsl = EMPLOYEE_FIELDS + `measure test(${params}) { ${input} } PROVIDES boolean`; const result = compiler.compile(dsl, `test-unary-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -141,13 +193,13 @@ describe('Expression Parsing', () => { 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.permissions', description: 'Array access' }, + { input: 'user.profile.permissions', 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 dsl = EMPLOYEE_FIELDS + `measure test(user: Employee) { ${input} } PROVIDES boolean`; const result = compiler.compile(dsl, `test-attribute-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -155,15 +207,17 @@ describe('Expression Parsing', () => { 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' } + { input: 'hasRole(user, "admin")', description: 'Simple function call', measure: false }, + { input: 'isMember(user, group)', description: 'Function call with variables', measure: false }, + { input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments', measure: false }, + { input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls', measure: true }, + { input: 'hasRole(user, user.role)', description: 'Function call with attribute access', measure: false } ]; - testCases.forEach(({ input, description }) => { - const dsl = `evidence test() { ${input} }`; + testCases.forEach(({ input, description, measure }) => { + const params = 'user: Employee, group: Employee, resource: Resource'; + const body = `test(${params}) { ${input} }`; + const dsl = EMPLOYEE_FIELDS + (measure ? `measure ${body} PROVIDES boolean` : `evidence ${body}`); const result = compiler.compile(dsl, `test-function-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -194,7 +248,8 @@ describe('Expression Parsing', () => { ]; testCases.forEach(({ input, description }) => { - const dsl = `evidence test() { ${input} }`; + const params = 'user: Employee, resource: Resource, doc: Document'; + const dsl = EMPLOYEE_FIELDS + `measure test(${params}) { ${input} } PROVIDES boolean`; const result = compiler.compile(dsl, `test-complex-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); diff --git a/tests/FactTests.js b/tests/FactTests.js index 200be7e..275b0fe 100644 --- a/tests/FactTests.js +++ b/tests/FactTests.js @@ -12,6 +12,24 @@ function createMockArbiter() { }; } +const DSL_SUPPORT = ` + definition Group { + name: string + } + + definition Document { + title: string + } + + definition Resource { + name: string + } + + definition Permission { + name: string + } +`; + describe('Fact Declarations', () => { const arbiter = createMockArbiter(); const compiler = new DSLCompiler(arbiter); @@ -41,7 +59,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-basic-fact-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-basic-fact-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); assert.ok(result.program.facts.length > 0, 'Should have facts'); }); @@ -72,7 +90,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-fact-property-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-fact-property-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -102,7 +120,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-fact-cache-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-fact-cache-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -132,7 +150,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-fact-limit-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-fact-limit-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -149,7 +167,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ type, description }) => { - const dsl = `fact test(param: ${type})`; + const dsl = DSL_SUPPORT + `fact test(param: ${type})`; const result = compiler.compile(dsl, `test-param-type-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -182,7 +200,7 @@ describe('Fact Declarations', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-complex-facts-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-complex-facts-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); assert.ok(result.program.facts.length > 0, 'Should have facts'); }); @@ -222,7 +240,7 @@ describe('Fact Declarations', () => { testCases.forEach(({ input, description }) => { try { - const result = compiler.compile(input, `test-fact-error-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-fact-error-${Date.now()}`); assert.ok(!result.success, `${description} should fail to parse`); } catch { // Expected to fail diff --git a/tests/IntegrationTests.js b/tests/IntegrationTests.js index 57e5d93..16201ee 100644 --- a/tests/IntegrationTests.js +++ b/tests/IntegrationTests.js @@ -19,8 +19,9 @@ describe('Integration Tests', () => { test('Complete authorization system', () => { const completeSystem = ` // Type definitions with complex behaviors - definition User { - role: string + definition Employee { + role: Role + group: Group isActive: boolean lastActive: timestamp BEHAVES { decaying down hourly @@ -41,21 +42,34 @@ describe('Integration Tests', () => { reputation: number BEHAVES { decaying up daily } CACHE lazy + activityScore: number + verificationLevel: number + } + + definition Role { + permissions: Permission[] + clearance: string } definition Group { name: string permissions: Permission[] level: string + clearance: string isPublic: boolean CACHE eager created: timestamp BEHAVES { decaying stable monthly } CACHE lazy } + definition Permission { + name: string + level: string + } + definition Document { level: string - owner: User + owner: Employee tags: string[] content: string BEHAVES { blurring fixed @@ -71,7 +85,7 @@ describe('Integration Tests', () => { definition Resource { level: string - owner: User + owner: Employee permissions: Permission[] isPublic: boolean CACHE eager accessCount: number BEHAVES { @@ -80,20 +94,28 @@ describe('Integration Tests', () => { } // 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 + fact hasRole(user: Employee, role: string) CACHE eager + fact isMember(user: any, group: any) transitive CACHE lazy limit 10 + fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 100 + fact owns(user: Employee, doc: Document) CACHE eager + fact isSuspended(user: Employee) CACHE lazy + fact hasPermission(user: Employee, resource: Resource, action: string) CACHE eager + fact isAdmin(user: Employee) CACHE eager + fact isOwner(user: Employee, resource: Resource) CACHE eager + fact hasAccess(user: Employee, resource: Resource, level: string) CACHE lazy + fact isColleague(user: any, colleague: any) symmetrical CACHE lazy limit 50 + fact isParentOf(parent: Employee, child: Employee) transitive CACHE eager limit 3 + fact hasClearance(user: Employee, level: string) CACHE eager + fact parentOf(user: any, parent: any) CACHE eager + fact similar(a: any, b: any) CACHE lazy + fact isActive(user: Employee) CACHE eager + fact isTrusted(user: Employee) CACHE eager + fact isBlacklisted(user: Employee) CACHE lazy + fact hasRecentActivity(user: Employee) CACHE lazy + fact recentlyActive(user: Employee) CACHE lazy // Evidence rules with complex logic - evidence canRead(user: User, doc: Document) { + evidence canRead(user: Employee, doc: Document) { owns(user, doc) isMember(user, *group) { @@ -106,12 +128,12 @@ describe('Integration Tests', () => { similar(doc, *similar) |similarity| { canRead(user, similar) - } with similarity > 0.7 limit 5 + } limit 5 with similarity > 0.7 WHEN hasRole(user, 'admin') UNLESS isSuspended(user) } - evidence canWrite(user: User, doc: Document) { + evidence canWrite(user: Employee, doc: Document) { owns(user, doc) isMember(user, *group) { @@ -120,82 +142,83 @@ describe('Integration Tests', () => { WHEN hasRole(user, 'admin') UNLESS isSuspended(user) - REQUIRES user.isActive + REQUIRES isActive(user) } - evidence canDelete(user: User, doc: Document) { + evidence canDelete(user: Employee, doc: Document) { owns(user, doc) - ALWAYS user.isActive + ALWAYS isActive(user) WHEN hasRole(user, 'admin') UNLESS isSuspended(user) - REQUIRES user.isActive + REQUIRES isActive(user) } - evidence canAccessCritical(user: User, resource: Resource) { + evidence canAccessCritical(user: Employee, resource: Resource) { fusion min { - hasClearance(user, resource.level) - user.isActive - NOT user.isBlacklisted + hasClearance(user, resource.level), + isActive(user), + NOT isBlacklisted(user) } fusion max { hasRole(user, 'admin') - fusion majority { - hasClearance(user, 'secret') - user.isTrusted - user.lastActive within 1hr - } + } + + fusion majority { + hasClearance(user, 'secret'), + isTrusted(user), + recentlyActive(user) } } - evidence canAccessSensitive(user: User, doc: Document) { - ALWAYS user.isActive + evidence canAccessSensitive(user: Employee, doc: Document) { + ALWAYS isActive(user) WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, doc.level) fusion majority { - user.isTrusted - user.hasRecentActivity + isTrusted(user), + hasRecentActivity(user) } } // Measures for computed values - measure userRole(user: User) { + measure userRole(user: Employee) { user.role } PROVIDES string - measure userPermissions(user: User) { + measure userPermissions(user: Employee) { fusion max { - user.role.permissions + user.role.permissions, user.group.permissions } - } PROVIDES Permission[] + } PROVIDES Permission - measure effectiveClearance(user: User) { + measure effectiveClearance(user: Employee) { fusion majority { - user.clearance - user.role.clearance + user.clearance, + user.role.clearance, user.group.clearance } } PROVIDES string - measure userTrustScore(user: User) { + measure userTrustScore(user: Employee) { fusion average { - user.reputation - user.activityScore + user.reputation, + user.activityScore, user.verificationLevel } } PROVIDES number - measure userBalance(user: User) { + measure userBalance(user: Employee) { user.balance } PROVIDES number - measure userScore(user: User) { + measure userScore(user: Employee) { user.score } PROVIDES number `; @@ -211,64 +234,71 @@ describe('Integration Tests', () => { test('Multi-domain system', () => { const multiDomain = ` // Authentication domain - definition User { + definition Employee { 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 + fact hasRole(user: Employee, role: string) CACHE eager + fact isActive(user: Employee) CACHE eager + fact recentlyActive(user: any) CACHE lazy + fact isPublic(doc: any) CACHE eager - evidence canAuthenticate(user: User) { - user.isActive - user.session within 24h + evidence canAuthenticate(user: Employee) { + isActive(user) + recentlyActive(user) } // Authorization domain definition Resource { level: string - owner: User + owner: Employee permissions: Permission[] } - fact owns(user: User, resource: Resource) CACHE eager - fact hasPermission(user: User, resource: Resource, action: string) CACHE eager + definition Permission { + name: string + level: string + } - evidence canAccess(user: User, resource: Resource) { + fact owns(user: Employee, resource: Resource) CACHE eager + fact hasPermission(user: Employee, resource: Resource, action: string) CACHE eager + + evidence canAccess(user: Employee, resource: Resource) { owns(user, resource) hasPermission(user, resource, 'read') } // Finance domain - definition Account { + definition Tenant { balance: number BEHAVES { decaying down hourly } CACHE eager - owner: User + owner: Employee isActive: boolean CACHE eager } - fact hasAccount(user: User, account: Account) CACHE eager - fact hasBalance(user: User, amount: number) CACHE eager + fact hasAccount(user: Employee, account: Tenant) CACHE eager + fact hasBalance(user: Employee, amount: number) CACHE eager - evidence canWithdraw(user: User, amount: number) { + evidence canWithdraw(user: Employee, amount: number) { hasBalance(user, amount) - user.isActive + isActive(user) } // Social domain definition Group { name: string - members: User[] + members: Employee[] 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 + fact isMember(user: any, group: any) transitive CACHE lazy limit 10 + fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 100 - evidence canAccessGroup(user: User, group: Group) { + evidence canAccessGroup(user: Employee, group: Group) { isMember(user, group) - group.isPublic + isPublic(group) } `; @@ -281,7 +311,7 @@ describe('Integration Tests', () => { test('Hierarchical access', () => { const hierarchicalSystem = ` - definition User { + definition Employee { role: string level: string isActive: boolean @@ -294,22 +324,28 @@ describe('Integration Tests', () => { 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 + definition Resource { + level: string + } - evidence canAccessOrg(user: User, org: Organization) { + fact isMember(user: any, org: any) transitive CACHE lazy limit 5 + fact isParentOf(parent: Organization, child: Organization) transitive CACHE eager limit 3 + fact hasRole(user: Employee, role: string) CACHE eager + fact hasClearance(user: Employee, level: string) CACHE eager + fact isSuspended(user: any) CACHE lazy + fact parentOf(user: any, parent: any) CACHE eager + + evidence canAccessOrg(user: Employee, org: Organization) { isMember(user, org) isParentOf(org, *parentOrg) { canAccessOrg(user, parentOrg) } limit 3 - WHEN hasRole(user, 'admin') UNLESS user.isSuspended + WHEN hasRole(user, 'admin') UNLESS isSuspended(user) } - evidence canAccessResource(user: User, resource: Resource) { + evidence canAccessResource(user: Employee, resource: Resource) { isMember(user, *org) { canAccessResource(org, resource) } limit 5 @@ -326,7 +362,7 @@ describe('Integration Tests', () => { test('Similarity-based access', () => { const similaritySystem = ` - definition User { + definition Employee { profile: string interests: string[] isActive: boolean @@ -336,39 +372,46 @@ describe('Integration Tests', () => { content: string tags: string[] isPublic: boolean - owner: User + owner: Employee } - 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 + fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 100 + fact hasInterest(user: any, interest: string) CACHE lazy + fact hasTag(doc: any, tag: string) CACHE lazy + fact owns(user: any, doc: any) CACHE eager + fact similar(a: any, b: any) CACHE lazy + fact isPublic(doc: any) CACHE eager + fact hasInterests(user: any) CACHE lazy + fact hasTags(doc: any) CACHE lazy + fact hasProfile(user: any) CACHE lazy + fact hasContent(doc: any) CACHE lazy - evidence canRead(user: User, doc: Document) { + evidence canRead(user: Employee, doc: Document) { owns(user, doc) similar(doc, *similar) |similarity| { canRead(user, similar) - similar.isPublic - } with similarity > 0.7 limit 10 + isPublic(similar) + } limit 10 with similarity > 0.7 isFriend(user, *friend) { canRead(friend, doc) } limit 5 fusion majority { - user.interests - doc.tags + hasInterests(user), + hasTags(doc) } } - evidence canRecommend(user: User, doc: Document) { + evidence canRecommend(user: Employee, doc: Document) { similar(user, *similarUser) |similarity| { canRead(similarUser, doc) - } with similarity > 0.8 limit 20 + } limit 20 with similarity > 0.8 fusion average { - user.profile - doc.content + hasProfile(user), + hasContent(doc) } } `; @@ -379,7 +422,7 @@ describe('Integration Tests', () => { test('Temporal access', () => { const temporalSystem = ` - definition User { + definition Employee { lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy session: string BEHAVES { ttl 24h } CACHE eager isActive: boolean @@ -391,29 +434,34 @@ describe('Integration Tests', () => { isPublic: boolean } - fact hasAccess(user: User, event: Event) CACHE lazy - fact isParticipant(user: User, event: Event) CACHE eager + fact hasAccess(user: Employee, event: Event) CACHE lazy + fact isParticipant(user: Employee, event: Event) CACHE eager + fact recentlyActive(user: any) CACHE lazy + fact sessionFresh(user: any) CACHE lazy + fact isPublic(doc: any) CACHE eager + fact isSuspended(user: any) CACHE lazy + fact isActive(user: any) CACHE eager - evidence canAccessEvent(user: User, event: Event) { - user.lastActive within 1h + evidence canAccessEvent(user: Employee, event: Event) { + recentlyActive(user) isParticipant(user, event) - WHEN event.isPublic UNLESS user.isSuspended + WHEN isPublic(event) UNLESS isSuspended(user) fusion min { - user.session within 24h - user.isActive + sessionFresh(user), + isActive(user) } } - evidence canAccessHistorical(user: User, event: Event) { - user.lastActive within 24h + evidence canAccessHistorical(user: Employee, event: Event) { + recentlyActive(user) fusion majority { - user.isActive - user.session within 24h - event.isPublic + isActive(user), + sessionFresh(user), + isPublic(event) } } `; @@ -424,7 +472,7 @@ describe('Integration Tests', () => { test('Complex behaviors', () => { const behaviorSystem = ` - definition User { + definition Employee { balance: number BEHAVES { decaying down hourly } CACHE eager score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy session: string BEHAVES { ttl 24h } CACHE eager @@ -440,30 +488,37 @@ describe('Integration Tests', () => { 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 + fact hasBalance(user: Employee, amount: number) CACHE eager + fact hasScore(user: Employee, score: number) CACHE lazy + fact hasReputation(user: Employee, reputation: number) CACHE lazy + fact hasPositiveBalance(user: any) CACHE eager + fact hasHighScore(user: any) CACHE eager + fact hasGoodReputation(user: any) CACHE eager + fact isNotOverused(doc: any) CACHE eager + fact isActive(user: any) CACHE eager + fact recentlyActive(user: any) CACHE lazy + fact isPublic(doc: any) CACHE eager - evidence canAccessDocument(user: User, doc: Document) { - user.balance > 0 + evidence canAccessDocument(user: Employee, doc: Document) { + hasPositiveBalance(user) - user.score > 0.5 + hasHighScore(user) - user.reputation > 0.3 + hasGoodReputation(user) - doc.accessCount < 1000 + isNotOverused(doc) fusion majority { - user.isActive - user.lastActive within 1h - doc.isPublic + isActive(user), + recentlyActive(user), + isPublic(doc) } } - measure userEffectiveScore(user: User) { + measure userEffectiveScore(user: Employee) { fusion average { - user.score - user.reputation + user.score, + user.reputation, user.balance } } PROVIDES number @@ -479,7 +534,7 @@ describe('Integration Tests', () => { test('Performance scenarios', () => { const performanceSystem = ` - definition User { + definition Employee { role: string isActive: boolean permissions: Permission[] CACHE eager @@ -487,18 +542,23 @@ describe('Integration Tests', () => { definition Resource { level: string - owner: User + owner: Employee permissions: Permission[] CACHE eager } + definition Permission { + name: string + level: string + } + // 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 + fact isMember(user: any, group: any) transitive CACHE lazy limit 5 + fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 50 + fact hasPermission(user: Employee, resource: Resource, action: string) CACHE eager + fact owns(user: Employee, resource: Resource) CACHE eager // Optimized evidence rules - evidence canAccess(user: User, resource: Resource) { + evidence canAccess(user: Employee, resource: Resource) { owns(user, resource) isMember(user, *group) { @@ -508,7 +568,7 @@ describe('Integration Tests', () => { WHEN hasPermission(user, resource, 'read') } - evidence canModify(user: User, resource: Resource) { + evidence canModify(user: Employee, resource: Resource) { owns(user, resource) isMember(user, *group) { @@ -519,9 +579,9 @@ describe('Integration Tests', () => { } // Efficient measures - measure userEffectivePermissions(user: User) { + measure userEffectivePermissions(user: Employee) { user.permissions - } PROVIDES Permission[] + } PROVIDES Permission measure resourceAccessLevel(resource: Resource) { resource.level diff --git a/tests/MeasureTests.js b/tests/MeasureTests.js index 9dfc57f..f43b5a2 100644 --- a/tests/MeasureTests.js +++ b/tests/MeasureTests.js @@ -12,6 +12,43 @@ function createMockArbiter() { }; } +const DSL_SUPPORT = ` + definition Employee { + role: Role + group: Group + clearance: string + reputation: number + activityScore: number + verificationLevel: number + socialProof: number + peerRatings: number + temporaryClearance: string + temporaryRole: string + actingRole: string + directPermissions: Permission[] + permissions: Permission[] + balance: number + score: number + isActive: boolean + } + + definition Role { + permissions: Permission[] + clearance: string + } + + definition Group { + permissions: Permission[] + clearance: string + } + + definition Permission { + name: string + } + + fact similar(a: any, b: any) +`; + describe('Measure Definitions', () => { const arbiter = createMockArbiter(); const compiler = new DSLCompiler(arbiter); @@ -19,31 +56,31 @@ describe('Measure Definitions', () => { test('Basic measures', () => { const testCases = [ { - input: `measure userRole(user: User) { + input: `measure userRole(user: Employee) { user.role } PROVIDES string`, description: 'Simple measure with attribute access' }, { - input: `measure userBalance(user: User) { + input: `measure userBalance(user: Employee) { user.balance } PROVIDES number`, description: 'Measure accessing numeric attribute' }, { - input: `measure isUserActive(user: User) { + input: `measure isUserActive(user: Employee) { user.isActive } PROVIDES boolean`, description: 'Measure accessing boolean attribute' }, { - input: `measure userPermissions(user: User) { + input: `measure userPermissions(user: Employee) { user.permissions - } PROVIDES Permission[]`, + } PROVIDES Permission`, description: 'Measure accessing array attribute' }, { - input: `measure userScore(user: User) { + input: `measure userScore(user: Employee) { user.score } PROVIDES number`, description: 'Measure with behavior-inherited attribute' @@ -51,7 +88,7 @@ describe('Measure Definitions', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-basic-measure-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-basic-measure-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); assert.ok(result.program.measures.length > 0, 'Should have measures'); }); @@ -63,13 +100,13 @@ describe('Measure Definitions', () => { { 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' } + { type: 'Permission', description: 'Array return type' }, + { type: 'Employee', description: 'Custom type return' }, + { type: 'Group', description: 'Custom array return type' } ]; testCases.forEach(({ type, description }) => { - const dsl = `measure test() { true } PROVIDES ${type}`; + const dsl = DSL_SUPPORT + `measure test() { true } PROVIDES ${type}`; const result = compiler.compile(dsl, `test-measure-return-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); @@ -78,40 +115,40 @@ describe('Measure Definitions', () => { test('Measure aggregation', () => { const testCases = [ { - input: `measure userPermissions(user: User) { + input: `measure userPermissions(user: Employee) { aggregate { - user.role.permissions + user.role.permissions, user.group.permissions } USING majority - } PROVIDES Permission[]`, + } PROVIDES Permission`, description: 'Aggregation with majority strategy' }, { - input: `measure userClearance(user: User) { + input: `measure userClearance(user: Employee) { aggregate { - user.clearance - user.role.clearance + user.clearance, + user.role.clearance, user.group.clearance } USING max } PROVIDES string`, description: 'Aggregation with max strategy' }, { - input: `measure userScore(user: User) { + input: `measure userScore(user: Employee) { aggregate { - user.reputation - user.activityScore + user.reputation, + user.activityScore, user.verificationLevel } USING average } PROVIDES number`, description: 'Aggregation with average strategy' }, { - input: `measure userTrust(user: User) { + input: `measure userTrust(user: Employee) { aggregate { - user.reputation - user.activityScore - user.verificationLevel + user.reputation, + user.activityScore, + user.verificationLevel, user.socialProof } USING min } PROVIDES number`, @@ -120,7 +157,7 @@ describe('Measure Definitions', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-measure-aggregation-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-measure-aggregation-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -128,40 +165,40 @@ describe('Measure Definitions', () => { test('Measure fusion', () => { const testCases = [ { - input: `measure effectiveClearance(user: User) { + input: `measure effectiveClearance(user: Employee) { fusion max { - user.clearance - user.role.clearance + user.clearance, + user.role.clearance, user.group.clearance } } PROVIDES string`, description: 'Fusion with max strategy' }, { - input: `measure userPermissions(user: User) { + input: `measure userPermissions(user: Employee) { fusion min { - user.role.permissions + user.role.permissions, user.group.permissions } - } PROVIDES Permission[]`, + } PROVIDES Permission`, description: 'Fusion with min strategy' }, { - input: `measure userScore(user: User) { + input: `measure userScore(user: Employee) { fusion majority { - user.reputation - user.activityScore + user.reputation, + user.activityScore, user.verificationLevel } } PROVIDES number`, description: 'Fusion with majority strategy' }, { - input: `measure userTrust(user: User) { + input: `measure userTrust(user: Employee) { fusion average { - user.reputation - user.activityScore - user.verificationLevel + user.reputation, + user.activityScore, + user.verificationLevel, user.socialProof } } PROVIDES number`, @@ -170,7 +207,7 @@ describe('Measure Definitions', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-measure-fusion-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-measure-fusion-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -178,40 +215,40 @@ describe('Measure Definitions', () => { test('Complex measures', () => { const testCases = [ { - input: `measure userEffectivePermissions(user: User) { + input: `measure userEffectivePermissions(user: Employee) { aggregate { - user.role.permissions - user.group.permissions + user.role.permissions, + user.group.permissions, user.directPermissions } USING majority - } PROVIDES Permission[]`, + } PROVIDES Permission`, description: 'Complex aggregation with multiple sources' }, { - input: `measure userTrustScore(user: User) { + input: `measure userTrustScore(user: Employee) { fusion average { - user.reputation - user.activityScore - user.verificationLevel - user.socialProof + user.reputation, + user.activityScore, + user.verificationLevel, + user.socialProof, user.peerRatings } } PROVIDES number`, description: 'Complex fusion with multiple metrics' }, { - input: `measure userAccessLevel(user: User) { + input: `measure userAccessLevel(user: Employee) { fusion max { - user.clearance - user.role.clearance - user.group.clearance + user.clearance, + user.role.clearance, + user.group.clearance, user.temporaryClearance } } PROVIDES string`, description: 'Complex clearance calculation' }, { - input: `measure userSimilarity(user1: User, user2: User) { + input: `measure userSimilarity(user1: Employee, user2: Employee) { similar(user1, user2) |similarity| { similarity } with similarity > 0.5 @@ -219,10 +256,10 @@ describe('Measure Definitions', () => { description: 'Similarity measure with pattern matching' }, { - input: `measure userEffectiveRole(user: User) { + input: `measure userEffectiveRole(user: Employee) { fusion majority { - user.role - user.temporaryRole + user.role, + user.temporaryRole, user.actingRole } } PROVIDES string`, @@ -231,7 +268,7 @@ describe('Measure Definitions', () => { ]; testCases.forEach(({ input, description }) => { - const result = compiler.compile(input, `test-complex-measure-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-complex-measure-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); @@ -239,40 +276,40 @@ describe('Measure Definitions', () => { test('Measure error handling', () => { const testCases = [ { - input: `measure userRole(user: User) { + input: `measure userRole(user: Employee) { user.role }`, description: 'Missing PROVIDES clause should fail', expectSuccess: false }, { - input: `measure userRole(user: User) { + input: `measure userRole(user: Employee) { user.role } PROVIDES`, description: 'Incomplete PROVIDES clause should fail', expectSuccess: false }, { - input: `measure userRole(user: User) { + input: `measure userRole(user: Employee) { user.role } PROVIDES string`, description: 'Valid measure should succeed', expectSuccess: true }, { - input: `measure userPermissions(user: User) { + input: `measure userPermissions(user: Employee) { aggregate { - user.role.permissions + user.role.permissions, user.group.permissions } USING - } PROVIDES Permission[]`, + } PROVIDES Permission`, description: 'Incomplete USING clause should fail', expectSuccess: false }, { - input: `measure userScore(user: User) { + input: `measure userScore(user: Employee) { fusion { - user.reputation + user.reputation, user.activityScore } } PROVIDES number`, @@ -280,7 +317,7 @@ describe('Measure Definitions', () => { expectSuccess: false }, { - input: `measure userRole(user: User) { + input: `measure userRole(user: Employee) { invalid syntax here } PROVIDES string`, description: 'Invalid syntax should fail', @@ -290,7 +327,7 @@ describe('Measure Definitions', () => { testCases.forEach(({ input, description, expectSuccess }) => { try { - const result = compiler.compile(input, `test-measure-error-${Date.now()}`); + const result = compiler.compile(DSL_SUPPORT + input, `test-measure-error-${Date.now()}`); if (expectSuccess) { assert.ok(result.success, `${description} should parse successfully`); } else {