evidence-dsl: extract Evidence DSL v2 compiler from @arbiter/core
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
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { PeggyDSLParser } from '../src/parser/PeggyDSLParser.js';
|
||||
|
||||
describe('Peggy DSL Parser', () => {
|
||||
const parser = new PeggyDSLParser();
|
||||
|
||||
test('Basic parsing', () => {
|
||||
const dsl = `
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
const program = parser.parse(dsl);
|
||||
assert.ok(program !== null, 'Program should be created');
|
||||
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
|
||||
assert.ok(program.facts.length === 1, 'Should have 1 fact');
|
||||
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
|
||||
});
|
||||
|
||||
test('Complex DSL parsing', () => {
|
||||
const dsl = `
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
clearance: string BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
} CACHE eager
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string) CACHE eager
|
||||
fact isMember(user: User, group: Group) transitive CACHE lazy
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
hasRole(user, 'admin')
|
||||
|
||||
isMember(user, *group) {
|
||||
canRead(group, doc)
|
||||
} limit 5
|
||||
|
||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||
}
|
||||
`;
|
||||
|
||||
const program = parser.parse(dsl);
|
||||
assert.ok(program !== null, 'Program should be created');
|
||||
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
|
||||
assert.ok(program.facts.length === 2, 'Should have 2 facts');
|
||||
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
|
||||
});
|
||||
|
||||
test('Error handling', () => {
|
||||
const invalidDSL = `
|
||||
definition User {
|
||||
role: string
|
||||
// Missing closing brace
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
// Missing semicolon
|
||||
`;
|
||||
|
||||
assert.throws(
|
||||
() => parser.parse(invalidDSL),
|
||||
/Parsing failed/,
|
||||
'Should have parsing error message'
|
||||
);
|
||||
});
|
||||
|
||||
test('Validation', () => {
|
||||
const validDSL = `
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
`;
|
||||
|
||||
const invalidDSL = `
|
||||
definition User {
|
||||
role: string
|
||||
// Missing closing brace
|
||||
`;
|
||||
|
||||
const validResult = parser.validate(validDSL);
|
||||
assert.ok(validResult.success, 'Valid DSL should pass validation');
|
||||
assert.ok(validResult.program !== null, 'Valid DSL should return program');
|
||||
|
||||
const invalidResult = parser.validate(invalidDSL);
|
||||
assert.ok(!invalidResult.success, 'Invalid DSL should fail validation');
|
||||
assert.ok(invalidResult.errors.length > 0, 'Should have validation errors');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user