""" Safe predicate evaluator for XIC v2 control flow. Supports simple expressions referencing fused symbol fields and helper functions. Allowed: comparisons, boolean ops, dominant_contains('glyph://id'). """ import ast from typing import Any, Dict ALLOWED_NODE_TYPES = ( ast.Expression, ast.BoolOp, ast.BinOp, ast.UnaryOp, ast.Compare, ast.Name, ast.Load, ast.Constant, ast.Call, ast.Attribute, ast.And, ast.Or, ast.Not, ast.Gt, ast.Lt, ast.GtE, ast.LtE, ast.Eq, ast.NotEq, ast.In, ast.NotIn ) def _validate_node(node: ast.AST): """Recursively validate AST node is safe for eval.""" if not isinstance(node, ALLOWED_NODE_TYPES): raise ValueError(f"Unsafe predicate node: {type(node).__name__}") for child in ast.iter_child_nodes(node): _validate_node(child) class DotDict: """Helper class that allows dict access via dot notation.""" def __init__(self, data: Dict[str, Any]): self.__dict__.update(data) def _build_context(fused: Dict[str, Any], dominant: list): """Build safe evaluation context with helpers and fused symbol fields.""" def dominant_contains(glyph_id: str) -> bool: """Check if a glyph is in the dominant list.""" return any(g == glyph_id for g, _ in dominant) safe = { "dominant_contains": dominant_contains, "fused": DotDict(fused or {}), } return safe def eval_predicate( expr: str, fused: Dict[str, Any] | None = None, dominant: list | None = None ) -> bool: """ Evaluate predicate expression safely. Example predicates: "fused.global_resonance_score > 0.7" "dominant_contains('glyph://entropy') and fused.global_resonance_score > 0.5" Args: expr: Predicate expression string fused: Fused symbol dict with fields like global_resonance_score, glyph_ids dominant: List of (glyph_id, weight) tuples for dominant glyphs Returns: Boolean result of predicate evaluation """ if dominant is None: dominant = [] # Parse and validate AST try: expr_ast = ast.parse(expr, mode="eval") except SyntaxError as e: raise ValueError(f"Invalid predicate syntax: {e}") _validate_node(expr_ast) # Compile and evaluate compiled = compile(expr_ast, "", "eval") safe_ctx = _build_context(fused or {}, dominant) try: return bool(eval(compiled, {}, safe_ctx)) except Exception as e: raise ValueError(f"Predicate evaluation error: {e}")