feat: Implement Policy Engine Evaluation Service and Cache with unit tests
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

Temp commit to debug
This commit is contained in:
master
2025-11-05 07:35:53 +00:00
parent 40e7f827da
commit 9253620833
125 changed files with 18735 additions and 17215 deletions

View File

@@ -1,12 +1,16 @@
using System.Diagnostics;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.WebService.Diagnostics;
using StellaOps.Scanner.WebService.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.WebService.Diagnostics;
using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.Validation;
namespace StellaOps.Scanner.WebService.Endpoints;
@@ -56,27 +60,69 @@ internal static class HealthEndpoints
return Json(document, StatusCodes.Status200OK);
}
private static async Task<IResult> HandleReady(
ServiceStatus status,
HttpContext context,
CancellationToken cancellationToken)
{
ApplyNoCache(context.Response);
await Task.CompletedTask;
status.RecordReadyCheck(success: true, latency: TimeSpan.Zero, error: null);
var snapshot = status.CreateSnapshot();
var ready = snapshot.Ready;
var document = new ReadyDocument(
Status: ready.IsReady ? "ready" : "unready",
CheckedAt: ready.CheckedAt,
LatencyMs: ready.Latency?.TotalMilliseconds,
Error: ready.Error);
return Json(document, StatusCodes.Status200OK);
}
private static async Task<IResult> HandleReady(
ServiceStatus status,
ISurfaceValidatorRunner validatorRunner,
ISurfaceEnvironment surfaceEnvironment,
ILoggerFactory loggerFactory,
HttpContext context,
CancellationToken cancellationToken)
{
ApplyNoCache(context.Response);
ArgumentNullException.ThrowIfNull(loggerFactory);
var logger = loggerFactory.CreateLogger("Scanner.WebService.Health");
var stopwatch = Stopwatch.StartNew();
var success = true;
string? error = null;
try
{
var validationContext = SurfaceValidationContext.Create(
context.RequestServices,
"Scanner.WebService.ReadyCheck",
surfaceEnvironment.Settings,
properties: new Dictionary<string, object?>
{
["path"] = context.Request.Path.ToString()
});
await validatorRunner.EnsureAsync(validationContext, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (SurfaceValidationException ex)
{
success = false;
error = ex.Message;
}
catch (Exception ex)
{
success = false;
error = ex.Message;
logger.LogError(ex, "Surface validation failed during ready check.");
}
finally
{
stopwatch.Stop();
}
status.RecordReadyCheck(success, stopwatch.Elapsed, error);
var snapshot = status.CreateSnapshot();
var ready = snapshot.Ready;
var document = new ReadyDocument(
Status: ready.IsReady ? "ready" : "unready",
CheckedAt: ready.CheckedAt,
LatencyMs: ready.Latency?.TotalMilliseconds,
Error: ready.Error);
var statusCode = success ? StatusCodes.Status200OK : StatusCodes.Status503ServiceUnavailable;
return Json(document, statusCode);
}
private static void ApplyNoCache(HttpResponse response)
{

View File

@@ -1,17 +1,18 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Policy;
using StellaOps.Scanner.WebService.Constants;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Infrastructure;
using StellaOps.Scanner.WebService.Security;
using StellaOps.Scanner.WebService.Services;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using StellaOps.Policy;
using StellaOps.Scanner.WebService.Constants;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Infrastructure;
using StellaOps.Scanner.WebService.Security;
using StellaOps.Scanner.WebService.Services;
namespace StellaOps.Scanner.WebService.Endpoints;
@@ -49,25 +50,30 @@ internal static class ReportEndpoints
});
}
private static async Task<IResult> HandleCreateReportAsync(
ReportRequestDto request,
PolicyPreviewService previewService,
IReportSigner signer,
TimeProvider timeProvider,
IReportEventDispatcher eventDispatcher,
HttpContext context,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(previewService);
ArgumentNullException.ThrowIfNull(signer);
ArgumentNullException.ThrowIfNull(timeProvider);
ArgumentNullException.ThrowIfNull(eventDispatcher);
if (string.IsNullOrWhiteSpace(request.ImageDigest))
{
return ProblemResultFactory.Create(
context,
private static async Task<IResult> HandleCreateReportAsync(
ReportRequestDto request,
PolicyPreviewService previewService,
IReportSigner signer,
TimeProvider timeProvider,
IReportEventDispatcher eventDispatcher,
ISurfacePointerService surfacePointerService,
ILoggerFactory loggerFactory,
HttpContext context,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(previewService);
ArgumentNullException.ThrowIfNull(signer);
ArgumentNullException.ThrowIfNull(timeProvider);
ArgumentNullException.ThrowIfNull(eventDispatcher);
ArgumentNullException.ThrowIfNull(surfacePointerService);
ArgumentNullException.ThrowIfNull(loggerFactory);
var logger = loggerFactory.CreateLogger("Scanner.WebService.Reports");
if (string.IsNullOrWhiteSpace(request.ImageDigest))
{
return ProblemResultFactory.Create(
context,
ProblemTypes.Validation,
"Invalid report request",
StatusCodes.Status400BadRequest,
@@ -127,26 +133,46 @@ internal static class ReportEndpoints
.ToArray();
var issuesDto = preview.Issues.Select(PolicyDtoMapper.ToIssueDto).ToArray();
var summary = BuildSummary(projectedVerdicts);
var verdict = ComputeVerdict(projectedVerdicts);
var reportId = CreateReportId(request.ImageDigest!, preview.PolicyDigest);
var generatedAt = timeProvider.GetUtcNow();
var document = new ReportDocumentDto
{
ReportId = reportId,
ImageDigest = request.ImageDigest!,
GeneratedAt = generatedAt,
Verdict = verdict,
Policy = new ReportPolicyDto
{
RevisionId = preview.RevisionId,
Digest = preview.PolicyDigest
},
Summary = summary,
Verdicts = projectedVerdicts,
Issues = issuesDto
};
var summary = BuildSummary(projectedVerdicts);
var verdict = ComputeVerdict(projectedVerdicts);
var reportId = CreateReportId(request.ImageDigest!, preview.PolicyDigest);
var generatedAt = timeProvider.GetUtcNow();
SurfacePointersDto? surfacePointers = null;
try
{
surfacePointers = await surfacePointerService
.TryBuildAsync(request.ImageDigest!, context.RequestAborted)
.ConfigureAwait(false);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
if (!context.RequestAborted.IsCancellationRequested)
{
logger.LogDebug(ex, "Failed to build surface pointers for digest {Digest}.", request.ImageDigest);
}
}
var document = new ReportDocumentDto
{
ReportId = reportId,
ImageDigest = request.ImageDigest!,
GeneratedAt = generatedAt,
Verdict = verdict,
Policy = new ReportPolicyDto
{
RevisionId = preview.RevisionId,
Digest = preview.PolicyDigest
},
Summary = summary,
Verdicts = projectedVerdicts,
Issues = issuesDto,
Surface = surfacePointers
};
var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(document, SerializerOptions);
var signature = signer.Sign(payloadBytes);
@@ -169,11 +195,11 @@ internal static class ReportEndpoints
};
}
var response = new ReportResponseDto
{
Report = document,
Dsse = envelope
};
var response = new ReportResponseDto
{
Report = document,
Dsse = envelope
};
await eventDispatcher
.PublishAsync(request, preview, document, envelope, context, cancellationToken)

View File

@@ -140,10 +140,12 @@ internal static class ScanEndpoints
private static async Task<IResult> HandleStatusAsync(
string scanId,
IScanCoordinator coordinator,
ISurfacePointerService surfacePointerService,
HttpContext context,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(coordinator);
ArgumentNullException.ThrowIfNull(surfacePointerService);
if (!ScanId.TryParse(scanId, out var parsed))
{
@@ -163,7 +165,23 @@ internal static class ScanEndpoints
ProblemTypes.NotFound,
"Scan not found",
StatusCodes.Status404NotFound,
detail: "Requested scan could not be located.");
detail: "Requested scan could not be located.");
}
SurfacePointersDto? surfacePointers = null;
var digest = snapshot.Target.Digest;
if (!string.IsNullOrWhiteSpace(digest))
{
try
{
surfacePointers = await surfacePointerService
.TryBuildAsync(digest!, context.RequestAborted)
.ConfigureAwait(false);
}
catch (OperationCanceledException) when (context.RequestAborted.IsCancellationRequested)
{
throw;
}
}
var response = new ScanStatusResponse(
@@ -172,7 +190,8 @@ internal static class ScanEndpoints
Image: new ScanStatusTarget(snapshot.Target.Reference, snapshot.Target.Digest),
CreatedAt: snapshot.CreatedAt,
UpdatedAt: snapshot.UpdatedAt,
FailureReason: snapshot.FailureReason);
FailureReason: snapshot.FailureReason,
Surface: surfacePointers);
return Json(response, StatusCodes.Status200OK);
}