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

This commit is contained in:
StellaOps Bot
2025-11-26 20:23:28 +02:00
parent 4831c7fcb0
commit d63af51f84
139 changed files with 8010 additions and 2795 deletions

View File

@@ -0,0 +1,53 @@
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Services;
namespace StellaOps.Policy.Engine.Tests;
public sealed class EvidenceSummaryServiceTests
{
[Fact]
public void Summarize_BuildsDeterministicSummary()
{
var timeProvider = new FixedTimeProvider(new DateTimeOffset(2025, 11, 26, 0, 0, 0, TimeSpan.Zero));
var service = new EvidenceSummaryService(timeProvider);
var request = new EvidenceSummaryRequest(
EvidenceHash: "stub-evidence-hash",
FilePath: "/etc/passwd",
Digest: "sha256:123",
IngestedAt: null,
ConnectorId: "connector-1");
var response = service.Summarize(request);
Assert.Equal("stub-evidence-hash", response.EvidenceHash);
Assert.Equal("info", response.Summary.Severity); // first byte bucketed to info
Assert.Equal("/etc/passwd", response.Summary.Locator.FilePath);
Assert.Equal("sha256:123", response.Summary.Locator.Digest);
Assert.Equal("connector-1", response.Summary.Provenance.ConnectorId);
Assert.Equal(new DateTimeOffset(2025, 12, 13, 05, 00, 11, TimeSpan.Zero), response.Summary.Provenance.IngestedAt);
Assert.Contains("stub-eviden", response.Summary.Headline);
Assert.Equal(
new[] { "severity:info", "path:/etc/passwd", "connector:connector-1" },
response.Summary.Signals);
}
[Fact]
public void Summarize_RequiresEvidenceHash()
{
var timeProvider = new FixedTimeProvider(DateTimeOffset.UnixEpoch);
var service = new EvidenceSummaryService(timeProvider);
Assert.Throws<ArgumentException>(() =>
service.Summarize(new EvidenceSummaryRequest(string.Empty, null, null, null, null)));
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _now;
public FixedTimeProvider(DateTimeOffset now) => _now = now;
public override DateTimeOffset GetUtcNow() => _now;
}
}

View File

@@ -0,0 +1,77 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using Microsoft.Extensions.Options;
using StellaOps.Policy;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Engine.Services;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyBundleServiceTests
{
private const string BaselineDsl = """
policy "Baseline Production Policy" syntax "stella-dsl@1" {
rule r1 { when true then status := "ok" because "baseline" }
}
""";
[Fact]
public async Task CompileAndStoreAsync_SucceedsAndStoresBundle()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("stella-dsl@1", BaselineDsl), signingKeyId: "test-key");
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.True(response.Success);
Assert.NotNull(response.Digest);
Assert.StartsWith("sig:sha256:", response.Signature);
Assert.True(response.SizeBytes > 0);
}
[Fact]
public async Task CompileAndStoreAsync_FailsWithBadSyntax()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("unknown", "policy bad"), signingKeyId: null);
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.False(response.Success);
Assert.Null(response.Digest);
Assert.NotEmpty(response.Diagnostics);
}
private static ServiceHarness CreateServices()
{
var compiler = new PolicyCompiler();
var complexity = new PolicyComplexityAnalyzer();
var options = Options.Create(new PolicyEngineOptions());
var compilationService = new PolicyCompilationService(compiler, complexity, new StaticOptionsMonitor(options.Value), TimeProvider.System);
var repo = new InMemoryPolicyPackRepository();
return new ServiceHarness(
new PolicyBundleService(compilationService, repo, TimeProvider.System));
}
private sealed record ServiceHarness(PolicyBundleService BundleService);
private sealed class StaticOptionsMonitor : IOptionsMonitor<PolicyEngineOptions>
{
private readonly PolicyEngineOptions _value;
public StaticOptionsMonitor(PolicyEngineOptions value) => _value = value;
public PolicyEngineOptions CurrentValue => _value;
public PolicyEngineOptions Get(string? name) => _value;
public IDisposable OnChange(Action<PolicyEngineOptions, string> listener) => NullDisposable.Instance;
private sealed class NullDisposable : IDisposable
{
public static readonly NullDisposable Instance = new();
public void Dispose() { }
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Immutable;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Services;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyRuntimeEvaluatorTests
{
[Fact]
public async Task EvaluateAsync_ReturnsDeterministicDecisionAndCaches()
{
var repo = new InMemoryPolicyPackRepository();
await repo.StoreBundleAsync(
"pack-1",
1,
new PolicyBundleRecord(
Digest: "sha256:abc",
Signature: "sig:sha256:abc",
Size: 4,
CreatedAt: DateTimeOffset.UnixEpoch,
Payload: new byte[] { 1, 2, 3, 4 }.ToImmutableArray()),
CancellationToken.None);
var evaluator = new PolicyRuntimeEvaluator(repo);
var request = new PolicyEvaluationRequest("pack-1", 1, "subject-a");
var first = await evaluator.EvaluateAsync(request, CancellationToken.None);
var second = await evaluator.EvaluateAsync(request, CancellationToken.None);
Assert.Equal(first.Decision, second.Decision);
Assert.False(first.Cached);
Assert.True(second.Cached);
Assert.Equal("pack-1", first.PackId);
Assert.Equal(1, first.Version);
}
[Fact]
public async Task EvaluateAsync_ThrowsWhenBundleMissing()
{
var evaluator = new PolicyRuntimeEvaluator(new InMemoryPolicyPackRepository());
var request = new PolicyEvaluationRequest("pack-x", 1, "subject-a");
await Assert.ThrowsAsync<InvalidOperationException>(() => evaluator.EvaluateAsync(request, CancellationToken.None));
}
}

View File

@@ -0,0 +1,80 @@
using System.Linq;
using System.Text.Json;
using StellaOps.Policy.RiskProfile.Canonicalization;
using Xunit;
namespace StellaOps.Policy.RiskProfile.Tests;
public class RiskProfileCanonicalizerTests
{
[Fact]
public void Canonicalize_SortsSignalsAndOverrides()
{
const string input = """
{
"version": "1.0.0",
"id": "profile",
"signals": [
{"name": "kev", "source": "cisa", "type": "boolean"},
{"name": "reachability", "type": "boolean", "source": "signals"}
],
"weights": {"reachability": 0.6, "kev": 0.4},
"overrides": {
"severity": [
{"when": {"kev": true}, "set": "critical"},
{"when": {"reachability": false}, "set": "low"}
],
"decisions": [
{"when": {"reachability": false}, "action": "review"},
{"when": {"kev": true}, "action": "deny"}
]
}
}
""";
var canonical = RiskProfileCanonicalizer.CanonicalizeToString(input);
const string expected = "{\"id\":\"profile\",\"overrides\":{\"decisions\":[{\"action\":\"deny\",\"when\":{\"kev\":true}},{\"action\":\"review\",\"when\":{\"reachability\":false}}],\"severity\":[{\"set\":\"critical\",\"when\":{\"kev\":true}},{\"set\":\"low\",\"when\":{\"reachability\":false}}]},\"signals\":[{\"name\":\"kev\",\"source\":\"cisa\",\"type\":\"boolean\"},{\"name\":\"reachability\",\"source\":\"signals\",\"type\":\"boolean\"}],\"version\":\"1.0.0\",\"weights\":{\"kev\":0.4,\"reachability\":0.6}}";
Assert.Equal(expected, canonical);
}
[Fact]
public void ComputeDigest_IgnoresOrderingNoise()
{
const string a = """
{"id":"p","version":"1.0.0","signals":[{"name":"b","source":"x","type":"boolean"},{"name":"a","source":"y","type":"boolean"}],"weights":{"b":0.5,"a":0.5},"overrides":{"severity":[{"when":{"a":true},"set":"high"}],"decisions":[{"when":{"b":false},"action":"review"}]}}
""";
const string b = """
{"version":"1.0.0","id":"p","weights":{"a":0.5,"b":0.5},"signals":[{"source":"y","name":"a","type":"boolean"},{"type":"boolean","name":"b","source":"x"}],"overrides":{"decisions":[{"action":"review","when":{"b":false}}],"severity":[{"set":"high","when":{"a":true}}]}}
""";
var hashA = RiskProfileCanonicalizer.ComputeDigest(a);
var hashB = RiskProfileCanonicalizer.ComputeDigest(b);
Assert.Equal(hashA, hashB);
}
[Fact]
public void Merge_ReplacesSignalsAndWeights()
{
const string baseProfile = """
{"id":"p","version":"1.0.0","signals":[{"name":"reachability","source":"signals","type":"boolean"}],"weights":{"reachability":0.7},"overrides":{"decisions":[{"when":{"reachability":false},"action":"review"}]}}
""";
const string overlay = """
{"signals":[{"name":"kev","source":"cisa","type":"boolean"}],"weights":{"kev":0.5},"overrides":{"decisions":[{"when":{"kev":true},"action":"deny"}]}}
""";
var merged = RiskProfileCanonicalizer.Merge(baseProfile, overlay);
using var doc = JsonDocument.Parse(merged);
var root = doc.RootElement;
Assert.Equal(2, root.GetProperty("signals").GetArrayLength());
Assert.Equal(2, root.GetProperty("weights").EnumerateObject().Count());
var decisions = root.GetProperty("overrides").GetProperty("decisions").EnumerateArray().ToArray();
Assert.Contains(decisions, d => d.GetProperty("action").GetString() == "deny");
Assert.Contains(decisions, d => d.GetProperty("action").GetString() == "review");
}
}