feat: Implement Runtime Facts ingestion service and NDJSON reader
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added RuntimeFactsNdjsonReader for reading NDJSON formatted runtime facts.
- Introduced IRuntimeFactsIngestionService interface and its implementation.
- Enhanced Program.cs to register new services and endpoints for runtime facts.
- Updated CallgraphIngestionService to include CAS URI in stored artifacts.
- Created RuntimeFactsValidationException for validation errors during ingestion.
- Added tests for RuntimeFactsIngestionService and RuntimeFactsNdjsonReader.
- Implemented SignalsSealedModeMonitor for compliance checks in sealed mode.
- Updated project dependencies for testing utilities.
This commit is contained in:
master
2025-11-10 07:56:15 +02:00
parent 9df52d84aa
commit 69c59defdc
132 changed files with 19718 additions and 9334 deletions

View File

@@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Observations;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Analyzers.Lang.Deno;
public sealed class DenoLanguageAnalyzer : ILanguageAnalyzer
{
public string Id => "deno";
public string DisplayName => "Deno Analyzer";
public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(writer);
var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, cancellationToken).ConfigureAwait(false);
var moduleGraph = DenoModuleGraphResolver.Resolve(workspace, cancellationToken);
var compatibility = DenoNpmCompatibilityAdapter.Analyze(workspace, moduleGraph, cancellationToken);
var bundleScan = DenoBundleScanner.Scan(context.RootPath, cancellationToken);
var bundleObservations = DenoBundleScanner.ToObservations(bundleScan);
var containerInputs = DenoContainerAdapter.CollectInputs(workspace, bundleObservations);
var containerRecords = DenoContainerEmitter.BuildRecords(Id, containerInputs);
writer.AddRange(containerRecords);
var observationDocument = DenoObservationBuilder.Build(moduleGraph, compatibility, bundleObservations);
var observationJson = DenoObservationSerializer.Serialize(observationDocument);
var observationHash = DenoObservationSerializer.ComputeSha256(observationJson);
var observationBytes = Encoding.UTF8.GetBytes(observationJson);
var observationMetadata = new[]
{
new KeyValuePair<string, string?>("deno.observation.hash", observationHash),
new KeyValuePair<string, string?>("deno.observation.entrypoints", observationDocument.Entrypoints.Length.ToString(CultureInfo.InvariantCulture)),
new KeyValuePair<string, string?>("deno.observation.capabilities", observationDocument.Capabilities.Length.ToString(CultureInfo.InvariantCulture)),
new KeyValuePair<string, string?>("deno.observation.bundles", observationDocument.Bundles.Length.ToString(CultureInfo.InvariantCulture))
};
TryPersistObservation(context, observationBytes, observationMetadata);
var observationEvidence = new[]
{
new LanguageComponentEvidence(
LanguageEvidenceKind.Derived,
"deno.observation",
"document",
observationJson,
observationHash)
};
writer.AddFromExplicitKey(
analyzerId: Id,
componentKey: "observation::deno",
purl: null,
name: "Deno Observation Summary",
version: null,
type: "deno-observation",
metadata: observationMetadata,
evidence: observationEvidence);
// Task 5+ will convert moduleGraph + compatibility and bundle insights into SBOM components and evidence records.
GC.KeepAlive(moduleGraph);
GC.KeepAlive(compatibility);
GC.KeepAlive(bundleObservations);
GC.KeepAlive(containerInputs);
GC.KeepAlive(observationDocument);
}
private void TryPersistObservation(
LanguageAnalyzerContext context,
byte[] observationBytes,
IEnumerable<KeyValuePair<string, string?>> metadata)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(observationBytes);
if (context.AnalysisStore is not { } analysisStore)
{
return;
}
var metadataDictionary = CreateMetadata(metadata);
var payload = new AnalyzerObservationPayload(
analyzerId: Id,
kind: "deno.observation",
mediaType: "application/json",
content: observationBytes,
metadata: metadataDictionary,
view: "observations");
analysisStore.Set(ScanAnalysisKeys.DenoObservationPayload, payload);
}
private static IReadOnlyDictionary<string, string?>? CreateMetadata(IEnumerable<KeyValuePair<string, string?>> metadata)
{
Dictionary<string, string?>? dictionary = null;
foreach (var pair in metadata ?? Array.Empty<KeyValuePair<string, string?>>())
{
if (string.IsNullOrWhiteSpace(pair.Key) || string.IsNullOrWhiteSpace(pair.Value))
{
continue;
}
dictionary ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
dictionary[pair.Key] = pair.Value;
}
return dictionary;
}
}