Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Policy.Freshness;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
/// <summary>
|
||||
/// Policy gate that enforces evidence TTL requirements.
|
||||
/// Blocks, warns, or degrades confidence based on evidence staleness.
|
||||
/// </summary>
|
||||
public sealed class EvidenceFreshnessGate : IPolicyGate
|
||||
{
|
||||
private readonly IEvidenceTtlEnforcer _ttlEnforcer;
|
||||
private readonly ILogger<EvidenceFreshnessGate> _logger;
|
||||
|
||||
public EvidenceFreshnessGate(
|
||||
IEvidenceTtlEnforcer ttlEnforcer,
|
||||
ILogger<EvidenceFreshnessGate> logger)
|
||||
{
|
||||
_ttlEnforcer = ttlEnforcer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<GateResult> EvaluateAsync(
|
||||
MergeResult mergeResult,
|
||||
PolicyGateContext context,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// Build evidence bundle from context
|
||||
var evidenceBundle = BuildEvidenceBundleFromContext(context);
|
||||
|
||||
var freshnessResult = _ttlEnforcer.CheckFreshness(evidenceBundle, DateTimeOffset.UtcNow);
|
||||
|
||||
var details = ImmutableDictionary.CreateBuilder<string, object>();
|
||||
details.Add("overall_status", freshnessResult.OverallStatus.ToString());
|
||||
details.Add("recommended_action", freshnessResult.RecommendedAction.ToString());
|
||||
details.Add("checked_at", freshnessResult.CheckedAt);
|
||||
details.Add("checks", freshnessResult.Checks.Select(c => new
|
||||
{
|
||||
type = c.Type.ToString(),
|
||||
status = c.Status.ToString(),
|
||||
expires_at = c.ExpiresAt,
|
||||
remaining_hours = c.Remaining.TotalHours,
|
||||
message = c.Message
|
||||
}).ToList());
|
||||
|
||||
// Determine pass/fail based on recommended action
|
||||
var passed = freshnessResult.OverallStatus switch
|
||||
{
|
||||
FreshnessStatus.Fresh => true,
|
||||
FreshnessStatus.Warning => true, // Warnings don't block by default
|
||||
FreshnessStatus.Stale when freshnessResult.RecommendedAction == StaleEvidenceAction.Warn => true,
|
||||
FreshnessStatus.Stale when freshnessResult.RecommendedAction == StaleEvidenceAction.DegradeConfidence => true,
|
||||
FreshnessStatus.Stale when freshnessResult.RecommendedAction == StaleEvidenceAction.Block => false,
|
||||
_ => true
|
||||
};
|
||||
|
||||
var reason = passed
|
||||
? freshnessResult.HasWarnings
|
||||
? $"Evidence approaching expiration: {string.Join(", ", freshnessResult.Checks.Where(c => c.Status == FreshnessStatus.Warning).Select(c => c.Type))}"
|
||||
: null
|
||||
: $"Stale evidence detected: {string.Join(", ", freshnessResult.Checks.Where(c => c.Status == FreshnessStatus.Stale).Select(c => c.Type))}";
|
||||
|
||||
if (!passed)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Evidence freshness gate failed: {Reason}. Stale evidence: {StaleTypes}",
|
||||
reason,
|
||||
string.Join(", ", freshnessResult.Checks.Where(c => c.Status == FreshnessStatus.Stale).Select(c => c.Type)));
|
||||
}
|
||||
else if (freshnessResult.HasWarnings)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Evidence freshness warning: {WarningTypes}",
|
||||
string.Join(", ", freshnessResult.Checks.Where(c => c.Status == FreshnessStatus.Warning).Select(c => c.Type)));
|
||||
}
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = "EvidenceFreshness",
|
||||
Passed = passed,
|
||||
Reason = reason,
|
||||
Details = details.ToImmutable()
|
||||
});
|
||||
}
|
||||
|
||||
private static EvidenceBundle BuildEvidenceBundleFromContext(PolicyGateContext context)
|
||||
{
|
||||
// In a real implementation, this would extract evidence metadata from the context
|
||||
// For now, return a minimal bundle
|
||||
// This should be extended when evidence metadata is added to PolicyGateContext
|
||||
return new EvidenceBundle
|
||||
{
|
||||
// Evidence would be populated from context metadata
|
||||
// This is a placeholder until PolicyGateContext is extended with evidence timestamps
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
using VexStatus = StellaOps.Policy.Confidence.Models.VexStatus;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed record MinimumConfidenceGateOptions
|
||||
{
|
||||
public bool Enabled { get; init; } = true;
|
||||
public IReadOnlyDictionary<string, double> Thresholds { get; init; } = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["production"] = 0.75,
|
||||
["staging"] = 0.60,
|
||||
["development"] = 0.40,
|
||||
};
|
||||
public IReadOnlyCollection<VexStatus> ApplyToStatuses { get; init; } = new[]
|
||||
{
|
||||
VexStatus.NotAffected,
|
||||
VexStatus.Fixed,
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class MinimumConfidenceGate : IPolicyGate
|
||||
{
|
||||
private readonly MinimumConfidenceGateOptions _options;
|
||||
|
||||
public MinimumConfidenceGate(MinimumConfidenceGateOptions? options = null)
|
||||
{
|
||||
_options = options ?? new MinimumConfidenceGateOptions();
|
||||
}
|
||||
|
||||
public Task<GateResult> EvaluateAsync(MergeResult mergeResult, PolicyGateContext context, CancellationToken ct = default)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
return Task.FromResult(Pass("disabled"));
|
||||
}
|
||||
|
||||
if (mergeResult.Status == VexStatus.Affected)
|
||||
{
|
||||
return Task.FromResult(Pass("affected_bypass"));
|
||||
}
|
||||
|
||||
if (!_options.ApplyToStatuses.Contains(mergeResult.Status))
|
||||
{
|
||||
return Task.FromResult(Pass("status_not_applicable"));
|
||||
}
|
||||
|
||||
var threshold = GetThreshold(context.Environment);
|
||||
var passed = mergeResult.Confidence >= threshold;
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("threshold", threshold)
|
||||
.Add("confidence", mergeResult.Confidence)
|
||||
.Add("environment", context.Environment);
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(MinimumConfidenceGate),
|
||||
Passed = passed,
|
||||
Reason = passed ? null : "confidence_below_threshold",
|
||||
Details = details,
|
||||
});
|
||||
}
|
||||
|
||||
private double GetThreshold(string environment)
|
||||
{
|
||||
if (_options.Thresholds.TryGetValue(environment, out var threshold))
|
||||
{
|
||||
return threshold;
|
||||
}
|
||||
|
||||
if (_options.Thresholds.TryGetValue("production", out var prod))
|
||||
{
|
||||
return prod;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static GateResult Pass(string reason) => new()
|
||||
{
|
||||
GateName = nameof(MinimumConfidenceGate),
|
||||
Passed = true,
|
||||
Reason = reason,
|
||||
Details = ImmutableDictionary<string, object>.Empty,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed record PolicyGateContext
|
||||
{
|
||||
public string Environment { get; init; } = "production";
|
||||
public int UnknownCount { get; init; }
|
||||
public IReadOnlyList<double> UnknownClaimScores { get; init; } = Array.Empty<double>();
|
||||
public IReadOnlyDictionary<string, double> SourceInfluence { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
|
||||
public bool HasReachabilityProof { get; init; }
|
||||
public string? Severity { get; init; }
|
||||
public IReadOnlyCollection<string> ReasonCodes { get; init; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public sealed record GateResult
|
||||
{
|
||||
public required string GateName { get; init; }
|
||||
public required bool Passed { get; init; }
|
||||
public required string? Reason { get; init; }
|
||||
public required ImmutableDictionary<string, object> Details { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GateEvaluationResult
|
||||
{
|
||||
public required bool AllPassed { get; init; }
|
||||
public required ImmutableArray<GateResult> Results { get; init; }
|
||||
public GateResult? FirstFailure => Results.FirstOrDefault(r => !r.Passed);
|
||||
}
|
||||
|
||||
public interface IPolicyGate
|
||||
{
|
||||
Task<GateResult> EvaluateAsync(
|
||||
MergeResult mergeResult,
|
||||
PolicyGateContext context,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public sealed record PolicyGateRegistryOptions
|
||||
{
|
||||
public bool StopOnFirstFailure { get; init; } = true;
|
||||
}
|
||||
|
||||
public interface IPolicyGateRegistry
|
||||
{
|
||||
void Register<TGate>(string name) where TGate : IPolicyGate;
|
||||
|
||||
Task<GateEvaluationResult> EvaluateAsync(
|
||||
MergeResult mergeResult,
|
||||
PolicyGateContext context,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed class PolicyGateRegistry : IPolicyGateRegistry
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly PolicyGateRegistryOptions _options;
|
||||
private readonly List<GateDescriptor> _gates = new();
|
||||
|
||||
public PolicyGateRegistry(IServiceProvider serviceProvider, PolicyGateRegistryOptions? options = null)
|
||||
{
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
_options = options ?? new PolicyGateRegistryOptions();
|
||||
}
|
||||
|
||||
public void Register<TGate>(string name) where TGate : IPolicyGate
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Gate name must be provided.", nameof(name));
|
||||
}
|
||||
|
||||
_gates.Add(new GateDescriptor(name, typeof(TGate)));
|
||||
}
|
||||
|
||||
public async Task<GateEvaluationResult> EvaluateAsync(
|
||||
TrustLattice.MergeResult mergeResult,
|
||||
PolicyGateContext context,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(mergeResult);
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var results = new List<GateResult>();
|
||||
foreach (var gate in _gates)
|
||||
{
|
||||
var instance = _serviceProvider.GetService(gate.Type) as IPolicyGate
|
||||
?? (IPolicyGate)Activator.CreateInstance(gate.Type)!;
|
||||
|
||||
var result = await instance.EvaluateAsync(mergeResult, context, ct).ConfigureAwait(false);
|
||||
if (string.IsNullOrWhiteSpace(result.GateName))
|
||||
{
|
||||
result = result with { GateName = gate.Name };
|
||||
}
|
||||
|
||||
if (result.Details is null)
|
||||
{
|
||||
result = result with { Details = ImmutableDictionary<string, object>.Empty };
|
||||
}
|
||||
|
||||
results.Add(result);
|
||||
|
||||
if (!result.Passed && _options.StopOnFirstFailure)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new GateEvaluationResult
|
||||
{
|
||||
AllPassed = results.All(r => r.Passed),
|
||||
Results = results.ToImmutableArray(),
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record GateDescriptor(string Name, Type Type);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
using VexStatus = StellaOps.Policy.Confidence.Models.VexStatus;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed record ReachabilityRequirementGateOptions
|
||||
{
|
||||
public bool Enabled { get; init; } = true;
|
||||
public string SeverityThreshold { get; init; } = "CRITICAL";
|
||||
public IReadOnlyCollection<VexStatus> RequiredForStatuses { get; init; } = new[]
|
||||
{
|
||||
VexStatus.NotAffected,
|
||||
};
|
||||
public IReadOnlyCollection<string> BypassReasons { get; init; } = new[]
|
||||
{
|
||||
"component_not_present",
|
||||
"vulnerable_configuration_unused",
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class ReachabilityRequirementGate : IPolicyGate
|
||||
{
|
||||
private readonly ReachabilityRequirementGateOptions _options;
|
||||
|
||||
public ReachabilityRequirementGate(ReachabilityRequirementGateOptions? options = null)
|
||||
{
|
||||
_options = options ?? new ReachabilityRequirementGateOptions();
|
||||
}
|
||||
|
||||
public Task<GateResult> EvaluateAsync(MergeResult mergeResult, PolicyGateContext context, CancellationToken ct = default)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
return Task.FromResult(Pass("disabled"));
|
||||
}
|
||||
|
||||
if (!_options.RequiredForStatuses.Contains(mergeResult.Status))
|
||||
{
|
||||
return Task.FromResult(Pass("status_not_applicable"));
|
||||
}
|
||||
|
||||
var severityRank = SeverityRank(context.Severity);
|
||||
var thresholdRank = SeverityRank(_options.SeverityThreshold);
|
||||
if (severityRank < thresholdRank)
|
||||
{
|
||||
return Task.FromResult(Pass("severity_below_threshold"));
|
||||
}
|
||||
|
||||
if (HasBypass(context.ReasonCodes))
|
||||
{
|
||||
return Task.FromResult(Pass("bypass_reason"));
|
||||
}
|
||||
|
||||
var passed = context.HasReachabilityProof;
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("severity", context.Severity ?? string.Empty)
|
||||
.Add("threshold", _options.SeverityThreshold)
|
||||
.Add("hasReachabilityProof", context.HasReachabilityProof);
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(ReachabilityRequirementGate),
|
||||
Passed = passed,
|
||||
Reason = passed ? null : "reachability_proof_missing",
|
||||
Details = details,
|
||||
});
|
||||
}
|
||||
|
||||
private bool HasBypass(IReadOnlyCollection<string> reasons)
|
||||
=> reasons.Any(reason => _options.BypassReasons.Contains(reason, StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
private static int SeverityRank(string? severity)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(severity))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return severity.Trim().ToUpperInvariant() switch
|
||||
{
|
||||
"CRITICAL" => 4,
|
||||
"HIGH" => 3,
|
||||
"MEDIUM" => 2,
|
||||
"LOW" => 1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
private static GateResult Pass(string reason) => new()
|
||||
{
|
||||
GateName = nameof(ReachabilityRequirementGate),
|
||||
Passed = true,
|
||||
Reason = reason,
|
||||
Details = ImmutableDictionary<string, object>.Empty,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
using VexStatus = StellaOps.Policy.Confidence.Models.VexStatus;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed record SourceQuotaGateOptions
|
||||
{
|
||||
public bool Enabled { get; init; } = true;
|
||||
public double MaxInfluencePercent { get; init; } = 60;
|
||||
public double CorroborationDelta { get; init; } = 0.10;
|
||||
public IReadOnlyCollection<VexStatus> RequireCorroborationFor { get; init; } = new[]
|
||||
{
|
||||
VexStatus.NotAffected,
|
||||
VexStatus.Fixed,
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class SourceQuotaGate : IPolicyGate
|
||||
{
|
||||
private readonly SourceQuotaGateOptions _options;
|
||||
|
||||
public SourceQuotaGate(SourceQuotaGateOptions? options = null)
|
||||
{
|
||||
_options = options ?? new SourceQuotaGateOptions();
|
||||
}
|
||||
|
||||
public Task<GateResult> EvaluateAsync(MergeResult mergeResult, PolicyGateContext context, CancellationToken ct = default)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
return Task.FromResult(Pass("disabled"));
|
||||
}
|
||||
|
||||
if (!_options.RequireCorroborationFor.Contains(mergeResult.Status))
|
||||
{
|
||||
return Task.FromResult(Pass("status_not_applicable"));
|
||||
}
|
||||
|
||||
var influence = context.SourceInfluence.Count > 0
|
||||
? context.SourceInfluence
|
||||
: ComputeInfluence(mergeResult);
|
||||
|
||||
if (influence.Count == 0)
|
||||
{
|
||||
return Task.FromResult(Pass("no_sources"));
|
||||
}
|
||||
|
||||
var maxAllowed = _options.MaxInfluencePercent / 100.0;
|
||||
var ordered = influence.OrderByDescending(kv => kv.Value).ToList();
|
||||
var top = ordered[0];
|
||||
var second = ordered.Count > 1 ? ordered[1] : new KeyValuePair<string, double>(string.Empty, 0);
|
||||
var corroborated = ordered.Count > 1 && (top.Value - second.Value) <= _options.CorroborationDelta;
|
||||
|
||||
var passed = top.Value <= maxAllowed || corroborated;
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("maxInfluence", maxAllowed)
|
||||
.Add("topSource", top.Key)
|
||||
.Add("topShare", top.Value)
|
||||
.Add("secondShare", second.Value)
|
||||
.Add("corroborated", corroborated);
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(SourceQuotaGate),
|
||||
Passed = passed,
|
||||
Reason = passed ? null : "source_quota_exceeded",
|
||||
Details = details,
|
||||
});
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, double> ComputeInfluence(MergeResult mergeResult)
|
||||
{
|
||||
var relevant = mergeResult.AllClaims.Where(c => c.Status == mergeResult.Status).ToList();
|
||||
var total = relevant.Sum(c => c.AdjustedScore);
|
||||
if (total <= 0)
|
||||
{
|
||||
return new Dictionary<string, double>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
return relevant
|
||||
.GroupBy(c => c.SourceId, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.Sum(c => c.AdjustedScore) / total, StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private static GateResult Pass(string reason) => new()
|
||||
{
|
||||
GateName = nameof(SourceQuotaGate),
|
||||
Passed = true,
|
||||
Reason = reason,
|
||||
Details = ImmutableDictionary<string, object>.Empty,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Policy.TrustLattice;
|
||||
|
||||
namespace StellaOps.Policy.Gates;
|
||||
|
||||
public sealed record UnknownsBudgetGateOptions
|
||||
{
|
||||
public bool Enabled { get; init; } = true;
|
||||
public int MaxUnknownCount { get; init; } = 5;
|
||||
public double MaxCumulativeUncertainty { get; init; } = 2.0;
|
||||
public bool EscalateOnFail { get; init; } = true;
|
||||
}
|
||||
|
||||
public sealed class UnknownsBudgetGate : IPolicyGate
|
||||
{
|
||||
private readonly UnknownsBudgetGateOptions _options;
|
||||
|
||||
public UnknownsBudgetGate(UnknownsBudgetGateOptions? options = null)
|
||||
{
|
||||
_options = options ?? new UnknownsBudgetGateOptions();
|
||||
}
|
||||
|
||||
public Task<GateResult> EvaluateAsync(MergeResult mergeResult, PolicyGateContext context, CancellationToken ct = default)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
return Task.FromResult(Pass("disabled"));
|
||||
}
|
||||
|
||||
var unknownCount = context.UnknownCount;
|
||||
var cumulative = context.UnknownClaimScores.Sum(score => 1.0 - score);
|
||||
|
||||
var countExceeded = unknownCount > _options.MaxUnknownCount;
|
||||
var cumulativeExceeded = cumulative > _options.MaxCumulativeUncertainty;
|
||||
var passed = !countExceeded && !cumulativeExceeded;
|
||||
|
||||
var details = ImmutableDictionary<string, object>.Empty
|
||||
.Add("unknownCount", unknownCount)
|
||||
.Add("maxUnknownCount", _options.MaxUnknownCount)
|
||||
.Add("cumulativeUncertainty", cumulative)
|
||||
.Add("maxCumulativeUncertainty", _options.MaxCumulativeUncertainty);
|
||||
|
||||
return Task.FromResult(new GateResult
|
||||
{
|
||||
GateName = nameof(UnknownsBudgetGate),
|
||||
Passed = passed,
|
||||
Reason = passed ? null : "unknowns_budget_exceeded",
|
||||
Details = details,
|
||||
});
|
||||
}
|
||||
|
||||
private static GateResult Pass(string reason) => new()
|
||||
{
|
||||
GateName = nameof(UnknownsBudgetGate),
|
||||
Passed = true,
|
||||
Reason = reason,
|
||||
Details = ImmutableDictionary<string, object>.Empty,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user