up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-01 21:16:22 +02:00
parent c11d87d252
commit 909d9b6220
208 changed files with 860954 additions and 832 deletions

View File

@@ -58,6 +58,7 @@ internal sealed class PolicyRuntimeEvaluationService
private readonly IPolicyPackRepository _repository;
private readonly IPolicyEvaluationCache _cache;
private readonly PolicyEvaluator _evaluator;
private readonly ReachabilityFacts.ReachabilityFactsJoiningService? _reachabilityFacts;
private readonly TimeProvider _timeProvider;
private readonly ILogger<PolicyRuntimeEvaluationService> _logger;
@@ -71,12 +72,14 @@ internal sealed class PolicyRuntimeEvaluationService
IPolicyPackRepository repository,
IPolicyEvaluationCache cache,
PolicyEvaluator evaluator,
ReachabilityFacts.ReachabilityFactsJoiningService? reachabilityFacts,
TimeProvider timeProvider,
ILogger<PolicyRuntimeEvaluationService> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator));
_reachabilityFacts = reachabilityFacts;
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -90,35 +93,38 @@ internal sealed class PolicyRuntimeEvaluationService
{
ArgumentNullException.ThrowIfNull(request);
using var activity = PolicyEngineTelemetry.StartEvaluateActivity(
request.TenantId, request.PackId, runId: null);
activity?.SetTag("policy.version", request.Version);
activity?.SetTag("subject.purl", request.SubjectPurl);
activity?.SetTag("advisory.id", request.AdvisoryId);
var startTimestamp = _timeProvider.GetTimestamp();
var evaluationTimestamp = request.EvaluationTimestamp ?? _timeProvider.GetUtcNow();
var effectiveRequest = _reachabilityFacts is null
? request
: await EnrichReachabilityAsync(request, cancellationToken).ConfigureAwait(false);
using var activity = PolicyEngineTelemetry.StartEvaluateActivity(
effectiveRequest.TenantId, effectiveRequest.PackId, runId: null);
activity?.SetTag("policy.version", effectiveRequest.Version);
activity?.SetTag("subject.purl", effectiveRequest.SubjectPurl);
activity?.SetTag("advisory.id", effectiveRequest.AdvisoryId);
// Load the compiled policy bundle
var bundle = await _repository.GetBundleAsync(request.PackId, request.Version, cancellationToken)
var bundle = await _repository.GetBundleAsync(effectiveRequest.PackId, effectiveRequest.Version, cancellationToken)
.ConfigureAwait(false);
if (bundle is null)
{
PolicyEngineTelemetry.RecordError("evaluation", request.TenantId);
PolicyEngineTelemetry.RecordEvaluationFailure(request.TenantId, request.PackId, "bundle_not_found");
PolicyEngineTelemetry.RecordError("evaluation", effectiveRequest.TenantId);
PolicyEngineTelemetry.RecordEvaluationFailure(effectiveRequest.TenantId, effectiveRequest.PackId, "bundle_not_found");
activity?.SetStatus(ActivityStatusCode.Error, "Bundle not found");
throw new InvalidOperationException(
$"Policy bundle not found for pack '{request.PackId}' version {request.Version}.");
$"Policy bundle not found for pack '{effectiveRequest.PackId}' version {effectiveRequest.Version}.");
}
// Compute deterministic cache key
var subjectDigest = ComputeSubjectDigest(request.TenantId, request.SubjectPurl, request.AdvisoryId);
var contextDigest = ComputeContextDigest(request);
var subjectDigest = ComputeSubjectDigest(effectiveRequest.TenantId, effectiveRequest.SubjectPurl, effectiveRequest.AdvisoryId);
var contextDigest = ComputeContextDigest(effectiveRequest);
var cacheKey = PolicyEvaluationCacheKey.Create(bundle.Digest, subjectDigest, contextDigest);
// Try cache lookup unless bypassed
if (!request.BypassCache)
if (!effectiveRequest.BypassCache)
{
var cacheResult = await _cache.GetAsync(cacheKey, cancellationToken).ConfigureAwait(false);
if (cacheResult.CacheHit && cacheResult.Entry is not null)
@@ -132,10 +138,10 @@ internal sealed class PolicyRuntimeEvaluationService
activity?.SetStatus(ActivityStatusCode.Ok);
_logger.LogDebug(
"Cache hit for evaluation {PackId}@{Version} subject {Subject} from {Source}",
request.PackId, request.Version, request.SubjectPurl, cacheResult.Source);
effectiveRequest.PackId, effectiveRequest.Version, effectiveRequest.SubjectPurl, cacheResult.Source);
return CreateResponseFromCache(
request, bundle.Digest, cacheResult.Entry, cacheResult.Source, duration);
effectiveRequest, bundle.Digest, cacheResult.Entry, cacheResult.Source, duration);
}
}
@@ -153,13 +159,13 @@ internal sealed class PolicyRuntimeEvaluationService
}
var context = new PolicyEvaluationContext(
request.Severity,
effectiveRequest.Severity,
new PolicyEvaluationEnvironment(ImmutableDictionary<string, string>.Empty),
request.Advisory,
request.Vex,
request.Sbom,
request.Exceptions,
request.Reachability,
effectiveRequest.Advisory,
effectiveRequest.Vex,
effectiveRequest.Sbom,
effectiveRequest.Exceptions,
effectiveRequest.Reachability,
evaluationTimestamp);
var evalRequest = new Evaluation.PolicyEvaluationRequest(document, context);
@@ -187,11 +193,25 @@ internal sealed class PolicyRuntimeEvaluationService
var evalDurationSeconds = evalDuration / 1000.0;
// Record metrics
PolicyEngineTelemetry.RecordEvaluationLatency(evalDurationSeconds, request.TenantId, request.PackId);
PolicyEngineTelemetry.RecordEvaluation(request.TenantId, request.PackId, "full");
PolicyEngineTelemetry.RecordEvaluationLatency(evalDurationSeconds, effectiveRequest.TenantId, effectiveRequest.PackId);
PolicyEngineTelemetry.RecordEvaluation(effectiveRequest.TenantId, effectiveRequest.PackId, "full");
if (!string.IsNullOrEmpty(result.RuleName))
{
PolicyEngineTelemetry.RecordRuleFired(request.PackId, result.RuleName);
PolicyEngineTelemetry.RecordRuleFired(effectiveRequest.PackId, result.RuleName);
}
if (result.AppliedException is not null)
{
PolicyEngineTelemetry.RecordExceptionApplication(effectiveRequest.TenantId, result.AppliedException.EffectType.ToString());
PolicyEngineTelemetry.RecordExceptionApplicationLatency(evalDurationSeconds, effectiveRequest.TenantId, result.AppliedException.EffectType.ToString());
_logger.LogInformation(
"Applied exception {ExceptionId} (effect {EffectType}) for tenant {TenantId} pack {PackId}@{Version} aoc {CompilationId}",
result.AppliedException.ExceptionId,
result.AppliedException.EffectType,
effectiveRequest.TenantId,
effectiveRequest.PackId,
effectiveRequest.Version,
bundle.AocMetadata?.CompilationId ?? "none");
}
activity?.SetTag("evaluation.status", result.Status);
@@ -201,11 +221,11 @@ internal sealed class PolicyRuntimeEvaluationService
_logger.LogDebug(
"Evaluated {PackId}@{Version} subject {Subject} in {Duration}ms - {Status}",
request.PackId, request.Version, request.SubjectPurl, evalDuration, result.Status);
effectiveRequest.PackId, effectiveRequest.Version, effectiveRequest.SubjectPurl, evalDuration, result.Status);
return new RuntimeEvaluationResponse(
request.PackId,
request.Version,
effectiveRequest.PackId,
effectiveRequest.Version,
bundle.Digest,
result.Status,
result.Severity,
@@ -240,8 +260,12 @@ internal sealed class PolicyRuntimeEvaluationService
var cacheHits = 0;
var cacheMisses = 0;
var hydratedRequests = _reachabilityFacts is null
? requests
: await EnrichReachabilityBatchAsync(requests, cancellationToken).ConfigureAwait(false);
// Group by pack/version for bundle loading efficiency
var groups = requests.GroupBy(r => (r.PackId, r.Version));
var groups = hydratedRequests.GroupBy(r => (r.PackId, r.Version));
foreach (var group in groups)
{
@@ -351,6 +375,20 @@ internal sealed class PolicyRuntimeEvaluationService
PolicyEngineTelemetry.RecordRuleFired(packId, result.RuleName);
}
if (result.AppliedException is not null)
{
PolicyEngineTelemetry.RecordExceptionApplication(request.TenantId, result.AppliedException.EffectType.ToString());
PolicyEngineTelemetry.RecordExceptionApplicationLatency(duration / 1000.0, request.TenantId, result.AppliedException.EffectType.ToString());
_logger.LogInformation(
"Applied exception {ExceptionId} (effect {EffectType}) for tenant {TenantId} pack {PackId}@{Version} aoc {CompilationId}",
result.AppliedException.ExceptionId,
result.AppliedException.EffectType,
request.TenantId,
request.PackId,
request.Version,
bundle.AocMetadata?.CompilationId ?? "none");
}
results.Add(new RuntimeEvaluationResponse(
request.PackId,
request.Version,
@@ -448,7 +486,15 @@ internal sealed class PolicyRuntimeEvaluationService
vexStatements = request.Vex.Statements.Select(s => $"{s.Status}:{s.Justification}").OrderBy(s => s).ToArray(),
sbomTags = request.Sbom.Tags.OrderBy(t => t).ToArray(),
exceptionCount = request.Exceptions.Instances.Length,
reachability = request.Reachability.State,
reachability = new
{
state = request.Reachability.State,
confidence = request.Reachability.Confidence,
score = request.Reachability.Score,
hasRuntimeEvidence = request.Reachability.HasRuntimeEvidence,
source = request.Reachability.Source,
method = request.Reachability.Method
},
};
var json = JsonSerializer.Serialize(contextData, ContextSerializerOptions);
@@ -470,5 +516,98 @@ internal sealed class PolicyRuntimeEvaluationService
var elapsed = _timeProvider.GetElapsedTime(startTimestamp);
return (long)elapsed.TotalMilliseconds;
}
private async Task<RuntimeEvaluationRequest> EnrichReachabilityAsync(
RuntimeEvaluationRequest request,
CancellationToken cancellationToken)
{
if (_reachabilityFacts is null || !request.Reachability.IsUnknown)
{
return request;
}
var fact = await _reachabilityFacts
.GetFactAsync(request.TenantId, request.SubjectPurl, request.AdvisoryId, cancellationToken)
.ConfigureAwait(false);
if (fact is null)
{
return request;
}
var reachability = new PolicyEvaluationReachability(
State: fact.State.ToString().ToLowerInvariant(),
Confidence: fact.Confidence,
Score: fact.Score,
HasRuntimeEvidence: fact.HasRuntimeEvidence,
Source: fact.Source,
Method: fact.Method.ToString().ToLowerInvariant(),
EvidenceRef: fact.EvidenceRef ?? fact.EvidenceHash);
ReachabilityFacts.ReachabilityFactsTelemetry.RecordFactApplied(reachability.State);
return request with { Reachability = reachability };
}
private async Task<IReadOnlyList<RuntimeEvaluationRequest>> EnrichReachabilityBatchAsync(
IReadOnlyList<RuntimeEvaluationRequest> requests,
CancellationToken cancellationToken)
{
if (_reachabilityFacts is null)
{
return requests;
}
var enriched = new List<RuntimeEvaluationRequest>(requests.Count);
foreach (var tenantGroup in requests.GroupBy(r => r.TenantId, StringComparer.Ordinal))
{
var pending = tenantGroup
.Where(r => r.Reachability.IsUnknown)
.Select(r => new ReachabilityFacts.ReachabilityFactsRequest(r.SubjectPurl, r.AdvisoryId))
.Distinct()
.ToList();
ReachabilityFacts.ReachabilityFactsBatch? batch = null;
if (pending.Count > 0)
{
batch = await _reachabilityFacts
.GetFactsBatchAsync(tenantGroup.Key, pending, cancellationToken)
.ConfigureAwait(false);
}
var lookup = batch?.Found ?? new Dictionary<ReachabilityFacts.ReachabilityFactKey, ReachabilityFacts.ReachabilityFact>();
foreach (var request in tenantGroup)
{
if (!request.Reachability.IsUnknown)
{
enriched.Add(request);
continue;
}
var key = new ReachabilityFacts.ReachabilityFactKey(request.TenantId, request.SubjectPurl, request.AdvisoryId);
if (lookup.TryGetValue(key, out var fact))
{
var reachability = new PolicyEvaluationReachability(
State: fact.State.ToString().ToLowerInvariant(),
Confidence: fact.Confidence,
Score: fact.Score,
HasRuntimeEvidence: fact.HasRuntimeEvidence,
Source: fact.Source,
Method: fact.Method.ToString().ToLowerInvariant(),
EvidenceRef: fact.EvidenceRef ?? fact.EvidenceHash);
ReachabilityFacts.ReachabilityFactsTelemetry.RecordFactApplied(reachability.State);
enriched.Add(request with { Reachability = reachability });
}
else
{
enriched.Add(request);
}
}
}
return enriched;
}
}