133 lines
4.0 KiB
C#
133 lines
4.0 KiB
C#
// <copyright file="FacetDrift.cs" company="StellaOps">
|
|
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
|
|
// </copyright>
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
namespace StellaOps.Facet;
|
|
|
|
/// <summary>
|
|
/// Drift detection result for a single facet.
|
|
/// </summary>
|
|
public sealed record FacetDrift
|
|
{
|
|
/// <summary>
|
|
/// Gets the facet this drift applies to.
|
|
/// </summary>
|
|
public required string FacetId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the files added since baseline.
|
|
/// </summary>
|
|
public required ImmutableArray<FacetFileEntry> Added { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the files removed since baseline.
|
|
/// </summary>
|
|
public required ImmutableArray<FacetFileEntry> Removed { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the files modified since baseline.
|
|
/// </summary>
|
|
public required ImmutableArray<FacetFileModification> Modified { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the drift score (0-100, higher = more drift).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The drift score weighs additions, removals, and modifications
|
|
/// to produce a single measure of change magnitude.
|
|
/// </remarks>
|
|
public required decimal DriftScore { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the quota evaluation result.
|
|
/// </summary>
|
|
public required QuotaVerdict QuotaVerdict { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the number of files in baseline facet seal.
|
|
/// </summary>
|
|
public required int BaselineFileCount { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the total number of changes (added + removed + modified).
|
|
/// </summary>
|
|
public int TotalChanges => Added.Length + Removed.Length + Modified.Length;
|
|
|
|
/// <summary>
|
|
/// Gets the churn percentage = (changes / baseline count) * 100.
|
|
/// </summary>
|
|
public decimal ChurnPercent => BaselineFileCount > 0
|
|
? TotalChanges / (decimal)BaselineFileCount * 100
|
|
: Added.Length > 0 ? 100m : 0m;
|
|
|
|
/// <summary>
|
|
/// Gets whether this facet has any drift.
|
|
/// </summary>
|
|
public bool HasDrift => TotalChanges > 0;
|
|
|
|
/// <summary>
|
|
/// Gets a no-drift instance for a facet.
|
|
/// </summary>
|
|
public static FacetDrift NoDrift(string facetId, int baselineFileCount) => new()
|
|
{
|
|
FacetId = facetId,
|
|
Added = [],
|
|
Removed = [],
|
|
Modified = [],
|
|
DriftScore = 0m,
|
|
QuotaVerdict = QuotaVerdict.Ok,
|
|
BaselineFileCount = baselineFileCount
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggregated drift report for all facets in an image.
|
|
/// </summary>
|
|
public sealed record FacetDriftReport
|
|
{
|
|
/// <summary>
|
|
/// Gets the image digest analyzed.
|
|
/// </summary>
|
|
public required string ImageDigest { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the baseline seal used for comparison.
|
|
/// </summary>
|
|
public required string BaselineSealId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets when the analysis was performed.
|
|
/// </summary>
|
|
public required DateTimeOffset AnalyzedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the per-facet drift results.
|
|
/// </summary>
|
|
public required ImmutableArray<FacetDrift> FacetDrifts { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the overall verdict (worst of all facets).
|
|
/// </summary>
|
|
public required QuotaVerdict OverallVerdict { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the total files changed across all facets.
|
|
/// </summary>
|
|
public int TotalChangedFiles => FacetDrifts.Sum(d => d.TotalChanges);
|
|
|
|
/// <summary>
|
|
/// Gets the facets with any drift.
|
|
/// </summary>
|
|
public IEnumerable<FacetDrift> DriftedFacets => FacetDrifts.Where(d => d.HasDrift);
|
|
|
|
/// <summary>
|
|
/// Gets the facets with quota violations.
|
|
/// </summary>
|
|
public IEnumerable<FacetDrift> QuotaViolations =>
|
|
FacetDrifts.Where(d => d.QuotaVerdict is QuotaVerdict.Warning
|
|
or QuotaVerdict.Blocked
|
|
or QuotaVerdict.RequiresVex);
|
|
}
|