up
This commit is contained in:
115
src/Graph/StellaOps.Graph.Api/Services/InMemoryOverlayService.cs
Normal file
115
src/Graph/StellaOps.Graph.Api/Services/InMemoryOverlayService.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
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<IDictionary<string, Dictionary<string, OverlayPayload>>> GetOverlaysAsync(string tenant, IEnumerable<string> nodeIds, bool sampleExplain, CancellationToken ct = default)
|
||||
{
|
||||
var result = new Dictionary<string, Dictionary<string, OverlayPayload>>(StringComparer.Ordinal);
|
||||
var explainEmitted = false;
|
||||
|
||||
foreach (var nodeId in nodeIds)
|
||||
{
|
||||
var cacheKey = $"overlay:{tenant}:{nodeId}";
|
||||
if (!_cache.TryGetValue(cacheKey, out Dictionary<string, OverlayPayload>? cachedBase))
|
||||
{
|
||||
_metrics.OverlayCacheMiss.Add(1);
|
||||
cachedBase = new Dictionary<string, OverlayPayload>(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<string, OverlayPayload>(cachedBase, StringComparer.Ordinal);
|
||||
|
||||
if (sampleExplain && !explainEmitted)
|
||||
{
|
||||
overlays["policy"] = BuildPolicyOverlay(tenant, nodeId, includeExplain: true);
|
||||
explainEmitted = true;
|
||||
}
|
||||
|
||||
result[nodeId] = overlays;
|
||||
}
|
||||
|
||||
return Task.FromResult<IDictionary<string, Dictionary<string, OverlayPayload>>>(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<string>()
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user