This commit is contained in:
StellaOps Bot
2025-11-27 21:10:06 +02:00
parent cfa2274d31
commit 8abbf9574d
106 changed files with 7078 additions and 3197 deletions

View File

@@ -4,22 +4,34 @@ using Microsoft.Extensions.Options;
using StellaOps.Policy;
using StellaOps.Policy.Engine.Compilation;
using StellaOps.Policy.Engine.Options;
namespace StellaOps.Policy.Engine.Services;
/// <summary>
/// Provides deterministic compilation for <c>stella-dsl@1</c> policy documents and exposes
/// basic statistics consumed by API/CLI surfaces.
/// </summary>
using StellaOps.PolicyDsl;
using DslCompiler = StellaOps.PolicyDsl.PolicyCompiler;
using DslCompilationResult = StellaOps.PolicyDsl.PolicyCompilationResult;
using IrDocument = StellaOps.PolicyDsl.PolicyIrDocument;
using IrAction = StellaOps.PolicyDsl.PolicyIrAction;
using IrAssignmentAction = StellaOps.PolicyDsl.PolicyIrAssignmentAction;
using IrAnnotateAction = StellaOps.PolicyDsl.PolicyIrAnnotateAction;
using IrIgnoreAction = StellaOps.PolicyDsl.PolicyIrIgnoreAction;
using IrEscalateAction = StellaOps.PolicyDsl.PolicyIrEscalateAction;
using IrRequireVexAction = StellaOps.PolicyDsl.PolicyIrRequireVexAction;
using IrWarnAction = StellaOps.PolicyDsl.PolicyIrWarnAction;
using IrDeferAction = StellaOps.PolicyDsl.PolicyIrDeferAction;
namespace StellaOps.Policy.Engine.Services;
/// <summary>
/// Provides deterministic compilation for <c>stella-dsl@1</c> policy documents and exposes
/// basic statistics consumed by API/CLI surfaces.
/// </summary>
internal sealed class PolicyCompilationService
{
private readonly PolicyCompiler compiler;
private readonly DslCompiler compiler;
private readonly PolicyComplexityAnalyzer complexityAnalyzer;
private readonly IOptionsMonitor<PolicyEngineOptions> optionsMonitor;
private readonly TimeProvider timeProvider;
public PolicyCompilationService(
PolicyCompiler compiler,
DslCompiler compiler,
PolicyComplexityAnalyzer complexityAnalyzer,
IOptionsMonitor<PolicyEngineOptions> optionsMonitor,
TimeProvider timeProvider)
@@ -29,30 +41,30 @@ internal sealed class PolicyCompilationService
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
this.timeProvider = timeProvider ?? TimeProvider.System;
}
public PolicyCompilationResultDto Compile(PolicyCompileRequest request)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (request.Dsl is null || string.IsNullOrWhiteSpace(request.Dsl.Source))
{
throw new ArgumentException("Compilation requires DSL source.", nameof(request));
}
if (!string.Equals(request.Dsl.Syntax, "stella-dsl@1", StringComparison.Ordinal))
{
public PolicyCompilationResultDto Compile(PolicyCompileRequest request)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (request.Dsl is null || string.IsNullOrWhiteSpace(request.Dsl.Source))
{
throw new ArgumentException("Compilation requires DSL source.", nameof(request));
}
if (!string.Equals(request.Dsl.Syntax, "stella-dsl@1", StringComparison.Ordinal))
{
return PolicyCompilationResultDto.FromFailure(
ImmutableArray.Create(PolicyIssue.Error(
PolicyDslDiagnosticCodes.UnsupportedSyntaxVersion,
DiagnosticCodes.UnsupportedSyntaxVersion,
$"Unsupported syntax '{request.Dsl.Syntax ?? "null"}'. Expected 'stella-dsl@1'.",
"dsl.syntax")),
complexity: null,
durationMilliseconds: 0);
}
var start = timeProvider.GetTimestamp();
var result = compiler.Compile(request.Dsl.Source);
var elapsed = timeProvider.GetElapsedTime(start, timeProvider.GetTimestamp());
@@ -95,11 +107,11 @@ internal sealed class PolicyCompilationService
? ImmutableArray.Create(diagnostic)
: diagnostics.Add(diagnostic);
}
internal sealed record PolicyCompileRequest(PolicyDslPayload Dsl);
internal sealed record PolicyDslPayload(string Syntax, string Source);
internal sealed record PolicyCompileRequest(PolicyDslPayload Dsl);
public sealed record PolicyDslPayload(string Syntax, string Source);
internal sealed record PolicyCompilationResultDto(
bool Success,
string? Digest,
@@ -116,7 +128,7 @@ internal sealed record PolicyCompilationResultDto(
new(false, null, null, ImmutableArray<byte>.Empty, diagnostics, complexity, durationMilliseconds);
public static PolicyCompilationResultDto FromSuccess(
PolicyCompilationResult compilationResult,
DslCompilationResult compilationResult,
PolicyComplexityReport complexity,
long durationMilliseconds)
{
@@ -136,45 +148,45 @@ internal sealed record PolicyCompilationResultDto(
durationMilliseconds);
}
}
internal sealed record PolicyCompilationStatistics(
int RuleCount,
ImmutableDictionary<string, int> ActionCounts)
{
public static PolicyCompilationStatistics Create(PolicyIrDocument document)
{
var actions = ImmutableDictionary.CreateBuilder<string, int>(StringComparer.OrdinalIgnoreCase);
void Increment(string key)
{
actions[key] = actions.TryGetValue(key, out var existing) ? existing + 1 : 1;
}
foreach (var rule in document.Rules)
{
foreach (var action in rule.ThenActions)
{
Increment(GetActionKey(action));
}
foreach (var action in rule.ElseActions)
{
Increment($"else:{GetActionKey(action)}");
}
}
return new PolicyCompilationStatistics(document.Rules.Length, actions.ToImmutable());
}
private static string GetActionKey(PolicyIrAction action) => action switch
{
PolicyIrAssignmentAction => "assign",
PolicyIrAnnotateAction => "annotate",
PolicyIrIgnoreAction => "ignore",
PolicyIrEscalateAction => "escalate",
PolicyIrRequireVexAction => "requireVex",
PolicyIrWarnAction => "warn",
PolicyIrDeferAction => "defer",
_ => "unknown"
};
}
internal sealed record PolicyCompilationStatistics(
int RuleCount,
ImmutableDictionary<string, int> ActionCounts)
{
public static PolicyCompilationStatistics Create(IrDocument document)
{
var actions = ImmutableDictionary.CreateBuilder<string, int>(StringComparer.OrdinalIgnoreCase);
void Increment(string key)
{
actions[key] = actions.TryGetValue(key, out var existing) ? existing + 1 : 1;
}
foreach (var rule in document.Rules)
{
foreach (var action in rule.ThenActions)
{
Increment(GetActionKey(action));
}
foreach (var action in rule.ElseActions)
{
Increment($"else:{GetActionKey(action)}");
}
}
return new PolicyCompilationStatistics(document.Rules.Length, actions.ToImmutable());
}
private static string GetActionKey(IrAction action) => action switch
{
IrAssignmentAction => "assign",
IrAnnotateAction => "annotate",
IrIgnoreAction => "ignore",
IrEscalateAction => "escalate",
IrRequireVexAction => "requireVex",
IrWarnAction => "warn",
IrDeferAction => "defer",
_ => "unknown"
};
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Policy.Engine.Compilation;
using StellaOps.PolicyDsl;
using StellaOps.Policy.Engine.Evaluation;
namespace StellaOps.Policy.Engine.Services;
@@ -23,19 +23,19 @@ internal sealed partial class PolicyEvaluationService
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
internal PolicyEvaluationResult Evaluate(PolicyIrDocument document, PolicyEvaluationContext context)
internal Evaluation.PolicyEvaluationResult Evaluate(PolicyIrDocument document, Evaluation.PolicyEvaluationContext context)
{
if (document is null)
{
throw new ArgumentNullException(nameof(document));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
throw new ArgumentNullException(nameof(document));
}
var request = new PolicyEvaluationRequest(document, context);
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var request = new Evaluation.PolicyEvaluationRequest(document, context);
return evaluator.Evaluate(request);
}