finish off sprint advisories and sprints

This commit is contained in:
master
2026-01-24 00:12:43 +02:00
parent 726d70dc7f
commit c70e83719e
266 changed files with 46699 additions and 1328 deletions

View File

@@ -129,7 +129,11 @@ public sealed class EvidenceReconciler : IEvidenceReconciler
.ConfigureAwait(false);
// Step 4: VEX ingestion + lattice merge.
var (mergedStatements, conflictCount) = await MergeVexStatementsAsync(index, options, ct).ConfigureAwait(false);
var (mergedStatements, conflictCount) = await MergeVexStatementsAsync(
index,
Path.Combine(inputDirectory, "attestations"),
options,
ct).ConfigureAwait(false);
// Step 5: Graph emission.
var graph = BuildGraph(
@@ -247,6 +251,7 @@ public sealed class EvidenceReconciler : IEvidenceReconciler
private static async Task<(Dictionary<string, VexStatement> Statements, int ConflictCount)> MergeVexStatementsAsync(
ArtifactIndex index,
string attestationsDirectory,
ReconciliationOptions options,
CancellationToken ct)
{
@@ -258,9 +263,12 @@ public sealed class EvidenceReconciler : IEvidenceReconciler
{
foreach (var vexRef in entry.VexDocuments)
{
// Resolve relative path to absolute
var absolutePath = Path.Combine(attestationsDirectory, vexRef.FilePath.Replace('/', Path.DirectorySeparatorChar));
if (!documentCache.TryGetValue(vexRef.FilePath, out var document))
{
var loaded = await TryLoadOpenVexDocumentAsync(vexRef.FilePath, ct).ConfigureAwait(false);
var loaded = await TryLoadOpenVexDocumentAsync(absolutePath, ct).ConfigureAwait(false);
if (loaded is null)
{
continue;

View File

@@ -248,6 +248,7 @@ public sealed record NormalizationOptions
SortArrays = true,
LowercaseUris = true,
StripTimestamps = true,
StripVolatileFields = true,
NormalizeKeys = true
};
@@ -266,6 +267,13 @@ public sealed record NormalizationOptions
/// </summary>
public bool StripTimestamps { get; init; }
/// <summary>
/// Strip SBOM-specific volatile fields that vary between generation runs
/// (e.g., serialNumber, metadata.tools, creationInfo.creators).
/// See docs/contracts/sbom-volatile-fields.json for the authoritative field list.
/// </summary>
public bool StripVolatileFields { get; init; }
/// <summary>
/// Normalize JSON keys to camelCase.
/// </summary>

View File

@@ -233,6 +233,7 @@ public sealed class SbomNormalizer
/// <summary>
/// Normalizes CycloneDX metadata.
/// Strips volatile fields: timestamp, tools (per docs/contracts/sbom-volatile-fields.json).
/// </summary>
private JsonNode NormalizeCycloneDxMetadata(JsonNode node)
{
@@ -245,7 +246,12 @@ public sealed class SbomNormalizer
var sortedKeys = obj
.Select(kv => kv.Key)
.Where(key => _options.StripTimestamps ? key != "timestamp" : true)
.Where(key =>
{
if (_options.StripTimestamps && key == "timestamp") return false;
if (_options.StripVolatileFields && key is "tools" or "authors") return false;
return true;
})
.OrderBy(k => k, StringComparer.Ordinal);
foreach (var key in sortedKeys)
@@ -386,6 +392,7 @@ public sealed class SbomNormalizer
/// <summary>
/// Normalizes SPDX creation info.
/// Strips volatile fields: created, creators, licenseListVersion (per docs/contracts/sbom-volatile-fields.json).
/// </summary>
private JsonNode NormalizeSpdxCreationInfo(JsonNode node)
{
@@ -398,7 +405,12 @@ public sealed class SbomNormalizer
var sortedKeys = obj
.Select(kv => kv.Key)
.Where(key => _options.StripTimestamps ? key != "created" : true)
.Where(key =>
{
if (_options.StripTimestamps && key == "created") return false;
if (_options.StripVolatileFields && key is "creators" or "licenseListVersion") return false;
return true;
})
.OrderBy(k => k, StringComparer.Ordinal);
foreach (var key in sortedKeys)
@@ -442,14 +454,23 @@ public sealed class SbomNormalizer
return obj.ToJsonString();
}
private static bool ShouldStripCycloneDxField(string key)
private bool ShouldStripCycloneDxField(string key)
{
// Fields that should be stripped for canonical form
return key == "$schema";
// Always strip $schema (non-content metadata)
if (key == "$schema") return true;
if (!_options.StripVolatileFields) return false;
// Volatile fields per docs/contracts/sbom-volatile-fields.json
return key is "serialNumber";
}
private static bool ShouldStripSpdxField(string key)
private bool ShouldStripSpdxField(string key)
{
if (!_options.StripVolatileFields) return false;
// No root-level SPDX fields are stripped; volatile fields live
// inside creationInfo and are handled by NormalizeSpdxCreationInfo.
return false;
}