Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Slices/PolicyBinding.cs

174 lines
5.2 KiB
C#

using System.Text.Json.Serialization;
namespace StellaOps.Scanner.Reachability.Slices;
/// <summary>
/// Policy binding mode for slices.
/// </summary>
public enum PolicyBindingMode
{
/// <summary>
/// Slice is invalid if policy changes at all.
/// </summary>
Strict,
/// <summary>
/// Slice is valid with newer policy versions only.
/// </summary>
Forward,
/// <summary>
/// Slice is valid with any policy version.
/// </summary>
Any
}
/// <summary>
/// Policy binding information for a reachability slice.
/// </summary>
public sealed record PolicyBinding
{
/// <summary>
/// Content-addressed hash of the policy DSL.
/// </summary>
[JsonPropertyName("policyDigest")]
public required string PolicyDigest { get; init; }
/// <summary>
/// Semantic version of the policy.
/// </summary>
[JsonPropertyName("policyVersion")]
public required string PolicyVersion { get; init; }
/// <summary>
/// When the policy was bound to this slice.
/// </summary>
[JsonPropertyName("boundAt")]
public required DateTimeOffset BoundAt { get; init; }
/// <summary>
/// Binding mode for validation.
/// </summary>
[JsonPropertyName("mode")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public required PolicyBindingMode Mode { get; init; }
/// <summary>
/// Optional policy name/identifier.
/// </summary>
[JsonPropertyName("policyName")]
public string? PolicyName { get; init; }
/// <summary>
/// Optional policy source (e.g., git commit hash).
/// </summary>
[JsonPropertyName("policySource")]
public string? PolicySource { get; init; }
}
/// <summary>
/// Result of policy binding validation.
/// </summary>
public sealed record PolicyBindingValidationResult
{
public required bool Valid { get; init; }
public string? FailureReason { get; init; }
public required PolicyBinding SlicePolicy { get; init; }
public required PolicyBinding CurrentPolicy { get; init; }
}
/// <summary>
/// Validator for policy bindings.
/// </summary>
public sealed class PolicyBindingValidator
{
/// <summary>
/// Validate a policy binding against current policy.
/// </summary>
public PolicyBindingValidationResult Validate(
PolicyBinding sliceBinding,
PolicyBinding currentPolicy)
{
ArgumentNullException.ThrowIfNull(sliceBinding);
ArgumentNullException.ThrowIfNull(currentPolicy);
var result = sliceBinding.Mode switch
{
PolicyBindingMode.Strict => ValidateStrict(sliceBinding, currentPolicy),
PolicyBindingMode.Forward => ValidateForward(sliceBinding, currentPolicy),
PolicyBindingMode.Any => ValidateAny(sliceBinding, currentPolicy),
_ => throw new ArgumentException($"Unknown policy binding mode: {sliceBinding.Mode}")
};
return result with
{
SlicePolicy = sliceBinding,
CurrentPolicy = currentPolicy
};
}
private static PolicyBindingValidationResult ValidateStrict(
PolicyBinding sliceBinding,
PolicyBinding currentPolicy)
{
var digestMatch = string.Equals(
sliceBinding.PolicyDigest,
currentPolicy.PolicyDigest,
StringComparison.Ordinal);
return new PolicyBindingValidationResult
{
Valid = digestMatch,
FailureReason = digestMatch
? null
: $"Policy digest mismatch. Slice bound to {sliceBinding.PolicyDigest}, current is {currentPolicy.PolicyDigest}.",
SlicePolicy = sliceBinding,
CurrentPolicy = currentPolicy
};
}
private static PolicyBindingValidationResult ValidateForward(
PolicyBinding sliceBinding,
PolicyBinding currentPolicy)
{
// Check if current policy version is newer or equal
if (!Version.TryParse(sliceBinding.PolicyVersion, out var sliceVersion) ||
!Version.TryParse(currentPolicy.PolicyVersion, out var currentVersion))
{
return new PolicyBindingValidationResult
{
Valid = false,
FailureReason = "Invalid version format for forward compatibility check.",
SlicePolicy = sliceBinding,
CurrentPolicy = currentPolicy
};
}
var isForwardCompatible = currentVersion >= sliceVersion;
return new PolicyBindingValidationResult
{
Valid = isForwardCompatible,
FailureReason = isForwardCompatible
? null
: $"Policy version downgrade detected. Slice bound to {sliceVersion}, current is {currentVersion}.",
SlicePolicy = sliceBinding,
CurrentPolicy = currentPolicy
};
}
private static PolicyBindingValidationResult ValidateAny(
PolicyBinding sliceBinding,
PolicyBinding currentPolicy)
{
// Always valid in 'any' mode
return new PolicyBindingValidationResult
{
Valid = true,
FailureReason = null,
SlicePolicy = sliceBinding,
CurrentPolicy = currentPolicy
};
}
}