feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user