namespace StellaOps.Provcache;
///
/// High-level service interface for Provcache operations.
/// Orchestrates cache store and repository with metrics and invalidation logic.
///
public interface IProvcacheService
{
///
/// Gets a cached decision by VeriKey.
///
/// The cache key.
/// If true, skip cache and force re-evaluation.
/// Cancellation token.
/// The cache result with decision if found.
Task GetAsync(
string veriKey,
bool bypassCache = false,
CancellationToken cancellationToken = default);
///
/// Stores a decision in the cache.
///
/// The cache entry to store.
/// Cancellation token.
/// True if the entry was stored successfully.
Task SetAsync(ProvcacheEntry entry, CancellationToken cancellationToken = default);
///
/// Gets or computes a decision using a factory function for cache misses.
///
/// The cache key.
/// Factory function to create the entry on cache miss.
/// If true, skip cache and force re-computation.
/// Cancellation token.
/// The cached or newly computed entry.
Task GetOrComputeAsync(
string veriKey,
Func> factory,
bool bypassCache = false,
CancellationToken cancellationToken = default);
///
/// Invalidates a cache entry by VeriKey.
///
/// The cache key.
/// Reason for invalidation (for audit log).
/// Cancellation token.
/// True if the entry existed and was invalidated.
Task InvalidateAsync(
string veriKey,
string? reason = null,
CancellationToken cancellationToken = default);
///
/// Invalidates entries by invalidation criteria.
///
/// The invalidation request.
/// Cancellation token.
/// Invalidation result with count of affected entries.
Task InvalidateByAsync(
InvalidationRequest request,
CancellationToken cancellationToken = default);
///
/// Gets cache metrics for monitoring.
///
/// Cancellation token.
/// Cache metrics.
Task GetMetricsAsync(CancellationToken cancellationToken = default);
///
/// Prunes expired entries from the cache.
///
/// Cancellation token.
/// Number of entries pruned.
Task PruneExpiredAsync(CancellationToken cancellationToken = default);
}
///
/// Result of a cache service lookup.
///
public sealed record ProvcacheServiceResult
{
///
/// The cache result status.
///
public required ProvcacheResultStatus Status { get; init; }
///
/// The cache entry if found.
///
public ProvcacheEntry? Entry { get; init; }
///
/// Whether the result came from cache (true) or needs computation (false).
///
public bool WasCached => Status == ProvcacheResultStatus.CacheHit;
///
/// Source of the cache hit for diagnostics.
///
public string? Source { get; init; }
///
/// Time taken for the lookup in milliseconds.
///
public double ElapsedMs { get; init; }
///
/// Creates a cache hit result.
///
public static ProvcacheServiceResult Hit(ProvcacheEntry entry, string source, double elapsedMs) => new()
{
Status = ProvcacheResultStatus.CacheHit,
Entry = entry,
Source = source,
ElapsedMs = elapsedMs
};
///
/// Creates a cache miss result.
///
public static ProvcacheServiceResult Miss(double elapsedMs) => new()
{
Status = ProvcacheResultStatus.CacheMiss,
Entry = null,
Source = null,
ElapsedMs = elapsedMs
};
///
/// Creates a bypassed result (cache was skipped).
///
public static ProvcacheServiceResult Bypassed() => new()
{
Status = ProvcacheResultStatus.Bypassed,
Entry = null,
Source = null,
ElapsedMs = 0
};
///
/// Creates an expired result.
///
public static ProvcacheServiceResult Expired(ProvcacheEntry entry, double elapsedMs) => new()
{
Status = ProvcacheResultStatus.Expired,
Entry = entry,
Source = "expired",
ElapsedMs = elapsedMs
};
}
///
/// Cache result status.
///
public enum ProvcacheResultStatus
{
///
/// Entry was found in cache and is valid.
///
CacheHit,
///
/// Entry was not found in cache.
///
CacheMiss,
///
/// Cache was bypassed (force re-computation).
///
Bypassed,
///
/// Entry was found but has expired.
///
Expired
}
///
/// Request for cache invalidation by criteria.
///
public sealed record InvalidationRequest
{
///
/// The invalidation type.
///
public required InvalidationType Type { get; init; }
///
/// The value to match for invalidation.
///
public required string Value { get; init; }
///
/// Reason for invalidation (for audit log).
///
public string? Reason { get; init; }
///
/// Actor who initiated the invalidation.
///
public string? Actor { get; init; }
///
/// Creates an invalidation request by policy hash.
///
public static InvalidationRequest ByPolicyHash(string policyHash, string? reason = null) => new()
{
Type = InvalidationType.PolicyHash,
Value = policyHash,
Reason = reason ?? "policy-update"
};
///
/// Creates an invalidation request by signer set hash.
///
public static InvalidationRequest BySignerSetHash(string signerSetHash, string? reason = null) => new()
{
Type = InvalidationType.SignerSetHash,
Value = signerSetHash,
Reason = reason ?? "signer-revocation"
};
///
/// Creates an invalidation request by feed epoch.
///
public static InvalidationRequest ByFeedEpochOlderThan(string feedEpoch, string? reason = null) => new()
{
Type = InvalidationType.FeedEpochOlderThan,
Value = feedEpoch,
Reason = reason ?? "feed-update"
};
///
/// Creates an invalidation request by key pattern.
///
public static InvalidationRequest ByPattern(string pattern, string? reason = null) => new()
{
Type = InvalidationType.Pattern,
Value = pattern,
Reason = reason ?? "pattern-invalidation"
};
}
///
/// Type of invalidation criteria.
///
public enum InvalidationType
{
///
/// Invalidate by policy hash.
///
PolicyHash,
///
/// Invalidate by signer set hash.
///
SignerSetHash,
///
/// Invalidate entries with feed epoch older than specified.
///
FeedEpochOlderThan,
///
/// Invalidate by key pattern.
///
Pattern,
///
/// Invalidate expired entries.
///
Expired
}
///
/// Result of an invalidation operation.
///
public sealed record InvalidationResult
{
///
/// Number of entries invalidated.
///
public required long EntriesAffected { get; init; }
///
/// The invalidation request that was executed.
///
public required InvalidationRequest Request { get; init; }
///
/// Timestamp of the invalidation.
///
public required DateTimeOffset Timestamp { get; init; }
///
/// Whether the invalidation was logged for audit.
///
public bool WasLogged { get; init; }
}
///
/// Cache metrics for monitoring and observability.
///
public sealed record ProvcacheMetrics
{
///
/// Total cache requests since startup.
///
public long TotalRequests { get; init; }
///
/// Total cache hits since startup.
///
public long TotalHits { get; init; }
///
/// Total cache misses since startup.
///
public long TotalMisses { get; init; }
///
/// Cache hit rate (0.0 - 1.0).
///
public double HitRate => TotalRequests == 0 ? 0.0 : (double)TotalHits / TotalRequests;
///
/// Average lookup latency in milliseconds.
///
public double AvgLatencyMs { get; init; }
///
/// P99 lookup latency in milliseconds.
///
public double P99LatencyMs { get; init; }
///
/// Current number of entries in cache.
///
public long CurrentEntryCount { get; init; }
///
/// Total invalidations since startup.
///
public long TotalInvalidations { get; init; }
///
/// Valkey cache health status.
///
public bool ValkeyCacheHealthy { get; init; }
///
/// Postgres repository health status.
///
public bool PostgresRepositoryHealthy { get; init; }
///
/// Timestamp when metrics were collected.
///
public DateTimeOffset CollectedAt { get; init; }
}