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:
@@ -40,7 +40,7 @@ internal static class TaskPackPlanHasher
|
||||
var json = CanonicalJson.Serialize(canonical);
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(json));
|
||||
return ConvertToHex(hashBytes);
|
||||
return $"sha256:{ConvertToHex(hashBytes)}";
|
||||
}
|
||||
|
||||
private static string ConvertToHex(byte[] hashBytes)
|
||||
|
||||
@@ -22,16 +22,17 @@ public sealed class TaskPackPlanner
|
||||
this.egressPolicy = egressPolicy;
|
||||
}
|
||||
|
||||
public TaskPackPlanResult Plan(TaskPackManifest manifest, IDictionary<string, JsonNode?>? providedInputs = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
|
||||
var errors = ImmutableArray.CreateBuilder<TaskPackPlanError>();
|
||||
|
||||
var validation = validator.Validate(manifest);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
foreach (var error in validation.Errors)
|
||||
public TaskPackPlanResult Plan(TaskPackManifest manifest, IDictionary<string, JsonNode?>? providedInputs = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
|
||||
var errors = ImmutableArray.CreateBuilder<TaskPackPlanError>();
|
||||
ValidateSandboxAndSlo(manifest, errors);
|
||||
|
||||
var validation = validator.Validate(manifest);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
foreach (var error in validation.Errors)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError(error.Path, error.Message));
|
||||
}
|
||||
@@ -106,10 +107,70 @@ public sealed class TaskPackPlanner
|
||||
return new TaskPackPlanResult(plan, ImmutableArray<TaskPackPlanError>.Empty);
|
||||
}
|
||||
|
||||
private static void ValidateSandboxAndSlo(TaskPackManifest manifest, ImmutableArray<TaskPackPlanError>.Builder errors)
|
||||
{
|
||||
// TP6: sandbox quotas must be present.
|
||||
var sandbox = manifest.Spec.Sandbox;
|
||||
if (sandbox is null)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox", "Sandbox settings are required (mode, egressAllowlist, CPU/memory, quotaSeconds)."));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sandbox.Mode))
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox.mode", "Sandbox mode is required (sealed or restricted)."));
|
||||
}
|
||||
|
||||
if (sandbox.EgressAllowlist is null)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox.egressAllowlist", "Egress allowlist must be declared (empty list allowed)."));
|
||||
}
|
||||
|
||||
if (sandbox.CpuLimitMillicores <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox.cpuLimitMillicores", "CPU limit must be > 0."));
|
||||
}
|
||||
|
||||
if (sandbox.MemoryLimitMiB <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox.memoryLimitMiB", "Memory limit must be > 0."));
|
||||
}
|
||||
|
||||
if (sandbox.QuotaSeconds <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.sandbox.quotaSeconds", "quotaSeconds must be > 0."));
|
||||
}
|
||||
}
|
||||
|
||||
// TP9: SLOs must be declared and positive.
|
||||
var slo = manifest.Spec.Slo;
|
||||
if (slo is null)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.slo", "SLO section is required (runP95Seconds, approvalP95Seconds, maxQueueDepth)."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (slo.RunP95Seconds <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.slo.runP95Seconds", "runP95Seconds must be > 0."));
|
||||
}
|
||||
|
||||
if (slo.ApprovalP95Seconds <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.slo.approvalP95Seconds", "approvalP95Seconds must be > 0."));
|
||||
}
|
||||
|
||||
if (slo.MaxQueueDepth <= 0)
|
||||
{
|
||||
errors.Add(new TaskPackPlanError("spec.slo.maxQueueDepth", "maxQueueDepth must be > 0."));
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, JsonNode?> MaterializeInputs(
|
||||
IReadOnlyList<TaskPackInput>? definitions,
|
||||
IDictionary<string, JsonNode?>? providedInputs,
|
||||
ImmutableArray<TaskPackPlanError>.Builder errors)
|
||||
IDictionary<string, JsonNode?>? providedInputs,
|
||||
ImmutableArray<TaskPackPlanError>.Builder errors)
|
||||
{
|
||||
var effective = new Dictionary<string, JsonNode?>(StringComparer.Ordinal);
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ public sealed class TaskPackMaintainer
|
||||
public string? Email { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TaskPackSpec
|
||||
{
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyList<TaskPackInput>? Inputs { get; init; }
|
||||
|
||||
public sealed class TaskPackSpec
|
||||
{
|
||||
[JsonPropertyName("inputs")]
|
||||
public IReadOnlyList<TaskPackInput>? Inputs { get; init; }
|
||||
|
||||
[JsonPropertyName("secrets")]
|
||||
public IReadOnlyList<TaskPackSecret>? Secrets { get; init; }
|
||||
|
||||
@@ -72,11 +72,17 @@ public sealed class TaskPackSpec
|
||||
public IReadOnlyList<TaskPackOutput>? Outputs { get; init; }
|
||||
|
||||
[JsonPropertyName("success")]
|
||||
public TaskPackSuccess? Success { get; init; }
|
||||
|
||||
[JsonPropertyName("failure")]
|
||||
public TaskPackFailure? Failure { get; init; }
|
||||
}
|
||||
public TaskPackSuccess? Success { get; init; }
|
||||
|
||||
[JsonPropertyName("failure")]
|
||||
public TaskPackFailure? Failure { get; init; }
|
||||
|
||||
[JsonPropertyName("sandbox")]
|
||||
public TaskPackSandbox? Sandbox { get; init; }
|
||||
|
||||
[JsonPropertyName("slo")]
|
||||
public TaskPackSlo? Slo { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TaskPackInput
|
||||
{
|
||||
@@ -255,11 +261,41 @@ public sealed class TaskPackFailure
|
||||
public TaskPackRetryPolicy? Retries { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TaskPackRetryPolicy
|
||||
{
|
||||
[JsonPropertyName("maxAttempts")]
|
||||
public int MaxAttempts { get; init; }
|
||||
|
||||
[JsonPropertyName("backoffSeconds")]
|
||||
public int BackoffSeconds { get; init; }
|
||||
}
|
||||
public sealed class TaskPackRetryPolicy
|
||||
{
|
||||
[JsonPropertyName("maxAttempts")]
|
||||
public int MaxAttempts { get; init; }
|
||||
|
||||
[JsonPropertyName("backoffSeconds")]
|
||||
public int BackoffSeconds { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TaskPackSandbox
|
||||
{
|
||||
[JsonPropertyName("mode")]
|
||||
public string? Mode { get; init; }
|
||||
|
||||
[JsonPropertyName("egressAllowlist")]
|
||||
public IReadOnlyList<string>? EgressAllowlist { get; init; }
|
||||
|
||||
[JsonPropertyName("cpuLimitMillicores")]
|
||||
public int CpuLimitMillicores { get; init; }
|
||||
|
||||
[JsonPropertyName("memoryLimitMiB")]
|
||||
public int MemoryLimitMiB { get; init; }
|
||||
|
||||
[JsonPropertyName("quotaSeconds")]
|
||||
public int QuotaSeconds { get; init; }
|
||||
}
|
||||
|
||||
public sealed class TaskPackSlo
|
||||
{
|
||||
[JsonPropertyName("runP95Seconds")]
|
||||
public int RunP95Seconds { get; init; }
|
||||
|
||||
[JsonPropertyName("approvalP95Seconds")]
|
||||
public int ApprovalP95Seconds { get; init; }
|
||||
|
||||
[JsonPropertyName("maxQueueDepth")]
|
||||
public int MaxQueueDepth { get; init; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user