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.
This commit is contained in:
+34
-29
@@ -19,11 +19,11 @@ describe('Type Definitions', () => {
|
|||||||
test('Basic definitions', () => {
|
test('Basic definitions', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `definition User { role: string }`,
|
input: `definition Employee { role: string }`,
|
||||||
description: 'Simple definition with one field'
|
description: 'Simple definition with one field'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
role: string
|
role: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
}`,
|
}`,
|
||||||
@@ -57,7 +57,8 @@ describe('Type Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ type, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-field-type-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -73,7 +74,8 @@ describe('Type Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ type, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-array-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -82,61 +84,61 @@ describe('Type Definitions', () => {
|
|||||||
test('Behaviors', () => {
|
test('Behaviors', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
balance: number BEHAVES { decaying down hourly }
|
balance: number BEHAVES { decaying down hourly }
|
||||||
}`,
|
}`,
|
||||||
description: 'Decay behavior - down hourly'
|
description: 'Decay behavior - down hourly'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
reputation: number BEHAVES { decaying up daily }
|
reputation: number BEHAVES { decaying up daily }
|
||||||
}`,
|
}`,
|
||||||
description: 'Decay behavior - up daily'
|
description: 'Decay behavior - up daily'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
score: number BEHAVES { decaying neutral weekly }
|
score: number BEHAVES { decaying neutral weekly }
|
||||||
}`,
|
}`,
|
||||||
description: 'Decay behavior - neutral weekly'
|
description: 'Decay behavior - neutral weekly'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
stability: number BEHAVES { decaying stable monthly }
|
stability: number BEHAVES { decaying stable monthly }
|
||||||
}`,
|
}`,
|
||||||
description: 'Decay behavior - stable monthly'
|
description: 'Decay behavior - stable monthly'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
confidence: number BEHAVES { blurring fixed }
|
confidence: number BEHAVES { blurring fixed }
|
||||||
}`,
|
}`,
|
||||||
description: 'Blur behavior - fixed'
|
description: 'Blur behavior - fixed'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
accuracy: number BEHAVES { blurring adaptive }
|
accuracy: number BEHAVES { blurring adaptive }
|
||||||
}`,
|
}`,
|
||||||
description: 'Blur behavior - adaptive'
|
description: 'Blur behavior - adaptive'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
precision: number BEHAVES { blurring confidence confidence_90 }
|
precision: number BEHAVES { blurring confidence confidence_90 }
|
||||||
}`,
|
}`,
|
||||||
description: 'Blur behavior - confidence with level'
|
description: 'Blur behavior - confidence with level'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
session: string BEHAVES { ttl 1h }
|
session: string BEHAVES { ttl 1h }
|
||||||
}`,
|
}`,
|
||||||
description: 'TTL behavior - hours'
|
description: 'TTL behavior - hours'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
token: string BEHAVES { ttl 24h }
|
token: string BEHAVES { ttl 24h }
|
||||||
}`,
|
}`,
|
||||||
description: 'TTL behavior - 24 hours'
|
description: 'TTL behavior - 24 hours'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
cache: string BEHAVES { ttl 7d }
|
cache: string BEHAVES { ttl 7d }
|
||||||
}`,
|
}`,
|
||||||
description: 'TTL behavior - days'
|
description: 'TTL behavior - days'
|
||||||
@@ -152,25 +154,25 @@ describe('Type Definitions', () => {
|
|||||||
test('Caching', () => {
|
test('Caching', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
role: string CACHE eager
|
role: string CACHE eager
|
||||||
}`,
|
}`,
|
||||||
description: 'Eager caching'
|
description: 'Eager caching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
score: number CACHE lazy
|
score: number CACHE lazy
|
||||||
}`,
|
}`,
|
||||||
description: 'Lazy caching'
|
description: 'Lazy caching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||||
}`,
|
}`,
|
||||||
description: 'Behavior with eager caching'
|
description: 'Behavior with eager caching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
reputation: number BEHAVES { blurring adaptive } CACHE lazy
|
reputation: number BEHAVES { blurring adaptive } CACHE lazy
|
||||||
}`,
|
}`,
|
||||||
description: 'Behavior with lazy caching'
|
description: 'Behavior with lazy caching'
|
||||||
@@ -186,7 +188,7 @@ describe('Type Definitions', () => {
|
|||||||
test('Complex definitions', () => {
|
test('Complex definitions', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `definition User {
|
input: `definition Employee {
|
||||||
role: string
|
role: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
lastActive: timestamp BEHAVES {
|
lastActive: timestamp BEHAVES {
|
||||||
@@ -206,10 +208,12 @@ describe('Type Definitions', () => {
|
|||||||
description: 'Complex definition with multiple behaviors and caching'
|
description: 'Complex definition with multiple behaviors and caching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition Group {
|
input: `definition Employee { role: string }
|
||||||
|
definition Permission { name: string }
|
||||||
|
definition Group {
|
||||||
name: string
|
name: string
|
||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
members: User[]
|
members: Employee[]
|
||||||
created: timestamp BEHAVES {
|
created: timestamp BEHAVES {
|
||||||
decaying stable monthly
|
decaying stable monthly
|
||||||
} CACHE lazy
|
} CACHE lazy
|
||||||
@@ -218,9 +222,10 @@ describe('Type Definitions', () => {
|
|||||||
description: 'Definition with arrays and mixed behaviors'
|
description: 'Definition with arrays and mixed behaviors'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition Document {
|
input: `definition Employee { role: string }
|
||||||
|
definition Document {
|
||||||
level: string
|
level: string
|
||||||
owner: User
|
owner: Employee
|
||||||
tags: string[]
|
tags: string[]
|
||||||
content: string BEHAVES {
|
content: string BEHAVES {
|
||||||
blurring fixed
|
blurring fixed
|
||||||
@@ -246,27 +251,27 @@ describe('Type Definitions', () => {
|
|||||||
test('Definition error handling', () => {
|
test('Definition error handling', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `definition User { role: string`,
|
input: `definition Employee { role: string`,
|
||||||
description: 'Missing closing brace should fail'
|
description: 'Missing closing brace should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User { role: }`,
|
input: `definition Employee { role: }`,
|
||||||
description: 'Missing field type should fail'
|
description: 'Missing field type should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User { : string }`,
|
input: `definition Employee { : string }`,
|
||||||
description: 'Missing field name should fail'
|
description: 'Missing field name should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User { role: string BEHAVES { }`,
|
input: `definition Employee { role: string BEHAVES { }`,
|
||||||
description: 'Incomplete behavior should fail'
|
description: 'Incomplete behavior should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `definition User { role: string CACHE }`,
|
input: `definition Employee { role: string CACHE }`,
|
||||||
description: 'Incomplete cache directive should fail'
|
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'
|
description: 'Invalid behavior should fail'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
+119
-75
@@ -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', () => {
|
describe('Evidence Rules', () => {
|
||||||
const arbiter = createMockArbiter();
|
const arbiter = createMockArbiter();
|
||||||
const compiler = new DSLCompiler(arbiter);
|
const compiler = new DSLCompiler(arbiter);
|
||||||
@@ -19,35 +61,35 @@ describe('Evidence Rules', () => {
|
|||||||
test('Basic evidence', () => {
|
test('Basic evidence', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
hasRole(user, 'admin')
|
hasRole(user, 'admin')
|
||||||
}`,
|
}`,
|
||||||
description: 'Simple evidence with function call'
|
description: 'Simple evidence with function call'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
user.isActive
|
isActive(user)
|
||||||
}`,
|
}`,
|
||||||
description: 'Evidence with attribute access'
|
description: 'Evidence with attribute access'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canModify(user: User, doc: Document) {
|
input: `evidence canModify(user: Employee, doc: Document) {
|
||||||
user.isActive
|
isActive(user)
|
||||||
hasRole(user, 'admin')
|
hasRole(user, 'admin')
|
||||||
}`,
|
}`,
|
||||||
description: 'Evidence with multiple conditions'
|
description: 'Evidence with multiple conditions'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canDelete(user: User, doc: Document) {
|
input: `evidence canDelete(user: Employee, doc: Document) {
|
||||||
owns(user, doc)
|
owns(user, doc)
|
||||||
user.isActive
|
isActive(user)
|
||||||
}`,
|
}`,
|
||||||
description: 'Evidence with ownership and status'
|
description: 'Evidence with ownership and status'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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.success, `${description} should parse successfully`);
|
||||||
assert.ok(result.program.evidence.length > 0, 'Should have evidence');
|
assert.ok(result.program.evidence.length > 0, 'Should have evidence');
|
||||||
});
|
});
|
||||||
@@ -56,32 +98,32 @@ describe('Evidence Rules', () => {
|
|||||||
test('Defeasible logic', () => {
|
test('Defeasible logic', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
}`,
|
}`,
|
||||||
description: 'ALWAYS rule - strict requirement'
|
description: 'ALWAYS rule - strict requirement'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
WHEN hasRole(user, 'admin')
|
WHEN hasRole(user, 'admin')
|
||||||
}`,
|
}`,
|
||||||
description: 'WHEN rule - defeasible condition'
|
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)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
}`,
|
}`,
|
||||||
description: 'WHEN/UNLESS rule - defeasible with defeater'
|
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)
|
REQUIRES hasClearance(user, resource.level)
|
||||||
}`,
|
}`,
|
||||||
description: 'REQUIRES rule - inverse defeater'
|
description: 'REQUIRES rule - inverse defeater'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccessCritical(user: User, resource: Resource) {
|
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
|
|
||||||
@@ -90,16 +132,16 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Complex defeasible logic with all rule types'
|
description: 'Complex defeasible logic with all rule types'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccessSensitive(user: User, doc: Document) {
|
input: `evidence canAccessSensitive(user: Employee, doc: Document) {
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
|
|
||||||
REQUIRES hasClearance(user, doc.level)
|
REQUIRES hasClearance(user, doc.level)
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.isTrusted
|
isTrusted(user),
|
||||||
user.hasRecentActivity
|
hasRecentActivity(user)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Defeasible logic with fusion'
|
description: 'Defeasible logic with fusion'
|
||||||
@@ -107,7 +149,7 @@ describe('Evidence Rules', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -115,7 +157,7 @@ describe('Evidence Rules', () => {
|
|||||||
test('Pattern matching', () => {
|
test('Pattern matching', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
canRead(group, doc)
|
canRead(group, doc)
|
||||||
}
|
}
|
||||||
@@ -123,7 +165,7 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Basic pattern matching with wildcard'
|
description: 'Basic pattern matching with wildcard'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
canRead(group, doc)
|
canRead(group, doc)
|
||||||
} limit 5
|
} limit 5
|
||||||
@@ -131,7 +173,7 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Pattern matching with limit'
|
description: 'Pattern matching with limit'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
similar(doc, *similar) |similarity| {
|
similar(doc, *similar) |similarity| {
|
||||||
canRead(user, similar)
|
canRead(user, similar)
|
||||||
} with similarity > 0.7
|
} with similarity > 0.7
|
||||||
@@ -139,15 +181,15 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Pattern matching with binding and condition'
|
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| {
|
similar(doc, *similar) |similarity| {
|
||||||
canRead(user, similar)
|
canRead(user, similar)
|
||||||
} with similarity > 0.7 limit 5
|
} limit 5 with similarity > 0.7
|
||||||
}`,
|
}`,
|
||||||
description: 'Pattern matching with binding, condition, and limit'
|
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(user, *group) {
|
||||||
isMember(group, *parentGroup) {
|
isMember(group, *parentGroup) {
|
||||||
canRead(parentGroup, doc)
|
canRead(parentGroup, doc)
|
||||||
@@ -157,7 +199,7 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Nested pattern matching'
|
description: 'Nested pattern matching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
isFriend(user, *friend) {
|
isFriend(user, *friend) {
|
||||||
isMember(friend, *group) {
|
isMember(friend, *group) {
|
||||||
canRead(group, doc)
|
canRead(group, doc)
|
||||||
@@ -169,7 +211,7 @@ describe('Evidence Rules', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -177,58 +219,59 @@ describe('Evidence Rules', () => {
|
|||||||
test('Fusion', () => {
|
test('Fusion', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
fusion min {
|
fusion min {
|
||||||
hasClearance(user, resource.level)
|
hasClearance(user, resource.level),
|
||||||
user.isActive
|
isActive(user)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Min fusion - all conditions must be true'
|
description: 'Min fusion - all conditions must be true'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
fusion max {
|
fusion max {
|
||||||
hasRole(user, 'admin')
|
hasRole(user, 'admin'),
|
||||||
hasRole(user, 'superuser')
|
hasRole(user, 'superuser')
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Max fusion - any condition can be true'
|
description: 'Max fusion - any condition can be true'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
fusion majority {
|
fusion majority {
|
||||||
hasClearance(user, 'secret')
|
hasClearance(user, 'secret'),
|
||||||
user.isTrusted
|
isTrusted(user),
|
||||||
user.hasRecentActivity
|
hasRecentActivity(user)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Majority fusion - most conditions must be true'
|
description: 'Majority fusion - most conditions must be true'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccessCritical(user: User, resource: Resource) {
|
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
|
||||||
fusion min {
|
fusion min {
|
||||||
hasClearance(user, resource.level)
|
hasClearance(user, resource.level),
|
||||||
user.isActive
|
isActive(user),
|
||||||
NOT user.isBlacklisted
|
NOT isBlacklisted(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fusion max {
|
fusion max {
|
||||||
hasRole(user, 'admin')
|
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'
|
description: 'Nested fusion with different strategies'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccess(user: User, resource: Resource) {
|
input: `evidence canAccess(user: Employee, resource: Resource) {
|
||||||
fusion average {
|
fusion average {
|
||||||
user.reputation
|
reputationScore(user),
|
||||||
user.activityScore
|
activityScore(user),
|
||||||
user.verificationLevel
|
verificationLevel(user)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Average fusion for numeric values'
|
description: 'Average fusion for numeric values'
|
||||||
@@ -236,7 +279,7 @@ describe('Evidence Rules', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -244,7 +287,7 @@ describe('Evidence Rules', () => {
|
|||||||
test('Complex evidence', () => {
|
test('Complex evidence', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
owns(user, doc)
|
owns(user, doc)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -257,39 +300,40 @@ describe('Evidence Rules', () => {
|
|||||||
|
|
||||||
similar(doc, *similar) |similarity| {
|
similar(doc, *similar) |similarity| {
|
||||||
canRead(user, similar)
|
canRead(user, similar)
|
||||||
} with similarity > 0.7 limit 5
|
} limit 5 with similarity > 0.7
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
}`,
|
}`,
|
||||||
description: 'Complex evidence with all features'
|
description: 'Complex evidence with all features'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canAccessCritical(user: User, resource: Resource) {
|
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
|
|
||||||
REQUIRES hasClearance(user, resource.level)
|
REQUIRES hasClearance(user, resource.level)
|
||||||
|
|
||||||
fusion min {
|
fusion min {
|
||||||
hasClearance(user, resource.level)
|
hasClearance(user, resource.level),
|
||||||
user.isActive
|
isActive(user),
|
||||||
NOT user.isBlacklisted
|
NOT isBlacklisted(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fusion max {
|
fusion max {
|
||||||
hasRole(user, 'admin')
|
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'
|
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)
|
owns(user, doc)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -298,13 +342,13 @@ describe('Evidence Rules', () => {
|
|||||||
|
|
||||||
similar(doc, *similar) |similarity| {
|
similar(doc, *similar) |similarity| {
|
||||||
canModify(user, similar)
|
canModify(user, similar)
|
||||||
similar.isEditable
|
isEditable(similar)
|
||||||
} with similarity > 0.8 limit 2
|
} limit 2 with similarity > 0.8
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.isTrusted
|
isTrusted(user),
|
||||||
user.hasRecentActivity
|
hasRecentActivity(user),
|
||||||
doc.isPublic
|
isPublic(doc)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
description: 'Modification access with similarity and fusion'
|
description: 'Modification access with similarity and fusion'
|
||||||
@@ -312,7 +356,7 @@ describe('Evidence Rules', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -320,26 +364,26 @@ describe('Evidence Rules', () => {
|
|||||||
test('Evidence error handling', () => {
|
test('Evidence error handling', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
hasRole(user, 'admin'
|
hasRole(user, 'admin'
|
||||||
}`,
|
}`,
|
||||||
description: 'Missing closing parenthesis should fail'
|
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
|
WHEN hasRole(user, 'admin') UNLESS
|
||||||
}`,
|
}`,
|
||||||
description: 'Incomplete UNLESS condition should fail'
|
description: 'Incomplete UNLESS condition should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
fusion min {
|
fusion min {
|
||||||
hasRole(user, 'admin')
|
hasRole(user, 'admin')
|
||||||
}`,
|
}`,
|
||||||
description: 'Incomplete fusion should fail'
|
description: 'Incomplete fusion should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
canRead(group, doc)
|
canRead(group, doc)
|
||||||
} with
|
} with
|
||||||
@@ -347,7 +391,7 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Incomplete with clause should fail'
|
description: 'Incomplete with clause should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
canRead(group, doc)
|
canRead(group, doc)
|
||||||
} limit
|
} limit
|
||||||
@@ -355,7 +399,7 @@ describe('Evidence Rules', () => {
|
|||||||
description: 'Incomplete limit should fail'
|
description: 'Incomplete limit should fail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `evidence canRead(user: User, doc: Document) {
|
input: `evidence canRead(user: Employee, doc: Document) {
|
||||||
invalid syntax here
|
invalid syntax here
|
||||||
}`,
|
}`,
|
||||||
description: 'Invalid syntax should fail'
|
description: 'Invalid syntax should fail'
|
||||||
@@ -364,7 +408,7 @@ describe('Evidence Rules', () => {
|
|||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
testCases.forEach(({ input, description }) => {
|
||||||
try {
|
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`);
|
assert.ok(!result.success, `${description} should fail to parse`);
|
||||||
} catch {
|
} catch {
|
||||||
// Expected to fail
|
// Expected to fail
|
||||||
|
|||||||
+90
-35
@@ -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', () => {
|
describe('Expression Parsing', () => {
|
||||||
const arbiter = createMockArbiter();
|
const arbiter = createMockArbiter();
|
||||||
const compiler = new DSLCompiler(arbiter);
|
const compiler = new DSLCompiler(arbiter);
|
||||||
@@ -19,34 +70,34 @@ describe('Expression Parsing', () => {
|
|||||||
test('Arithmetic operator precedence', () => {
|
test('Arithmetic operator precedence', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: '1 + 2 * 3',
|
input: 'score(1 + 2 * 3) > score(0)',
|
||||||
expected: 'Should evaluate as 1 + (2 * 3) = 7',
|
expected: 'Should evaluate as 1 + (2 * 3) = 7',
|
||||||
description: 'Multiplication before addition'
|
description: 'Multiplication before addition'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: '10 - 3 * 2',
|
input: 'score(10 - 3 * 2) > score(0)',
|
||||||
expected: 'Should evaluate as 10 - (3 * 2) = 4',
|
expected: 'Should evaluate as 10 - (3 * 2) = 4',
|
||||||
description: 'Multiplication before subtraction'
|
description: 'Multiplication before subtraction'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: '8 / 2 * 4',
|
input: 'score(8 / 2 * 4) > score(0)',
|
||||||
expected: 'Should evaluate as (8 / 2) * 4 = 16',
|
expected: 'Should evaluate as (8 / 2) * 4 = 16',
|
||||||
description: 'Left-associative division and multiplication'
|
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',
|
expected: 'Should evaluate as 2 + (3 * 4) - 5 = 9',
|
||||||
description: 'Mixed arithmetic with correct precedence'
|
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',
|
expected: 'Should evaluate as (1 + 2) * 3 = 9',
|
||||||
description: 'Parentheses override precedence'
|
description: 'Parentheses override precedence'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, expected, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-arithmetic-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -82,7 +133,7 @@ describe('Expression Parsing', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, expected, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-logical-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -90,18 +141,19 @@ describe('Expression Parsing', () => {
|
|||||||
|
|
||||||
test('Comparison operators', () => {
|
test('Comparison operators', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{ input: '1 == 1', description: 'Equality comparison' },
|
{ input: 'score(1) == score(1)', description: 'Equality comparison', measure: false },
|
||||||
{ input: '1 != 2', description: 'Inequality comparison' },
|
{ input: 'score(1) != score(2)', description: 'Inequality comparison', measure: false },
|
||||||
{ input: '5 > 3', description: 'Greater than' },
|
{ input: 'score(5) > score(3)', description: 'Greater than', measure: false },
|
||||||
{ input: '3 < 5', description: 'Less than' },
|
{ input: 'score(3) < score(5)', description: 'Less than', measure: false },
|
||||||
{ input: '4 >= 4', description: 'Greater than or equal' },
|
{ input: 'score(4) >= score(4)', description: 'Greater than or equal', measure: false },
|
||||||
{ input: '4 <= 4', description: 'Less than or equal' },
|
{ input: 'score(4) <= score(4)', description: 'Less than or equal', measure: false },
|
||||||
{ input: '1 == 1 && 2 > 1', description: 'Comparison with logical operators' },
|
{ input: 'score(1) == score(1) && score(2) > score(1)', description: 'Comparison with logical operators', measure: true },
|
||||||
{ input: '1 + 2 == 3', description: 'Arithmetic in comparison' }
|
{ input: 'score(1 + 2) == score(3)', description: 'Arithmetic in comparison', measure: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
testCases.forEach(({ input, description, measure }) => {
|
||||||
const dsl = `evidence test() { ${input} }`;
|
const body = `test() { ${input} }`;
|
||||||
|
const dsl = SCORE_FACT + (measure ? `measure ${body} PROVIDES boolean` : `evidence ${body}`);
|
||||||
const result = compiler.compile(dsl, `test-comparison-${Date.now()}`);
|
const result = compiler.compile(dsl, `test-comparison-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -116,7 +168,7 @@ describe('Expression Parsing', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-temporal-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -124,14 +176,14 @@ describe('Expression Parsing', () => {
|
|||||||
|
|
||||||
test('Unary operators', () => {
|
test('Unary operators', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{ input: 'NOT true', description: 'NOT operator' },
|
{ input: 'NOT true', description: 'NOT operator', params: '' },
|
||||||
{ input: '!false', description: 'Alternative NOT operator' },
|
{ input: '! false', description: 'Alternative NOT operator', params: '' },
|
||||||
{ input: 'NOT (true && false)', description: 'NOT with parenthesized expression' },
|
{ input: 'NOT (true && false)', description: 'NOT with parenthesized expression', params: '' },
|
||||||
{ input: 'NOT user.isSuspended', description: 'NOT with attribute access' }
|
{ input: 'NOT user.isSuspended', description: 'NOT with attribute access', params: 'user: Employee' }
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
testCases.forEach(({ input, description, params }) => {
|
||||||
const dsl = `evidence test() { ${input} }`;
|
const dsl = EMPLOYEE_FIELDS + `measure test(${params}) { ${input} } PROVIDES boolean`;
|
||||||
const result = compiler.compile(dsl, `test-unary-${Date.now()}`);
|
const result = compiler.compile(dsl, `test-unary-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -141,13 +193,13 @@ describe('Expression Parsing', () => {
|
|||||||
const testCases = [
|
const testCases = [
|
||||||
{ input: 'user.role', description: 'Simple attribute access' },
|
{ input: 'user.role', description: 'Simple attribute access' },
|
||||||
{ input: 'user.profile.name', description: 'Nested attribute access' },
|
{ input: 'user.profile.name', description: 'Nested attribute access' },
|
||||||
{ input: 'user.permissions[0]', description: 'Array access' },
|
{ input: 'user.permissions', description: 'Array access' },
|
||||||
{ input: 'user.role.permissions[0]', description: 'Nested attribute with array access' },
|
{ input: 'user.profile.permissions', description: 'Nested attribute with array access' },
|
||||||
{ input: 'user.isActive && user.role == "admin"', description: 'Attribute access in logical expression' }
|
{ input: 'user.isActive && user.role == "admin"', description: 'Attribute access in logical expression' }
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-attribute-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -155,15 +207,17 @@ describe('Expression Parsing', () => {
|
|||||||
|
|
||||||
test('Function calls', () => {
|
test('Function calls', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{ input: 'hasRole(user, "admin")', description: 'Simple function call' },
|
{ input: 'hasRole(user, "admin")', description: 'Simple function call', measure: false },
|
||||||
{ input: 'isMember(user, group)', description: 'Function call with variables' },
|
{ input: 'isMember(user, group)', description: 'Function call with variables', measure: false },
|
||||||
{ input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments' },
|
{ input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments', measure: false },
|
||||||
{ input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls' },
|
{ input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls', measure: true },
|
||||||
{ input: 'hasRole(user, user.role)', description: 'Function call with attribute access' }
|
{ input: 'hasRole(user, user.role)', description: 'Function call with attribute access', measure: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
testCases.forEach(({ input, description, measure }) => {
|
||||||
const dsl = `evidence test() { ${input} }`;
|
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()}`);
|
const result = compiler.compile(dsl, `test-function-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -194,7 +248,8 @@ describe('Expression Parsing', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-complex-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
|
|||||||
+25
-7
@@ -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', () => {
|
describe('Fact Declarations', () => {
|
||||||
const arbiter = createMockArbiter();
|
const arbiter = createMockArbiter();
|
||||||
const compiler = new DSLCompiler(arbiter);
|
const compiler = new DSLCompiler(arbiter);
|
||||||
@@ -41,7 +59,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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.success, `${description} should parse successfully`);
|
||||||
assert.ok(result.program.facts.length > 0, 'Should have facts');
|
assert.ok(result.program.facts.length > 0, 'Should have facts');
|
||||||
});
|
});
|
||||||
@@ -72,7 +90,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -102,7 +120,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -132,7 +150,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -149,7 +167,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ type, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-param-type-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -182,7 +200,7 @@ describe('Fact Declarations', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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.success, `${description} should parse successfully`);
|
||||||
assert.ok(result.program.facts.length > 0, 'Should have facts');
|
assert.ok(result.program.facts.length > 0, 'Should have facts');
|
||||||
});
|
});
|
||||||
@@ -222,7 +240,7 @@ describe('Fact Declarations', () => {
|
|||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
testCases.forEach(({ input, description }) => {
|
||||||
try {
|
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`);
|
assert.ok(!result.success, `${description} should fail to parse`);
|
||||||
} catch {
|
} catch {
|
||||||
// Expected to fail
|
// Expected to fail
|
||||||
|
|||||||
+187
-127
@@ -19,8 +19,9 @@ describe('Integration Tests', () => {
|
|||||||
test('Complete authorization system', () => {
|
test('Complete authorization system', () => {
|
||||||
const completeSystem = `
|
const completeSystem = `
|
||||||
// Type definitions with complex behaviors
|
// Type definitions with complex behaviors
|
||||||
definition User {
|
definition Employee {
|
||||||
role: string
|
role: Role
|
||||||
|
group: Group
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
lastActive: timestamp BEHAVES {
|
lastActive: timestamp BEHAVES {
|
||||||
decaying down hourly
|
decaying down hourly
|
||||||
@@ -41,21 +42,34 @@ describe('Integration Tests', () => {
|
|||||||
reputation: number BEHAVES {
|
reputation: number BEHAVES {
|
||||||
decaying up daily
|
decaying up daily
|
||||||
} CACHE lazy
|
} CACHE lazy
|
||||||
|
activityScore: number
|
||||||
|
verificationLevel: number
|
||||||
|
}
|
||||||
|
|
||||||
|
definition Role {
|
||||||
|
permissions: Permission[]
|
||||||
|
clearance: string
|
||||||
}
|
}
|
||||||
|
|
||||||
definition Group {
|
definition Group {
|
||||||
name: string
|
name: string
|
||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
level: string
|
level: string
|
||||||
|
clearance: string
|
||||||
isPublic: boolean CACHE eager
|
isPublic: boolean CACHE eager
|
||||||
created: timestamp BEHAVES {
|
created: timestamp BEHAVES {
|
||||||
decaying stable monthly
|
decaying stable monthly
|
||||||
} CACHE lazy
|
} CACHE lazy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definition Permission {
|
||||||
|
name: string
|
||||||
|
level: string
|
||||||
|
}
|
||||||
|
|
||||||
definition Document {
|
definition Document {
|
||||||
level: string
|
level: string
|
||||||
owner: User
|
owner: Employee
|
||||||
tags: string[]
|
tags: string[]
|
||||||
content: string BEHAVES {
|
content: string BEHAVES {
|
||||||
blurring fixed
|
blurring fixed
|
||||||
@@ -71,7 +85,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
definition Resource {
|
definition Resource {
|
||||||
level: string
|
level: string
|
||||||
owner: User
|
owner: Employee
|
||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
isPublic: boolean CACHE eager
|
isPublic: boolean CACHE eager
|
||||||
accessCount: number BEHAVES {
|
accessCount: number BEHAVES {
|
||||||
@@ -80,20 +94,28 @@ describe('Integration Tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Facts with various properties and caching
|
// Facts with various properties and caching
|
||||||
fact hasRole(user: User, role: string) CACHE eager
|
fact hasRole(user: Employee, role: string) CACHE eager
|
||||||
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
|
fact isMember(user: any, group: any) transitive CACHE lazy limit 10
|
||||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 100
|
||||||
fact owns(user: User, doc: Document) CACHE eager
|
fact owns(user: Employee, doc: Document) CACHE eager
|
||||||
fact isSuspended(user: User) CACHE lazy
|
fact isSuspended(user: Employee) CACHE lazy
|
||||||
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
fact hasPermission(user: Employee, resource: Resource, action: string) CACHE eager
|
||||||
fact isAdmin(user: User) CACHE eager
|
fact isAdmin(user: Employee) CACHE eager
|
||||||
fact isOwner(user: User, resource: Resource) CACHE eager
|
fact isOwner(user: Employee, resource: Resource) CACHE eager
|
||||||
fact hasAccess(user: User, resource: Resource, level: string) CACHE lazy
|
fact hasAccess(user: Employee, resource: Resource, level: string) CACHE lazy
|
||||||
fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 50
|
fact isColleague(user: any, colleague: any) symmetrical CACHE lazy limit 50
|
||||||
fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3
|
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 rules with complex logic
|
||||||
evidence canRead(user: User, doc: Document) {
|
evidence canRead(user: Employee, doc: Document) {
|
||||||
owns(user, doc)
|
owns(user, doc)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -106,12 +128,12 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
similar(doc, *similar) |similarity| {
|
similar(doc, *similar) |similarity| {
|
||||||
canRead(user, similar)
|
canRead(user, similar)
|
||||||
} with similarity > 0.7 limit 5
|
} limit 5 with similarity > 0.7
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
evidence canWrite(user: User, doc: Document) {
|
evidence canWrite(user: Employee, doc: Document) {
|
||||||
owns(user, doc)
|
owns(user, doc)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -120,82 +142,83 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
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)
|
owns(user, doc)
|
||||||
|
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(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 {
|
fusion min {
|
||||||
hasClearance(user, resource.level)
|
hasClearance(user, resource.level),
|
||||||
user.isActive
|
isActive(user),
|
||||||
NOT user.isBlacklisted
|
NOT isBlacklisted(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fusion max {
|
fusion max {
|
||||||
hasRole(user, 'admin')
|
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) {
|
evidence canAccessSensitive(user: Employee, doc: Document) {
|
||||||
ALWAYS user.isActive
|
ALWAYS isActive(user)
|
||||||
|
|
||||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||||
|
|
||||||
REQUIRES hasClearance(user, doc.level)
|
REQUIRES hasClearance(user, doc.level)
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.isTrusted
|
isTrusted(user),
|
||||||
user.hasRecentActivity
|
hasRecentActivity(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measures for computed values
|
// Measures for computed values
|
||||||
measure userRole(user: User) {
|
measure userRole(user: Employee) {
|
||||||
user.role
|
user.role
|
||||||
} PROVIDES string
|
} PROVIDES string
|
||||||
|
|
||||||
measure userPermissions(user: User) {
|
measure userPermissions(user: Employee) {
|
||||||
fusion max {
|
fusion max {
|
||||||
user.role.permissions
|
user.role.permissions,
|
||||||
user.group.permissions
|
user.group.permissions
|
||||||
}
|
}
|
||||||
} PROVIDES Permission[]
|
} PROVIDES Permission
|
||||||
|
|
||||||
measure effectiveClearance(user: User) {
|
measure effectiveClearance(user: Employee) {
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.clearance
|
user.clearance,
|
||||||
user.role.clearance
|
user.role.clearance,
|
||||||
user.group.clearance
|
user.group.clearance
|
||||||
}
|
}
|
||||||
} PROVIDES string
|
} PROVIDES string
|
||||||
|
|
||||||
measure userTrustScore(user: User) {
|
measure userTrustScore(user: Employee) {
|
||||||
fusion average {
|
fusion average {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel
|
||||||
}
|
}
|
||||||
} PROVIDES number
|
} PROVIDES number
|
||||||
|
|
||||||
measure userBalance(user: User) {
|
measure userBalance(user: Employee) {
|
||||||
user.balance
|
user.balance
|
||||||
} PROVIDES number
|
} PROVIDES number
|
||||||
|
|
||||||
measure userScore(user: User) {
|
measure userScore(user: Employee) {
|
||||||
user.score
|
user.score
|
||||||
} PROVIDES number
|
} PROVIDES number
|
||||||
`;
|
`;
|
||||||
@@ -211,64 +234,71 @@ describe('Integration Tests', () => {
|
|||||||
test('Multi-domain system', () => {
|
test('Multi-domain system', () => {
|
||||||
const multiDomain = `
|
const multiDomain = `
|
||||||
// Authentication domain
|
// Authentication domain
|
||||||
definition User {
|
definition Employee {
|
||||||
role: string
|
role: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
||||||
session: string BEHAVES { ttl 24h } CACHE eager
|
session: string BEHAVES { ttl 24h } CACHE eager
|
||||||
}
|
}
|
||||||
|
|
||||||
fact hasRole(user: User, role: string) CACHE eager
|
fact hasRole(user: Employee, role: string) CACHE eager
|
||||||
fact isActive(user: User) CACHE eager
|
fact isActive(user: Employee) CACHE eager
|
||||||
|
fact recentlyActive(user: any) CACHE lazy
|
||||||
|
fact isPublic(doc: any) CACHE eager
|
||||||
|
|
||||||
evidence canAuthenticate(user: User) {
|
evidence canAuthenticate(user: Employee) {
|
||||||
user.isActive
|
isActive(user)
|
||||||
user.session within 24h
|
recentlyActive(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization domain
|
// Authorization domain
|
||||||
definition Resource {
|
definition Resource {
|
||||||
level: string
|
level: string
|
||||||
owner: User
|
owner: Employee
|
||||||
permissions: Permission[]
|
permissions: Permission[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fact owns(user: User, resource: Resource) CACHE eager
|
definition Permission {
|
||||||
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
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)
|
owns(user, resource)
|
||||||
hasPermission(user, resource, 'read')
|
hasPermission(user, resource, 'read')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finance domain
|
// Finance domain
|
||||||
definition Account {
|
definition Tenant {
|
||||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||||
owner: User
|
owner: Employee
|
||||||
isActive: boolean CACHE eager
|
isActive: boolean CACHE eager
|
||||||
}
|
}
|
||||||
|
|
||||||
fact hasAccount(user: User, account: Account) CACHE eager
|
fact hasAccount(user: Employee, account: Tenant) CACHE eager
|
||||||
fact hasBalance(user: User, amount: number) 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)
|
hasBalance(user, amount)
|
||||||
user.isActive
|
isActive(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Social domain
|
// Social domain
|
||||||
definition Group {
|
definition Group {
|
||||||
name: string
|
name: string
|
||||||
members: User[]
|
members: Employee[]
|
||||||
isPublic: boolean CACHE eager
|
isPublic: boolean CACHE eager
|
||||||
}
|
}
|
||||||
|
|
||||||
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
|
fact isMember(user: any, group: any) transitive CACHE lazy limit 10
|
||||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
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)
|
isMember(user, group)
|
||||||
group.isPublic
|
isPublic(group)
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -281,7 +311,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
test('Hierarchical access', () => {
|
test('Hierarchical access', () => {
|
||||||
const hierarchicalSystem = `
|
const hierarchicalSystem = `
|
||||||
definition User {
|
definition Employee {
|
||||||
role: string
|
role: string
|
||||||
level: string
|
level: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
@@ -294,22 +324,28 @@ describe('Integration Tests', () => {
|
|||||||
parent: Organization
|
parent: Organization
|
||||||
}
|
}
|
||||||
|
|
||||||
fact isMember(user: User, org: Organization) transitive CACHE lazy limit 5
|
definition Resource {
|
||||||
fact isParentOf(parent: Organization, child: Organization) transitive CACHE eager limit 3
|
level: string
|
||||||
fact hasRole(user: User, role: string) CACHE eager
|
}
|
||||||
fact hasClearance(user: User, level: string) CACHE eager
|
|
||||||
|
|
||||||
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)
|
isMember(user, org)
|
||||||
|
|
||||||
isParentOf(org, *parentOrg) {
|
isParentOf(org, *parentOrg) {
|
||||||
canAccessOrg(user, parentOrg)
|
canAccessOrg(user, parentOrg)
|
||||||
} limit 3
|
} 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) {
|
isMember(user, *org) {
|
||||||
canAccessResource(org, resource)
|
canAccessResource(org, resource)
|
||||||
} limit 5
|
} limit 5
|
||||||
@@ -326,7 +362,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
test('Similarity-based access', () => {
|
test('Similarity-based access', () => {
|
||||||
const similaritySystem = `
|
const similaritySystem = `
|
||||||
definition User {
|
definition Employee {
|
||||||
profile: string
|
profile: string
|
||||||
interests: string[]
|
interests: string[]
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
@@ -336,39 +372,46 @@ describe('Integration Tests', () => {
|
|||||||
content: string
|
content: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
isPublic: boolean
|
isPublic: boolean
|
||||||
owner: User
|
owner: Employee
|
||||||
}
|
}
|
||||||
|
|
||||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 100
|
||||||
fact hasInterest(user: User, interest: string) CACHE lazy
|
fact hasInterest(user: any, interest: string) CACHE lazy
|
||||||
fact hasTag(doc: Document, tag: 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)
|
owns(user, doc)
|
||||||
|
|
||||||
similar(doc, *similar) |similarity| {
|
similar(doc, *similar) |similarity| {
|
||||||
canRead(user, similar)
|
canRead(user, similar)
|
||||||
similar.isPublic
|
isPublic(similar)
|
||||||
} with similarity > 0.7 limit 10
|
} limit 10 with similarity > 0.7
|
||||||
|
|
||||||
isFriend(user, *friend) {
|
isFriend(user, *friend) {
|
||||||
canRead(friend, doc)
|
canRead(friend, doc)
|
||||||
} limit 5
|
} limit 5
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.interests
|
hasInterests(user),
|
||||||
doc.tags
|
hasTags(doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evidence canRecommend(user: User, doc: Document) {
|
evidence canRecommend(user: Employee, doc: Document) {
|
||||||
similar(user, *similarUser) |similarity| {
|
similar(user, *similarUser) |similarity| {
|
||||||
canRead(similarUser, doc)
|
canRead(similarUser, doc)
|
||||||
} with similarity > 0.8 limit 20
|
} limit 20 with similarity > 0.8
|
||||||
|
|
||||||
fusion average {
|
fusion average {
|
||||||
user.profile
|
hasProfile(user),
|
||||||
doc.content
|
hasContent(doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -379,7 +422,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
test('Temporal access', () => {
|
test('Temporal access', () => {
|
||||||
const temporalSystem = `
|
const temporalSystem = `
|
||||||
definition User {
|
definition Employee {
|
||||||
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
||||||
session: string BEHAVES { ttl 24h } CACHE eager
|
session: string BEHAVES { ttl 24h } CACHE eager
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
@@ -391,29 +434,34 @@ describe('Integration Tests', () => {
|
|||||||
isPublic: boolean
|
isPublic: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
fact hasAccess(user: User, event: Event) CACHE lazy
|
fact hasAccess(user: Employee, event: Event) CACHE lazy
|
||||||
fact isParticipant(user: User, event: Event) CACHE eager
|
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) {
|
evidence canAccessEvent(user: Employee, event: Event) {
|
||||||
user.lastActive within 1h
|
recentlyActive(user)
|
||||||
|
|
||||||
isParticipant(user, event)
|
isParticipant(user, event)
|
||||||
|
|
||||||
WHEN event.isPublic UNLESS user.isSuspended
|
WHEN isPublic(event) UNLESS isSuspended(user)
|
||||||
|
|
||||||
fusion min {
|
fusion min {
|
||||||
user.session within 24h
|
sessionFresh(user),
|
||||||
user.isActive
|
isActive(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evidence canAccessHistorical(user: User, event: Event) {
|
evidence canAccessHistorical(user: Employee, event: Event) {
|
||||||
user.lastActive within 24h
|
recentlyActive(user)
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.isActive
|
isActive(user),
|
||||||
user.session within 24h
|
sessionFresh(user),
|
||||||
event.isPublic
|
isPublic(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -424,7 +472,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
test('Complex behaviors', () => {
|
test('Complex behaviors', () => {
|
||||||
const behaviorSystem = `
|
const behaviorSystem = `
|
||||||
definition User {
|
definition Employee {
|
||||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||||
score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy
|
score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy
|
||||||
session: string BEHAVES { ttl 24h } CACHE eager
|
session: string BEHAVES { ttl 24h } CACHE eager
|
||||||
@@ -440,30 +488,37 @@ describe('Integration Tests', () => {
|
|||||||
isPublic: boolean CACHE eager
|
isPublic: boolean CACHE eager
|
||||||
}
|
}
|
||||||
|
|
||||||
fact hasBalance(user: User, amount: number) CACHE eager
|
fact hasBalance(user: Employee, amount: number) CACHE eager
|
||||||
fact hasScore(user: User, score: number) CACHE lazy
|
fact hasScore(user: Employee, score: number) CACHE lazy
|
||||||
fact hasReputation(user: User, reputation: 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) {
|
evidence canAccessDocument(user: Employee, doc: Document) {
|
||||||
user.balance > 0
|
hasPositiveBalance(user)
|
||||||
|
|
||||||
user.score > 0.5
|
hasHighScore(user)
|
||||||
|
|
||||||
user.reputation > 0.3
|
hasGoodReputation(user)
|
||||||
|
|
||||||
doc.accessCount < 1000
|
isNotOverused(doc)
|
||||||
|
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.isActive
|
isActive(user),
|
||||||
user.lastActive within 1h
|
recentlyActive(user),
|
||||||
doc.isPublic
|
isPublic(doc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
measure userEffectiveScore(user: User) {
|
measure userEffectiveScore(user: Employee) {
|
||||||
fusion average {
|
fusion average {
|
||||||
user.score
|
user.score,
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.balance
|
user.balance
|
||||||
}
|
}
|
||||||
} PROVIDES number
|
} PROVIDES number
|
||||||
@@ -479,7 +534,7 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
test('Performance scenarios', () => {
|
test('Performance scenarios', () => {
|
||||||
const performanceSystem = `
|
const performanceSystem = `
|
||||||
definition User {
|
definition Employee {
|
||||||
role: string
|
role: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
permissions: Permission[] CACHE eager
|
permissions: Permission[] CACHE eager
|
||||||
@@ -487,18 +542,23 @@ describe('Integration Tests', () => {
|
|||||||
|
|
||||||
definition Resource {
|
definition Resource {
|
||||||
level: string
|
level: string
|
||||||
owner: User
|
owner: Employee
|
||||||
permissions: Permission[] CACHE eager
|
permissions: Permission[] CACHE eager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definition Permission {
|
||||||
|
name: string
|
||||||
|
level: string
|
||||||
|
}
|
||||||
|
|
||||||
// High-frequency facts with limits
|
// High-frequency facts with limits
|
||||||
fact isMember(user: User, group: Group) transitive CACHE lazy limit 5
|
fact isMember(user: any, group: any) transitive CACHE lazy limit 5
|
||||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50
|
fact isFriend(user: any, friend: any) symmetrical CACHE eager limit 50
|
||||||
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
fact hasPermission(user: Employee, resource: Resource, action: string) CACHE eager
|
||||||
fact owns(user: User, resource: Resource) CACHE eager
|
fact owns(user: Employee, resource: Resource) CACHE eager
|
||||||
|
|
||||||
// Optimized evidence rules
|
// Optimized evidence rules
|
||||||
evidence canAccess(user: User, resource: Resource) {
|
evidence canAccess(user: Employee, resource: Resource) {
|
||||||
owns(user, resource)
|
owns(user, resource)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -508,7 +568,7 @@ describe('Integration Tests', () => {
|
|||||||
WHEN hasPermission(user, resource, 'read')
|
WHEN hasPermission(user, resource, 'read')
|
||||||
}
|
}
|
||||||
|
|
||||||
evidence canModify(user: User, resource: Resource) {
|
evidence canModify(user: Employee, resource: Resource) {
|
||||||
owns(user, resource)
|
owns(user, resource)
|
||||||
|
|
||||||
isMember(user, *group) {
|
isMember(user, *group) {
|
||||||
@@ -519,9 +579,9 @@ describe('Integration Tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Efficient measures
|
// Efficient measures
|
||||||
measure userEffectivePermissions(user: User) {
|
measure userEffectivePermissions(user: Employee) {
|
||||||
user.permissions
|
user.permissions
|
||||||
} PROVIDES Permission[]
|
} PROVIDES Permission
|
||||||
|
|
||||||
measure resourceAccessLevel(resource: Resource) {
|
measure resourceAccessLevel(resource: Resource) {
|
||||||
resource.level
|
resource.level
|
||||||
|
|||||||
+104
-67
@@ -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', () => {
|
describe('Measure Definitions', () => {
|
||||||
const arbiter = createMockArbiter();
|
const arbiter = createMockArbiter();
|
||||||
const compiler = new DSLCompiler(arbiter);
|
const compiler = new DSLCompiler(arbiter);
|
||||||
@@ -19,31 +56,31 @@ describe('Measure Definitions', () => {
|
|||||||
test('Basic measures', () => {
|
test('Basic measures', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `measure userRole(user: User) {
|
input: `measure userRole(user: Employee) {
|
||||||
user.role
|
user.role
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Simple measure with attribute access'
|
description: 'Simple measure with attribute access'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userBalance(user: User) {
|
input: `measure userBalance(user: Employee) {
|
||||||
user.balance
|
user.balance
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
description: 'Measure accessing numeric attribute'
|
description: 'Measure accessing numeric attribute'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure isUserActive(user: User) {
|
input: `measure isUserActive(user: Employee) {
|
||||||
user.isActive
|
user.isActive
|
||||||
} PROVIDES boolean`,
|
} PROVIDES boolean`,
|
||||||
description: 'Measure accessing boolean attribute'
|
description: 'Measure accessing boolean attribute'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userPermissions(user: User) {
|
input: `measure userPermissions(user: Employee) {
|
||||||
user.permissions
|
user.permissions
|
||||||
} PROVIDES Permission[]`,
|
} PROVIDES Permission`,
|
||||||
description: 'Measure accessing array attribute'
|
description: 'Measure accessing array attribute'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userScore(user: User) {
|
input: `measure userScore(user: Employee) {
|
||||||
user.score
|
user.score
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
description: 'Measure with behavior-inherited attribute'
|
description: 'Measure with behavior-inherited attribute'
|
||||||
@@ -51,7 +88,7 @@ describe('Measure Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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.success, `${description} should parse successfully`);
|
||||||
assert.ok(result.program.measures.length > 0, 'Should have measures');
|
assert.ok(result.program.measures.length > 0, 'Should have measures');
|
||||||
});
|
});
|
||||||
@@ -63,13 +100,13 @@ describe('Measure Definitions', () => {
|
|||||||
{ type: 'number', description: 'Number return type' },
|
{ type: 'number', description: 'Number return type' },
|
||||||
{ type: 'boolean', description: 'Boolean return type' },
|
{ type: 'boolean', description: 'Boolean return type' },
|
||||||
{ type: 'timestamp', description: 'Timestamp return type' },
|
{ type: 'timestamp', description: 'Timestamp return type' },
|
||||||
{ type: 'Permission[]', description: 'Array return type' },
|
{ type: 'Permission', description: 'Array return type' },
|
||||||
{ type: 'User', description: 'Custom type return' },
|
{ type: 'Employee', description: 'Custom type return' },
|
||||||
{ type: 'Group[]', description: 'Custom array return type' }
|
{ type: 'Group', description: 'Custom array return type' }
|
||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ type, description }) => {
|
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()}`);
|
const result = compiler.compile(dsl, `test-measure-return-${Date.now()}`);
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
@@ -78,40 +115,40 @@ describe('Measure Definitions', () => {
|
|||||||
test('Measure aggregation', () => {
|
test('Measure aggregation', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `measure userPermissions(user: User) {
|
input: `measure userPermissions(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.role.permissions
|
user.role.permissions,
|
||||||
user.group.permissions
|
user.group.permissions
|
||||||
} USING majority
|
} USING majority
|
||||||
} PROVIDES Permission[]`,
|
} PROVIDES Permission`,
|
||||||
description: 'Aggregation with majority strategy'
|
description: 'Aggregation with majority strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userClearance(user: User) {
|
input: `measure userClearance(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.clearance
|
user.clearance,
|
||||||
user.role.clearance
|
user.role.clearance,
|
||||||
user.group.clearance
|
user.group.clearance
|
||||||
} USING max
|
} USING max
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Aggregation with max strategy'
|
description: 'Aggregation with max strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userScore(user: User) {
|
input: `measure userScore(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel
|
||||||
} USING average
|
} USING average
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
description: 'Aggregation with average strategy'
|
description: 'Aggregation with average strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userTrust(user: User) {
|
input: `measure userTrust(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel,
|
||||||
user.socialProof
|
user.socialProof
|
||||||
} USING min
|
} USING min
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
@@ -120,7 +157,7 @@ describe('Measure Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -128,40 +165,40 @@ describe('Measure Definitions', () => {
|
|||||||
test('Measure fusion', () => {
|
test('Measure fusion', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `measure effectiveClearance(user: User) {
|
input: `measure effectiveClearance(user: Employee) {
|
||||||
fusion max {
|
fusion max {
|
||||||
user.clearance
|
user.clearance,
|
||||||
user.role.clearance
|
user.role.clearance,
|
||||||
user.group.clearance
|
user.group.clearance
|
||||||
}
|
}
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Fusion with max strategy'
|
description: 'Fusion with max strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userPermissions(user: User) {
|
input: `measure userPermissions(user: Employee) {
|
||||||
fusion min {
|
fusion min {
|
||||||
user.role.permissions
|
user.role.permissions,
|
||||||
user.group.permissions
|
user.group.permissions
|
||||||
}
|
}
|
||||||
} PROVIDES Permission[]`,
|
} PROVIDES Permission`,
|
||||||
description: 'Fusion with min strategy'
|
description: 'Fusion with min strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userScore(user: User) {
|
input: `measure userScore(user: Employee) {
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel
|
||||||
}
|
}
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
description: 'Fusion with majority strategy'
|
description: 'Fusion with majority strategy'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userTrust(user: User) {
|
input: `measure userTrust(user: Employee) {
|
||||||
fusion average {
|
fusion average {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel,
|
||||||
user.socialProof
|
user.socialProof
|
||||||
}
|
}
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
@@ -170,7 +207,7 @@ describe('Measure Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -178,40 +215,40 @@ describe('Measure Definitions', () => {
|
|||||||
test('Complex measures', () => {
|
test('Complex measures', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `measure userEffectivePermissions(user: User) {
|
input: `measure userEffectivePermissions(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.role.permissions
|
user.role.permissions,
|
||||||
user.group.permissions
|
user.group.permissions,
|
||||||
user.directPermissions
|
user.directPermissions
|
||||||
} USING majority
|
} USING majority
|
||||||
} PROVIDES Permission[]`,
|
} PROVIDES Permission`,
|
||||||
description: 'Complex aggregation with multiple sources'
|
description: 'Complex aggregation with multiple sources'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userTrustScore(user: User) {
|
input: `measure userTrustScore(user: Employee) {
|
||||||
fusion average {
|
fusion average {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore,
|
||||||
user.verificationLevel
|
user.verificationLevel,
|
||||||
user.socialProof
|
user.socialProof,
|
||||||
user.peerRatings
|
user.peerRatings
|
||||||
}
|
}
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
description: 'Complex fusion with multiple metrics'
|
description: 'Complex fusion with multiple metrics'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userAccessLevel(user: User) {
|
input: `measure userAccessLevel(user: Employee) {
|
||||||
fusion max {
|
fusion max {
|
||||||
user.clearance
|
user.clearance,
|
||||||
user.role.clearance
|
user.role.clearance,
|
||||||
user.group.clearance
|
user.group.clearance,
|
||||||
user.temporaryClearance
|
user.temporaryClearance
|
||||||
}
|
}
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Complex clearance calculation'
|
description: 'Complex clearance calculation'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userSimilarity(user1: User, user2: User) {
|
input: `measure userSimilarity(user1: Employee, user2: Employee) {
|
||||||
similar(user1, user2) |similarity| {
|
similar(user1, user2) |similarity| {
|
||||||
similarity
|
similarity
|
||||||
} with similarity > 0.5
|
} with similarity > 0.5
|
||||||
@@ -219,10 +256,10 @@ describe('Measure Definitions', () => {
|
|||||||
description: 'Similarity measure with pattern matching'
|
description: 'Similarity measure with pattern matching'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userEffectiveRole(user: User) {
|
input: `measure userEffectiveRole(user: Employee) {
|
||||||
fusion majority {
|
fusion majority {
|
||||||
user.role
|
user.role,
|
||||||
user.temporaryRole
|
user.temporaryRole,
|
||||||
user.actingRole
|
user.actingRole
|
||||||
}
|
}
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
@@ -231,7 +268,7 @@ describe('Measure Definitions', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
testCases.forEach(({ input, description }) => {
|
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`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -239,40 +276,40 @@ describe('Measure Definitions', () => {
|
|||||||
test('Measure error handling', () => {
|
test('Measure error handling', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: `measure userRole(user: User) {
|
input: `measure userRole(user: Employee) {
|
||||||
user.role
|
user.role
|
||||||
}`,
|
}`,
|
||||||
description: 'Missing PROVIDES clause should fail',
|
description: 'Missing PROVIDES clause should fail',
|
||||||
expectSuccess: false
|
expectSuccess: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userRole(user: User) {
|
input: `measure userRole(user: Employee) {
|
||||||
user.role
|
user.role
|
||||||
} PROVIDES`,
|
} PROVIDES`,
|
||||||
description: 'Incomplete PROVIDES clause should fail',
|
description: 'Incomplete PROVIDES clause should fail',
|
||||||
expectSuccess: false
|
expectSuccess: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userRole(user: User) {
|
input: `measure userRole(user: Employee) {
|
||||||
user.role
|
user.role
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Valid measure should succeed',
|
description: 'Valid measure should succeed',
|
||||||
expectSuccess: true
|
expectSuccess: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userPermissions(user: User) {
|
input: `measure userPermissions(user: Employee) {
|
||||||
aggregate {
|
aggregate {
|
||||||
user.role.permissions
|
user.role.permissions,
|
||||||
user.group.permissions
|
user.group.permissions
|
||||||
} USING
|
} USING
|
||||||
} PROVIDES Permission[]`,
|
} PROVIDES Permission`,
|
||||||
description: 'Incomplete USING clause should fail',
|
description: 'Incomplete USING clause should fail',
|
||||||
expectSuccess: false
|
expectSuccess: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userScore(user: User) {
|
input: `measure userScore(user: Employee) {
|
||||||
fusion {
|
fusion {
|
||||||
user.reputation
|
user.reputation,
|
||||||
user.activityScore
|
user.activityScore
|
||||||
}
|
}
|
||||||
} PROVIDES number`,
|
} PROVIDES number`,
|
||||||
@@ -280,7 +317,7 @@ describe('Measure Definitions', () => {
|
|||||||
expectSuccess: false
|
expectSuccess: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: `measure userRole(user: User) {
|
input: `measure userRole(user: Employee) {
|
||||||
invalid syntax here
|
invalid syntax here
|
||||||
} PROVIDES string`,
|
} PROVIDES string`,
|
||||||
description: 'Invalid syntax should fail',
|
description: 'Invalid syntax should fail',
|
||||||
@@ -290,7 +327,7 @@ describe('Measure Definitions', () => {
|
|||||||
|
|
||||||
testCases.forEach(({ input, description, expectSuccess }) => {
|
testCases.forEach(({ input, description, expectSuccess }) => {
|
||||||
try {
|
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) {
|
if (expectSuccess) {
|
||||||
assert.ok(result.success, `${description} should parse successfully`);
|
assert.ok(result.success, `${description} should parse successfully`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user