ae21605fb7
The Evidence DSL (ADR-000) is a thin declarative layer that compiles to engine rule types. It has zero runtime coupling to the core engine (DSLCompiler takes an arbiter as a duck-typed argument; the only shared code was the ip-utils helpers, now local). Extracting it into its own package keeps the core artifact free of the DSL surface. - @arbiter/evidence-dsl depends on @arbiter/core (config formats are the compilation target) - deep-path exports for the compiler, parser, generator, validation, and built-in functions (the surface the core's DSL tests consume) - tests moved alongside; generate-parser script + peggy devDep local - CI: test on push, publish on v* tags
535 lines
15 KiB
JavaScript
535 lines
15 KiB
JavaScript
import { describe, test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DSLCompiler } from '../src/DSLCompiler.js';
|
|
|
|
function createMockArbiter() {
|
|
const relationConfigs = new Map();
|
|
return {
|
|
relationConfigs,
|
|
setRelationConfig(relation, config) {
|
|
relationConfigs.set(relation, config);
|
|
}
|
|
};
|
|
}
|
|
|
|
describe('Integration Tests', () => {
|
|
const arbiter = createMockArbiter();
|
|
const compiler = new DSLCompiler(arbiter);
|
|
|
|
test('Complete authorization system', () => {
|
|
const completeSystem = `
|
|
// Type definitions with complex behaviors
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
lastActive: timestamp BEHAVES {
|
|
decaying down hourly
|
|
} CACHE lazy
|
|
isSuspended: boolean
|
|
balance: number BEHAVES {
|
|
decaying down hourly
|
|
} CACHE eager
|
|
score: number BEHAVES {
|
|
blurring adaptive confidence_95
|
|
} CACHE lazy
|
|
session: string BEHAVES {
|
|
ttl 24h
|
|
} CACHE eager
|
|
clearance: string BEHAVES {
|
|
blurring fixed
|
|
} CACHE eager
|
|
reputation: number BEHAVES {
|
|
decaying up daily
|
|
} CACHE lazy
|
|
}
|
|
|
|
definition Group {
|
|
name: string
|
|
permissions: Permission[]
|
|
level: string
|
|
isPublic: boolean CACHE eager
|
|
created: timestamp BEHAVES {
|
|
decaying stable monthly
|
|
} CACHE lazy
|
|
}
|
|
|
|
definition Document {
|
|
level: string
|
|
owner: User
|
|
tags: string[]
|
|
content: string BEHAVES {
|
|
blurring fixed
|
|
} CACHE lazy
|
|
accessCount: number BEHAVES {
|
|
decaying up daily
|
|
} CACHE eager
|
|
expiresAt: timestamp BEHAVES {
|
|
ttl 30d
|
|
} CACHE eager
|
|
isPublic: boolean CACHE eager
|
|
}
|
|
|
|
definition Resource {
|
|
level: string
|
|
owner: User
|
|
permissions: Permission[]
|
|
isPublic: boolean CACHE eager
|
|
accessCount: number BEHAVES {
|
|
decaying up daily
|
|
} CACHE eager
|
|
}
|
|
|
|
// Facts with various properties and caching
|
|
fact hasRole(user: User, role: string) CACHE eager
|
|
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
|
|
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
|
fact owns(user: User, doc: Document) CACHE eager
|
|
fact isSuspended(user: User) CACHE lazy
|
|
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
|
fact isAdmin(user: User) CACHE eager
|
|
fact isOwner(user: User, resource: Resource) CACHE eager
|
|
fact hasAccess(user: User, resource: Resource, level: string) CACHE lazy
|
|
fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 50
|
|
fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3
|
|
|
|
// Evidence rules with complex logic
|
|
evidence canRead(user: User, doc: Document) {
|
|
owns(user, doc)
|
|
|
|
isMember(user, *group) {
|
|
canRead(group, doc)
|
|
} limit 5
|
|
|
|
parentOf(user, *parent) {
|
|
canRead(parent, doc)
|
|
} limit 3
|
|
|
|
similar(doc, *similar) |similarity| {
|
|
canRead(user, similar)
|
|
} with similarity > 0.7 limit 5
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
|
}
|
|
|
|
evidence canWrite(user: User, doc: Document) {
|
|
owns(user, doc)
|
|
|
|
isMember(user, *group) {
|
|
canWrite(group, doc)
|
|
} limit 3
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
|
|
|
REQUIRES user.isActive
|
|
}
|
|
|
|
evidence canDelete(user: User, doc: Document) {
|
|
owns(user, doc)
|
|
|
|
ALWAYS user.isActive
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
|
|
|
REQUIRES user.isActive
|
|
}
|
|
|
|
evidence canAccessCritical(user: User, resource: Resource) {
|
|
fusion min {
|
|
hasClearance(user, resource.level)
|
|
user.isActive
|
|
NOT user.isBlacklisted
|
|
}
|
|
|
|
fusion max {
|
|
hasRole(user, 'admin')
|
|
fusion majority {
|
|
hasClearance(user, 'secret')
|
|
user.isTrusted
|
|
user.lastActive within 1hr
|
|
}
|
|
}
|
|
}
|
|
|
|
evidence canAccessSensitive(user: User, doc: Document) {
|
|
ALWAYS user.isActive
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
|
|
|
REQUIRES hasClearance(user, doc.level)
|
|
|
|
fusion majority {
|
|
user.isTrusted
|
|
user.hasRecentActivity
|
|
}
|
|
}
|
|
|
|
// Measures for computed values
|
|
measure userRole(user: User) {
|
|
user.role
|
|
} PROVIDES string
|
|
|
|
measure userPermissions(user: User) {
|
|
fusion max {
|
|
user.role.permissions
|
|
user.group.permissions
|
|
}
|
|
} PROVIDES Permission[]
|
|
|
|
measure effectiveClearance(user: User) {
|
|
fusion majority {
|
|
user.clearance
|
|
user.role.clearance
|
|
user.group.clearance
|
|
}
|
|
} PROVIDES string
|
|
|
|
measure userTrustScore(user: User) {
|
|
fusion average {
|
|
user.reputation
|
|
user.activityScore
|
|
user.verificationLevel
|
|
}
|
|
} PROVIDES number
|
|
|
|
measure userBalance(user: User) {
|
|
user.balance
|
|
} PROVIDES number
|
|
|
|
measure userScore(user: User) {
|
|
user.score
|
|
} PROVIDES number
|
|
`;
|
|
|
|
const result = compiler.compile(completeSystem, 'test-complete-system');
|
|
assert.ok(result.success, 'Complete authorization system should compile successfully');
|
|
assert.ok(result.program.definitions.length >= 4, 'Should have multiple definitions');
|
|
assert.ok(result.program.facts.length >= 10, 'Should have multiple facts');
|
|
assert.ok(result.program.evidence.length >= 5, 'Should have multiple evidence rules');
|
|
assert.ok(result.program.measures.length >= 6, 'Should have multiple measures');
|
|
});
|
|
|
|
test('Multi-domain system', () => {
|
|
const multiDomain = `
|
|
// Authentication domain
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
|
session: string BEHAVES { ttl 24h } CACHE eager
|
|
}
|
|
|
|
fact hasRole(user: User, role: string) CACHE eager
|
|
fact isActive(user: User) CACHE eager
|
|
|
|
evidence canAuthenticate(user: User) {
|
|
user.isActive
|
|
user.session within 24h
|
|
}
|
|
|
|
// Authorization domain
|
|
definition Resource {
|
|
level: string
|
|
owner: User
|
|
permissions: Permission[]
|
|
}
|
|
|
|
fact owns(user: User, resource: Resource) CACHE eager
|
|
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
|
|
|
evidence canAccess(user: User, resource: Resource) {
|
|
owns(user, resource)
|
|
hasPermission(user, resource, 'read')
|
|
}
|
|
|
|
// Finance domain
|
|
definition Account {
|
|
balance: number BEHAVES { decaying down hourly } CACHE eager
|
|
owner: User
|
|
isActive: boolean CACHE eager
|
|
}
|
|
|
|
fact hasAccount(user: User, account: Account) CACHE eager
|
|
fact hasBalance(user: User, amount: number) CACHE eager
|
|
|
|
evidence canWithdraw(user: User, amount: number) {
|
|
hasBalance(user, amount)
|
|
user.isActive
|
|
}
|
|
|
|
// Social domain
|
|
definition Group {
|
|
name: string
|
|
members: User[]
|
|
isPublic: boolean CACHE eager
|
|
}
|
|
|
|
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
|
|
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
|
|
|
evidence canAccessGroup(user: User, group: Group) {
|
|
isMember(user, group)
|
|
group.isPublic
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(multiDomain, 'test-multi-domain');
|
|
assert.ok(result.success, 'Multi-domain system should compile successfully');
|
|
assert.ok(result.program.definitions.length >= 4, 'Should have multiple domain definitions');
|
|
assert.ok(result.program.facts.length >= 8, 'Should have multiple domain facts');
|
|
assert.ok(result.program.evidence.length >= 4, 'Should have multiple domain evidence rules');
|
|
});
|
|
|
|
test('Hierarchical access', () => {
|
|
const hierarchicalSystem = `
|
|
definition User {
|
|
role: string
|
|
level: string
|
|
isActive: boolean
|
|
clearance: string
|
|
}
|
|
|
|
definition Organization {
|
|
name: string
|
|
level: string
|
|
parent: Organization
|
|
}
|
|
|
|
fact isMember(user: User, org: Organization) transitive CACHE lazy limit 5
|
|
fact isParentOf(parent: Organization, child: Organization) transitive CACHE eager limit 3
|
|
fact hasRole(user: User, role: string) CACHE eager
|
|
fact hasClearance(user: User, level: string) CACHE eager
|
|
|
|
evidence canAccessOrg(user: User, org: Organization) {
|
|
isMember(user, org)
|
|
|
|
isParentOf(org, *parentOrg) {
|
|
canAccessOrg(user, parentOrg)
|
|
} limit 3
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS user.isSuspended
|
|
}
|
|
|
|
evidence canAccessResource(user: User, resource: Resource) {
|
|
isMember(user, *org) {
|
|
canAccessResource(org, resource)
|
|
} limit 5
|
|
|
|
parentOf(user, *parent) {
|
|
canAccessResource(parent, resource)
|
|
} limit 2
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(hierarchicalSystem, 'test-hierarchical');
|
|
assert.ok(result.success, 'Hierarchical access system should compile successfully');
|
|
});
|
|
|
|
test('Similarity-based access', () => {
|
|
const similaritySystem = `
|
|
definition User {
|
|
profile: string
|
|
interests: string[]
|
|
isActive: boolean
|
|
}
|
|
|
|
definition Document {
|
|
content: string
|
|
tags: string[]
|
|
isPublic: boolean
|
|
owner: User
|
|
}
|
|
|
|
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
|
fact hasInterest(user: User, interest: string) CACHE lazy
|
|
fact hasTag(doc: Document, tag: string) CACHE lazy
|
|
|
|
evidence canRead(user: User, doc: Document) {
|
|
owns(user, doc)
|
|
|
|
similar(doc, *similar) |similarity| {
|
|
canRead(user, similar)
|
|
similar.isPublic
|
|
} with similarity > 0.7 limit 10
|
|
|
|
isFriend(user, *friend) {
|
|
canRead(friend, doc)
|
|
} limit 5
|
|
|
|
fusion majority {
|
|
user.interests
|
|
doc.tags
|
|
}
|
|
}
|
|
|
|
evidence canRecommend(user: User, doc: Document) {
|
|
similar(user, *similarUser) |similarity| {
|
|
canRead(similarUser, doc)
|
|
} with similarity > 0.8 limit 20
|
|
|
|
fusion average {
|
|
user.profile
|
|
doc.content
|
|
}
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(similaritySystem, 'test-similarity');
|
|
assert.ok(result.success, 'Similarity-based access system should compile successfully');
|
|
});
|
|
|
|
test('Temporal access', () => {
|
|
const temporalSystem = `
|
|
definition User {
|
|
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
|
session: string BEHAVES { ttl 24h } CACHE eager
|
|
isActive: boolean
|
|
}
|
|
|
|
definition Event {
|
|
startTime: timestamp
|
|
endTime: timestamp
|
|
isPublic: boolean
|
|
}
|
|
|
|
fact hasAccess(user: User, event: Event) CACHE lazy
|
|
fact isParticipant(user: User, event: Event) CACHE eager
|
|
|
|
evidence canAccessEvent(user: User, event: Event) {
|
|
user.lastActive within 1h
|
|
|
|
isParticipant(user, event)
|
|
|
|
WHEN event.isPublic UNLESS user.isSuspended
|
|
|
|
fusion min {
|
|
user.session within 24h
|
|
user.isActive
|
|
}
|
|
}
|
|
|
|
evidence canAccessHistorical(user: User, event: Event) {
|
|
user.lastActive within 24h
|
|
|
|
fusion majority {
|
|
user.isActive
|
|
user.session within 24h
|
|
event.isPublic
|
|
}
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(temporalSystem, 'test-temporal');
|
|
assert.ok(result.success, 'Temporal access system should compile successfully');
|
|
});
|
|
|
|
test('Complex behaviors', () => {
|
|
const behaviorSystem = `
|
|
definition User {
|
|
balance: number BEHAVES { decaying down hourly } CACHE eager
|
|
score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy
|
|
session: string BEHAVES { ttl 24h } CACHE eager
|
|
reputation: number BEHAVES { decaying up daily } CACHE lazy
|
|
clearance: string BEHAVES { blurring fixed } CACHE eager
|
|
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
|
}
|
|
|
|
definition Document {
|
|
content: string BEHAVES { blurring fixed } CACHE lazy
|
|
accessCount: number BEHAVES { decaying up daily } CACHE eager
|
|
expiresAt: timestamp BEHAVES { ttl 30d } CACHE eager
|
|
isPublic: boolean CACHE eager
|
|
}
|
|
|
|
fact hasBalance(user: User, amount: number) CACHE eager
|
|
fact hasScore(user: User, score: number) CACHE lazy
|
|
fact hasReputation(user: User, reputation: number) CACHE lazy
|
|
|
|
evidence canAccessDocument(user: User, doc: Document) {
|
|
user.balance > 0
|
|
|
|
user.score > 0.5
|
|
|
|
user.reputation > 0.3
|
|
|
|
doc.accessCount < 1000
|
|
|
|
fusion majority {
|
|
user.isActive
|
|
user.lastActive within 1h
|
|
doc.isPublic
|
|
}
|
|
}
|
|
|
|
measure userEffectiveScore(user: User) {
|
|
fusion average {
|
|
user.score
|
|
user.reputation
|
|
user.balance
|
|
}
|
|
} PROVIDES number
|
|
|
|
measure documentPopularity(doc: Document) {
|
|
doc.accessCount
|
|
} PROVIDES number
|
|
`;
|
|
|
|
const result = compiler.compile(behaviorSystem, 'test-behaviors');
|
|
assert.ok(result.success, 'Complex behaviors system should compile successfully');
|
|
});
|
|
|
|
test('Performance scenarios', () => {
|
|
const performanceSystem = `
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
permissions: Permission[] CACHE eager
|
|
}
|
|
|
|
definition Resource {
|
|
level: string
|
|
owner: User
|
|
permissions: Permission[] CACHE eager
|
|
}
|
|
|
|
// High-frequency facts with limits
|
|
fact isMember(user: User, group: Group) transitive CACHE lazy limit 5
|
|
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50
|
|
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
|
fact owns(user: User, resource: Resource) CACHE eager
|
|
|
|
// Optimized evidence rules
|
|
evidence canAccess(user: User, resource: Resource) {
|
|
owns(user, resource)
|
|
|
|
isMember(user, *group) {
|
|
canAccess(group, resource)
|
|
} limit 3
|
|
|
|
WHEN hasPermission(user, resource, 'read')
|
|
}
|
|
|
|
evidence canModify(user: User, resource: Resource) {
|
|
owns(user, resource)
|
|
|
|
isMember(user, *group) {
|
|
canModify(group, resource)
|
|
} limit 2
|
|
|
|
WHEN hasPermission(user, resource, 'write')
|
|
}
|
|
|
|
// Efficient measures
|
|
measure userEffectivePermissions(user: User) {
|
|
user.permissions
|
|
} PROVIDES Permission[]
|
|
|
|
measure resourceAccessLevel(resource: Resource) {
|
|
resource.level
|
|
} PROVIDES string
|
|
`;
|
|
|
|
const result = compiler.compile(performanceSystem, 'test-performance');
|
|
assert.ok(result.success, 'Performance scenarios should compile successfully');
|
|
});
|
|
});
|