Add signal contracts for reachability, exploitability, trust, and unknown symbols
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals DSSE Sign & Evidence Locker / sign-signals-artifacts (push) Has been cancelled
Signals DSSE Sign & Evidence Locker / verify-signatures (push) Has been cancelled

- Introduced `ReachabilityState`, `RuntimeHit`, `ExploitabilitySignal`, `ReachabilitySignal`, `SignalEnvelope`, `SignalType`, `TrustSignal`, and `UnknownSymbolSignal` records to define various signal types and their properties.
- Implemented JSON serialization attributes for proper data interchange.
- Created project files for the new signal contracts library and corresponding test projects.
- Added deterministic test fixtures for micro-interaction testing.
- Included cryptographic keys for secure operations with cosign.
This commit is contained in:
StellaOps Bot
2025-12-05 00:27:00 +02:00
parent b018949a8d
commit 8768c27f30
192 changed files with 27569 additions and 2552 deletions

View File

@@ -0,0 +1,71 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using StellaOps.Graph.Indexer.Ingestion.Sbom;
namespace StellaOps.Graph.Indexer.Ingestion.Inspector;
public sealed class GraphInspectorProcessor
{
private readonly GraphInspectorTransformer _transformer;
private readonly IGraphDocumentWriter _writer;
private readonly ILogger<GraphInspectorProcessor> _logger;
public GraphInspectorProcessor(
GraphInspectorTransformer transformer,
IGraphDocumentWriter writer,
ILogger<GraphInspectorProcessor> logger)
{
_transformer = transformer ?? throw new ArgumentNullException(nameof(transformer));
_writer = writer ?? throw new ArgumentNullException(nameof(writer));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task ProcessAsync(GraphInspectorSnapshot snapshot, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(snapshot);
cancellationToken.ThrowIfCancellationRequested();
var stopwatch = Stopwatch.StartNew();
GraphBuildBatch batch;
try
{
batch = _transformer.Transform(snapshot);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(
ex,
"graph-indexer: failed to transform graph.inspect snapshot for tenant {Tenant} artifact {ArtifactDigest}",
snapshot.Tenant,
snapshot.ArtifactDigest);
throw;
}
try
{
cancellationToken.ThrowIfCancellationRequested();
await _writer.WriteAsync(batch, cancellationToken).ConfigureAwait(false);
stopwatch.Stop();
_logger.LogInformation(
"graph-indexer: ingested graph.inspect snapshot for tenant {Tenant} artifact {ArtifactDigest} with {NodeCount} nodes and {EdgeCount} edges in {DurationMs:F2} ms",
snapshot.Tenant,
snapshot.ArtifactDigest,
batch.Nodes.Length,
batch.Edges.Length,
stopwatch.Elapsed.TotalMilliseconds);
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(
ex,
"graph-indexer: failed to persist graph.inspect snapshot for tenant {Tenant} artifact {ArtifactDigest}",
snapshot.Tenant,
snapshot.ArtifactDigest);
throw;
}
}
}

View File

@@ -56,6 +56,8 @@ public sealed class GraphInspectorTransformer
},
relationship.Provenance);
nodes.Add(targetNode);
var edge = CreateRelationshipEdge(snapshot, componentNode, targetNode, relationship);
edges.Add(edge);
}
@@ -85,16 +87,40 @@ public sealed class GraphInspectorTransformer
}
}
foreach (var node in componentNodes.Values)
{
var id = node["id"]!.GetValue<string>();
if (!nodes.Any(n => n["id"]!.GetValue<string>() == id))
{
nodes.Add(node);
}
}
// Ensure all relationship targets are represented as component nodes even if they were not emitted during primary loops.
var targetPurls = snapshot.Components
.SelectMany(c => c.Relationships ?? Array.Empty<GraphInspectorRelationship>())
.Select(r => r.TargetPurl)
.Where(p => !string.IsNullOrWhiteSpace(p))
.Distinct(StringComparer.OrdinalIgnoreCase);
foreach (var targetPurl in targetPurls)
{
var key = $"{targetPurl.Trim()}|{DefaultSourceType}";
var targetNode = GetOrCreateComponentNode(
snapshot,
componentNodes,
new GraphInspectorComponent { Purl = targetPurl },
provenanceOverride: null);
componentNodes[key] = targetNode;
nodes.Add(targetNode);
}
var orderedNodes = nodes
.Distinct(JsonNodeEqualityComparer.Instance)
.Select(n => (JsonObject)n)
.OrderBy(n => n["kind"]!.GetValue<string>(), StringComparer.Ordinal)
.ThenBy(n => n["id"]!.GetValue<string>(), StringComparer.Ordinal)
.ToImmutableArray();
var orderedEdges = edges
.Distinct(JsonNodeEqualityComparer.Instance)
.Select(e => (JsonObject)e)
.OrderBy(e => e["kind"]!.GetValue<string>(), StringComparer.Ordinal)
.ThenBy(e => e["id"]!.GetValue<string>(), StringComparer.Ordinal)
.ToImmutableArray();
@@ -409,25 +435,4 @@ public sealed class GraphInspectorTransformer
EventOffset: eventOffset);
}
private sealed class JsonNodeEqualityComparer : IEqualityComparer<JsonNode>
{
public static readonly JsonNodeEqualityComparer Instance = new();
public bool Equals(JsonNode? x, JsonNode? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.ToJsonString() == y.ToJsonString();
}
public int GetHashCode(JsonNode obj) => obj.ToJsonString().GetHashCode(StringComparison.Ordinal);
}
}

View File

@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace StellaOps.Graph.Indexer.Ingestion.Inspector;
public static class InspectorIngestServiceCollectionExtensions
{
public static IServiceCollection AddInspectorIngestPipeline(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.TryAddSingleton<GraphInspectorTransformer>();
services.TryAddSingleton<GraphInspectorProcessor>(provider =>
{
var transformer = provider.GetRequiredService<GraphInspectorTransformer>();
var writer = provider.GetRequiredService<Ingestion.Sbom.IGraphDocumentWriter>();
var logger = provider.GetService<ILogger<GraphInspectorProcessor>>() ?? NullLogger<GraphInspectorProcessor>.Instance;
return new GraphInspectorProcessor(transformer, writer, logger);
});
return services;
}
}