up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,283 +1,283 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Compilation;
|
||||
|
||||
/// <summary>
|
||||
/// Computes deterministic complexity metrics for compiled policies.
|
||||
/// </summary>
|
||||
internal sealed class PolicyComplexityAnalyzer
|
||||
{
|
||||
public PolicyComplexityReport Analyze(PolicyIrDocument document)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
var metrics = new ComplexityMetrics();
|
||||
metrics.RuleCount = document.Rules.IsDefault ? 0 : document.Rules.Length;
|
||||
|
||||
VisitMetadata(document.Metadata.Values, metrics);
|
||||
VisitMetadata(document.Settings.Values, metrics);
|
||||
VisitProfiles(document.Profiles, metrics);
|
||||
|
||||
if (!document.Rules.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var rule in document.Rules)
|
||||
{
|
||||
metrics.ConditionCount++;
|
||||
VisitExpression(rule.When, metrics, depth: 0);
|
||||
|
||||
VisitActions(rule.ThenActions, metrics);
|
||||
VisitActions(rule.ElseActions, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
var score = CalculateScore(metrics);
|
||||
var roundedScore = Math.Round(score, 3, MidpointRounding.AwayFromZero);
|
||||
|
||||
return new PolicyComplexityReport(
|
||||
roundedScore,
|
||||
metrics.RuleCount,
|
||||
metrics.ActionCount,
|
||||
metrics.ExpressionCount,
|
||||
metrics.InvocationCount,
|
||||
metrics.MemberAccessCount,
|
||||
metrics.IdentifierCount,
|
||||
metrics.LiteralCount,
|
||||
metrics.MaxDepth,
|
||||
metrics.ProfileCount,
|
||||
metrics.ProfileBindings,
|
||||
metrics.ConditionCount,
|
||||
metrics.ListItems);
|
||||
}
|
||||
|
||||
private static void VisitProfiles(ImmutableArray<PolicyIrProfile> profiles, ComplexityMetrics metrics)
|
||||
{
|
||||
if (profiles.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
metrics.ProfileCount++;
|
||||
|
||||
if (!profile.Maps.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var map in profile.Maps)
|
||||
{
|
||||
if (map.Entries.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in map.Entries)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
metrics.LiteralCount++; // weight values contribute to literal count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!profile.Environments.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var environment in profile.Environments)
|
||||
{
|
||||
if (environment.Entries.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in environment.Entries)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
metrics.ConditionCount++;
|
||||
VisitExpression(entry.Condition, metrics, depth: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!profile.Scalars.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var scalar in profile.Scalars)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
VisitLiteral(scalar.Value, metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitMetadata(IEnumerable<PolicyIrLiteral> literals, ComplexityMetrics metrics)
|
||||
{
|
||||
foreach (var literal in literals)
|
||||
{
|
||||
VisitLiteral(literal, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitLiteral(PolicyIrLiteral literal, ComplexityMetrics metrics)
|
||||
{
|
||||
switch (literal)
|
||||
{
|
||||
case PolicyIrListLiteral list when !list.Items.IsDefaultOrEmpty:
|
||||
foreach (var item in list.Items)
|
||||
{
|
||||
VisitLiteral(item, metrics);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
metrics.LiteralCount++;
|
||||
}
|
||||
|
||||
private static void VisitActions(ImmutableArray<PolicyIrAction> actions, ComplexityMetrics metrics)
|
||||
{
|
||||
if (actions.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
metrics.ActionCount++;
|
||||
switch (action)
|
||||
{
|
||||
case PolicyIrAssignmentAction assignment:
|
||||
VisitExpression(assignment.Value, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrAnnotateAction annotate:
|
||||
VisitExpression(annotate.Value, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrIgnoreAction ignore when ignore.Until is not null:
|
||||
VisitExpression(ignore.Until, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrEscalateAction escalate:
|
||||
VisitExpression(escalate.To, metrics, depth: 0);
|
||||
VisitExpression(escalate.When, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrRequireVexAction require when !require.Conditions.IsEmpty:
|
||||
foreach (var condition in require.Conditions.Values)
|
||||
{
|
||||
VisitExpression(condition, metrics, depth: 0);
|
||||
}
|
||||
break;
|
||||
case PolicyIrWarnAction warn when warn.Message is not null:
|
||||
VisitExpression(warn.Message, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrDeferAction defer when defer.Until is not null:
|
||||
VisitExpression(defer.Until, metrics, depth: 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitExpression(PolicyExpression? expression, ComplexityMetrics metrics, int depth)
|
||||
{
|
||||
if (expression is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
metrics.ExpressionCount++;
|
||||
var currentDepth = depth + 1;
|
||||
if (currentDepth > metrics.MaxDepth)
|
||||
{
|
||||
metrics.MaxDepth = currentDepth;
|
||||
}
|
||||
|
||||
switch (expression)
|
||||
{
|
||||
case PolicyLiteralExpression:
|
||||
metrics.LiteralCount++;
|
||||
break;
|
||||
case PolicyListExpression listExpression:
|
||||
if (!listExpression.Items.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var item in listExpression.Items)
|
||||
{
|
||||
metrics.ListItems++;
|
||||
VisitExpression(item, metrics, currentDepth);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PolicyIdentifierExpression:
|
||||
metrics.IdentifierCount++;
|
||||
break;
|
||||
case PolicyMemberAccessExpression member:
|
||||
metrics.MemberAccessCount++;
|
||||
VisitExpression(member.Target, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyInvocationExpression invocation:
|
||||
metrics.InvocationCount++;
|
||||
VisitExpression(invocation.Target, metrics, currentDepth);
|
||||
if (!invocation.Arguments.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var argument in invocation.Arguments)
|
||||
{
|
||||
VisitExpression(argument, metrics, currentDepth);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PolicyIndexerExpression indexer:
|
||||
VisitExpression(indexer.Target, metrics, currentDepth);
|
||||
VisitExpression(indexer.Index, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyUnaryExpression unary:
|
||||
VisitExpression(unary.Operand, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyBinaryExpression binary:
|
||||
VisitExpression(binary.Left, metrics, currentDepth);
|
||||
VisitExpression(binary.Right, metrics, currentDepth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateScore(ComplexityMetrics metrics)
|
||||
{
|
||||
return metrics.RuleCount * 5d
|
||||
+ metrics.ActionCount * 1.5d
|
||||
+ metrics.ExpressionCount * 0.75d
|
||||
+ metrics.InvocationCount * 1.5d
|
||||
+ metrics.MemberAccessCount * 1.0d
|
||||
+ metrics.IdentifierCount * 0.5d
|
||||
+ metrics.LiteralCount * 0.25d
|
||||
+ metrics.ProfileBindings * 0.5d
|
||||
+ metrics.ConditionCount * 1.25d
|
||||
+ metrics.MaxDepth * 2d
|
||||
+ metrics.ListItems * 0.25d;
|
||||
}
|
||||
|
||||
private sealed class ComplexityMetrics
|
||||
{
|
||||
public int RuleCount;
|
||||
public int ActionCount;
|
||||
public int ExpressionCount;
|
||||
public int InvocationCount;
|
||||
public int MemberAccessCount;
|
||||
public int IdentifierCount;
|
||||
public int LiteralCount;
|
||||
public int ProfileCount;
|
||||
public int ProfileBindings;
|
||||
public int ConditionCount;
|
||||
public int MaxDepth;
|
||||
public int ListItems;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record PolicyComplexityReport(
|
||||
double Score,
|
||||
int RuleCount,
|
||||
int ActionCount,
|
||||
int ExpressionCount,
|
||||
int InvocationCount,
|
||||
int MemberAccessCount,
|
||||
int IdentifierCount,
|
||||
int LiteralCount,
|
||||
int MaxExpressionDepth,
|
||||
int ProfileCount,
|
||||
int ProfileBindingCount,
|
||||
int ConditionCount,
|
||||
int ListItemCount);
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Compilation;
|
||||
|
||||
/// <summary>
|
||||
/// Computes deterministic complexity metrics for compiled policies.
|
||||
/// </summary>
|
||||
internal sealed class PolicyComplexityAnalyzer
|
||||
{
|
||||
public PolicyComplexityReport Analyze(PolicyIrDocument document)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
|
||||
var metrics = new ComplexityMetrics();
|
||||
metrics.RuleCount = document.Rules.IsDefault ? 0 : document.Rules.Length;
|
||||
|
||||
VisitMetadata(document.Metadata.Values, metrics);
|
||||
VisitMetadata(document.Settings.Values, metrics);
|
||||
VisitProfiles(document.Profiles, metrics);
|
||||
|
||||
if (!document.Rules.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var rule in document.Rules)
|
||||
{
|
||||
metrics.ConditionCount++;
|
||||
VisitExpression(rule.When, metrics, depth: 0);
|
||||
|
||||
VisitActions(rule.ThenActions, metrics);
|
||||
VisitActions(rule.ElseActions, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
var score = CalculateScore(metrics);
|
||||
var roundedScore = Math.Round(score, 3, MidpointRounding.AwayFromZero);
|
||||
|
||||
return new PolicyComplexityReport(
|
||||
roundedScore,
|
||||
metrics.RuleCount,
|
||||
metrics.ActionCount,
|
||||
metrics.ExpressionCount,
|
||||
metrics.InvocationCount,
|
||||
metrics.MemberAccessCount,
|
||||
metrics.IdentifierCount,
|
||||
metrics.LiteralCount,
|
||||
metrics.MaxDepth,
|
||||
metrics.ProfileCount,
|
||||
metrics.ProfileBindings,
|
||||
metrics.ConditionCount,
|
||||
metrics.ListItems);
|
||||
}
|
||||
|
||||
private static void VisitProfiles(ImmutableArray<PolicyIrProfile> profiles, ComplexityMetrics metrics)
|
||||
{
|
||||
if (profiles.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
metrics.ProfileCount++;
|
||||
|
||||
if (!profile.Maps.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var map in profile.Maps)
|
||||
{
|
||||
if (map.Entries.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in map.Entries)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
metrics.LiteralCount++; // weight values contribute to literal count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!profile.Environments.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var environment in profile.Environments)
|
||||
{
|
||||
if (environment.Entries.IsDefaultOrEmpty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entry in environment.Entries)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
metrics.ConditionCount++;
|
||||
VisitExpression(entry.Condition, metrics, depth: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!profile.Scalars.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var scalar in profile.Scalars)
|
||||
{
|
||||
metrics.ProfileBindings++;
|
||||
VisitLiteral(scalar.Value, metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitMetadata(IEnumerable<PolicyIrLiteral> literals, ComplexityMetrics metrics)
|
||||
{
|
||||
foreach (var literal in literals)
|
||||
{
|
||||
VisitLiteral(literal, metrics);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitLiteral(PolicyIrLiteral literal, ComplexityMetrics metrics)
|
||||
{
|
||||
switch (literal)
|
||||
{
|
||||
case PolicyIrListLiteral list when !list.Items.IsDefaultOrEmpty:
|
||||
foreach (var item in list.Items)
|
||||
{
|
||||
VisitLiteral(item, metrics);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
metrics.LiteralCount++;
|
||||
}
|
||||
|
||||
private static void VisitActions(ImmutableArray<PolicyIrAction> actions, ComplexityMetrics metrics)
|
||||
{
|
||||
if (actions.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
metrics.ActionCount++;
|
||||
switch (action)
|
||||
{
|
||||
case PolicyIrAssignmentAction assignment:
|
||||
VisitExpression(assignment.Value, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrAnnotateAction annotate:
|
||||
VisitExpression(annotate.Value, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrIgnoreAction ignore when ignore.Until is not null:
|
||||
VisitExpression(ignore.Until, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrEscalateAction escalate:
|
||||
VisitExpression(escalate.To, metrics, depth: 0);
|
||||
VisitExpression(escalate.When, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrRequireVexAction require when !require.Conditions.IsEmpty:
|
||||
foreach (var condition in require.Conditions.Values)
|
||||
{
|
||||
VisitExpression(condition, metrics, depth: 0);
|
||||
}
|
||||
break;
|
||||
case PolicyIrWarnAction warn when warn.Message is not null:
|
||||
VisitExpression(warn.Message, metrics, depth: 0);
|
||||
break;
|
||||
case PolicyIrDeferAction defer when defer.Until is not null:
|
||||
VisitExpression(defer.Until, metrics, depth: 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitExpression(PolicyExpression? expression, ComplexityMetrics metrics, int depth)
|
||||
{
|
||||
if (expression is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
metrics.ExpressionCount++;
|
||||
var currentDepth = depth + 1;
|
||||
if (currentDepth > metrics.MaxDepth)
|
||||
{
|
||||
metrics.MaxDepth = currentDepth;
|
||||
}
|
||||
|
||||
switch (expression)
|
||||
{
|
||||
case PolicyLiteralExpression:
|
||||
metrics.LiteralCount++;
|
||||
break;
|
||||
case PolicyListExpression listExpression:
|
||||
if (!listExpression.Items.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var item in listExpression.Items)
|
||||
{
|
||||
metrics.ListItems++;
|
||||
VisitExpression(item, metrics, currentDepth);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PolicyIdentifierExpression:
|
||||
metrics.IdentifierCount++;
|
||||
break;
|
||||
case PolicyMemberAccessExpression member:
|
||||
metrics.MemberAccessCount++;
|
||||
VisitExpression(member.Target, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyInvocationExpression invocation:
|
||||
metrics.InvocationCount++;
|
||||
VisitExpression(invocation.Target, metrics, currentDepth);
|
||||
if (!invocation.Arguments.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var argument in invocation.Arguments)
|
||||
{
|
||||
VisitExpression(argument, metrics, currentDepth);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PolicyIndexerExpression indexer:
|
||||
VisitExpression(indexer.Target, metrics, currentDepth);
|
||||
VisitExpression(indexer.Index, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyUnaryExpression unary:
|
||||
VisitExpression(unary.Operand, metrics, currentDepth);
|
||||
break;
|
||||
case PolicyBinaryExpression binary:
|
||||
VisitExpression(binary.Left, metrics, currentDepth);
|
||||
VisitExpression(binary.Right, metrics, currentDepth);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateScore(ComplexityMetrics metrics)
|
||||
{
|
||||
return metrics.RuleCount * 5d
|
||||
+ metrics.ActionCount * 1.5d
|
||||
+ metrics.ExpressionCount * 0.75d
|
||||
+ metrics.InvocationCount * 1.5d
|
||||
+ metrics.MemberAccessCount * 1.0d
|
||||
+ metrics.IdentifierCount * 0.5d
|
||||
+ metrics.LiteralCount * 0.25d
|
||||
+ metrics.ProfileBindings * 0.5d
|
||||
+ metrics.ConditionCount * 1.25d
|
||||
+ metrics.MaxDepth * 2d
|
||||
+ metrics.ListItems * 0.25d;
|
||||
}
|
||||
|
||||
private sealed class ComplexityMetrics
|
||||
{
|
||||
public int RuleCount;
|
||||
public int ActionCount;
|
||||
public int ExpressionCount;
|
||||
public int InvocationCount;
|
||||
public int MemberAccessCount;
|
||||
public int IdentifierCount;
|
||||
public int LiteralCount;
|
||||
public int ProfileCount;
|
||||
public int ProfileBindings;
|
||||
public int ConditionCount;
|
||||
public int MaxDepth;
|
||||
public int ListItems;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record PolicyComplexityReport(
|
||||
double Score,
|
||||
int RuleCount,
|
||||
int ActionCount,
|
||||
int ExpressionCount,
|
||||
int InvocationCount,
|
||||
int MemberAccessCount,
|
||||
int IdentifierCount,
|
||||
int LiteralCount,
|
||||
int MaxExpressionDepth,
|
||||
int ProfileCount,
|
||||
int ProfileBindingCount,
|
||||
int ConditionCount,
|
||||
int ListItemCount);
|
||||
|
||||
Reference in New Issue
Block a user