Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Services/PolicyRuntimeEvaluator.cs
StellaOps Bot d63af51f84
Some checks failed
api-governance / spectral-lint (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
up
2025-11-26 20:23:28 +02:00

79 lines
2.6 KiB
C#

using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Policy.Engine.Domain;
namespace StellaOps.Policy.Engine.Services;
/// <summary>
/// Deterministic runtime evaluator with per-digest caching.
/// </summary>
internal sealed class PolicyRuntimeEvaluator
{
private readonly IPolicyPackRepository _repository;
private readonly ConcurrentDictionary<string, PolicyEvaluationResponse> _cache = new(StringComparer.Ordinal);
public PolicyRuntimeEvaluator(IPolicyPackRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task<PolicyEvaluationResponse> EvaluateAsync(PolicyEvaluationRequest request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.PackId))
{
throw new ArgumentException("packId required", nameof(request));
}
if (request.Version <= 0)
{
throw new ArgumentException("version must be positive", nameof(request));
}
if (string.IsNullOrWhiteSpace(request.Subject))
{
throw new ArgumentException("subject required", nameof(request));
}
var bundle = await _repository.GetBundleAsync(request.PackId, request.Version, cancellationToken).ConfigureAwait(false);
if (bundle is null)
{
throw new InvalidOperationException("Bundle not found for requested revision.");
}
var cacheKey = $"{bundle.Digest}|{request.Subject}";
if (_cache.TryGetValue(cacheKey, out var cached))
{
return cached with { Cached = true };
}
var decision = ComputeDecision(bundle.Digest, request.Subject);
var correlationId = ComputeCorrelationId(cacheKey);
var response = new PolicyEvaluationResponse(
request.PackId,
request.Version,
bundle.Digest,
decision,
correlationId,
Cached: false);
_cache.TryAdd(cacheKey, response);
return response;
}
private static string ComputeDecision(string digest, string subject)
{
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(Encoding.UTF8.GetBytes($"{digest}|{subject}"), hash);
return (hash[0] & 1) == 0 ? "allow" : "deny";
}
private static string ComputeCorrelationId(string value)
{
Span<byte> hash = stackalloc byte[32];
SHA256.HashData(Encoding.UTF8.GetBytes(value), hash);
return Convert.ToHexString(hash);
}
}