using Microsoft.Extensions.Caching.Memory; using StellaOps.Graph.Api.Contracts; namespace StellaOps.Graph.Api.Services; public sealed class InMemoryOverlayService : IOverlayService { private readonly IMemoryCache _cache; private static readonly DateTimeOffset FixedTimestamp = new(2025, 11, 23, 0, 0, 0, TimeSpan.Zero); private readonly IGraphMetrics _metrics; public InMemoryOverlayService(IMemoryCache cache, IGraphMetrics metrics) { _cache = cache; _metrics = metrics; } public Task>> GetOverlaysAsync(string tenant, IEnumerable nodeIds, bool sampleExplain, CancellationToken ct = default) { var result = new Dictionary>(StringComparer.Ordinal); var explainEmitted = false; foreach (var nodeId in nodeIds) { var cacheKey = $"overlay:{tenant}:{nodeId}"; if (!_cache.TryGetValue(cacheKey, out Dictionary? cachedBase)) { _metrics.OverlayCacheMiss.Add(1); cachedBase = new Dictionary(StringComparer.Ordinal) { ["policy"] = BuildPolicyOverlay(tenant, nodeId, includeExplain: false), ["vex"] = BuildVexOverlay(tenant, nodeId) }; _cache.Set(cacheKey, cachedBase, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }); } else { _metrics.OverlayCacheHit.Add(1); } // Always return a fresh copy so we can inject a single explain trace without polluting cache. var overlays = new Dictionary(cachedBase, StringComparer.Ordinal); if (sampleExplain && !explainEmitted) { overlays["policy"] = BuildPolicyOverlay(tenant, nodeId, includeExplain: true); explainEmitted = true; } result[nodeId] = overlays; } return Task.FromResult>>(result); } private static OverlayPayload BuildPolicyOverlay(string tenant, string nodeId, bool includeExplain) { var overlayId = ComputeOverlayId(tenant, nodeId, "policy"); return new OverlayPayload( Kind: "policy", Version: "policy.overlay.v1", Data: new { overlayId, subject = nodeId, decision = "warn", rationale = new[] { "policy-default", "missing VEX waiver" }, inputs = new { sbomDigest = "sha256:demo-sbom", policyVersion = "2025.11.23", advisoriesDigest = "sha256:demo-advisories" }, policyVersion = "2025.11.23", createdAt = FixedTimestamp, explainTrace = includeExplain ? new[] { "matched rule POLICY-ENGINE-30-001", $"node {nodeId} lacks VEX waiver" } : null }); } private static OverlayPayload BuildVexOverlay(string tenant, string nodeId) { var overlayId = ComputeOverlayId(tenant, nodeId, "vex"); return new OverlayPayload( Kind: "vex", Version: "openvex.v1", Data: new { overlayId, subject = nodeId, status = "not_affected", justification = "component_not_present", issued = FixedTimestamp, impacts = Array.Empty() }); } private static string ComputeOverlayId(string tenant, string nodeId, string overlayKind) { using var sha = System.Security.Cryptography.SHA256.Create(); var bytes = System.Text.Encoding.UTF8.GetBytes($"{tenant}|{nodeId}|{overlayKind}"); var hash = sha.ComputeHash(bytes); return Convert.ToHexString(hash).ToLowerInvariant(); } }