using Microsoft.Extensions.Logging; namespace StellaOps.Policy.Unknowns; /// /// Unknowns budget configuration for policy evaluation. /// public sealed record UnknownsBudgetConfig { /// /// Maximum allowed critical severity unknowns. /// public int MaxCriticalUnknowns { get; init; } = 0; /// /// Maximum allowed high severity unknowns. /// public int MaxHighUnknowns { get; init; } = 5; /// /// Maximum allowed medium severity unknowns. /// public int MaxMediumUnknowns { get; init; } = 20; /// /// Maximum allowed low severity unknowns. /// public int MaxLowUnknowns { get; init; } = 50; /// /// Maximum total unknowns across all severities. /// public int? MaxTotalUnknowns { get; init; } /// /// Action to take when budget is exceeded. /// public UnknownsBudgetAction Action { get; init; } = UnknownsBudgetAction.Block; /// /// Environment-specific overrides. /// public Dictionary? EnvironmentOverrides { get; init; } } /// /// Action to take when unknowns budget is exceeded. /// public enum UnknownsBudgetAction { /// /// Block deployment/approval. /// Block, /// /// Warn but allow deployment. /// Warn, /// /// Log only, no enforcement. /// Log } /// /// Counts of unknowns by severity. /// 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; } /// /// Result of unknowns budget enforcement. /// 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? Violations { get; init; } } /// /// Enforces unknowns budget for policy decisions. /// public sealed class UnknownsBudgetEnforcer { private readonly ILogger _logger; public UnknownsBudgetEnforcer(ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } /// /// Evaluate unknowns counts against budget. /// 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(); 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 }; } /// /// Check if deployment should be blocked based on budget result. /// 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 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; } } }