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

@@ -130,11 +130,11 @@ public sealed class RoaringImpactIndexTests
}
[Fact]
public async Task ResolveAllAsync_UsageOnlyFiltersEntrypointImages()
{
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
var (entryStream, entryDigest) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
var nonEntryDigestValue = "sha256:" + new string('1', 64);
public async Task ResolveAllAsync_UsageOnlyFiltersEntrypointImages()
{
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
var (entryStream, entryDigest) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
var nonEntryDigestValue = "sha256:" + new string('1', 64);
var (nonEntryStream, nonEntryDigest) = CreateBomIndex(component, ComponentUsage.Create(false), nonEntryDigestValue);
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
@@ -159,12 +159,88 @@ public sealed class RoaringImpactIndexTests
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var usageOnly = await index.ResolveAllAsync(selector, usageOnly: true);
usageOnly.Images.Should().ContainSingle(image => image.ImageDigest == entryDigest);
var allImages = await index.ResolveAllAsync(selector, usageOnly: false);
allImages.Images.Should().HaveCount(2);
}
var usageOnly = await index.ResolveAllAsync(selector, usageOnly: true);
usageOnly.Images.Should().ContainSingle(image => image.ImageDigest == entryDigest);
var allImages = await index.ResolveAllAsync(selector, usageOnly: false);
allImages.Images.Should().HaveCount(2);
}
[Fact]
public async Task RemoveAsync_RemovesImageAndComponents()
{
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
var (stream1, digest1) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
var (stream2, digest2) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
await index.IngestAsync(new ImpactIndexIngestionRequest
{
TenantId = "tenant-alpha",
ImageDigest = digest1,
Registry = "docker.io",
Repository = "library/service",
BomIndexStream = stream1,
});
await index.IngestAsync(new ImpactIndexIngestionRequest
{
TenantId = "tenant-alpha",
ImageDigest = digest2,
Registry = "docker.io",
Repository = "library/service",
BomIndexStream = stream2,
});
await index.RemoveAsync(digest1);
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var impact = await index.ResolveByPurlsAsync(new[] { "pkg:npm/a@1.0.0" }, usageOnly: false, selector);
impact.Images.Should().ContainSingle(img => img.ImageDigest == digest2);
}
[Fact]
public async Task CreateSnapshotAsync_CompactsIdsAndRestores()
{
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
var (stream1, digest1) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
var (stream2, digest2) = CreateBomIndex(component, ComponentUsage.Create(false));
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
await index.IngestAsync(new ImpactIndexIngestionRequest
{
TenantId = "tenant-alpha",
ImageDigest = digest1,
Registry = "docker.io",
Repository = "library/service",
BomIndexStream = stream1,
});
await index.IngestAsync(new ImpactIndexIngestionRequest
{
TenantId = "tenant-alpha",
ImageDigest = digest2,
Registry = "docker.io",
Repository = "library/service",
BomIndexStream = stream2,
});
await index.RemoveAsync(digest1);
var snapshot = await index.CreateSnapshotAsync();
var restored = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
await restored.RestoreSnapshotAsync(snapshot);
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
var resolved = await restored.ResolveAllAsync(selector, usageOnly: false);
resolved.Images.Should().ContainSingle(img => img.ImageDigest == digest2);
resolved.SnapshotId.Should().Be(snapshot.SnapshotId);
}
private static (Stream Stream, string Digest) CreateBomIndex(ComponentIdentity identity, ComponentUsage usage, string? digest = null)
{