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

110 lines
4.2 KiB
C#

// <copyright file="IFacetSealStore.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under BUSL-1.1.
// </copyright>
using System.Collections.Immutable;
namespace StellaOps.Facet;
/// <summary>
/// Persistent store for <see cref="FacetSeal"/> instances.
/// </summary>
/// <remarks>
/// <para>
/// Implementations provide storage and retrieval of facet seals for drift detection
/// and quota enforcement. Seals are indexed by image digest and creation time.
/// </para>
/// <para>
/// Sprint: SPRINT_20260105_002_003_FACET (QTA-012)
/// </para>
/// </remarks>
public interface IFacetSealStore
{
/// <summary>
/// Get the most recent seal for an image digest.
/// </summary>
/// <param name="imageDigest">The image digest (e.g., "sha256:{hex}").</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The latest seal, or null if no seal exists for this image.</returns>
Task<FacetSeal?> GetLatestSealAsync(string imageDigest, CancellationToken ct = default);
/// <summary>
/// Get a seal by its combined Merkle root (unique identifier).
/// </summary>
/// <param name="combinedMerkleRoot">The seal's combined Merkle root.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The seal, or null if not found.</returns>
Task<FacetSeal?> GetByCombinedRootAsync(string combinedMerkleRoot, CancellationToken ct = default);
/// <summary>
/// Get seal history for an image digest.
/// </summary>
/// <param name="imageDigest">The image digest.</param>
/// <param name="limit">Maximum number of seals to return.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Seals in descending order by creation time (most recent first).</returns>
Task<ImmutableArray<FacetSeal>> GetHistoryAsync(
string imageDigest,
int limit = 10,
CancellationToken ct = default);
/// <summary>
/// Save a seal to the store.
/// </summary>
/// <param name="seal">The seal to save.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>A task representing the async operation.</returns>
/// <exception cref="ArgumentNullException">If seal is null.</exception>
/// <exception cref="SealAlreadyExistsException">If a seal with the same combined root exists.</exception>
Task SaveAsync(FacetSeal seal, CancellationToken ct = default);
/// <summary>
/// Check if a seal exists for an image digest.
/// </summary>
/// <param name="imageDigest">The image digest.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>True if at least one seal exists.</returns>
Task<bool> ExistsAsync(string imageDigest, CancellationToken ct = default);
/// <summary>
/// Delete all seals for an image digest.
/// </summary>
/// <param name="imageDigest">The image digest.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Number of seals deleted.</returns>
Task<int> DeleteByImageAsync(string imageDigest, CancellationToken ct = default);
/// <summary>
/// Purge seals older than the specified retention period.
/// </summary>
/// <param name="retentionPeriod">Retention period from creation time.</param>
/// <param name="keepAtLeast">Minimum seals to keep per image digest.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Number of seals purged.</returns>
Task<int> PurgeOldSealsAsync(
TimeSpan retentionPeriod,
int keepAtLeast = 1,
CancellationToken ct = default);
}
/// <summary>
/// Exception thrown when attempting to save a duplicate seal.
/// </summary>
public sealed class SealAlreadyExistsException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="SealAlreadyExistsException"/> class.
/// </summary>
/// <param name="combinedMerkleRoot">The duplicate seal's combined root.</param>
public SealAlreadyExistsException(string combinedMerkleRoot)
: base($"A seal with combined Merkle root '{combinedMerkleRoot}' already exists.")
{
CombinedMerkleRoot = combinedMerkleRoot;
}
/// <summary>
/// Gets the duplicate seal's combined Merkle root.
/// </summary>
public string CombinedMerkleRoot { get; }
}