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