feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies.
- Documented roles and guidelines in AGENTS.md for Scheduler module.
- Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs.
- Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics.
- Developed API endpoints for managing resolver jobs and retrieving metrics.
- Defined models for resolver job requests and responses.
- Integrated dependency injection for resolver job services.
- Implemented ImpactIndexSnapshot for persisting impact index data.
- Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring.
- Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService.
- Created dotnet-filter.sh script to handle command-line arguments for dotnet.
- Established nuget-prime project for managing package downloads.
This commit is contained in:
master
2025-11-18 07:52:15 +02:00
parent e69b57d467
commit 8355e2ff75
299 changed files with 13293 additions and 2444 deletions

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using MongoDB.Bson.Serialization.Attributes;
namespace StellaOps.Concelier.WebService.Tests;
/// <summary>
/// Minimal linkset document used only for seeding the Mongo collection in WebService integration tests.
/// Matches the shape written by the linkset ingestion pipeline.
/// </summary>
internal sealed class AdvisoryLinksetDocument
{
[BsonElement("tenantId")]
public string TenantId { get; init; } = string.Empty;
[BsonElement("source")]
public string Source { get; init; } = string.Empty;
[BsonElement("advisoryId")]
public string AdvisoryId { get; init; } = string.Empty;
[BsonElement("observations")]
public IReadOnlyList<string> Observations { get; init; } = Array.Empty<string>();
[BsonElement("createdAt")]
public DateTime CreatedAt { get; init; }
[BsonElement("normalized")]
public AdvisoryLinksetNormalizedDocument Normalized { get; init; } = new();
}
internal sealed class AdvisoryLinksetNormalizedDocument
{
[BsonElement("purls")]
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
[BsonElement("versions")]
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
}
/// <summary>
/// Shape used when reading /linksets responses in WebService endpoint tests.
/// </summary>
internal sealed class AdvisoryLinksetQueryResponse
{
public AdvisoryLinksetResponse[] Linksets { get; init; } = Array.Empty<AdvisoryLinksetResponse>();
public bool HasMore { get; init; }
public string? NextCursor { get; init; }
}
internal sealed class AdvisoryLinksetResponse
{
public string AdvisoryId { get; init; } = string.Empty;
public IReadOnlyList<string> Purls { get; init; } = Array.Empty<string>();
public IReadOnlyList<string> Versions { get; init; } = Array.Empty<string>();
}

View File

@@ -33,6 +33,7 @@ using StellaOps.Concelier.Merge.Services;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Mongo.Observations;
using StellaOps.Concelier.Storage.Mongo.Linksets;
using StellaOps.Concelier.Core.Raw;
using StellaOps.Concelier.WebService.Jobs;
using StellaOps.Concelier.WebService.Options;
@@ -376,13 +377,12 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
var root = document.RootElement;
Assert.Equal("CVE-2025-0001", root.GetProperty("advisoryKey").GetString());
Assert.False(string.IsNullOrWhiteSpace(root.GetProperty("fingerprint").GetString()));
Assert.Equal(1, root.GetProperty("total").GetInt32());
Assert.False(root.GetProperty("truncated").GetBoolean());
var entry = Assert.Single(root.GetProperty("entries").EnumerateArray());
Assert.Equal("workaround", entry.GetProperty("type").GetString());
Assert.Equal("tenant-a:chunk:newest", entry.GetProperty("documentId").GetString());
Assert.Equal("/references/0", entry.GetProperty("fieldPath").GetString());
Assert.False(string.IsNullOrWhiteSpace(entry.GetProperty("chunkId").GetString()));
var content = entry.GetProperty("content");
@@ -391,6 +391,8 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Assert.Equal("https://vendor.example/workaround", content.GetProperty("url").GetString());
var provenance = entry.GetProperty("provenance");
Assert.Equal("tenant-a:chunk:newest", provenance.GetProperty("documentId").GetString());
Assert.Equal("/references/0", provenance.GetProperty("observationPath").GetString());
Assert.Equal("nvd", provenance.GetProperty("source").GetString());
Assert.Equal("workaround", provenance.GetProperty("kind").GetString());
Assert.Equal("tenant-a:chunk:newest", provenance.GetProperty("value").GetString());
@@ -638,6 +640,9 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
using var client = _factory.CreateClient();
long expectedSegments = 0;
string expectedTruncatedTag = "false";
var metrics = await CaptureMetricsAsync(
AdvisoryAiMetrics.MeterName,
new[]
@@ -654,6 +659,13 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
var first = await client.GetAsync(url);
first.EnsureSuccessStatusCode();
using (var firstDocument = await first.Content.ReadFromJsonAsync<JsonDocument>())
{
Assert.NotNull(firstDocument);
expectedSegments = firstDocument!.RootElement.GetProperty("entries").GetArrayLength();
expectedTruncatedTag = firstDocument.RootElement.GetProperty("truncated").GetBoolean() ? "true" : "false";
}
var second = await client.GetAsync(url);
second.EnsureSuccessStatusCode();
});
@@ -679,7 +691,11 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Assert.True(metrics.TryGetValue("advisory_ai_chunk_segments", out var segmentMeasurements));
Assert.Equal(2, segmentMeasurements!.Count);
Assert.Contains(segmentMeasurements!, measurement => GetTagValue(measurement, "truncated") == "false");
Assert.All(segmentMeasurements!, measurement =>
{
Assert.Equal(expectedSegments, measurement.Value);
Assert.Equal(expectedTruncatedTag, GetTagValue(measurement, "truncated"));
});
Assert.True(metrics.TryGetValue("advisory_ai_chunk_sources", out var sourceMeasurements));
Assert.Equal(2, sourceMeasurements!.Count);
@@ -2522,6 +2538,7 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
Array.Empty<string>(),
references,
Array.Empty<string>(),
Array.Empty<string>(),
new Dictionary<string, string> { ["note"] = "ingest-test" }));
}