//
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
//
using System.Collections.Immutable;
namespace StellaOps.Facet;
///
/// Sealed manifest of facets for an image at a point in time.
///
///
///
/// A FacetSeal captures the cryptographic state of all facets in an image,
/// enabling drift detection and quota enforcement on subsequent scans.
///
///
/// The seal can be optionally signed with DSSE for authenticity verification.
///
///
public sealed record FacetSeal
{
///
/// Current schema version.
///
public const string CurrentSchemaVersion = "1.0.0";
///
/// Gets the schema version for forward compatibility.
///
public string SchemaVersion { get; init; } = CurrentSchemaVersion;
///
/// Gets the image digest this seal applies to.
///
///
/// Format: "sha256:{hex}" or "sha512:{hex}".
///
public required string ImageDigest { get; init; }
///
/// Gets when the seal was created.
///
public required DateTimeOffset CreatedAt { get; init; }
///
/// Gets the optional build attestation reference (in-toto provenance).
///
public string? BuildAttestationRef { get; init; }
///
/// Gets the individual facet seals.
///
public required ImmutableArray Facets { get; init; }
///
/// Gets the quota configuration per facet.
///
///
/// Keys are facet IDs. Facets without explicit quotas use default values.
///
public ImmutableDictionary? Quotas { get; init; }
///
/// Gets the combined Merkle root of all facet roots.
///
///
/// Computed from facet Merkle roots in sorted order by FacetId.
/// Enables single-value integrity verification.
///
public required string CombinedMerkleRoot { get; init; }
///
/// Gets the optional DSSE signature over canonical form.
///
///
/// Base64-encoded DSSE envelope when the seal is signed.
///
public string? Signature { get; init; }
///
/// Gets the signing key identifier, if signed.
///
public string? SigningKeyId { get; init; }
///
/// Gets whether this seal is signed.
///
public bool IsSigned => !string.IsNullOrEmpty(Signature);
///
/// Gets the quota for a specific facet, or default if not configured.
///
/// The facet identifier.
/// The configured quota or .
public FacetQuota GetQuota(string facetId)
{
if (Quotas is not null &&
Quotas.TryGetValue(facetId, out var quota))
{
return quota;
}
return FacetQuota.Default;
}
///
/// Gets a facet entry by ID.
///
/// The facet identifier.
/// The facet entry or null if not found.
public FacetEntry? GetFacet(string facetId)
=> Facets.FirstOrDefault(f => f.FacetId == facetId);
}