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);