feat: Add tests for RichGraphPublisher and RichGraphWriter
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
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled

- Implement unit tests for RichGraphPublisher to verify graph publishing to CAS.
- Implement unit tests for RichGraphWriter to ensure correct writing of canonical graphs and metadata.

feat: Implement AOC Guard validation logic

- Add AOC Guard validation logic to enforce document structure and field constraints.
- Introduce violation codes for various validation errors.
- Implement tests for AOC Guard to validate expected behavior.

feat: Create Console Status API client and service

- Implement ConsoleStatusClient for fetching console status and streaming run events.
- Create ConsoleStatusService to manage console status polling and event subscriptions.
- Add tests for ConsoleStatusClient to verify API interactions.

feat: Develop Console Status component

- Create ConsoleStatusComponent for displaying console status and run events.
- Implement UI for showing status metrics and handling user interactions.
- Add styles for console status display.

test: Add tests for Console Status store

- Implement tests for ConsoleStatusStore to verify event handling and state management.
This commit is contained in:
StellaOps Bot
2025-12-01 07:34:50 +02:00
parent 7df0677e34
commit c11d87d252
108 changed files with 4773 additions and 351 deletions

View File

@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Scanner.Reachability;
/// <summary>
/// Writes richgraph-v1 documents to disk with canonical ordering and BLAKE3 hash.
/// </summary>
public sealed class RichGraphWriter
{
private static readonly JsonWriterOptions JsonOptions = new()
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = false,
SkipValidation = false
};
public async Task<RichGraphWriteResult> WriteAsync(
RichGraph graph,
string outputRoot,
string analysisId,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(graph);
ArgumentException.ThrowIfNullOrWhiteSpace(outputRoot);
ArgumentException.ThrowIfNullOrWhiteSpace(analysisId);
var trimmed = graph.Trimmed();
var root = Path.Combine(outputRoot, "reachability_graphs", analysisId);
Directory.CreateDirectory(root);
var graphPath = Path.Combine(root, "richgraph-v1.json");
await using (var stream = File.Create(graphPath))
await using (var writer = new Utf8JsonWriter(stream, JsonOptions))
{
WriteGraph(writer, trimmed);
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}
var bytes = await File.ReadAllBytesAsync(graphPath, cancellationToken).ConfigureAwait(false);
var graphHash = ComputeSha256(bytes);
var metaPath = Path.Combine(root, "meta.json");
await using (var stream = File.Create(metaPath))
await using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
{
writer.WriteStartObject();
writer.WriteString("schema", trimmed.Schema);
writer.WriteString("graph_hash", graphHash);
writer.WritePropertyName("files");
writer.WriteStartArray();
writer.WriteStartObject();
writer.WriteString("path", graphPath);
writer.WriteString("hash", graphHash);
writer.WriteEndObject();
writer.WriteEndArray();
writer.WriteEndObject();
await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}
return new RichGraphWriteResult(graphPath, metaPath, graphHash, trimmed.Nodes.Count, trimmed.Edges.Count);
}
private static void WriteGraph(Utf8JsonWriter writer, RichGraph graph)
{
writer.WriteStartObject();
writer.WriteString("schema", graph.Schema);
writer.WritePropertyName("analyzer");
writer.WriteStartObject();
writer.WriteString("name", graph.Analyzer.Name);
writer.WriteString("version", graph.Analyzer.Version);
if (!string.IsNullOrWhiteSpace(graph.Analyzer.ToolchainDigest))
{
writer.WriteString("toolchain_digest", graph.Analyzer.ToolchainDigest);
}
writer.WriteEndObject();
writer.WritePropertyName("nodes");
writer.WriteStartArray();
foreach (var node in graph.Nodes)
{
writer.WriteStartObject();
writer.WriteString("id", node.Id);
writer.WriteString("symbol_id", node.SymbolId);
writer.WriteString("lang", node.Lang);
writer.WriteString("kind", node.Kind);
if (!string.IsNullOrWhiteSpace(node.Display)) writer.WriteString("display", node.Display);
if (!string.IsNullOrWhiteSpace(node.CodeId)) writer.WriteString("code_id", node.CodeId);
if (!string.IsNullOrWhiteSpace(node.Purl)) writer.WriteString("purl", node.Purl);
if (!string.IsNullOrWhiteSpace(node.BuildId)) writer.WriteString("build_id", node.BuildId);
if (!string.IsNullOrWhiteSpace(node.SymbolDigest)) writer.WriteString("symbol_digest", node.SymbolDigest);
if (node.Evidence is { Count: > 0 })
{
writer.WritePropertyName("evidence");
writer.WriteStartArray();
foreach (var e in node.Evidence) writer.WriteStringValue(e);
writer.WriteEndArray();
}
if (node.Attributes is { Count: > 0 })
{
writer.WritePropertyName("attributes");
writer.WriteStartObject();
foreach (var kv in node.Attributes)
{
writer.WriteString(kv.Key, kv.Value);
}
writer.WriteEndObject();
}
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WritePropertyName("edges");
writer.WriteStartArray();
foreach (var edge in graph.Edges)
{
writer.WriteStartObject();
writer.WriteString("from", edge.From);
writer.WriteString("to", edge.To);
writer.WriteString("kind", edge.Kind);
if (!string.IsNullOrWhiteSpace(edge.Purl)) writer.WriteString("purl", edge.Purl);
if (!string.IsNullOrWhiteSpace(edge.SymbolDigest)) writer.WriteString("symbol_digest", edge.SymbolDigest);
writer.WriteNumber("confidence", edge.Confidence);
if (edge.Evidence is { Count: > 0 })
{
writer.WritePropertyName("evidence");
writer.WriteStartArray();
foreach (var e in edge.Evidence) writer.WriteStringValue(e);
writer.WriteEndArray();
}
if (edge.Candidates is { Count: > 0 })
{
writer.WritePropertyName("candidates");
writer.WriteStartArray();
foreach (var c in edge.Candidates) writer.WriteStringValue(c);
writer.WriteEndArray();
}
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WritePropertyName("roots");
writer.WriteStartArray();
foreach (var root in graph.Roots)
{
writer.WriteStartObject();
writer.WriteString("id", root.Id);
writer.WriteString("phase", root.Phase);
if (!string.IsNullOrWhiteSpace(root.Source)) writer.WriteString("source", root.Source);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
private static string ComputeSha256(IReadOnlyList<byte> bytes)
{
using var sha = SHA256.Create();
var hash = sha.ComputeHash(bytes.ToArray());
return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant();
}
}
public sealed record RichGraphWriteResult(
string GraphPath,
string MetaPath,
string GraphHash,
int NodeCount,
int EdgeCount);