Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
185 lines
7.2 KiB
C#
185 lines
7.2 KiB
C#
using System.Collections.Immutable;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Policy.Engine.Events;
|
|
|
|
/// <summary>
|
|
/// Type of policy effective event.
|
|
/// </summary>
|
|
[JsonConverter(typeof(JsonStringEnumConverter<PolicyEffectiveEventType>))]
|
|
public enum PolicyEffectiveEventType
|
|
{
|
|
/// <summary>Policy decision changed for a subject.</summary>
|
|
[JsonPropertyName("policy.effective.updated")]
|
|
EffectiveUpdated,
|
|
|
|
/// <summary>Policy decision added for new subject.</summary>
|
|
[JsonPropertyName("policy.effective.added")]
|
|
EffectiveAdded,
|
|
|
|
/// <summary>Policy decision removed (subject no longer affected).</summary>
|
|
[JsonPropertyName("policy.effective.removed")]
|
|
EffectiveRemoved,
|
|
|
|
/// <summary>Batch re-evaluation completed.</summary>
|
|
[JsonPropertyName("policy.effective.batch_completed")]
|
|
BatchCompleted
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class for policy effective events.
|
|
/// </summary>
|
|
public abstract record PolicyEffectiveEvent(
|
|
[property: JsonPropertyName("event_id")] string EventId,
|
|
[property: JsonPropertyName("event_type")] PolicyEffectiveEventType EventType,
|
|
[property: JsonPropertyName("tenant_id")] string TenantId,
|
|
[property: JsonPropertyName("timestamp")] DateTimeOffset Timestamp,
|
|
[property: JsonPropertyName("correlation_id")] string? CorrelationId);
|
|
|
|
/// <summary>
|
|
/// Event emitted when a policy decision is updated for a subject.
|
|
/// </summary>
|
|
public sealed record PolicyEffectiveUpdatedEvent(
|
|
string EventId,
|
|
string TenantId,
|
|
DateTimeOffset Timestamp,
|
|
string? CorrelationId,
|
|
[property: JsonPropertyName("pack_id")] string PackId,
|
|
[property: JsonPropertyName("pack_version")] int PackVersion,
|
|
[property: JsonPropertyName("subject_purl")] string SubjectPurl,
|
|
[property: JsonPropertyName("advisory_id")] string AdvisoryId,
|
|
[property: JsonPropertyName("trigger_type")] string TriggerType,
|
|
[property: JsonPropertyName("diff")] PolicyDecisionDiff Diff)
|
|
: PolicyEffectiveEvent(EventId, PolicyEffectiveEventType.EffectiveUpdated, TenantId, Timestamp, CorrelationId);
|
|
|
|
/// <summary>
|
|
/// Diff metadata for policy decision changes.
|
|
/// </summary>
|
|
public sealed record PolicyDecisionDiff(
|
|
[property: JsonPropertyName("old_status")] string? OldStatus,
|
|
[property: JsonPropertyName("new_status")] string NewStatus,
|
|
[property: JsonPropertyName("old_severity")] string? OldSeverity,
|
|
[property: JsonPropertyName("new_severity")] string? NewSeverity,
|
|
[property: JsonPropertyName("old_rule")] string? OldRule,
|
|
[property: JsonPropertyName("new_rule")] string? NewRule,
|
|
[property: JsonPropertyName("old_priority")] int? OldPriority,
|
|
[property: JsonPropertyName("new_priority")] int? NewPriority,
|
|
[property: JsonPropertyName("status_changed")] bool StatusChanged,
|
|
[property: JsonPropertyName("severity_changed")] bool SeverityChanged,
|
|
[property: JsonPropertyName("rule_changed")] bool RuleChanged,
|
|
[property: JsonPropertyName("annotations_added")] ImmutableArray<string> AnnotationsAdded,
|
|
[property: JsonPropertyName("annotations_removed")] ImmutableArray<string> AnnotationsRemoved)
|
|
{
|
|
/// <summary>
|
|
/// Creates a diff between two policy decisions.
|
|
/// </summary>
|
|
public static PolicyDecisionDiff Create(
|
|
string? oldStatus, string newStatus,
|
|
string? oldSeverity, string? newSeverity,
|
|
string? oldRule, string? newRule,
|
|
int? oldPriority, int? newPriority,
|
|
ImmutableDictionary<string, string>? oldAnnotations,
|
|
ImmutableDictionary<string, string>? newAnnotations)
|
|
{
|
|
var oldKeys = oldAnnotations?.Keys ?? Enumerable.Empty<string>();
|
|
var newKeys = newAnnotations?.Keys ?? Enumerable.Empty<string>();
|
|
|
|
var annotationsAdded = newKeys
|
|
.Where(k => oldAnnotations?.ContainsKey(k) != true)
|
|
.OrderBy(k => k)
|
|
.ToImmutableArray();
|
|
|
|
var annotationsRemoved = oldKeys
|
|
.Where(k => newAnnotations?.ContainsKey(k) != true)
|
|
.OrderBy(k => k)
|
|
.ToImmutableArray();
|
|
|
|
return new PolicyDecisionDiff(
|
|
OldStatus: oldStatus,
|
|
NewStatus: newStatus,
|
|
OldSeverity: oldSeverity,
|
|
NewSeverity: newSeverity,
|
|
OldRule: oldRule,
|
|
NewRule: newRule,
|
|
OldPriority: oldPriority,
|
|
NewPriority: newPriority,
|
|
StatusChanged: !string.Equals(oldStatus, newStatus, StringComparison.Ordinal),
|
|
SeverityChanged: !string.Equals(oldSeverity, newSeverity, StringComparison.Ordinal),
|
|
RuleChanged: !string.Equals(oldRule, newRule, StringComparison.Ordinal),
|
|
AnnotationsAdded: annotationsAdded,
|
|
AnnotationsRemoved: annotationsRemoved);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event emitted when batch re-evaluation completes.
|
|
/// </summary>
|
|
public sealed record PolicyBatchCompletedEvent(
|
|
string EventId,
|
|
string TenantId,
|
|
DateTimeOffset Timestamp,
|
|
string? CorrelationId,
|
|
[property: JsonPropertyName("batch_id")] string BatchId,
|
|
[property: JsonPropertyName("trigger_type")] string TriggerType,
|
|
[property: JsonPropertyName("subjects_evaluated")] int SubjectsEvaluated,
|
|
[property: JsonPropertyName("decisions_changed")] int DecisionsChanged,
|
|
[property: JsonPropertyName("duration_ms")] long DurationMs,
|
|
[property: JsonPropertyName("summary")] PolicyBatchSummary Summary)
|
|
: PolicyEffectiveEvent(EventId, PolicyEffectiveEventType.BatchCompleted, TenantId, Timestamp, CorrelationId);
|
|
|
|
/// <summary>
|
|
/// Summary of changes in a batch re-evaluation.
|
|
/// </summary>
|
|
public sealed record PolicyBatchSummary(
|
|
[property: JsonPropertyName("status_upgrades")] int StatusUpgrades,
|
|
[property: JsonPropertyName("status_downgrades")] int StatusDowngrades,
|
|
[property: JsonPropertyName("new_blocks")] int NewBlocks,
|
|
[property: JsonPropertyName("blocks_removed")] int BlocksRemoved,
|
|
[property: JsonPropertyName("affected_advisories")] ImmutableArray<string> AffectedAdvisories,
|
|
[property: JsonPropertyName("affected_purls")] ImmutableArray<string> AffectedPurls);
|
|
|
|
/// <summary>
|
|
/// Request to schedule a re-evaluation job.
|
|
/// </summary>
|
|
public sealed record ReEvaluationJobRequest(
|
|
string JobId,
|
|
string TenantId,
|
|
string PackId,
|
|
int PackVersion,
|
|
string TriggerType,
|
|
string? CorrelationId,
|
|
DateTimeOffset CreatedAt,
|
|
PolicyChangePriority Priority,
|
|
ImmutableArray<string> AdvisoryIds,
|
|
ImmutableArray<string> SubjectPurls,
|
|
ImmutableArray<string> SbomIds,
|
|
ImmutableDictionary<string, string> Metadata)
|
|
{
|
|
/// <summary>
|
|
/// Creates a deterministic job ID.
|
|
/// </summary>
|
|
public static string CreateJobId(
|
|
string tenantId,
|
|
string packId,
|
|
int packVersion,
|
|
string triggerType,
|
|
DateTimeOffset createdAt)
|
|
{
|
|
var seed = $"{tenantId}|{packId}|{packVersion}|{triggerType}|{createdAt:O}";
|
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(seed));
|
|
return $"rej-{Convert.ToHexStringLower(bytes)[..16]}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Policy change priority from IncrementalOrchestrator namespace.
|
|
/// </summary>
|
|
public enum PolicyChangePriority
|
|
{
|
|
Normal = 0,
|
|
High = 1,
|
|
Emergency = 2
|
|
}
|