186 lines
5.7 KiB
C#
186 lines
5.7 KiB
C#
|
|
using StellaOps.Policy.Gates.Opa;
|
|
using StellaOps.Policy.TrustLattice;
|
|
using System.Collections.Immutable;
|
|
|
|
namespace StellaOps.Policy.Gates;
|
|
|
|
public record PolicyGateContext
|
|
{
|
|
public string Environment { get; init; } = "production";
|
|
public int UnknownCount { get; init; }
|
|
public IReadOnlyList<double> UnknownClaimScores { get; init; } = Array.Empty<double>();
|
|
public IReadOnlyDictionary<string, double> SourceInfluence { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
|
|
public bool HasReachabilityProof { get; init; }
|
|
public string? Severity { get; init; }
|
|
public IReadOnlyCollection<string> ReasonCodes { get; init; } = Array.Empty<string>();
|
|
|
|
/// <summary>
|
|
/// Subgraph slice for reachability proof.
|
|
/// Required for high-severity findings when RequireSubgraphProofForHighSeverity is enabled.
|
|
/// </summary>
|
|
public SubgraphSlice? SubgraphSlice { get; init; }
|
|
|
|
/// <summary>
|
|
/// Subject key for Signals lookup.
|
|
/// </summary>
|
|
public string? SubjectKey { get; init; }
|
|
|
|
/// <summary>
|
|
/// CVE ID if applicable.
|
|
/// </summary>
|
|
public string? CveId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Mutable metadata for audit trail.
|
|
/// Gates can add metadata here for later inspection.
|
|
/// </summary>
|
|
public Dictionary<string, string>? Metadata { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional supply chain evidence bundle for OPA policy evaluation.
|
|
/// When provided, OPA policies can access artifact metadata, SBOMs,
|
|
/// attestations, Rekor receipts, and VEX merge decisions.
|
|
/// </summary>
|
|
public OpaSupplyChainEvidence? SupplyChainEvidence { get; init; }
|
|
}
|
|
|
|
public sealed record GateResult
|
|
{
|
|
public required string GateName { get; init; }
|
|
public required bool Passed { get; init; }
|
|
public required string? Reason { get; init; }
|
|
public required ImmutableDictionary<string, object> Details { get; init; }
|
|
|
|
/// <summary>
|
|
/// Creates a passing gate result.
|
|
/// </summary>
|
|
public static GateResult Pass(string gateName, string reason, IEnumerable<string>? warnings = null)
|
|
{
|
|
var details = ImmutableDictionary<string, object>.Empty;
|
|
if (warnings != null)
|
|
{
|
|
var warningList = warnings.ToList();
|
|
if (warningList.Count > 0)
|
|
{
|
|
details = details.Add("warnings", warningList);
|
|
}
|
|
}
|
|
return new GateResult
|
|
{
|
|
GateName = gateName,
|
|
Passed = true,
|
|
Reason = reason,
|
|
Details = details
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a passing gate result with child gate results.
|
|
/// </summary>
|
|
public static GateResult Pass(string gateName, string reason, IReadOnlyList<GateResult>? childResults)
|
|
{
|
|
var details = childResults != null && childResults.Count > 0
|
|
? ImmutableDictionary<string, object>.Empty.Add("childResults", childResults)
|
|
: ImmutableDictionary<string, object>.Empty;
|
|
return new GateResult
|
|
{
|
|
GateName = gateName,
|
|
Passed = true,
|
|
Reason = reason,
|
|
Details = details
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a failing gate result.
|
|
/// </summary>
|
|
public static GateResult Fail(string gateName, string reason, ImmutableDictionary<string, object>? details = null)
|
|
{
|
|
return new GateResult
|
|
{
|
|
GateName = gateName,
|
|
Passed = false,
|
|
Reason = reason,
|
|
Details = details ?? ImmutableDictionary<string, object>.Empty
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a failing gate result with child gate results.
|
|
/// </summary>
|
|
public static GateResult Fail(string gateName, string reason, IReadOnlyList<GateResult>? childResults)
|
|
{
|
|
var details = childResults != null && childResults.Count > 0
|
|
? ImmutableDictionary<string, object>.Empty.Add("childResults", childResults)
|
|
: ImmutableDictionary<string, object>.Empty;
|
|
return new GateResult
|
|
{
|
|
GateName = gateName,
|
|
Passed = false,
|
|
Reason = reason,
|
|
Details = details
|
|
};
|
|
}
|
|
}
|
|
|
|
public sealed record GateEvaluationResult
|
|
{
|
|
public required bool AllPassed { get; init; }
|
|
public required ImmutableArray<GateResult> Results { get; init; }
|
|
public GateResult? FirstFailure => Results.FirstOrDefault(r => !r.Passed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Policy gate interface for gates that require MergeResult.
|
|
/// </summary>
|
|
public interface IPolicyGate
|
|
{
|
|
Task<GateResult> EvaluateAsync(
|
|
MergeResult mergeResult,
|
|
PolicyGateContext context,
|
|
CancellationToken ct = default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simplified policy gate interface for context-only evaluation.
|
|
/// Used by attestation, runtime witness, and CVE gates that don't require MergeResult.
|
|
/// </summary>
|
|
public interface IContextPolicyGate
|
|
{
|
|
/// <summary>
|
|
/// Gate identifier.
|
|
/// </summary>
|
|
string Id { get; }
|
|
|
|
/// <summary>
|
|
/// Display name for the gate.
|
|
/// </summary>
|
|
string DisplayName { get; }
|
|
|
|
/// <summary>
|
|
/// Description of what the gate checks.
|
|
/// </summary>
|
|
string Description { get; }
|
|
|
|
/// <summary>
|
|
/// Evaluates the gate against the given context.
|
|
/// </summary>
|
|
Task<GateResult> EvaluateAsync(PolicyGateContext context, CancellationToken ct = default);
|
|
}
|
|
|
|
public sealed record PolicyGateRegistryOptions
|
|
{
|
|
public bool StopOnFirstFailure { get; init; } = true;
|
|
}
|
|
|
|
public interface IPolicyGateRegistry
|
|
{
|
|
void Register<TGate>(string name) where TGate : IPolicyGate;
|
|
|
|
Task<GateEvaluationResult> EvaluateAsync(
|
|
MergeResult mergeResult,
|
|
PolicyGateContext context,
|
|
CancellationToken ct = default);
|
|
}
|