Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Pedigree/IPedigreeDataProvider.cs

280 lines
8.0 KiB
C#

// <copyright file="IPedigreeDataProvider.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
namespace StellaOps.Scanner.Emit.Pedigree;
/// <summary>
/// Provider interface for retrieving component pedigree data from Feedser.
/// Sprint: SPRINT_20260107_005_002 Task PD-001
/// </summary>
public interface IPedigreeDataProvider
{
/// <summary>
/// Retrieves pedigree data for a component identified by its PURL.
/// </summary>
/// <param name="purl">Package URL identifying the component.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Pedigree data if available, null otherwise.</returns>
Task<PedigreeData?> GetPedigreeAsync(
string purl,
CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves pedigree data for multiple components.
/// </summary>
/// <param name="purls">Package URLs identifying the components.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Dictionary of PURL to pedigree data (missing components not included).</returns>
Task<IReadOnlyDictionary<string, PedigreeData>> GetPedigreesBatchAsync(
IEnumerable<string> purls,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Aggregate of pedigree information for a component.
/// </summary>
public sealed record PedigreeData
{
/// <summary>
/// Gets the ancestor components (upstream sources).
/// </summary>
public ImmutableArray<AncestorComponent> Ancestors { get; init; } = ImmutableArray<AncestorComponent>.Empty;
/// <summary>
/// Gets the variant components (distro-specific packages derived from same source).
/// </summary>
public ImmutableArray<VariantComponent> Variants { get; init; } = ImmutableArray<VariantComponent>.Empty;
/// <summary>
/// Gets the relevant commits (security fixes, backports).
/// </summary>
public ImmutableArray<CommitInfo> Commits { get; init; } = ImmutableArray<CommitInfo>.Empty;
/// <summary>
/// Gets the patches applied to the component.
/// </summary>
public ImmutableArray<PatchInfo> Patches { get; init; } = ImmutableArray<PatchInfo>.Empty;
/// <summary>
/// Gets optional notes about the pedigree (e.g., backport explanation).
/// </summary>
public string? Notes { get; init; }
/// <summary>
/// Gets whether any pedigree data is present.
/// </summary>
public bool HasData =>
!Ancestors.IsDefaultOrEmpty ||
!Variants.IsDefaultOrEmpty ||
!Commits.IsDefaultOrEmpty ||
!Patches.IsDefaultOrEmpty ||
Notes is not null;
}
/// <summary>
/// Represents an upstream ancestor component.
/// </summary>
public sealed record AncestorComponent
{
/// <summary>
/// Gets the component type (e.g., "library", "application").
/// </summary>
public string Type { get; init; } = "library";
/// <summary>
/// Gets the component name.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Gets the upstream version.
/// </summary>
public required string Version { get; init; }
/// <summary>
/// Gets the Package URL for the ancestor.
/// </summary>
public string? Purl { get; init; }
/// <summary>
/// Gets the URL to the upstream project.
/// </summary>
public string? ProjectUrl { get; init; }
/// <summary>
/// Gets the relationship level (1 = direct parent, 2 = grandparent, etc.).
/// </summary>
public int Level { get; init; } = 1;
}
/// <summary>
/// Represents a variant component (distro-specific package).
/// </summary>
public sealed record VariantComponent
{
/// <summary>
/// Gets the component type.
/// </summary>
public string Type { get; init; } = "library";
/// <summary>
/// Gets the package name in the distribution.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Gets the distribution-specific version.
/// </summary>
public required string Version { get; init; }
/// <summary>
/// Gets the Package URL for the variant.
/// </summary>
public required string Purl { get; init; }
/// <summary>
/// Gets the distribution name (e.g., "debian", "rhel", "alpine").
/// </summary>
public string? Distribution { get; init; }
/// <summary>
/// Gets the distribution release (e.g., "bookworm", "9.3").
/// </summary>
public string? Release { get; init; }
}
/// <summary>
/// Represents commit information for a security fix or backport.
/// </summary>
public sealed record CommitInfo
{
/// <summary>
/// Gets the commit SHA (full or abbreviated).
/// </summary>
public required string Uid { get; init; }
/// <summary>
/// Gets the URL to view the commit.
/// </summary>
public string? Url { get; init; }
/// <summary>
/// Gets the commit message (may be truncated).
/// </summary>
public string? Message { get; init; }
/// <summary>
/// Gets the author information.
/// </summary>
public CommitActor? Author { get; init; }
/// <summary>
/// Gets the committer information.
/// </summary>
public CommitActor? Committer { get; init; }
/// <summary>
/// Gets the CVE IDs resolved by this commit, if known.
/// </summary>
public ImmutableArray<string> ResolvesCves { get; init; } = ImmutableArray<string>.Empty;
}
/// <summary>
/// Represents an actor (author or committer) in a commit.
/// </summary>
public sealed record CommitActor
{
/// <summary>
/// Gets the actor's name.
/// </summary>
public string? Name { get; init; }
/// <summary>
/// Gets the actor's email.
/// </summary>
public string? Email { get; init; }
/// <summary>
/// Gets the timestamp of the action.
/// </summary>
public DateTimeOffset? Timestamp { get; init; }
}
/// <summary>
/// Represents a patch applied to the component.
/// </summary>
public sealed record PatchInfo
{
/// <summary>
/// Gets the patch type.
/// </summary>
public PatchType Type { get; init; } = PatchType.Backport;
/// <summary>
/// Gets the URL to the patch file.
/// </summary>
public string? DiffUrl { get; init; }
/// <summary>
/// Gets the patch diff content (optional, may be truncated).
/// </summary>
public string? DiffText { get; init; }
/// <summary>
/// Gets the CVE IDs resolved by this patch.
/// </summary>
public ImmutableArray<PatchResolution> Resolves { get; init; } = ImmutableArray<PatchResolution>.Empty;
/// <summary>
/// Gets the functions affected by this patch.
/// </summary>
public ImmutableArray<string> AffectedFunctions { get; init; } = ImmutableArray<string>.Empty;
/// <summary>
/// Gets the source of the patch (e.g., "debian-security").
/// </summary>
public string? Source { get; init; }
}
/// <summary>
/// Patch type enumeration per CycloneDX 1.7 specification.
/// </summary>
public enum PatchType
{
/// <summary>Informal patch not associated with upstream.</summary>
Unofficial,
/// <summary>A patch that is a bugfix or security fix that does not change feature.</summary>
Monkey,
/// <summary>A patch that is a backport of a fix from a later version.</summary>
Backport,
/// <summary>A cherry-picked commit from upstream.</summary>
CherryPick
}
/// <summary>
/// Represents a vulnerability resolved by a patch.
/// </summary>
public sealed record PatchResolution
{
/// <summary>
/// Gets the vulnerability ID (e.g., "CVE-2024-1234").
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Gets the source of the vulnerability reference.
/// </summary>
public string? SourceName { get; init; }
/// <summary>
/// Gets the URL to the vulnerability reference.
/// </summary>
public string? SourceUrl { get; init; }
}