feat: add PolicyPackSelectorComponent with tests and integration

- Implemented PolicyPackSelectorComponent for selecting policy packs.
- Added unit tests for component behavior, including API success and error handling.
- Introduced monaco-workers type declarations for editor workers.
- Created acceptance tests for guardrails with stubs for AT1–AT10.
- Established SCA Failure Catalogue Fixtures for regression testing.
- Developed plugin determinism harness with stubs for PL1–PL10.
- Added scripts for evidence upload and verification processes.
This commit is contained in:
StellaOps Bot
2025-12-05 21:24:34 +02:00
parent 347c88342c
commit 18d87c64c5
220 changed files with 7700 additions and 518 deletions

View File

@@ -62,7 +62,7 @@ public sealed class PackRunApprovalDecisionServiceTests
NullLogger<PackRunApprovalDecisionService>.Instance);
var result = await service.ApplyAsync(
new PackRunApprovalDecisionRequest("missing", "approval", "hash", PackRunApprovalDecisionType.Approved, "actor", null),
new PackRunApprovalDecisionRequest("missing", "approval", "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", PackRunApprovalDecisionType.Approved, "actor", null),
CancellationToken.None);
Assert.Equal("not_found", result.Status);
@@ -107,6 +107,44 @@ public sealed class PackRunApprovalDecisionServiceTests
Assert.False(scheduler.ScheduledContexts.Any());
}
[Fact]
public async Task ApplyAsync_ReturnsPlanHashMismatchWhenFormatInvalid()
{
var plan = TestPlanFactory.CreatePlan();
var state = TestPlanFactory.CreateState("run-1", plan);
var approval = new PackRunApprovalState(
"security-review",
new[] { "Packs.Approve" },
new[] { "step-a" },
Array.Empty<string>(),
null,
DateTimeOffset.UtcNow.AddMinutes(-5),
PackRunApprovalStatus.Pending);
var approvalStore = new InMemoryApprovalStore(new Dictionary<string, IReadOnlyList<PackRunApprovalState>>
{
["run-1"] = new List<PackRunApprovalState> { approval }
});
var stateStore = new InMemoryStateStore(new Dictionary<string, PackRunState>
{
["run-1"] = state
});
var scheduler = new RecordingScheduler();
var service = new PackRunApprovalDecisionService(
approvalStore,
stateStore,
scheduler,
NullLogger<PackRunApprovalDecisionService>.Instance);
var result = await service.ApplyAsync(
new PackRunApprovalDecisionRequest("run-1", "security-review", "not-a-digest", PackRunApprovalDecisionType.Approved, "actor", null),
CancellationToken.None);
Assert.Equal("plan_hash_mismatch", result.Status);
Assert.False(scheduler.ScheduledContexts.Any());
}
private sealed class InMemoryApprovalStore : IPackRunApprovalStore
{
private readonly Dictionary<string, List<PackRunApprovalState>> _approvals;
@@ -214,7 +252,7 @@ internal static class TestPlanFactory
metadata,
new Dictionary<string, JsonNode?>(StringComparer.Ordinal),
new[] { step },
"hash-123",
"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
new[]
{
new TaskPackPlanApproval("security-review", new[] { "Packs.Approve" }, null, null)

View File

@@ -36,9 +36,24 @@ public sealed class TaskPackPlannerTests
var resultB = planner.Plan(manifest, inputs);
Assert.True(resultB.Success);
Assert.Equal(plan.Hash, resultB.Plan!.Hash);
}
Assert.Equal(plan.Hash, resultB.Plan!.Hash);
}
[Fact]
public void PlanHash_IsPrefixedSha256Digest()
{
var manifest = TestManifests.Load(TestManifests.Sample);
var planner = new TaskPackPlanner();
var result = planner.Plan(manifest);
Assert.True(result.Success);
var hash = result.Plan!.Hash;
Assert.StartsWith("sha256:", hash, StringComparison.Ordinal);
Assert.Equal(71, hash.Length); // "sha256:" + 64 hex characters
var hex = hash.Substring("sha256:".Length);
Assert.True(hex.All(c => Uri.IsHexDigit(c)), "Hash contains non-hex characters.");
}
[Fact]
public void Plan_WhenConditionEvaluatesFalse_DisablesStep()
{

View File

@@ -19,17 +19,27 @@ metadata:
version: 1.0.0
description: Sample pack for planner tests
tags: [tests]
spec:
inputs:
- name: dryRun
type: boolean
required: false
default: false
approvals:
spec:
inputs:
- name: dryRun
type: boolean
required: false
default: false
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
approvals:
- id: security-review
grants: ["packs.approve"]
steps:
- id: plan-step
steps:
- id: plan-step
name: Plan
run:
uses: builtin:plan
@@ -57,6 +67,16 @@ spec:
- name: sbomBundle
type: object
required: true
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: noop
run:
@@ -71,12 +91,22 @@ kind: TaskPack
metadata:
name: step-ref-pack
version: 1.0.0
spec:
steps:
- id: prepare
run:
uses: builtin:prepare
- id: consume
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: prepare
run:
uses: builtin:prepare
- id: consume
run:
uses: builtin:consume
with:
@@ -89,16 +119,26 @@ kind: TaskPack
metadata:
name: map-pack
version: 1.0.0
spec:
inputs:
- name: targets
type: array
required: true
steps:
- id: maintenance-loop
map:
items: "{{ inputs.targets }}"
step:
spec:
inputs:
- name: targets
type: array
required: true
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: maintenance-loop
map:
items: "{{ inputs.targets }}"
step:
id: echo-step
run:
uses: builtin:echo
@@ -112,16 +152,26 @@ kind: TaskPack
metadata:
name: secret-pack
version: 1.0.0
spec:
secrets:
spec:
secrets:
- name: apiKey
scope: packs.run
description: API authentication token
steps:
- id: use-secret
run:
uses: builtin:http
with:
description: API authentication token
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: use-secret
run:
uses: builtin:http
with:
token: "{{ secrets.apiKey }}"
""";
@@ -131,12 +181,22 @@ kind: TaskPack
metadata:
name: output-pack
version: 1.0.0
spec:
steps:
- id: generate
run:
uses: builtin:generate
outputs:
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: generate
run:
uses: builtin:generate
outputs:
- name: bundlePath
type: file
path: artifacts/report.txt
@@ -152,6 +212,16 @@ metadata:
name: failure-policy-pack
version: 1.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: build
run:
@@ -170,6 +240,16 @@ metadata:
name: parallel-pack
version: 1.1.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: fanout
parallel:
@@ -196,6 +276,16 @@ metadata:
name: policy-gate-pack
version: 1.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: prepare
run:
@@ -216,6 +306,16 @@ metadata:
name: egress-allowed
version: 1.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: fetch
run:
@@ -233,6 +333,16 @@ metadata:
name: egress-blocked
version: 1.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: fetch
run:
@@ -252,6 +362,16 @@ spec:
- name: targetUrl
type: string
required: false
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: fetch
run: