Files
git.stella-ops.org/src/Policy/__Libraries/StellaOps.Policy.Unknowns/UnknownsBudgetEnforcer.cs

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