up
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Policy.Engine.Caching;
|
||||
using StellaOps.Policy.Engine.Evaluation;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
using StellaOps.PolicyDsl;
|
||||
|
||||
namespace StellaOps.Policy.Engine.BatchEvaluation;
|
||||
|
||||
internal sealed record BatchEvaluationRequestDto(
|
||||
string TenantId,
|
||||
IReadOnlyList<BatchEvaluationItemDto> Items,
|
||||
int? PageSize = null,
|
||||
string? PageToken = null,
|
||||
int? BudgetMs = null);
|
||||
|
||||
internal sealed record BatchEvaluationItemDto(
|
||||
string PackId,
|
||||
int Version,
|
||||
string SubjectPurl,
|
||||
string AdvisoryId,
|
||||
EvaluationSeverityDto Severity,
|
||||
AdvisoryDto Advisory,
|
||||
VexEvidenceDto Vex,
|
||||
SbomDto Sbom,
|
||||
ExceptionsDto Exceptions,
|
||||
ReachabilityDto Reachability,
|
||||
DateTimeOffset? EvaluationTimestamp,
|
||||
bool BypassCache = false);
|
||||
|
||||
internal sealed record EvaluationSeverityDto(string Normalized, decimal? Score = null);
|
||||
|
||||
internal sealed record AdvisoryDto(IDictionary<string, string> Metadata, string Source = "unknown");
|
||||
|
||||
internal sealed record VexEvidenceDto(IReadOnlyList<VexStatementDto> Statements);
|
||||
|
||||
internal sealed record VexStatementDto(string Status, string Justification, string StatementId, DateTimeOffset? Timestamp = null);
|
||||
|
||||
internal sealed record SbomDto(IReadOnlyList<string> Tags, IReadOnlyList<ComponentDto>? Components = null);
|
||||
|
||||
internal sealed record ComponentDto(
|
||||
string Name,
|
||||
string Version,
|
||||
string Type,
|
||||
string? Purl = null,
|
||||
IDictionary<string, string>? Metadata = null);
|
||||
|
||||
internal sealed record ExceptionsDto(
|
||||
IDictionary<string, PolicyExceptionEffect>? Effects = null,
|
||||
IReadOnlyList<ExceptionInstanceDto>? Instances = null);
|
||||
|
||||
internal sealed record ExceptionInstanceDto(
|
||||
string Id,
|
||||
string EffectId,
|
||||
ExceptionScopeDto Scope,
|
||||
DateTimeOffset CreatedAt,
|
||||
IDictionary<string, string>? Metadata = null);
|
||||
|
||||
internal sealed record ExceptionScopeDto(
|
||||
IReadOnlyList<string>? RuleNames = null,
|
||||
IReadOnlyList<string>? Severities = null,
|
||||
IReadOnlyList<string>? Sources = null,
|
||||
IReadOnlyList<string>? Tags = null);
|
||||
|
||||
internal sealed record ReachabilityDto(
|
||||
string State,
|
||||
decimal Confidence = 0m,
|
||||
decimal Score = 0m,
|
||||
bool HasRuntimeEvidence = false,
|
||||
string? Source = null,
|
||||
string? Method = null,
|
||||
string? EvidenceRef = null);
|
||||
|
||||
internal sealed record BatchEvaluationResultDto(
|
||||
string PackId,
|
||||
int Version,
|
||||
string PolicyDigest,
|
||||
string Status,
|
||||
string? Severity,
|
||||
string? RuleName,
|
||||
int? Priority,
|
||||
IReadOnlyDictionary<string, string> Annotations,
|
||||
IReadOnlyList<string> Warnings,
|
||||
PolicyExceptionApplication? AppliedException,
|
||||
string CorrelationId,
|
||||
bool Cached,
|
||||
CacheSource CacheSource,
|
||||
long EvaluationDurationMs);
|
||||
|
||||
internal sealed record BatchEvaluationResponseDto(
|
||||
IReadOnlyList<BatchEvaluationResultDto> Results,
|
||||
string? NextPageToken,
|
||||
int Total,
|
||||
int Returned,
|
||||
int CacheHits,
|
||||
int CacheMisses,
|
||||
long DurationMs,
|
||||
long? BudgetRemainingMs);
|
||||
|
||||
internal static class BatchEvaluationValidator
|
||||
{
|
||||
public static bool TryValidate(BatchEvaluationRequestDto request, out string error)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
error = "Request body is required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Items is null || request.Items.Count == 0)
|
||||
{
|
||||
error = "At least one item is required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.PageSize is int size && (size <= 0 || size > 500))
|
||||
{
|
||||
error = "PageSize must be between 1 and 500.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Items.Any(static i => i.EvaluationTimestamp is null))
|
||||
{
|
||||
error = "Each item must provide evaluationTimestamp to keep evaluation deterministic.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class BatchEvaluationMapper
|
||||
{
|
||||
public static IReadOnlyList<RuntimeEvaluationRequest> ToRuntimeRequests(string tenantId, IEnumerable<BatchEvaluationItemDto> items)
|
||||
{
|
||||
return items.Select(item => ToRuntimeRequest(tenantId, item)).ToList();
|
||||
}
|
||||
|
||||
private static RuntimeEvaluationRequest ToRuntimeRequest(string tenantId, BatchEvaluationItemDto item)
|
||||
{
|
||||
var severity = new PolicyEvaluationSeverity(
|
||||
item.Severity.Normalized,
|
||||
item.Severity.Score);
|
||||
|
||||
var advisory = new PolicyEvaluationAdvisory(
|
||||
item.Advisory.Source,
|
||||
item.Advisory.Metadata.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
var vex = new PolicyEvaluationVexEvidence(
|
||||
item.Vex.Statements
|
||||
.Select(stmt => new PolicyEvaluationVexStatement(
|
||||
stmt.Status,
|
||||
stmt.Justification,
|
||||
stmt.StatementId,
|
||||
stmt.Timestamp))
|
||||
.ToImmutableArray());
|
||||
|
||||
var sbom = new PolicyEvaluationSbom(
|
||||
item.Sbom.Tags.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase),
|
||||
(item.Sbom.Components ?? Array.Empty<ComponentDto>())
|
||||
.Select(comp => new PolicyEvaluationComponent(
|
||||
comp.Name,
|
||||
comp.Version,
|
||||
comp.Type,
|
||||
comp.Purl,
|
||||
(comp.Metadata ?? new Dictionary<string, string>())
|
||||
.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)))
|
||||
.ToImmutableArray());
|
||||
|
||||
var exceptions = new PolicyEvaluationExceptions(
|
||||
(item.Exceptions.Effects ?? new Dictionary<string, PolicyExceptionEffect>())
|
||||
.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase),
|
||||
(item.Exceptions.Instances ?? Array.Empty<ExceptionInstanceDto>())
|
||||
.Select(instance => new PolicyEvaluationExceptionInstance(
|
||||
instance.Id,
|
||||
instance.EffectId,
|
||||
new PolicyEvaluationExceptionScope(
|
||||
Normalize(instance.Scope.RuleNames),
|
||||
Normalize(instance.Scope.Severities),
|
||||
Normalize(instance.Scope.Sources),
|
||||
Normalize(instance.Scope.Tags)),
|
||||
instance.CreatedAt,
|
||||
(instance.Metadata ?? new Dictionary<string, string>())
|
||||
.ToImmutableDictionary(StringComparer.OrdinalIgnoreCase)))
|
||||
.ToImmutableArray());
|
||||
|
||||
var reachability = new PolicyEvaluationReachability(
|
||||
item.Reachability.State,
|
||||
item.Reachability.Confidence,
|
||||
item.Reachability.Score,
|
||||
item.Reachability.HasRuntimeEvidence,
|
||||
item.Reachability.Source,
|
||||
item.Reachability.Method,
|
||||
item.Reachability.EvidenceRef);
|
||||
|
||||
return new RuntimeEvaluationRequest(
|
||||
PackId: item.PackId,
|
||||
Version: item.Version,
|
||||
TenantId: tenantId,
|
||||
SubjectPurl: item.SubjectPurl,
|
||||
AdvisoryId: item.AdvisoryId,
|
||||
Severity: severity,
|
||||
Advisory: advisory,
|
||||
Vex: vex,
|
||||
Sbom: sbom,
|
||||
Exceptions: exceptions,
|
||||
Reachability: reachability,
|
||||
EvaluationTimestamp: item.EvaluationTimestamp,
|
||||
BypassCache: item.BypassCache);
|
||||
}
|
||||
|
||||
private static ImmutableHashSet<string> Normalize(IReadOnlyList<string>? values)
|
||||
{
|
||||
return (values ?? Array.Empty<string>())
|
||||
.Where(static value => !string.IsNullOrWhiteSpace(value))
|
||||
.Select(static value => value.Trim())
|
||||
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IRuntimeEvaluationExecutor
|
||||
{
|
||||
Task<RuntimeEvaluationResponse> EvaluateAsync(RuntimeEvaluationRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
internal sealed class RuntimeEvaluationExecutor : IRuntimeEvaluationExecutor
|
||||
{
|
||||
private readonly PolicyRuntimeEvaluationService _service;
|
||||
|
||||
public RuntimeEvaluationExecutor(PolicyRuntimeEvaluationService service)
|
||||
{
|
||||
_service = service ?? throw new ArgumentNullException(nameof(service));
|
||||
}
|
||||
|
||||
public Task<RuntimeEvaluationResponse> EvaluateAsync(RuntimeEvaluationRequest request, CancellationToken cancellationToken) =>
|
||||
_service.EvaluateAsync(request, cancellationToken);
|
||||
}
|
||||
Reference in New Issue
Block a user