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,111 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Signals.Models;
using StellaOps.Signals.Options;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
public class ReachabilityScoringServiceTests
{
[Fact]
public async Task RecomputeAsync_UsesConfiguredWeights()
{
var callgraph = new CallgraphDocument
{
Id = "cg-1",
Language = "java",
Component = "demo",
Version = "1.0.0",
Nodes = new List<CallgraphNode>
{
new("main", "Main", "method", null, null, null),
new("svc", "Svc", "method", null, null, null),
new("target", "Target", "method", null, null, null)
},
Edges = new List<CallgraphEdge>
{
new("main", "svc", "call"),
new("svc", "target", "call")
}
};
var callgraphRepository = new InMemoryCallgraphRepository(callgraph);
var factRepository = new InMemoryReachabilityFactRepository();
var options = new SignalsOptions();
options.Scoring.ReachableConfidence = 0.8;
options.Scoring.UnreachableConfidence = 0.3;
options.Scoring.RuntimeBonus = 0.1;
options.Scoring.MaxConfidence = 0.95;
options.Scoring.MinConfidence = 0.1;
var service = new ReachabilityScoringService(
callgraphRepository,
factRepository,
TimeProvider.System,
Options.Create(options),
NullLogger<ReachabilityScoringService>.Instance);
var request = new ReachabilityRecomputeRequest
{
CallgraphId = callgraph.Id,
Subject = new ReachabilitySubject { Component = "demo", Version = "1.0.0" },
EntryPoints = new List<string> { "main" },
Targets = new List<string> { "target" },
RuntimeHits = new List<string> { "svc", "target" }
};
var fact = await service.RecomputeAsync(request, CancellationToken.None);
Assert.Equal(callgraph.Id, fact.CallgraphId);
Assert.Single(fact.States);
var state = fact.States[0];
Assert.True(state.Reachable);
Assert.Equal("target", state.Target);
Assert.Equal(new[] { "main", "svc", "target" }, state.Path);
Assert.Equal(0.9, state.Confidence, 2); // 0.8 + 0.1 runtime bonus
Assert.Contains("svc", state.Evidence.RuntimeHits);
Assert.Contains("target", state.Evidence.RuntimeHits);
}
private sealed class InMemoryCallgraphRepository : ICallgraphRepository
{
private readonly CallgraphDocument document;
public InMemoryCallgraphRepository(CallgraphDocument document)
{
this.document = document;
}
public Task<CallgraphDocument?> GetByIdAsync(string id, CancellationToken cancellationToken)
{
return Task.FromResult(document.Id == id ? document : null);
}
public Task<CallgraphDocument> UpsertAsync(CallgraphDocument document, CancellationToken cancellationToken)
{
// Not needed for this test
return Task.FromResult(document);
}
}
private sealed class InMemoryReachabilityFactRepository : IReachabilityFactRepository
{
public ReachabilityFactDocument? Last;
public Task<ReachabilityFactDocument?> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
{
return Task.FromResult(Last);
}
public Task<ReachabilityFactDocument> UpsertAsync(ReachabilityFactDocument document, CancellationToken cancellationToken)
{
Last = document;
return Task.FromResult(document);
}
}
}

View File

@@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Signals.Models;
using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
public class RuntimeFactsIngestionServiceTests
{
[Fact]
public async Task IngestAsync_AggregatesHits_AndRecomputesReachability()
{
var factRepository = new InMemoryReachabilityFactRepository();
var scoringService = new RecordingScoringService();
var service = new RuntimeFactsIngestionService(
factRepository,
TimeProvider.System,
scoringService,
NullLogger<RuntimeFactsIngestionService>.Instance);
var request = new RuntimeFactsIngestRequest
{
Subject = new ReachabilitySubject { Component = "web", Version = "2.1.0" },
CallgraphId = "cg-123",
Metadata = new Dictionary<string, string?> { { "source", "runtime" } },
Events = new List<RuntimeFactEvent>
{
new() { SymbolId = "svc.foo", HitCount = 2, Metadata = new Dictionary<string, string?> { { "pid", "12" } } },
new() { SymbolId = "svc.bar", HitCount = 1 },
new() { SymbolId = "svc.foo", HitCount = 3 }
}
};
var response = await service.IngestAsync(request, CancellationToken.None);
Assert.Equal("web|2.1.0", response.SubjectKey);
Assert.Equal("cg-123", response.CallgraphId);
var persisted = factRepository.Last ?? throw new Xunit.Sdk.XunitException("Fact not persisted");
Assert.Equal(2, persisted.RuntimeFacts?.Count);
var foo = persisted.RuntimeFacts?.Single(f => f.SymbolId == "svc.foo");
Assert.Equal(5, foo?.HitCount);
var bar = persisted.RuntimeFacts?.Single(f => f.SymbolId == "svc.bar");
Assert.Equal(1, bar?.HitCount);
var recorded = scoringService.LastRequest ?? throw new Xunit.Sdk.XunitException("Recompute not triggered");
Assert.Equal("cg-123", recorded.CallgraphId);
Assert.Contains("svc.foo", recorded.Targets);
Assert.Contains("svc.bar", recorded.RuntimeHits!);
Assert.Equal("runtime", recorded.Metadata?["source"]);
Assert.Equal("runtime", persisted.Metadata?["provenance.source"]);
Assert.Equal("cg-123", persisted.Metadata?["provenance.callgraphId"]);
Assert.NotNull(persisted.Metadata?["provenance.ingestedAt"]);
}
private sealed class InMemoryReachabilityFactRepository : IReachabilityFactRepository
{
public ReachabilityFactDocument? Last { get; private set; }
public Task<ReachabilityFactDocument?> GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken)
{
return Task.FromResult(Last);
}
public Task<ReachabilityFactDocument> UpsertAsync(ReachabilityFactDocument document, CancellationToken cancellationToken)
{
Last = document;
return Task.FromResult(document);
}
}
private sealed class RecordingScoringService : IReachabilityScoringService
{
public ReachabilityRecomputeRequest? LastRequest { get; private set; }
public Task<ReachabilityFactDocument> RecomputeAsync(ReachabilityRecomputeRequest request, CancellationToken cancellationToken)
{
LastRequest = request;
return Task.FromResult(new ReachabilityFactDocument
{
CallgraphId = request.CallgraphId,
Subject = request.Subject,
SubjectKey = request.Subject?.ToSubjectKey() ?? string.Empty,
EntryPoints = request.EntryPoints,
States = new List<ReachabilityStateDocument>(),
RuntimeFacts = new List<RuntimeFactDocument>()
});
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../StellaOps.Signals/StellaOps.Signals.csproj" />
</ItemGroup>
</Project>