Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Policy;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Engine.Caching;
using StellaOps.Policy.Engine.Evaluation;
using StellaOps.Policy.Engine.Services;
@@ -86,6 +87,7 @@ internal sealed record BatchEvaluationResultDto(
IReadOnlyDictionary<string, string> Annotations,
IReadOnlyList<string> Warnings,
PolicyExceptionApplication? AppliedException,
ConfidenceScore? Confidence,
string CorrelationId,
bool Cached,
CacheSource CacheSource,

View File

@@ -0,0 +1,150 @@
using System.Collections.Immutable;
using Microsoft.Extensions.Logging;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Services;
namespace StellaOps.Policy.Engine.BuildGate;
/// <summary>
/// Build gate that checks recheck policies before allowing deployment.
/// </summary>
public sealed class ExceptionRecheckGate : IBuildGate
{
private readonly IExceptionEvaluator _exceptionEvaluator;
private readonly IRecheckEvaluationService _recheckService;
private readonly ILogger<ExceptionRecheckGate> _logger;
public ExceptionRecheckGate(
IExceptionEvaluator exceptionEvaluator,
IRecheckEvaluationService recheckService,
ILogger<ExceptionRecheckGate> logger)
{
_exceptionEvaluator = exceptionEvaluator ?? throw new ArgumentNullException(nameof(exceptionEvaluator));
_recheckService = recheckService ?? throw new ArgumentNullException(nameof(recheckService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public string GateName => "exception-recheck";
public int Priority => 100;
public async Task<BuildGateResult> EvaluateAsync(
BuildGateContext context,
CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(context);
_logger.LogInformation(
"Evaluating exception recheck gate for artifact {Artifact}",
context.ArtifactDigest);
var evaluation = await _exceptionEvaluator.EvaluateAsync(new FindingContext
{
ArtifactDigest = context.ArtifactDigest,
Environment = context.Environment,
TenantId = context.TenantId
}, ct).ConfigureAwait(false);
var blockers = new List<string>();
var warnings = new List<string>();
foreach (var exception in evaluation.MatchingExceptions)
{
if (exception.RecheckPolicy is null)
{
continue;
}
var evalContext = new RecheckEvaluationContext
{
ArtifactDigest = context.ArtifactDigest,
Environment = context.Environment,
EvaluatedAt = context.EvaluatedAt,
ReachGraphChanged = context.ReachGraphChanged,
EpssScore = context.EpssScore,
CvssScore = context.CvssScore,
UnknownsCount = context.UnknownsCount,
NewCveInPackage = context.NewCveInPackage,
KevFlagged = context.KevFlagged,
VexStatusChanged = context.VexStatusChanged,
PackageVersionChanged = context.PackageVersionChanged
};
var result = await _recheckService.EvaluateAsync(exception, evalContext, ct).ConfigureAwait(false);
if (!result.IsTriggered)
{
continue;
}
foreach (var triggered in result.TriggeredConditions)
{
var message = $"Exception {exception.ExceptionId}: {triggered.Description} ({triggered.Action})";
if (triggered.Action is RecheckAction.Block or RecheckAction.Revoke or RecheckAction.RequireReapproval)
{
blockers.Add(message);
}
else if (triggered.Action == RecheckAction.Warn)
{
warnings.Add(message);
}
}
}
if (blockers.Count > 0)
{
return new BuildGateResult
{
Passed = false,
GateName = GateName,
Message = $"Recheck policy blocking: {string.Join("; ", blockers)}",
Blockers = blockers.ToImmutableArray(),
Warnings = warnings.ToImmutableArray()
};
}
return new BuildGateResult
{
Passed = true,
GateName = GateName,
Message = warnings.Count > 0
? $"Passed with {warnings.Count} warning(s)"
: "All exception recheck policies satisfied",
Blockers = [],
Warnings = warnings.ToImmutableArray()
};
}
}
public interface IBuildGate
{
string GateName { get; }
int Priority { get; }
Task<BuildGateResult> EvaluateAsync(BuildGateContext context, CancellationToken ct = default);
}
public sealed record BuildGateContext
{
public required string ArtifactDigest { get; init; }
public required string Environment { get; init; }
public string? Branch { get; init; }
public string? PipelineId { get; init; }
public Guid? TenantId { get; init; }
public DateTimeOffset EvaluatedAt { get; init; } = DateTimeOffset.UtcNow;
public bool ReachGraphChanged { get; init; }
public decimal? EpssScore { get; init; }
public decimal? CvssScore { get; init; }
public int? UnknownsCount { get; init; }
public bool NewCveInPackage { get; init; }
public bool KevFlagged { get; init; }
public bool VexStatusChanged { get; init; }
public bool PackageVersionChanged { get; init; }
}
public sealed record BuildGateResult
{
public required bool Passed { get; init; }
public required string GateName { get; init; }
public required string Message { get; init; }
public required ImmutableArray<string> Blockers { get; init; }
public required ImmutableArray<string> Warnings { get; init; }
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Engine.Evaluation;
namespace StellaOps.Policy.Engine.Caching;
@@ -93,7 +94,8 @@ public sealed record PolicyEvaluationCacheEntry(
string? ExceptionId,
string CorrelationId,
DateTimeOffset EvaluatedAt,
DateTimeOffset ExpiresAt);
DateTimeOffset ExpiresAt,
ConfidenceScore? Confidence);
/// <summary>
/// Result of a cache lookup.

View File

@@ -1,7 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Http;
using StellaOps.Policy.Confidence.Configuration;
using StellaOps.Policy.Confidence.Services;
using StellaOps.Policy.Engine.Attestation;
using StellaOps.Policy.Engine.BuildGate;
using StellaOps.Policy.Engine.Caching;
using StellaOps.Policy.Engine.EffectiveDecisionMap;
using StellaOps.Policy.Engine.Events;
@@ -13,6 +16,8 @@ using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Vex;
using StellaOps.Policy.Engine.WhatIfSimulation;
using StellaOps.Policy.Engine.Workers;
using StellaOps.Policy.Unknowns.Configuration;
using StellaOps.Policy.Unknowns.Services;
using StackExchange.Redis;
namespace StellaOps.Policy.Engine.DependencyInjection;
@@ -33,6 +38,13 @@ public static class PolicyEngineServiceCollectionExtensions
// Core compilation and evaluation services
services.TryAddSingleton<PolicyCompilationService>();
services.TryAddSingleton<PolicyEvaluator>();
services.AddOptions<ConfidenceWeightOptions>()
.BindConfiguration(ConfidenceWeightOptions.SectionName);
services.TryAddSingleton<IConfidenceCalculator, ConfidenceCalculator>();
services.AddOptions<UnknownBudgetOptions>()
.BindConfiguration(UnknownBudgetOptions.SectionName);
services.TryAddSingleton<IUnknownBudgetService, UnknownBudgetService>();
// Cache - uses IDistributedCacheFactory for transport flexibility
services.TryAddSingleton<IPolicyEvaluationCache, MessagingPolicyEvaluationCache>();
@@ -201,6 +213,15 @@ public static class PolicyEngineServiceCollectionExtensions
return services.AddPolicyDecisionAttestation();
}
/// <summary>
/// Adds build gate evaluators for exception recheck policies.
/// </summary>
public static IServiceCollection AddExceptionRecheckGate(this IServiceCollection services)
{
services.TryAddSingleton<IBuildGate, ExceptionRecheckGate>();
return services;
}
/// <summary>
/// Adds Redis connection for effective decision map and evaluation cache.
/// </summary>
@@ -340,4 +361,4 @@ public static class PolicyEngineServiceCollectionExtensions
return services;
}
}
}

View File

@@ -104,6 +104,7 @@ internal static class BatchEvaluationEndpoint
response.Annotations,
response.Warnings,
response.AppliedException,
response.Confidence,
response.CorrelationId,
response.Cached,
response.CacheSource,

View File

@@ -54,6 +54,7 @@ internal static class UnknownsEndpoints
[FromQuery] int limit = 100,
[FromQuery] int offset = 0,
IUnknownsRepository repository = null!,
IRemediationHintsRegistry hintsRegistry = null!,
CancellationToken ct = default)
{
var tenantId = ResolveTenantId(httpContext);
@@ -76,18 +77,9 @@ internal static class UnknownsEndpoints
unknowns = hot.Concat(warm).Concat(cold).Take(limit).ToList().AsReadOnly();
}
var items = unknowns.Select(u => new UnknownDto(
u.Id,
u.PackageId,
u.PackageVersion,
u.Band.ToString().ToLowerInvariant(),
u.Score,
u.UncertaintyFactor,
u.ExploitPressure,
u.FirstSeenAt,
u.LastEvaluatedAt,
u.ResolutionReason,
u.ResolvedAt)).ToList();
var items = unknowns
.Select(u => ToDto(u, hintsRegistry))
.ToList();
return TypedResults.Ok(new UnknownsListResponse(items, items.Count));
}
@@ -115,6 +107,7 @@ internal static class UnknownsEndpoints
HttpContext httpContext,
Guid id,
IUnknownsRepository repository = null!,
IRemediationHintsRegistry hintsRegistry = null!,
CancellationToken ct = default)
{
var tenantId = ResolveTenantId(httpContext);
@@ -126,7 +119,7 @@ internal static class UnknownsEndpoints
if (unknown is null)
return TypedResults.Problem($"Unknown with ID {id} not found.", statusCode: StatusCodes.Status404NotFound);
return TypedResults.Ok(new UnknownResponse(ToDto(unknown)));
return TypedResults.Ok(new UnknownResponse(ToDto(unknown, hintsRegistry)));
}
private static async Task<Results<Ok<UnknownResponse>, ProblemHttpResult>> Escalate(
@@ -135,6 +128,7 @@ internal static class UnknownsEndpoints
[FromBody] EscalateUnknownRequest request,
IUnknownsRepository repository = null!,
IUnknownRanker ranker = null!,
IRemediationHintsRegistry hintsRegistry = null!,
CancellationToken ct = default)
{
var tenantId = ResolveTenantId(httpContext);
@@ -164,7 +158,7 @@ internal static class UnknownsEndpoints
// TODO: T6 - Trigger rescan job via Scheduler integration
// await scheduler.CreateRescanJobAsync(unknown.PackageId, unknown.PackageVersion, ct);
return TypedResults.Ok(new UnknownResponse(ToDto(unknown)));
return TypedResults.Ok(new UnknownResponse(ToDto(unknown, hintsRegistry)));
}
private static async Task<Results<Ok<UnknownResponse>, ProblemHttpResult>> Resolve(
@@ -172,6 +166,7 @@ internal static class UnknownsEndpoints
Guid id,
[FromBody] ResolveUnknownRequest request,
IUnknownsRepository repository = null!,
IRemediationHintsRegistry hintsRegistry = null!,
CancellationToken ct = default)
{
var tenantId = ResolveTenantId(httpContext);
@@ -188,7 +183,7 @@ internal static class UnknownsEndpoints
var unknown = await repository.GetByIdAsync(tenantId, id, ct);
return TypedResults.Ok(new UnknownResponse(ToDto(unknown!)));
return TypedResults.Ok(new UnknownResponse(ToDto(unknown!, hintsRegistry)));
}
private static Guid ResolveTenantId(HttpContext context)
@@ -211,18 +206,42 @@ internal static class UnknownsEndpoints
return Guid.Empty;
}
private static UnknownDto ToDto(Unknown u) => new(
u.Id,
u.PackageId,
u.PackageVersion,
u.Band.ToString().ToLowerInvariant(),
u.Score,
u.UncertaintyFactor,
u.ExploitPressure,
u.FirstSeenAt,
u.LastEvaluatedAt,
u.ResolutionReason,
u.ResolvedAt);
private static UnknownDto ToDto(Unknown u, IRemediationHintsRegistry hintsRegistry)
{
var hint = hintsRegistry.GetHint(u.ReasonCode);
var shortCode = ShortCodes.TryGetValue(u.ReasonCode, out var code) ? code : "U-RCH";
return new UnknownDto(
u.Id,
u.PackageId,
u.PackageVersion,
u.Band.ToString().ToLowerInvariant(),
u.Score,
u.UncertaintyFactor,
u.ExploitPressure,
u.FirstSeenAt,
u.LastEvaluatedAt,
u.ResolutionReason,
u.ResolvedAt,
u.ReasonCode.ToString(),
shortCode,
u.RemediationHint ?? hint.ShortHint,
hint.DetailedHint,
hint.AutomationRef,
u.EvidenceRefs.Select(e => new EvidenceRefDto(e.Type, e.Uri, e.Digest)).ToList());
}
private static readonly IReadOnlyDictionary<UnknownReasonCode, string> ShortCodes =
new Dictionary<UnknownReasonCode, string>
{
[UnknownReasonCode.Reachability] = "U-RCH",
[UnknownReasonCode.Identity] = "U-ID",
[UnknownReasonCode.Provenance] = "U-PROV",
[UnknownReasonCode.VexConflict] = "U-VEX",
[UnknownReasonCode.FeedGap] = "U-FEED",
[UnknownReasonCode.ConfigUnknown] = "U-CONFIG",
[UnknownReasonCode.AnalyzerLimit] = "U-ANALYZER"
};
}
#region DTOs
@@ -239,7 +258,18 @@ public sealed record UnknownDto(
DateTimeOffset FirstSeenAt,
DateTimeOffset LastEvaluatedAt,
string? ResolutionReason,
DateTimeOffset? ResolvedAt);
DateTimeOffset? ResolvedAt,
string ReasonCode,
string ReasonCodeShort,
string? RemediationHint,
string? DetailedHint,
string? AutomationCommand,
IReadOnlyList<EvidenceRefDto> EvidenceRefs);
public sealed record EvidenceRefDto(
string Type,
string Uri,
string? Digest);
/// <summary>Response containing a list of unknowns.</summary>
public sealed record UnknownsListResponse(IReadOnlyList<UnknownDto> Items, int TotalCount);

View File

@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Policy;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Unknowns.Models;
using StellaOps.PolicyDsl;
namespace StellaOps.Policy.Engine.Evaluation;
@@ -18,9 +21,13 @@ internal sealed record PolicyEvaluationContext(
PolicyEvaluationVexEvidence Vex,
PolicyEvaluationSbom Sbom,
PolicyEvaluationExceptions Exceptions,
ImmutableArray<Unknown> Unknowns,
ImmutableArray<ExceptionObject> ExceptionObjects,
PolicyEvaluationReachability Reachability,
PolicyEvaluationEntropy Entropy,
DateTimeOffset? EvaluationTimestamp = null)
DateTimeOffset? EvaluationTimestamp = null,
string? PolicyDigest = null,
bool? ProvenanceAttested = null)
{
/// <summary>
/// Gets the evaluation timestamp for deterministic time-based operations.
@@ -39,8 +46,25 @@ internal sealed record PolicyEvaluationContext(
PolicyEvaluationVexEvidence vex,
PolicyEvaluationSbom sbom,
PolicyEvaluationExceptions exceptions,
DateTimeOffset? evaluationTimestamp = null)
: this(severity, environment, advisory, vex, sbom, exceptions, PolicyEvaluationReachability.Unknown, PolicyEvaluationEntropy.Unknown, evaluationTimestamp)
ImmutableArray<Unknown>? unknowns = null,
ImmutableArray<ExceptionObject>? exceptionObjects = null,
DateTimeOffset? evaluationTimestamp = null,
string? policyDigest = null,
bool? provenanceAttested = null)
: this(
severity,
environment,
advisory,
vex,
sbom,
exceptions,
unknowns ?? ImmutableArray<Unknown>.Empty,
exceptionObjects ?? ImmutableArray<ExceptionObject>.Empty,
PolicyEvaluationReachability.Unknown,
PolicyEvaluationEntropy.Unknown,
evaluationTimestamp,
policyDigest,
provenanceAttested)
{
}
}
@@ -100,7 +124,11 @@ internal sealed record PolicyEvaluationResult(
int? Priority,
ImmutableDictionary<string, string> Annotations,
ImmutableArray<string> Warnings,
PolicyExceptionApplication? AppliedException)
PolicyExceptionApplication? AppliedException,
ConfidenceScore? Confidence,
PolicyFailureReason? FailureReason = null,
string? FailureMessage = null,
BudgetStatusSummary? UnknownBudgetStatus = null)
{
public static PolicyEvaluationResult CreateDefault(string? severity) => new(
Matched: false,
@@ -110,7 +138,13 @@ internal sealed record PolicyEvaluationResult(
Priority: null,
Annotations: ImmutableDictionary<string, string>.Empty,
Warnings: ImmutableArray<string>.Empty,
AppliedException: null);
AppliedException: null,
Confidence: null);
}
internal enum PolicyFailureReason
{
UnknownBudgetExceeded
}
internal sealed record PolicyEvaluationExceptions(

View File

@@ -3,7 +3,15 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using StellaOps.Policy;
using StellaOps.Policy.Confidence.Configuration;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Confidence.Services;
using StellaOps.Policy.Unknowns.Models;
using StellaOps.Policy.Unknowns.Services;
using StellaOps.PolicyDsl;
namespace StellaOps.Policy.Engine.Evaluation;
@@ -13,6 +21,19 @@ namespace StellaOps.Policy.Engine.Evaluation;
/// </summary>
internal sealed class PolicyEvaluator
{
private readonly IConfidenceCalculator _confidenceCalculator;
private readonly IUnknownBudgetService? _budgetService;
public PolicyEvaluator(
IConfidenceCalculator? confidenceCalculator = null,
IUnknownBudgetService? budgetService = null)
{
_confidenceCalculator = confidenceCalculator
?? new ConfidenceCalculator(
new StaticOptionsMonitor<ConfidenceWeightOptions>(new ConfidenceWeightOptions()));
_budgetService = budgetService;
}
public PolicyEvaluationResult Evaluate(PolicyEvaluationRequest request)
{
if (request is null)
@@ -59,13 +80,18 @@ internal sealed class PolicyEvaluator
Priority: rule.Priority,
Annotations: runtime.Annotations.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase),
Warnings: runtime.Warnings.ToImmutableArray(),
AppliedException: null);
AppliedException: null,
Confidence: null);
return ApplyExceptions(request, baseResult);
var result = ApplyExceptions(request, baseResult);
var budgeted = ApplyUnknownBudget(request.Context, result);
return ApplyConfidence(request.Context, budgeted);
}
var defaultResult = PolicyEvaluationResult.CreateDefault(request.Context.Severity.Normalized);
return ApplyExceptions(request, defaultResult);
var defaultWithExceptions = ApplyExceptions(request, defaultResult);
var budgetedDefault = ApplyUnknownBudget(request.Context, defaultWithExceptions);
return ApplyConfidence(request.Context, budgetedDefault);
}
private static void ApplyAction(
@@ -417,4 +443,314 @@ internal sealed class PolicyEvaluator
AppliedException = application,
};
}
private PolicyEvaluationResult ApplyUnknownBudget(PolicyEvaluationContext context, PolicyEvaluationResult baseResult)
{
if (_budgetService is null || context.Unknowns.IsDefaultOrEmpty)
{
return baseResult;
}
var environment = ResolveEnvironmentName(context.Environment);
var budgetResult = _budgetService.CheckBudgetWithEscalation(
environment,
context.Unknowns,
context.ExceptionObjects);
var status = _budgetService.GetBudgetStatus(environment, context.Unknowns);
var annotations = baseResult.Annotations.ToBuilder();
annotations["unknownBudget.environment"] = environment;
annotations["unknownBudget.total"] = budgetResult.TotalUnknowns.ToString(CultureInfo.InvariantCulture);
annotations["unknownBudget.action"] = budgetResult.RecommendedAction.ToString();
if (budgetResult.TotalLimit.HasValue)
{
annotations["unknownBudget.totalLimit"] = budgetResult.TotalLimit.Value.ToString(CultureInfo.InvariantCulture);
}
annotations["unknownBudget.exceeded"] = (!budgetResult.IsWithinBudget).ToString();
if (!string.IsNullOrWhiteSpace(budgetResult.Message))
{
annotations["unknownBudget.message"] = budgetResult.Message!;
}
var warnings = baseResult.Warnings;
if (!budgetResult.IsWithinBudget
&& budgetResult.RecommendedAction is BudgetAction.Warn or BudgetAction.WarnUnlessException
&& !string.IsNullOrWhiteSpace(budgetResult.Message))
{
warnings = warnings.Add(budgetResult.Message!);
}
var result = baseResult with
{
Annotations = annotations.ToImmutable(),
Warnings = warnings,
UnknownBudgetStatus = status
};
if (_budgetService.ShouldBlock(budgetResult))
{
result = result with
{
Status = "blocked",
FailureReason = PolicyFailureReason.UnknownBudgetExceeded,
FailureMessage = budgetResult.Message ?? "Unknown budget exceeded"
};
}
return result;
}
private static string ResolveEnvironmentName(PolicyEvaluationEnvironment environment)
{
var name = environment.Get("name") ?? environment.Get("environment") ?? environment.Get("env");
return string.IsNullOrWhiteSpace(name) ? "default" : name.Trim();
}
private PolicyEvaluationResult ApplyConfidence(PolicyEvaluationContext context, PolicyEvaluationResult baseResult)
{
var input = BuildConfidenceInput(context, baseResult);
var confidence = _confidenceCalculator.Calculate(input);
return baseResult with { Confidence = confidence };
}
private static ConfidenceInput BuildConfidenceInput(PolicyEvaluationContext context, PolicyEvaluationResult result)
{
return new ConfidenceInput
{
Reachability = BuildReachabilityEvidence(context.Reachability),
Runtime = BuildRuntimeEvidence(context),
Vex = BuildVexEvidence(context),
Provenance = BuildProvenanceEvidence(context),
Policy = BuildPolicyEvidence(context, result),
Status = result.Status,
EvaluationTimestamp = context.Now
};
}
private static ReachabilityEvidence? BuildReachabilityEvidence(PolicyEvaluationReachability reachability)
{
if (reachability.IsUnknown && string.IsNullOrWhiteSpace(reachability.EvidenceRef))
{
return null;
}
var state = reachability.IsReachable
? (reachability.HasRuntimeEvidence ? ReachabilityState.ConfirmedReachable : ReachabilityState.StaticReachable)
: reachability.IsUnreachable
? (reachability.HasRuntimeEvidence ? ReachabilityState.ConfirmedUnreachable : ReachabilityState.StaticUnreachable)
: ReachabilityState.Unknown;
var digests = string.IsNullOrWhiteSpace(reachability.EvidenceRef)
? Array.Empty<string>()
: new[] { reachability.EvidenceRef! };
return new ReachabilityEvidence
{
State = state,
AnalysisConfidence = Clamp01(reachability.Confidence),
GraphDigests = digests
};
}
private static RuntimeEvidence? BuildRuntimeEvidence(PolicyEvaluationContext context)
{
if (!context.Reachability.HasRuntimeEvidence)
{
return null;
}
var posture = context.Reachability.IsReachable || context.Reachability.IsUnreachable
? RuntimePosture.Supports
: RuntimePosture.Unknown;
return new RuntimeEvidence
{
Posture = posture,
ObservationCount = 1,
LastObserved = context.Now,
SessionDigests = Array.Empty<string>()
};
}
private static VexEvidence? BuildVexEvidence(PolicyEvaluationContext context)
{
if (context.Vex.Statements.IsDefaultOrEmpty)
{
return null;
}
var issuer = string.IsNullOrWhiteSpace(context.Advisory.Source)
? "unknown"
: context.Advisory.Source;
var statements = context.Vex.Statements
.Select(statement =>
{
var timestamp = statement.Timestamp ?? DateTimeOffset.MinValue;
return new VexStatement
{
Status = MapVexStatus(statement.Status),
Issuer = issuer,
TrustScore = ComputeVexTrustScore(issuer, statement),
Timestamp = timestamp,
StatementDigest = ComputeVexDigest(issuer, statement, timestamp)
};
})
.ToList();
return new VexEvidence { Statements = statements };
}
private static ProvenanceEvidence? BuildProvenanceEvidence(PolicyEvaluationContext context)
{
var hasSbomComponents = !context.Sbom.Components.IsDefaultOrEmpty;
if (context.ProvenanceAttested is null && !hasSbomComponents)
{
return null;
}
var level = context.ProvenanceAttested == true ? ProvenanceLevel.Signed : ProvenanceLevel.Unsigned;
return new ProvenanceEvidence
{
Level = level,
SbomCompleteness = ComputeSbomCompleteness(context.Sbom),
AttestationDigests = Array.Empty<string>()
};
}
private static PolicyEvidence BuildPolicyEvidence(PolicyEvaluationContext context, PolicyEvaluationResult result)
{
var ruleName = result.RuleName ?? "default";
var matchStrength = result.Matched ? 0.9m : 0.6m;
return new PolicyEvidence
{
RuleName = ruleName,
MatchStrength = Clamp01(matchStrength),
EvaluationDigest = ComputePolicyEvaluationDigest(context.PolicyDigest, result)
};
}
private static VexStatus MapVexStatus(string status)
{
if (status.Equals("not_affected", StringComparison.OrdinalIgnoreCase))
{
return VexStatus.NotAffected;
}
if (status.Equals("fixed", StringComparison.OrdinalIgnoreCase))
{
return VexStatus.Fixed;
}
if (status.Equals("under_investigation", StringComparison.OrdinalIgnoreCase))
{
return VexStatus.UnderInvestigation;
}
if (status.Equals("affected", StringComparison.OrdinalIgnoreCase))
{
return VexStatus.Affected;
}
return VexStatus.UnderInvestigation;
}
private static decimal ComputeVexTrustScore(string issuer, PolicyEvaluationVexStatement statement)
{
var score = issuer.Contains("vendor", StringComparison.OrdinalIgnoreCase)
|| issuer.Contains("distro", StringComparison.OrdinalIgnoreCase)
? 0.85m
: 0.7m;
if (!string.IsNullOrWhiteSpace(statement.Justification))
{
score += 0.05m;
}
if (!string.IsNullOrWhiteSpace(statement.StatementId))
{
score += 0.05m;
}
return Clamp01(score);
}
private static string ComputeVexDigest(
string issuer,
PolicyEvaluationVexStatement statement,
DateTimeOffset timestamp)
{
var input = $"{issuer}|{statement.Status}|{statement.Justification}|{statement.StatementId}|{timestamp:O}";
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static decimal ComputeSbomCompleteness(PolicyEvaluationSbom sbom)
{
if (sbom.Components.IsDefaultOrEmpty)
{
return 0.4m;
}
var count = sbom.Components.Length;
return count switch
{
<= 5 => 0.6m,
<= 20 => 0.75m,
<= 100 => 0.85m,
_ => 0.9m
};
}
private static string ComputePolicyEvaluationDigest(string? policyDigest, PolicyEvaluationResult result)
{
var input = string.Join(
'|',
policyDigest ?? "unknown",
result.RuleName ?? "default",
result.Status,
result.Severity ?? "none",
result.Priority?.ToString(CultureInfo.InvariantCulture) ?? "none");
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(Encoding.UTF8.GetBytes(input), hash);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
private static decimal Clamp01(decimal value)
{
if (value <= 0m)
{
return 0m;
}
if (value >= 1m)
{
return 1m;
}
return value;
}
private sealed class StaticOptionsMonitor<T> : IOptionsMonitor<T>
{
private readonly T _value;
public StaticOptionsMonitor(T value) => _value = value;
public T CurrentValue => _value;
public T Get(string? name) => _value;
public IDisposable OnChange(Action<T, string> listener) => NullDisposable.Instance;
private sealed class NullDisposable : IDisposable
{
public static readonly NullDisposable Instance = new();
public void Dispose() { }
}
}
}

View File

@@ -8,19 +8,20 @@ namespace StellaOps.Policy.Engine.Services;
internal sealed partial class PolicyEvaluationService
{
private readonly PolicyEvaluator evaluator = new();
private readonly PolicyEvaluator _evaluator;
private readonly PathScopeMetrics _pathMetrics;
private readonly ILogger<PolicyEvaluationService> _logger;
public PolicyEvaluationService()
: this(new PathScopeMetrics(), NullLogger<PolicyEvaluationService>.Instance)
: this(new PathScopeMetrics(), NullLogger<PolicyEvaluationService>.Instance, new PolicyEvaluator())
{
}
public PolicyEvaluationService(PathScopeMetrics pathMetrics, ILogger<PolicyEvaluationService> logger)
public PolicyEvaluationService(PathScopeMetrics pathMetrics, ILogger<PolicyEvaluationService> logger, PolicyEvaluator evaluator)
{
_pathMetrics = pathMetrics ?? throw new ArgumentNullException(nameof(pathMetrics));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator));
}
internal Evaluation.PolicyEvaluationResult Evaluate(PolicyIrDocument document, Evaluation.PolicyEvaluationContext context)
@@ -36,7 +37,7 @@ internal sealed partial class PolicyEvaluationService
}
var request = new Evaluation.PolicyEvaluationRequest(document, context);
return evaluator.Evaluate(request);
return _evaluator.Evaluate(request);
}
// PathScopeSimulationService partial class relies on _pathMetrics.

View File

@@ -5,10 +5,13 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Engine.Caching;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Evaluation;
using StellaOps.Policy.Engine.Telemetry;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Unknowns.Models;
using StellaOps.PolicyDsl;
namespace StellaOps.Policy.Engine.Services;
@@ -48,6 +51,7 @@ internal sealed record RuntimeEvaluationResponse(
ImmutableDictionary<string, string> Annotations,
ImmutableArray<string> Warnings,
PolicyExceptionApplication? AppliedException,
ConfidenceScore? Confidence,
string CorrelationId,
bool Cached,
CacheSource CacheSource,
@@ -174,9 +178,13 @@ internal sealed class PolicyRuntimeEvaluationService
effectiveRequest.Vex,
effectiveRequest.Sbom,
effectiveRequest.Exceptions,
ImmutableArray<Unknown>.Empty,
ImmutableArray<ExceptionObject>.Empty,
effectiveRequest.Reachability,
entropy,
evaluationTimestamp);
evaluationTimestamp,
policyDigest: bundle.Digest,
provenanceAttested: effectiveRequest.ProvenanceAttested);
var evalRequest = new Evaluation.PolicyEvaluationRequest(document, context);
var result = _evaluator.Evaluate(evalRequest);
@@ -195,7 +203,8 @@ internal sealed class PolicyRuntimeEvaluationService
result.AppliedException?.ExceptionId,
correlationId,
evaluationTimestamp,
expiresAt);
expiresAt,
result.Confidence);
await _cache.SetAsync(cacheKey, cacheEntry, cancellationToken).ConfigureAwait(false);
@@ -244,6 +253,7 @@ internal sealed class PolicyRuntimeEvaluationService
result.Annotations,
result.Warnings,
result.AppliedException,
result.Confidence,
correlationId,
Cached: false,
CacheSource: CacheSource.None,
@@ -354,9 +364,13 @@ internal sealed class PolicyRuntimeEvaluationService
request.Vex,
request.Sbom,
request.Exceptions,
ImmutableArray<Unknown>.Empty,
ImmutableArray<ExceptionObject>.Empty,
request.Reachability,
entropy,
evaluationTimestamp);
evaluationTimestamp,
policyDigest: bundle.Digest,
provenanceAttested: request.ProvenanceAttested);
var evalRequest = new Evaluation.PolicyEvaluationRequest(document, context);
var result = _evaluator.Evaluate(evalRequest);
@@ -375,7 +389,8 @@ internal sealed class PolicyRuntimeEvaluationService
result.AppliedException?.ExceptionId,
correlationId,
evaluationTimestamp,
expiresAt);
expiresAt,
result.Confidence);
entriesToCache[key] = cacheEntry;
cacheMisses++;
@@ -413,6 +428,7 @@ internal sealed class PolicyRuntimeEvaluationService
result.Annotations,
result.Warnings,
result.AppliedException,
result.Confidence,
correlationId,
Cached: false,
CacheSource: CacheSource.None,
@@ -473,6 +489,7 @@ internal sealed class PolicyRuntimeEvaluationService
entry.Annotations,
entry.Warnings,
appliedException,
entry.Confidence,
entry.CorrelationId,
Cached: true,
CacheSource: source,
@@ -496,8 +513,12 @@ internal sealed class PolicyRuntimeEvaluationService
severityScore = request.Severity.Score,
advisorySource = request.Advisory.Source,
vexCount = request.Vex.Statements.Length,
vexStatements = request.Vex.Statements.Select(s => $"{s.Status}:{s.Justification}").OrderBy(s => s).ToArray(),
vexStatements = request.Vex.Statements
.Select(s => $"{s.Status}:{s.Justification}:{s.StatementId}:{s.Timestamp:O}")
.OrderBy(s => s)
.ToArray(),
sbomTags = request.Sbom.Tags.OrderBy(t => t).ToArray(),
sbomComponentCount = request.Sbom.Components.IsDefaultOrEmpty ? 0 : request.Sbom.Components.Length,
exceptionCount = request.Exceptions.Instances.Length,
reachability = new
{
@@ -506,7 +527,8 @@ internal sealed class PolicyRuntimeEvaluationService
score = request.Reachability.Score,
hasRuntimeEvidence = request.Reachability.HasRuntimeEvidence,
source = request.Reachability.Source,
method = request.Reachability.Method
method = request.Reachability.Method,
evidenceRef = request.Reachability.EvidenceRef
},
entropy = new
{

View File

@@ -7,3 +7,6 @@ This file mirrors sprint work for the Policy Engine module.
| `POLICY-GATE-401-033` | `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` | DONE (2025-12-13) | Implemented PolicyGateEvaluator (lattice/uncertainty/evidence completeness) and aligned tests/docs; see `src/Policy/StellaOps.Policy.Engine/Gates/PolicyGateEvaluator.cs` and `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Gates/PolicyGateEvaluatorTests.cs`. |
| `DET-3401-011` | `docs/implplan/SPRINT_3401_0001_0001_determinism_scoring_foundations.md` | DONE (2025-12-14) | Added `Explain` to `RiskScoringResult` and covered JSON serialization + null-coercion in `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Scoring/RiskScoringResultTests.cs`. |
| `PDA-3801-0001` | `docs/implplan/SPRINT_3801_0001_0001_policy_decision_attestation.md` | DONE (2025-12-19) | Implemented `PolicyDecisionAttestationService` + predicate model + DI wiring; covered signer/Rekor flows in `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Attestation/PolicyDecisionAttestationServiceTests.cs`. |
| `EXC-3900-0003-0002-T6` | `docs/implplan/SPRINT_3900_0003_0002_recheck_policy_evidence_hooks.md` | DONE (2025-12-22) | Added ExceptionRecheckGate and DI registration for build gate integration. |
| `UNK-4100-0001-T6` | `docs/implplan/SPRINT_4100_0001_0001_reason_coded_unknowns.md` | DONE (2025-12-22) | Extended unknowns API DTOs with reason codes, remediation hints, and evidence refs. |
| `UNK-4100-0001-0002` | `docs/implplan/SPRINT_4100_0001_0002_unknown_budgets.md` | DONE (2025-12-22) | Added unknown budget enforcement in policy evaluation, options binding, and budget service tests. |