feat: bounded self-recursion (transitive closure) for evidence
CI / publish (push) Successful in 9s
CI / test (push) Successful in 18s

An evidence whose config contains a chain step referencing ITSELF is now
unrolled at compile time into a bounded transitive closure: a union of paths
— base, hop+base, hop²+base, …, hop^N+base — where `hop` is the recursive
chain's steps before the self-reference and the depth N comes from the
pattern's `limit N` (or the compiler's maxRecursionDepth default, 3). The
base (the evidence's non-recursive statements) is verified as a condition
step at each path's terminal node, so the engine needs no new machinery.

- Chain configs carry the pattern's `limit` as maxDepth.
- resolveEvidenceReferences detects a self-reference (_findSelfReference),
  extracts the base (_extractBase), and unrolls (_unrollRecursiveEvidence).
- Pure recursion with no base case is a compile-time error; mutual cycles
  between distinct evidence remain a compile-time error.

Example: can_access_via = can_access OR (reports_to + can_access_via) up to
the declared limit grants access inherited up a reporting chain.

Tests: Recursion (unroll shape, base + multi-hop grants, depth-limit
enforcement, default depth, pure-recursion error, mutual-cycle guard).
This commit is contained in:
John Dvorak
2026-08-03 16:35:19 -07:00
parent 9111c4b20d
commit 4d498b07e8
4 changed files with 249 additions and 7 deletions
+2 -2
View File
@@ -7,10 +7,10 @@ import { validateDslText } from './validation/DSLValidation.js';
* Compiles DSL text into rule configurations for the zanzibar-graph system
*/
export class DSLCompiler {
constructor(arbiter) {
constructor(arbiter, options = {}) {
this.arbiter = arbiter;
this.parser = parse;
this.generator = new RuleGenerator(arbiter);
this.generator = new RuleGenerator(arbiter, options);
this.compiledPrograms = new Map();
}