namespace StellaOps.PolicyDsl; /// /// Factory for creating policy evaluation engines from compiled policy documents. /// public sealed class PolicyEngineFactory { private readonly PolicyCompiler _compiler = new(); /// /// Creates a policy engine from source code. /// /// The policy DSL source code. /// A policy engine if compilation succeeds, otherwise null with diagnostics. public PolicyEngineResult CreateFromSource(string source) { var compilation = _compiler.Compile(source); if (!compilation.Success || compilation.Document is null) { return new PolicyEngineResult(null, compilation.Diagnostics); } var engine = new PolicyEngine(compilation.Document, compilation.Checksum!); return new PolicyEngineResult(engine, compilation.Diagnostics); } /// /// Creates a policy engine from a pre-compiled IR document. /// /// The compiled policy IR document. /// The policy checksum. /// A policy engine. public PolicyEngine CreateFromDocument(PolicyIrDocument document, string checksum) { return new PolicyEngine(document, checksum); } } /// /// Result of creating a policy engine. /// public sealed record PolicyEngineResult( PolicyEngine? Engine, System.Collections.Immutable.ImmutableArray Diagnostics); /// /// A lightweight policy evaluation engine. /// public sealed class PolicyEngine { internal PolicyEngine(PolicyIrDocument document, string checksum) { Document = document; Checksum = checksum; } /// /// Gets the compiled policy document. /// public PolicyIrDocument Document { get; } /// /// Gets the policy checksum (SHA-256 of canonical representation). /// public string Checksum { get; } /// /// Gets the policy name. /// public string Name => Document.Name; /// /// Gets the policy syntax version. /// public string Syntax => Document.Syntax; /// /// Gets the number of rules in the policy. /// public int RuleCount => Document.Rules.Length; /// /// Evaluates the policy against the given signal context. /// /// The signal context to evaluate against. /// The evaluation result. public PolicyEvaluationResult Evaluate(SignalContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var matchedRules = new List(); var actions = new List(); foreach (var rule in Document.Rules.OrderByDescending(r => r.Priority)) { var matched = EvaluateExpression(rule.When, context); if (matched) { matchedRules.Add(rule.Name); foreach (var action in rule.ThenActions) { actions.Add(new EvaluatedAction(rule.Name, action, WasElseBranch: false)); } } else { foreach (var action in rule.ElseActions) { actions.Add(new EvaluatedAction(rule.Name, action, WasElseBranch: true)); } } } return new PolicyEvaluationResult( PolicyName: Name, PolicyChecksum: Checksum, MatchedRules: matchedRules.ToArray(), Actions: actions.ToArray()); } private static bool EvaluateExpression(PolicyExpression expression, SignalContext context) { return expression switch { PolicyBinaryExpression binary => EvaluateBinary(binary, context), PolicyUnaryExpression unary => EvaluateUnary(unary, context), PolicyLiteralExpression literal => literal.Value is bool b && b, PolicyIdentifierExpression identifier => context.HasSignal(identifier.Name), PolicyMemberAccessExpression member => EvaluateMemberAccess(member, context), _ => false, }; } private static bool EvaluateBinary(PolicyBinaryExpression binary, SignalContext context) { return binary.Operator switch { PolicyBinaryOperator.And => EvaluateExpression(binary.Left, context) && EvaluateExpression(binary.Right, context), PolicyBinaryOperator.Or => EvaluateExpression(binary.Left, context) || EvaluateExpression(binary.Right, context), PolicyBinaryOperator.Equal => EvaluateEquality(binary.Left, binary.Right, context, negate: false), PolicyBinaryOperator.NotEqual => EvaluateEquality(binary.Left, binary.Right, context, negate: true), _ => false, }; } private static bool EvaluateUnary(PolicyUnaryExpression unary, SignalContext context) { return unary.Operator switch { PolicyUnaryOperator.Not => !EvaluateExpression(unary.Operand, context), _ => false, }; } private static bool EvaluateMemberAccess(PolicyMemberAccessExpression member, SignalContext context) { var value = ResolveValue(member.Target, context); if (value is IDictionary dict) { return dict.TryGetValue(member.Member, out var v) && v is bool b && b; } return false; } private static bool EvaluateEquality(PolicyExpression left, PolicyExpression right, SignalContext context, bool negate) { var leftValue = ResolveValue(left, context); var rightValue = ResolveValue(right, context); var equal = Equals(leftValue, rightValue); return negate ? !equal : equal; } private static object? ResolveValue(PolicyExpression expression, SignalContext context) { return expression switch { PolicyLiteralExpression literal => literal.Value, PolicyIdentifierExpression identifier => context.GetSignal(identifier.Name), PolicyMemberAccessExpression member => ResolveMemberValue(member, context), _ => null, }; } private static object? ResolveMemberValue(PolicyMemberAccessExpression member, SignalContext context) { var target = ResolveValue(member.Target, context); if (target is IDictionary dict) { return dict.TryGetValue(member.Member, out var v) ? v : null; } return null; } } /// /// Result of evaluating a policy. /// public sealed record PolicyEvaluationResult( string PolicyName, string PolicyChecksum, string[] MatchedRules, EvaluatedAction[] Actions); /// /// An action that was evaluated as part of policy execution. /// public sealed record EvaluatedAction( string RuleName, PolicyIrAction Action, bool WasElseBranch);