test: fix legacy fixture suites against modern DSL validation
CI / test (push) Successful in 25s
CI / publish (push) Has been skipped

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:
John Dvorak
2026-08-03 09:12:20 -07:00
parent ae21605fb7
commit 0c2ddc282b
6 changed files with 562 additions and 343 deletions
+121 -77
View File
@@ -12,6 +12,48 @@ function createMockArbiter() {
};
}
const DSL_SUPPORT = `
definition Employee {
role: string
isActive: boolean
isTrusted: boolean
hasRecentActivity: boolean
lastActive: timestamp
isBlacklisted: boolean
session: string
}
definition Document {
level: string
isPublic: boolean
isEditable: boolean
}
definition Resource {
level: string
isPublic: boolean
}
fact hasRole(user: any, role: string)
fact hasClearance(user: any, level: string)
fact owns(user: any, doc: any)
fact isSuspended(user: any)
fact isActive(user: any)
fact isTrusted(user: any)
fact hasRecentActivity(user: any)
fact isBlacklisted(user: any)
fact isMember(user: any, group: any)
fact isFriend(user: any, friend: any)
fact similar(a: any, b: any)
fact parentOf(user: any, parent: any)
fact isEditable(doc: any)
fact isPublic(doc: any)
fact recentlyActive(user: any)
fact reputationScore(user: any)
fact activityScore(user: any)
fact verificationLevel(user: any)
`;
describe('Evidence Rules', () => {
const arbiter = createMockArbiter();
const compiler = new DSLCompiler(arbiter);
@@ -19,35 +61,35 @@ describe('Evidence Rules', () => {
test('Basic evidence', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
hasRole(user, 'admin')
}`,
description: 'Simple evidence with function call'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
user.isActive
input: `evidence canAccess(user: Employee, resource: Resource) {
isActive(user)
}`,
description: 'Evidence with attribute access'
},
{
input: `evidence canModify(user: User, doc: Document) {
user.isActive
input: `evidence canModify(user: Employee, doc: Document) {
isActive(user)
hasRole(user, 'admin')
}`,
description: 'Evidence with multiple conditions'
},
{
input: `evidence canDelete(user: User, doc: Document) {
input: `evidence canDelete(user: Employee, doc: Document) {
owns(user, doc)
user.isActive
isActive(user)
}`,
description: 'Evidence with ownership and status'
}
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-basic-evidence-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-basic-evidence-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
assert.ok(result.program.evidence.length > 0, 'Should have evidence');
});
@@ -56,32 +98,32 @@ describe('Evidence Rules', () => {
test('Defeasible logic', () => {
const testCases = [
{
input: `evidence canAccess(user: User, resource: Resource) {
ALWAYS user.isActive
input: `evidence canAccess(user: Employee, resource: Resource) {
ALWAYS isActive(user)
}`,
description: 'ALWAYS rule - strict requirement'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
WHEN hasRole(user, 'admin')
}`,
description: 'WHEN rule - defeasible condition'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}`,
description: 'WHEN/UNLESS rule - defeasible with defeater'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
REQUIRES hasClearance(user, resource.level)
}`,
description: 'REQUIRES rule - inverse defeater'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
ALWAYS user.isActive
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
ALWAYS isActive(user)
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
@@ -90,16 +132,16 @@ describe('Evidence Rules', () => {
description: 'Complex defeasible logic with all rule types'
},
{
input: `evidence canAccessSensitive(user: User, doc: Document) {
ALWAYS user.isActive
input: `evidence canAccessSensitive(user: Employee, doc: Document) {
ALWAYS isActive(user)
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, doc.level)
fusion majority {
user.isTrusted
user.hasRecentActivity
isTrusted(user),
hasRecentActivity(user)
}
}`,
description: 'Defeasible logic with fusion'
@@ -107,7 +149,7 @@ describe('Evidence Rules', () => {
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-defeasible-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-defeasible-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
@@ -115,7 +157,7 @@ describe('Evidence Rules', () => {
test('Pattern matching', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
}
@@ -123,7 +165,7 @@ describe('Evidence Rules', () => {
description: 'Basic pattern matching with wildcard'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} limit 5
@@ -131,7 +173,7 @@ describe('Evidence Rules', () => {
description: 'Pattern matching with limit'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7
@@ -139,15 +181,15 @@ describe('Evidence Rules', () => {
description: 'Pattern matching with binding and condition'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7 limit 5
} limit 5 with similarity > 0.7
}`,
description: 'Pattern matching with binding, condition, and limit'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isMember(user, *group) {
isMember(group, *parentGroup) {
canRead(parentGroup, doc)
@@ -157,7 +199,7 @@ describe('Evidence Rules', () => {
description: 'Nested pattern matching'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isFriend(user, *friend) {
isMember(friend, *group) {
canRead(group, doc)
@@ -169,7 +211,7 @@ describe('Evidence Rules', () => {
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-pattern-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-pattern-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
@@ -177,58 +219,59 @@ describe('Evidence Rules', () => {
test('Fusion', () => {
const testCases = [
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
fusion min {
hasClearance(user, resource.level)
user.isActive
hasClearance(user, resource.level),
isActive(user)
}
}`,
description: 'Min fusion - all conditions must be true'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
fusion max {
hasRole(user, 'admin')
hasRole(user, 'admin'),
hasRole(user, 'superuser')
}
}`,
description: 'Max fusion - any condition can be true'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.hasRecentActivity
hasClearance(user, 'secret'),
isTrusted(user),
hasRecentActivity(user)
}
}`,
description: 'Majority fusion - most conditions must be true'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
fusion min {
hasClearance(user, resource.level)
user.isActive
NOT user.isBlacklisted
hasClearance(user, resource.level),
isActive(user),
NOT isBlacklisted(user)
}
fusion max {
hasRole(user, 'admin')
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.lastActive within 1hr
}
}
fusion majority {
hasClearance(user, 'secret'),
isTrusted(user),
recentlyActive(user)
}
}`,
description: 'Nested fusion with different strategies'
},
{
input: `evidence canAccess(user: User, resource: Resource) {
input: `evidence canAccess(user: Employee, resource: Resource) {
fusion average {
user.reputation
user.activityScore
user.verificationLevel
reputationScore(user),
activityScore(user),
verificationLevel(user)
}
}`,
description: 'Average fusion for numeric values'
@@ -236,7 +279,7 @@ describe('Evidence Rules', () => {
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-fusion-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-fusion-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
@@ -244,7 +287,7 @@ describe('Evidence Rules', () => {
test('Complex evidence', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
owns(user, doc)
isMember(user, *group) {
@@ -257,39 +300,40 @@ describe('Evidence Rules', () => {
similar(doc, *similar) |similarity| {
canRead(user, similar)
} with similarity > 0.7 limit 5
} limit 5 with similarity > 0.7
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
}`,
description: 'Complex evidence with all features'
},
{
input: `evidence canAccessCritical(user: User, resource: Resource) {
ALWAYS user.isActive
input: `evidence canAccessCritical(user: Employee, resource: Resource) {
ALWAYS isActive(user)
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
REQUIRES hasClearance(user, resource.level)
fusion min {
hasClearance(user, resource.level)
user.isActive
NOT user.isBlacklisted
hasClearance(user, resource.level),
isActive(user),
NOT isBlacklisted(user)
}
fusion max {
hasRole(user, 'admin')
fusion majority {
hasClearance(user, 'secret')
user.isTrusted
user.lastActive within 1hr
}
}
fusion majority {
hasClearance(user, 'secret'),
isTrusted(user),
recentlyActive(user)
}
}`,
description: 'Critical access with all rule types and fusion'
},
{
input: `evidence canModify(user: User, doc: Document) {
input: `evidence canModify(user: Employee, doc: Document) {
owns(user, doc)
isMember(user, *group) {
@@ -298,13 +342,13 @@ describe('Evidence Rules', () => {
similar(doc, *similar) |similarity| {
canModify(user, similar)
similar.isEditable
} with similarity > 0.8 limit 2
isEditable(similar)
} limit 2 with similarity > 0.8
fusion majority {
user.isTrusted
user.hasRecentActivity
doc.isPublic
isTrusted(user),
hasRecentActivity(user),
isPublic(doc)
}
}`,
description: 'Modification access with similarity and fusion'
@@ -312,7 +356,7 @@ describe('Evidence Rules', () => {
];
testCases.forEach(({ input, description }) => {
const result = compiler.compile(input, `test-complex-evidence-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-complex-evidence-${Date.now()}`);
assert.ok(result.success, `${description} should parse successfully`);
});
});
@@ -320,26 +364,26 @@ describe('Evidence Rules', () => {
test('Evidence error handling', () => {
const testCases = [
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
hasRole(user, 'admin'
}`,
description: 'Missing closing parenthesis should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
WHEN hasRole(user, 'admin') UNLESS
}`,
description: 'Incomplete UNLESS condition should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
fusion min {
hasRole(user, 'admin')
}`,
description: 'Incomplete fusion should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} with
@@ -347,7 +391,7 @@ describe('Evidence Rules', () => {
description: 'Incomplete with clause should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
isMember(user, *group) {
canRead(group, doc)
} limit
@@ -355,7 +399,7 @@ describe('Evidence Rules', () => {
description: 'Incomplete limit should fail'
},
{
input: `evidence canRead(user: User, doc: Document) {
input: `evidence canRead(user: Employee, doc: Document) {
invalid syntax here
}`,
description: 'Invalid syntax should fail'
@@ -364,7 +408,7 @@ describe('Evidence Rules', () => {
testCases.forEach(({ input, description }) => {
try {
const result = compiler.compile(input, `test-evidence-error-${Date.now()}`);
const result = compiler.compile(DSL_SUPPORT + input, `test-evidence-error-${Date.now()}`);
assert.ok(!result.success, `${description} should fail to parse`);
} catch {
// Expected to fail