Files
git.stella-ops.org/src/__Libraries/StellaOps.Facet/FacetDrift.cs

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);
}