Add call graph fixtures for various languages and scenarios
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
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Reachability Corpus Validation / validate-corpus (push) Has been cancelled
Reachability Corpus Validation / validate-ground-truths (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Reachability Corpus Validation / determinism-check (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

- Introduced `all-edge-reasons.json` to test edge resolution reasons in .NET.
- Added `all-visibility-levels.json` to validate method visibility levels in .NET.
- Created `dotnet-aspnetcore-minimal.json` for a minimal ASP.NET Core application.
- Included `go-gin-api.json` for a Go Gin API application structure.
- Added `java-spring-boot.json` for the Spring PetClinic application in Java.
- Introduced `legacy-no-schema.json` for legacy application structure without schema.
- Created `node-express-api.json` for an Express.js API application structure.
This commit is contained in:
master
2025-12-16 10:44:24 +02:00
parent 4391f35d8a
commit 5a480a3c2a
223 changed files with 19367 additions and 727 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -12,4 +13,52 @@ public interface IDeploymentRefsRepository
/// Counts distinct deployments referencing a package.
/// </summary>
Task<int> CountDeploymentsAsync(string purl, CancellationToken cancellationToken = default);
/// <summary>
/// Gets deployment IDs referencing a package.
/// </summary>
Task<IReadOnlyList<string>> GetDeploymentIdsAsync(string purl, int limit, CancellationToken cancellationToken = default);
/// <summary>
/// Records or updates a deployment reference.
/// </summary>
Task UpsertAsync(DeploymentRef deployment, CancellationToken cancellationToken = default);
/// <summary>
/// Records multiple deployment references in a batch.
/// </summary>
Task BulkUpsertAsync(IEnumerable<DeploymentRef> deployments, CancellationToken cancellationToken = default);
/// <summary>
/// Gets deployment summary for a package.
/// </summary>
Task<DeploymentSummary?> GetSummaryAsync(string purl, CancellationToken cancellationToken = default);
}
/// <summary>
/// Represents a deployment reference record.
/// </summary>
public sealed class DeploymentRef
{
public required string Purl { get; init; }
public string? PurlVersion { get; init; }
public required string ImageId { get; init; }
public string? ImageDigest { get; init; }
public required string Environment { get; init; }
public string? Namespace { get; init; }
public string? Cluster { get; init; }
public string? Region { get; init; }
}
/// <summary>
/// Summary of deployments for a package.
/// </summary>
public sealed class DeploymentSummary
{
public required string Purl { get; init; }
public int ImageCount { get; init; }
public int EnvironmentCount { get; init; }
public int TotalDeployments { get; init; }
public DateTimeOffset? LastDeployment { get; init; }
public DateTimeOffset? FirstDeployment { get; init; }
}

View File

@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -15,11 +17,53 @@ public interface IGraphMetricsRepository
string symbolId,
string callgraphId,
CancellationToken cancellationToken = default);
/// <summary>
/// Stores computed metrics for a node.
/// </summary>
Task UpsertAsync(GraphMetrics metrics, CancellationToken cancellationToken = default);
/// <summary>
/// Bulk stores metrics for a call graph.
/// </summary>
Task BulkUpsertAsync(IEnumerable<GraphMetrics> metrics, CancellationToken cancellationToken = default);
/// <summary>
/// Gets callgraph IDs that need recomputation (older than threshold).
/// </summary>
Task<IReadOnlyList<string>> GetStaleCallgraphsAsync(
TimeSpan maxAge,
int limit,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes all metrics for a callgraph.
/// </summary>
Task DeleteByCallgraphAsync(string callgraphId, CancellationToken cancellationToken = default);
}
/// <summary>
/// Centrality metrics for a symbol.
/// </summary>
public sealed record GraphMetrics(
int Degree,
double Betweenness);
public sealed class GraphMetrics
{
public required string NodeId { get; init; }
public required string CallgraphId { get; init; }
public string NodeType { get; init; } = "symbol";
public int Degree { get; init; }
public int InDegree { get; init; }
public int OutDegree { get; init; }
public double Betweenness { get; init; }
public double? Closeness { get; init; }
public double? NormalizedBetweenness { get; init; }
public double? NormalizedDegree { get; init; }
public DateTimeOffset ComputedAt { get; init; }
public int? ComputationDurationMs { get; init; }
public string AlgorithmVersion { get; init; } = "1.0";
public int? TotalNodes { get; init; }
public int? TotalEdges { get; init; }
}

View File

@@ -1,4 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -7,6 +10,7 @@ namespace StellaOps.Signals.Persistence;
public sealed class InMemoryDeploymentRefsRepository : IDeploymentRefsRepository
{
private readonly ConcurrentDictionary<string, int> _deploymentsByPurl = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, List<DeploymentRef>> _refsByPurl = new(StringComparer.OrdinalIgnoreCase);
public void SetDeployments(string purl, int deployments)
{
@@ -28,6 +32,82 @@ public sealed class InMemoryDeploymentRefsRepository : IDeploymentRefsRepository
return Task.FromResult(0);
}
return Task.FromResult(_deploymentsByPurl.TryGetValue(purl.Trim(), out var count) ? count : 0);
var key = purl.Trim();
if (_deploymentsByPurl.TryGetValue(key, out var count))
return Task.FromResult(count);
if (_refsByPurl.TryGetValue(key, out var refs))
return Task.FromResult(refs.Count);
return Task.FromResult(0);
}
public Task<IReadOnlyList<string>> GetDeploymentIdsAsync(string purl, int limit, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(purl))
return Task.FromResult<IReadOnlyList<string>>(Array.Empty<string>());
if (_refsByPurl.TryGetValue(purl.Trim(), out var refs))
return Task.FromResult<IReadOnlyList<string>>(refs.Take(limit).Select(r => r.ImageId).ToList());
return Task.FromResult<IReadOnlyList<string>>(Array.Empty<string>());
}
public Task UpsertAsync(DeploymentRef deployment, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentNullException.ThrowIfNull(deployment);
var key = deployment.Purl.Trim();
_refsByPurl.AddOrUpdate(
key,
_ => new List<DeploymentRef> { deployment },
(_, list) =>
{
var existing = list.FindIndex(r =>
r.ImageId == deployment.ImageId &&
r.Environment == deployment.Environment);
if (existing >= 0)
list[existing] = deployment;
else
list.Add(deployment);
return list;
});
return Task.CompletedTask;
}
public Task BulkUpsertAsync(IEnumerable<DeploymentRef> deployments, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentNullException.ThrowIfNull(deployments);
foreach (var deployment in deployments)
{
UpsertAsync(deployment, cancellationToken).GetAwaiter().GetResult();
}
return Task.CompletedTask;
}
public Task<DeploymentSummary?> GetSummaryAsync(string purl, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(purl))
return Task.FromResult<DeploymentSummary?>(null);
if (!_refsByPurl.TryGetValue(purl.Trim(), out var refs) || refs.Count == 0)
return Task.FromResult<DeploymentSummary?>(null);
return Task.FromResult<DeploymentSummary?>(new DeploymentSummary
{
Purl = purl,
ImageCount = refs.Select(r => r.ImageId).Distinct().Count(),
EnvironmentCount = refs.Select(r => r.Environment).Distinct().Count(),
TotalDeployments = refs.Count
});
}
}

View File

@@ -1,4 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -30,6 +33,64 @@ public sealed class InMemoryGraphMetricsRepository : IGraphMetricsRepository
return Task.FromResult(_metrics.TryGetValue(key, out var metrics) ? metrics : null);
}
public Task UpsertAsync(GraphMetrics metrics, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentNullException.ThrowIfNull(metrics);
var key = BuildKey(metrics.NodeId, metrics.CallgraphId);
_metrics[key] = metrics;
return Task.CompletedTask;
}
public Task BulkUpsertAsync(IEnumerable<GraphMetrics> metrics, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ArgumentNullException.ThrowIfNull(metrics);
foreach (var m in metrics)
{
var key = BuildKey(m.NodeId, m.CallgraphId);
_metrics[key] = m;
}
return Task.CompletedTask;
}
public Task<IReadOnlyList<string>> GetStaleCallgraphsAsync(TimeSpan maxAge, int limit, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var cutoff = DateTimeOffset.UtcNow - maxAge;
var staleGraphs = _metrics.Values
.Where(m => m.ComputedAt < cutoff)
.Select(m => m.CallgraphId)
.Distinct()
.Take(limit)
.ToList();
return Task.FromResult<IReadOnlyList<string>>(staleGraphs);
}
public Task DeleteByCallgraphAsync(string callgraphId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(callgraphId))
return Task.CompletedTask;
var keysToRemove = _metrics.Keys
.Where(k => k.StartsWith(callgraphId.Trim() + "|", StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var key in keysToRemove)
{
_metrics.TryRemove(key, out _);
}
return Task.CompletedTask;
}
private static string BuildKey(string symbolId, string callgraphId)
=> $"{callgraphId.Trim()}|{symbolId.Trim()}";
}