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
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 07:52:25 +02:00
parent 5970f0d9bd
commit 150b3730ef
215 changed files with 8119 additions and 740 deletions

View File

@@ -37,6 +37,7 @@ using MongoDB.Driver;
using MongoDB.Bson;
using Microsoft.Extensions.Caching.Memory;
using StellaOps.Excititor.WebService.Contracts;
using StellaOps.Excititor.WebService.Graph;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@@ -523,10 +524,70 @@ var options = new VexObservationQueryOptions(
NextCursor: advisories.Count >= 200 ? $"{advisories[^1].AdvisoryId}:{advisories[^1].Source}" : null));
}
var response = new GraphLinkoutsResponse(items, notFound);
var response = new GraphLinkoutsResponse(items, notFound);
return Results.Ok(response);
}).WithName("PostGraphLinkouts");
// Cartographer overlays
app.MapGet("/v1/graph/overlays", async (
HttpContext context,
[FromQuery(Name = "purl")] string[]? purls,
[FromQuery] bool includeJustifications,
IOptions<VexMongoStorageOptions> storageOptions,
IOptions<GraphOptions> graphOptions,
IVexObservationQueryService queryService,
IMemoryCache cache,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
if (!TryResolveTenant(context, storageOptions.Value, requireHeader: true, out var tenant, out var tenantError))
{
return tenantError;
}
var orderedPurls = NormalizePurls(purls);
if (orderedPurls.Count == 0)
{
return Results.BadRequest("purl query parameter is required");
}
if (orderedPurls.Count > graphOptions.Value.MaxPurls)
{
return Results.BadRequest($"purls limit exceeded (max {graphOptions.Value.MaxPurls})");
}
var cacheKey = $"graph-overlays:{tenant}:{includeJustifications}:{string.Join('|', orderedPurls)}";
var now = timeProvider.GetUtcNow();
if (cache.TryGetValue<CachedGraphOverlay>(cacheKey, out var cached) && cached is not null)
{
var ageMs = (long)Math.Max(0, (now - cached.CachedAt).TotalMilliseconds);
return Results.Ok(new GraphOverlaysResponse(cached.Items, true, ageMs));
}
var options = new VexObservationQueryOptions(
tenant: tenant,
purls: orderedPurls,
limit: graphOptions.Value.MaxAdvisoriesPerPurl * orderedPurls.Count);
VexObservationQueryResult result;
try
{
result = await queryService.QueryAsync(options, cancellationToken).ConfigureAwait(false);
}
catch (FormatException ex)
{
return Results.BadRequest(ex.Message);
}
var overlays = GraphOverlayFactory.Build(orderedPurls, result.Observations, includeJustifications);
var response = new GraphOverlaysResponse(overlays, false, null);
cache.Set(cacheKey, new CachedGraphOverlay(overlays, now), TimeSpan.FromSeconds(graphOptions.Value.OverlayTtlSeconds));
return Results.Ok(response);
}).WithName("GetGraphOverlays");
app.MapPost("/ingest/vex", async (
HttpContext context,
VexIngestRequest request,