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

@@ -1,5 +1,7 @@
using System.Text.Json;
using StellaOps.Graph.Indexer.Ingestion.Inspector;
using StellaOps.Graph.Indexer.Schema;
using Xunit.Sdk;
namespace StellaOps.Graph.Indexer.Tests;
@@ -86,21 +88,49 @@ public sealed class GraphInspectorTransformerTests
EventOffset = 5123,
EvidenceHash = "c1"
}
},
new GraphInspectorComponent
{
Purl = "pkg:npm/lodash@4.17.21",
Scopes = Array.Empty<string>(),
Relationships = Array.Empty<GraphInspectorRelationship>(),
Advisories = Array.Empty<GraphInspectorAdvisoryObservation>(),
VexStatements = Array.Empty<GraphInspectorVexStatement>(),
Provenance = new GraphInspectorProvenance
{
Source = "concelier.linkset.v1",
CollectedAt = DateTimeOffset.Parse("2025-12-04T15:29:00Z"),
EventOffset = 6000,
EvidenceHash = "e1"
}
}
}
};
var transformer = new GraphInspectorTransformer();
Assert.NotEmpty(snapshot.Components.First().Relationships);
Assert.Contains("pkg:npm/lodash@4.17.21", snapshot.Components.SelectMany(c => c.Relationships).Select(r => r.TargetPurl));
var batch = transformer.Transform(snapshot);
// Nodes: artifact + source component + target component + advisory + vex
Assert.Equal(5, batch.Nodes.Length);
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "artifact");
var nodesDebug = string.Join(" | ", batch.Nodes.Select(n => n.ToJsonString()));
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "component" && n["canonical_key"]!["purl"]!.GetValue<string>() == "pkg:maven/org.example/foo@1.2.3");
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "component" && n["canonical_key"]!["purl"]!.GetValue<string>() == "pkg:npm/lodash@4.17.21");
Assert.True(
batch.Nodes.Any(n => n["kind"]!.GetValue<string>() == "component" && n["canonical_key"]!["purl"]!.GetValue<string>() == "pkg:npm/lodash@4.17.21"),
$"Missing target component node. Nodes: {nodesDebug}");
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "advisory");
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "vex_statement");
if (batch.Nodes.Length != 5)
{
var debug = string.Join(" | ", batch.Nodes.Select(n => n.ToJsonString()));
throw new XunitException($"Expected 5 nodes, got {batch.Nodes.Length}: {debug}");
}
// Edges: depends_on, affected_by, vex_exempts
Assert.Contains(batch.Edges, e => e["kind"]!.GetValue<string>() == "DEPENDS_ON");
@@ -108,8 +138,50 @@ public sealed class GraphInspectorTransformerTests
Assert.Contains(batch.Edges, e => e["kind"]!.GetValue<string>() == "VEX_EXEMPTS");
// Provenance should carry sbom digest and event offset from snapshot/provenance overrides.
var dependsOn = batch.Edges.Single(e => e["kind"]!.GetValue<string>() == "DEPENDS_ON");
var dependsOn = batch.Edges.First(e => e["kind"]!.GetValue<string>() == "DEPENDS_ON");
Assert.Equal("sha256:sbom", dependsOn["provenance"]!["sbom_digest"]!.GetValue<string>());
Assert.Equal(6000, dependsOn["provenance"]!["event_offset"]!.GetValue<long>());
}
[Fact]
public void Transform_AcceptsPublishedSample()
{
var samplePath = LocateRepoFile("docs/modules/graph/contracts/examples/graph.inspect.v1.sample.json");
var json = File.ReadAllText(samplePath);
var snapshot = JsonSerializer.Deserialize<GraphInspectorSnapshot>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
})!;
var transformer = new GraphInspectorTransformer();
var batch = transformer.Transform(snapshot);
Assert.NotEmpty(batch.Nodes);
Assert.NotEmpty(batch.Edges);
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "advisory");
Assert.Contains(batch.Nodes, n => n["kind"]!.GetValue<string>() == "vex_statement");
}
private static string LocateRepoFile(string relative)
{
var dir = AppContext.BaseDirectory;
while (!string.IsNullOrEmpty(dir))
{
var candidate = Path.Combine(dir, relative);
if (File.Exists(candidate))
{
return candidate;
}
var parent = Directory.GetParent(dir);
if (parent is null)
{
break;
}
dir = parent.FullName;
}
throw new FileNotFoundException($"Unable to locate '{relative}' from base directory {AppContext.BaseDirectory}");
}
}