save development progress
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// AdvisoryCacheKeys.cs
|
||||
// Sprint: SPRINT_8200_0013_0001_GW_valkey_advisory_cache
|
||||
// Task: VCACHE-8200-004, VCACHE-8200-005, VCACHE-8200-006, VCACHE-8200-007, VCACHE-8200-008
|
||||
// Description: Key schema for Concelier Valkey cache
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace StellaOps.Concelier.Cache.Valkey;
|
||||
|
||||
/// <summary>
|
||||
/// Static class for generating Valkey cache keys for canonical advisories.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Key Schema:
|
||||
/// <code>
|
||||
/// advisory:{merge_hash} → JSON(CanonicalAdvisory) - TTL based on interest_score
|
||||
/// rank:hot → ZSET { merge_hash: interest_score } - max 10,000 entries
|
||||
/// by:purl:{normalized_purl} → SET { merge_hash, ... } - TTL 24h
|
||||
/// by:cve:{cve_id} → STRING merge_hash - TTL 24h
|
||||
/// cache:stats:hits → INCR counter
|
||||
/// cache:stats:misses → INCR counter
|
||||
/// cache:warmup:last → STRING ISO8601 timestamp
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static class AdvisoryCacheKeys
|
||||
{
|
||||
/// <summary>
|
||||
/// Default key prefix for all cache keys.
|
||||
/// </summary>
|
||||
public const string DefaultPrefix = "concelier:";
|
||||
|
||||
/// <summary>
|
||||
/// Key for advisory by merge hash.
|
||||
/// Format: {prefix}advisory:{mergeHash}
|
||||
/// </summary>
|
||||
public static string Advisory(string mergeHash, string prefix = DefaultPrefix)
|
||||
=> $"{prefix}advisory:{mergeHash}";
|
||||
|
||||
/// <summary>
|
||||
/// Key for the hot advisory sorted set.
|
||||
/// Format: {prefix}rank:hot
|
||||
/// </summary>
|
||||
public static string HotSet(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}rank:hot";
|
||||
|
||||
/// <summary>
|
||||
/// Key for PURL index set.
|
||||
/// Format: {prefix}by:purl:{normalizedPurl}
|
||||
/// </summary>
|
||||
/// <param name="purl">The PURL (will be normalized).</param>
|
||||
/// <param name="prefix">Key prefix.</param>
|
||||
public static string ByPurl(string purl, string prefix = DefaultPrefix)
|
||||
=> $"{prefix}by:purl:{NormalizePurl(purl)}";
|
||||
|
||||
/// <summary>
|
||||
/// Key for CVE mapping.
|
||||
/// Format: {prefix}by:cve:{cveId}
|
||||
/// </summary>
|
||||
/// <param name="cve">The CVE identifier (case-insensitive).</param>
|
||||
/// <param name="prefix">Key prefix.</param>
|
||||
public static string ByCve(string cve, string prefix = DefaultPrefix)
|
||||
=> $"{prefix}by:cve:{cve.ToUpperInvariant()}";
|
||||
|
||||
/// <summary>
|
||||
/// Key for cache hit counter.
|
||||
/// Format: {prefix}cache:stats:hits
|
||||
/// </summary>
|
||||
public static string StatsHits(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}cache:stats:hits";
|
||||
|
||||
/// <summary>
|
||||
/// Key for cache miss counter.
|
||||
/// Format: {prefix}cache:stats:misses
|
||||
/// </summary>
|
||||
public static string StatsMisses(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}cache:stats:misses";
|
||||
|
||||
/// <summary>
|
||||
/// Key for last warmup timestamp.
|
||||
/// Format: {prefix}cache:warmup:last
|
||||
/// </summary>
|
||||
public static string WarmupLast(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}cache:warmup:last";
|
||||
|
||||
/// <summary>
|
||||
/// Key for warmup lock (for distributed coordination).
|
||||
/// Format: {prefix}cache:warmup:lock
|
||||
/// </summary>
|
||||
public static string WarmupLock(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}cache:warmup:lock";
|
||||
|
||||
/// <summary>
|
||||
/// Key for total cached advisories gauge.
|
||||
/// Format: {prefix}cache:stats:count
|
||||
/// </summary>
|
||||
public static string StatsCount(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}cache:stats:count";
|
||||
|
||||
/// <summary>
|
||||
/// Pattern to match all advisory keys (for scanning/cleanup).
|
||||
/// Format: {prefix}advisory:*
|
||||
/// </summary>
|
||||
public static string AdvisoryPattern(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}advisory:*";
|
||||
|
||||
/// <summary>
|
||||
/// Pattern to match all PURL index keys (for scanning/cleanup).
|
||||
/// Format: {prefix}by:purl:*
|
||||
/// </summary>
|
||||
public static string PurlIndexPattern(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}by:purl:*";
|
||||
|
||||
/// <summary>
|
||||
/// Pattern to match all CVE mapping keys (for scanning/cleanup).
|
||||
/// Format: {prefix}by:cve:*
|
||||
/// </summary>
|
||||
public static string CveMappingPattern(string prefix = DefaultPrefix)
|
||||
=> $"{prefix}by:cve:*";
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a PURL for use as a cache key.
|
||||
/// </summary>
|
||||
/// <param name="purl">The PURL to normalize.</param>
|
||||
/// <returns>Normalized PURL safe for use in cache keys.</returns>
|
||||
/// <remarks>
|
||||
/// Normalization:
|
||||
/// 1. Lowercase the entire PURL
|
||||
/// 2. Replace special characters that may cause issues in keys
|
||||
/// 3. Truncate very long PURLs to prevent oversized keys
|
||||
/// </remarks>
|
||||
public static string NormalizePurl(string purl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purl))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Normalize to lowercase
|
||||
var normalized = purl.ToLowerInvariant();
|
||||
|
||||
// Replace characters that could cause issues in Redis keys
|
||||
// Redis keys should avoid spaces and some special chars for simplicity
|
||||
var sb = new StringBuilder(normalized.Length);
|
||||
foreach (var c in normalized)
|
||||
{
|
||||
// Allow alphanumeric, standard PURL chars: : / @ . - _ %
|
||||
if (char.IsLetterOrDigit(c) ||
|
||||
c is ':' or '/' or '@' or '.' or '-' or '_' or '%')
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace other chars with underscore
|
||||
sb.Append('_');
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate if too long (Redis keys can be up to 512MB, but we want reasonable sizes)
|
||||
const int MaxKeyLength = 500;
|
||||
if (sb.Length > MaxKeyLength)
|
||||
{
|
||||
return sb.ToString(0, MaxKeyLength);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the merge hash from an advisory key.
|
||||
/// </summary>
|
||||
/// <param name="key">The full advisory key.</param>
|
||||
/// <param name="prefix">The key prefix used.</param>
|
||||
/// <returns>The merge hash, or null if key doesn't match expected format.</returns>
|
||||
public static string? ExtractMergeHash(string key, string prefix = DefaultPrefix)
|
||||
{
|
||||
var expectedStart = $"{prefix}advisory:";
|
||||
if (key.StartsWith(expectedStart, StringComparison.Ordinal))
|
||||
{
|
||||
return key[expectedStart.Length..];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the PURL from a PURL index key.
|
||||
/// </summary>
|
||||
/// <param name="key">The full PURL index key.</param>
|
||||
/// <param name="prefix">The key prefix used.</param>
|
||||
/// <returns>The normalized PURL, or null if key doesn't match expected format.</returns>
|
||||
public static string? ExtractPurl(string key, string prefix = DefaultPrefix)
|
||||
{
|
||||
var expectedStart = $"{prefix}by:purl:";
|
||||
if (key.StartsWith(expectedStart, StringComparison.Ordinal))
|
||||
{
|
||||
return key[expectedStart.Length..];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the CVE from a CVE mapping key.
|
||||
/// </summary>
|
||||
/// <param name="key">The full CVE mapping key.</param>
|
||||
/// <param name="prefix">The key prefix used.</param>
|
||||
/// <returns>The CVE identifier, or null if key doesn't match expected format.</returns>
|
||||
public static string? ExtractCve(string key, string prefix = DefaultPrefix)
|
||||
{
|
||||
var expectedStart = $"{prefix}by:cve:";
|
||||
if (key.StartsWith(expectedStart, StringComparison.Ordinal))
|
||||
{
|
||||
return key[expectedStart.Length..];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user