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