using System.Collections.Immutable;
namespace StellaOps.Scanner.Delta;
///
/// Scans only changed layers between two image versions for efficient delta scanning.
///
public interface IDeltaLayerScanner
{
///
/// Performs a delta scan comparing two image versions.
///
/// Reference to the old/baseline image.
/// Reference to the new image to scan.
/// Delta scan options.
/// Cancellation token.
/// Delta scan result with changed layers and composite SBOM.
Task ScanDeltaAsync(
string oldImage,
string newImage,
DeltaScanOptions? options = null,
CancellationToken cancellationToken = default);
///
/// Identifies layer changes between two images without scanning.
///
/// Reference to the old image.
/// Reference to the new image.
/// Cancellation token.
/// Layer change summary.
Task IdentifyLayerChangesAsync(
string oldImage,
string newImage,
CancellationToken cancellationToken = default);
}
///
/// Options for delta scanning.
///
public sealed record DeltaScanOptions
{
///
/// Whether to force a full scan even for unchanged layers.
/// Default is false.
///
public bool ForceFullScan { get; init; } = false;
///
/// Whether to use cached per-layer SBOMs for unchanged layers.
/// Default is true.
///
public bool UseCachedSboms { get; init; } = true;
///
/// Maximum age of cached SBOMs to consider valid (in days).
/// Default is 30 days.
///
public int MaxCacheAgeDays { get; init; } = 30;
///
/// SBOM format to produce (CycloneDX or SPDX).
/// Default is CycloneDX.
///
public string SbomFormat { get; init; } = "cyclonedx";
///
/// Whether to include layer attribution in the SBOM.
/// Default is true.
///
public bool IncludeLayerAttribution { get; init; } = true;
///
/// Target platform for multi-arch images.
/// If null, uses default platform.
///
public string? Platform { get; init; }
}
///
/// Result of a delta scan operation.
///
public sealed record DeltaScanResult
{
///
/// Old image reference.
///
public required string OldImage { get; init; }
///
/// Old image manifest digest.
///
public required string OldManifestDigest { get; init; }
///
/// New image reference.
///
public required string NewImage { get; init; }
///
/// New image manifest digest.
///
public required string NewManifestDigest { get; init; }
///
/// Layers that were added in the new image.
///
public ImmutableArray AddedLayers { get; init; } = [];
///
/// Layers that were removed from the old image.
///
public ImmutableArray RemovedLayers { get; init; } = [];
///
/// Layers that are unchanged between images.
///
public ImmutableArray UnchangedLayers { get; init; } = [];
///
/// Composite SBOM for the new image (combining cached + newly scanned).
///
public string? CompositeSbom { get; init; }
///
/// SBOM format (cyclonedx or spdx).
///
public string? SbomFormat { get; init; }
///
/// Total scan duration.
///
public TimeSpan ScanDuration { get; init; }
///
/// Duration spent scanning only added layers.
///
public TimeSpan AddedLayersScanDuration { get; init; }
///
/// Number of components found in added layers.
///
public int AddedComponentCount { get; init; }
///
/// Number of components from cached layers.
///
public int CachedComponentCount { get; init; }
///
/// Whether the scan used cached SBOMs.
///
public bool UsedCache { get; init; }
///
/// Percentage of layers that were reused from cache.
///
public double LayerReuseRatio =>
(AddedLayers.Length + UnchangedLayers.Length) > 0
? (double)UnchangedLayers.Length / (AddedLayers.Length + UnchangedLayers.Length)
: 0;
///
/// When the scan was performed.
///
public DateTimeOffset ScannedAt { get; init; } = DateTimeOffset.UtcNow;
}
///
/// Information about a layer change.
///
public sealed record LayerChangeInfo
{
///
/// Compressed layer digest.
///
public required string LayerDigest { get; init; }
///
/// Uncompressed content hash (diffID).
///
public required string DiffId { get; init; }
///
/// Layer index in the image.
///
public int LayerIndex { get; init; }
///
/// Size of the layer in bytes.
///
public long Size { get; init; }
///
/// Media type of the layer.
///
public string? MediaType { get; init; }
///
/// Whether this layer's SBOM was retrieved from cache.
///
public bool FromCache { get; init; }
///
/// Number of components found in this layer.
///
public int ComponentCount { get; init; }
}
///
/// Summary of layer changes between two images.
///
public sealed record LayerChangeSummary
{
///
/// Old image reference.
///
public required string OldImage { get; init; }
///
/// New image reference.
///
public required string NewImage { get; init; }
///
/// Total layers in old image.
///
public int OldLayerCount { get; init; }
///
/// Total layers in new image.
///
public int NewLayerCount { get; init; }
///
/// Number of layers added.
///
public int AddedCount { get; init; }
///
/// Number of layers removed.
///
public int RemovedCount { get; init; }
///
/// Number of layers unchanged.
///
public int UnchangedCount { get; init; }
///
/// Estimated scan savings (layers we don't need to scan).
///
public double EstimatedSavingsRatio => NewLayerCount > 0
? (double)UnchangedCount / NewLayerCount
: 0;
///
/// DiffIDs of added layers.
///
public ImmutableArray AddedDiffIds { get; init; } = [];
///
/// DiffIDs of removed layers.
///
public ImmutableArray RemovedDiffIds { get; init; } = [];
///
/// DiffIDs of unchanged layers.
///
public ImmutableArray UnchangedDiffIds { get; init; } = [];
}