216 lines
6.3 KiB
C#
216 lines
6.3 KiB
C#
using Microsoft.Extensions.Logging;
|
|
|
|
namespace StellaOps.Policy.Unknowns;
|
|
|
|
/// <summary>
|
|
/// Unknowns budget configuration for policy evaluation.
|
|
/// </summary>
|
|
public sealed record UnknownsBudgetConfig
|
|
{
|
|
/// <summary>
|
|
/// Maximum allowed critical severity unknowns.
|
|
/// </summary>
|
|
public int MaxCriticalUnknowns { get; init; } = 0;
|
|
|
|
/// <summary>
|
|
/// Maximum allowed high severity unknowns.
|
|
/// </summary>
|
|
public int MaxHighUnknowns { get; init; } = 5;
|
|
|
|
/// <summary>
|
|
/// Maximum allowed medium severity unknowns.
|
|
/// </summary>
|
|
public int MaxMediumUnknowns { get; init; } = 20;
|
|
|
|
/// <summary>
|
|
/// Maximum allowed low severity unknowns.
|
|
/// </summary>
|
|
public int MaxLowUnknowns { get; init; } = 50;
|
|
|
|
/// <summary>
|
|
/// Maximum total unknowns across all severities.
|
|
/// </summary>
|
|
public int? MaxTotalUnknowns { get; init; }
|
|
|
|
/// <summary>
|
|
/// Action to take when budget is exceeded.
|
|
/// </summary>
|
|
public UnknownsBudgetAction Action { get; init; } = UnknownsBudgetAction.Block;
|
|
|
|
/// <summary>
|
|
/// Environment-specific overrides.
|
|
/// </summary>
|
|
public Dictionary<string, UnknownsBudgetConfig>? EnvironmentOverrides { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Action to take when unknowns budget is exceeded.
|
|
/// </summary>
|
|
public enum UnknownsBudgetAction
|
|
{
|
|
/// <summary>
|
|
/// Block deployment/approval.
|
|
/// </summary>
|
|
Block,
|
|
|
|
/// <summary>
|
|
/// Warn but allow deployment.
|
|
/// </summary>
|
|
Warn,
|
|
|
|
/// <summary>
|
|
/// Log only, no enforcement.
|
|
/// </summary>
|
|
Log
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counts of unknowns by severity.
|
|
/// </summary>
|
|
public sealed record UnknownsCounts
|
|
{
|
|
public int Critical { get; init; }
|
|
public int High { get; init; }
|
|
public int Medium { get; init; }
|
|
public int Low { get; init; }
|
|
public int Total => Critical + High + Medium + Low;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result of unknowns budget enforcement.
|
|
/// </summary>
|
|
public sealed record UnknownsBudgetResult
|
|
{
|
|
public required bool WithinBudget { get; init; }
|
|
public required UnknownsCounts Counts { get; init; }
|
|
public required UnknownsBudgetConfig Budget { get; init; }
|
|
public required UnknownsBudgetAction Action { get; init; }
|
|
public IReadOnlyList<string>? Violations { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enforces unknowns budget for policy decisions.
|
|
/// </summary>
|
|
public sealed class UnknownsBudgetEnforcer
|
|
{
|
|
private readonly ILogger<UnknownsBudgetEnforcer> _logger;
|
|
|
|
public UnknownsBudgetEnforcer(ILogger<UnknownsBudgetEnforcer> logger)
|
|
{
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate unknowns counts against budget.
|
|
/// </summary>
|
|
public UnknownsBudgetResult Evaluate(
|
|
UnknownsCounts counts,
|
|
UnknownsBudgetConfig budget,
|
|
string? environment = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(counts);
|
|
ArgumentNullException.ThrowIfNull(budget);
|
|
|
|
var effectiveBudget = GetEffectiveBudget(budget, environment);
|
|
var violations = new List<string>();
|
|
|
|
if (counts.Critical > effectiveBudget.MaxCriticalUnknowns)
|
|
{
|
|
violations.Add($"Critical unknowns ({counts.Critical}) exceeds budget ({effectiveBudget.MaxCriticalUnknowns})");
|
|
}
|
|
|
|
if (counts.High > effectiveBudget.MaxHighUnknowns)
|
|
{
|
|
violations.Add($"High unknowns ({counts.High}) exceeds budget ({effectiveBudget.MaxHighUnknowns})");
|
|
}
|
|
|
|
if (counts.Medium > effectiveBudget.MaxMediumUnknowns)
|
|
{
|
|
violations.Add($"Medium unknowns ({counts.Medium}) exceeds budget ({effectiveBudget.MaxMediumUnknowns})");
|
|
}
|
|
|
|
if (counts.Low > effectiveBudget.MaxLowUnknowns)
|
|
{
|
|
violations.Add($"Low unknowns ({counts.Low}) exceeds budget ({effectiveBudget.MaxLowUnknowns})");
|
|
}
|
|
|
|
if (effectiveBudget.MaxTotalUnknowns.HasValue &&
|
|
counts.Total > effectiveBudget.MaxTotalUnknowns.Value)
|
|
{
|
|
violations.Add($"Total unknowns ({counts.Total}) exceeds budget ({effectiveBudget.MaxTotalUnknowns.Value})");
|
|
}
|
|
|
|
var withinBudget = violations.Count == 0;
|
|
|
|
if (!withinBudget)
|
|
{
|
|
LogViolations(violations, effectiveBudget.Action, environment);
|
|
}
|
|
|
|
return new UnknownsBudgetResult
|
|
{
|
|
WithinBudget = withinBudget,
|
|
Counts = counts,
|
|
Budget = effectiveBudget,
|
|
Action = effectiveBudget.Action,
|
|
Violations = violations
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if deployment should be blocked based on budget result.
|
|
/// </summary>
|
|
public bool ShouldBlock(UnknownsBudgetResult result)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(result);
|
|
|
|
return !result.WithinBudget && result.Action == UnknownsBudgetAction.Block;
|
|
}
|
|
|
|
private static UnknownsBudgetConfig GetEffectiveBudget(
|
|
UnknownsBudgetConfig budget,
|
|
string? environment)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(environment) ||
|
|
budget.EnvironmentOverrides is null ||
|
|
!budget.EnvironmentOverrides.TryGetValue(environment, out var override_))
|
|
{
|
|
return budget;
|
|
}
|
|
|
|
return override_;
|
|
}
|
|
|
|
private void LogViolations(
|
|
List<string> violations,
|
|
UnknownsBudgetAction action,
|
|
string? environment)
|
|
{
|
|
var envStr = string.IsNullOrWhiteSpace(environment) ? "" : $" (env: {environment})";
|
|
|
|
switch (action)
|
|
{
|
|
case UnknownsBudgetAction.Block:
|
|
_logger.LogError(
|
|
"Unknowns budget exceeded{Env}. Blocking deployment. Violations: {Violations}",
|
|
envStr,
|
|
string.Join("; ", violations));
|
|
break;
|
|
|
|
case UnknownsBudgetAction.Warn:
|
|
_logger.LogWarning(
|
|
"Unknowns budget exceeded{Env}. Allowing deployment with warning. Violations: {Violations}",
|
|
envStr,
|
|
string.Join("; ", violations));
|
|
break;
|
|
|
|
case UnknownsBudgetAction.Log:
|
|
_logger.LogInformation(
|
|
"Unknowns budget exceeded{Env}. Logging only. Violations: {Violations}",
|
|
envStr,
|
|
string.Join("; ", violations));
|
|
break;
|
|
}
|
|
}
|
|
}
|