up
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
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
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user