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
372 lines
11 KiB
C#
372 lines
11 KiB
C#
using System.Collections.Immutable;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Policy.Engine.WhatIfSimulation;
|
|
|
|
/// <summary>
|
|
/// Request for what-if simulation supporting hypothetical SBOM diffs and draft policies.
|
|
/// </summary>
|
|
public sealed record WhatIfSimulationRequest
|
|
{
|
|
/// <summary>
|
|
/// Tenant identifier.
|
|
/// </summary>
|
|
[JsonPropertyName("tenant_id")]
|
|
public required string TenantId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Base snapshot ID to apply diffs to.
|
|
/// </summary>
|
|
[JsonPropertyName("base_snapshot_id")]
|
|
public required string BaseSnapshotId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Active policy pack ID to use as baseline.
|
|
/// If DraftPolicy is provided, this will be compared against.
|
|
/// </summary>
|
|
[JsonPropertyName("baseline_pack_id")]
|
|
public string? BaselinePackId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Baseline policy version. If null, uses active version.
|
|
/// </summary>
|
|
[JsonPropertyName("baseline_pack_version")]
|
|
public int? BaselinePackVersion { get; init; }
|
|
|
|
/// <summary>
|
|
/// Draft policy to simulate (not yet activated).
|
|
/// If null, uses baseline policy.
|
|
/// </summary>
|
|
[JsonPropertyName("draft_policy")]
|
|
public WhatIfDraftPolicy? DraftPolicy { get; init; }
|
|
|
|
/// <summary>
|
|
/// SBOM diffs to apply hypothetically.
|
|
/// </summary>
|
|
[JsonPropertyName("sbom_diffs")]
|
|
public ImmutableArray<WhatIfSbomDiff> SbomDiffs { get; init; } = ImmutableArray<WhatIfSbomDiff>.Empty;
|
|
|
|
/// <summary>
|
|
/// Specific component PURLs to evaluate. If empty, evaluates affected by diffs.
|
|
/// </summary>
|
|
[JsonPropertyName("target_purls")]
|
|
public ImmutableArray<string> TargetPurls { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
/// <summary>
|
|
/// Maximum number of components to evaluate.
|
|
/// </summary>
|
|
[JsonPropertyName("limit")]
|
|
public int Limit { get; init; } = 1000;
|
|
|
|
/// <summary>
|
|
/// Whether to include detailed explanations for each decision.
|
|
/// </summary>
|
|
[JsonPropertyName("include_explanations")]
|
|
public bool IncludeExplanations { get; init; } = false;
|
|
|
|
/// <summary>
|
|
/// Correlation ID for tracing.
|
|
/// </summary>
|
|
[JsonPropertyName("correlation_id")]
|
|
public string? CorrelationId { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draft policy definition for simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfDraftPolicy
|
|
{
|
|
/// <summary>
|
|
/// Draft policy pack ID.
|
|
/// </summary>
|
|
[JsonPropertyName("pack_id")]
|
|
public required string PackId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Draft policy version.
|
|
/// </summary>
|
|
[JsonPropertyName("version")]
|
|
public int Version { get; init; }
|
|
|
|
/// <summary>
|
|
/// Raw YAML policy definition to compile and evaluate.
|
|
/// If provided, this is compiled on-the-fly.
|
|
/// </summary>
|
|
[JsonPropertyName("policy_yaml")]
|
|
public string? PolicyYaml { get; init; }
|
|
|
|
/// <summary>
|
|
/// Pre-compiled bundle digest if available.
|
|
/// </summary>
|
|
[JsonPropertyName("bundle_digest")]
|
|
public string? BundleDigest { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hypothetical SBOM modification for what-if simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfSbomDiff
|
|
{
|
|
/// <summary>
|
|
/// Type of modification: add, remove, upgrade, downgrade.
|
|
/// </summary>
|
|
[JsonPropertyName("operation")]
|
|
public required string Operation { get; init; }
|
|
|
|
/// <summary>
|
|
/// Component PURL being modified.
|
|
/// </summary>
|
|
[JsonPropertyName("purl")]
|
|
public required string Purl { get; init; }
|
|
|
|
/// <summary>
|
|
/// New version for upgrade/downgrade operations.
|
|
/// </summary>
|
|
[JsonPropertyName("new_version")]
|
|
public string? NewVersion { get; init; }
|
|
|
|
/// <summary>
|
|
/// Original version (for reference in upgrades/downgrades).
|
|
/// </summary>
|
|
[JsonPropertyName("original_version")]
|
|
public string? OriginalVersion { get; init; }
|
|
|
|
/// <summary>
|
|
/// Hypothetical advisory IDs affecting this component.
|
|
/// </summary>
|
|
[JsonPropertyName("advisory_ids")]
|
|
public ImmutableArray<string> AdvisoryIds { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
/// <summary>
|
|
/// Hypothetical VEX status for this component.
|
|
/// </summary>
|
|
[JsonPropertyName("vex_status")]
|
|
public string? VexStatus { get; init; }
|
|
|
|
/// <summary>
|
|
/// Hypothetical reachability state.
|
|
/// </summary>
|
|
[JsonPropertyName("reachability")]
|
|
public string? Reachability { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Response from what-if simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfSimulationResponse
|
|
{
|
|
/// <summary>
|
|
/// Simulation identifier.
|
|
/// </summary>
|
|
[JsonPropertyName("simulation_id")]
|
|
public required string SimulationId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Tenant identifier.
|
|
/// </summary>
|
|
[JsonPropertyName("tenant_id")]
|
|
public required string TenantId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Base snapshot ID used.
|
|
/// </summary>
|
|
[JsonPropertyName("base_snapshot_id")]
|
|
public required string BaseSnapshotId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Baseline policy used for comparison.
|
|
/// </summary>
|
|
[JsonPropertyName("baseline_policy")]
|
|
public required WhatIfPolicyRef BaselinePolicy { get; init; }
|
|
|
|
/// <summary>
|
|
/// Simulated policy (draft or modified).
|
|
/// </summary>
|
|
[JsonPropertyName("simulated_policy")]
|
|
public WhatIfPolicyRef? SimulatedPolicy { get; init; }
|
|
|
|
/// <summary>
|
|
/// Decision changes between baseline and simulation.
|
|
/// </summary>
|
|
[JsonPropertyName("decision_changes")]
|
|
public required ImmutableArray<WhatIfDecisionChange> DecisionChanges { get; init; }
|
|
|
|
/// <summary>
|
|
/// Summary of changes.
|
|
/// </summary>
|
|
[JsonPropertyName("summary")]
|
|
public required WhatIfSummary Summary { get; init; }
|
|
|
|
/// <summary>
|
|
/// When the simulation was executed.
|
|
/// </summary>
|
|
[JsonPropertyName("executed_at")]
|
|
public required DateTimeOffset ExecutedAt { get; init; }
|
|
|
|
/// <summary>
|
|
/// Execution duration in milliseconds.
|
|
/// </summary>
|
|
[JsonPropertyName("duration_ms")]
|
|
public long DurationMs { get; init; }
|
|
|
|
/// <summary>
|
|
/// Correlation ID.
|
|
/// </summary>
|
|
[JsonPropertyName("correlation_id")]
|
|
public string? CorrelationId { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Policy reference in simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfPolicyRef(
|
|
[property: JsonPropertyName("pack_id")] string PackId,
|
|
[property: JsonPropertyName("version")] int Version,
|
|
[property: JsonPropertyName("bundle_digest")] string? BundleDigest,
|
|
[property: JsonPropertyName("is_draft")] bool IsDraft);
|
|
|
|
/// <summary>
|
|
/// A decision change detected in what-if simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfDecisionChange
|
|
{
|
|
/// <summary>
|
|
/// Component PURL.
|
|
/// </summary>
|
|
[JsonPropertyName("purl")]
|
|
public required string Purl { get; init; }
|
|
|
|
/// <summary>
|
|
/// Advisory ID if applicable.
|
|
/// </summary>
|
|
[JsonPropertyName("advisory_id")]
|
|
public string? AdvisoryId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Type of change: new, removed, status_changed, severity_changed.
|
|
/// </summary>
|
|
[JsonPropertyName("change_type")]
|
|
public required string ChangeType { get; init; }
|
|
|
|
/// <summary>
|
|
/// Baseline decision.
|
|
/// </summary>
|
|
[JsonPropertyName("baseline")]
|
|
public WhatIfDecision? Baseline { get; init; }
|
|
|
|
/// <summary>
|
|
/// Simulated decision.
|
|
/// </summary>
|
|
[JsonPropertyName("simulated")]
|
|
public WhatIfDecision? Simulated { get; init; }
|
|
|
|
/// <summary>
|
|
/// SBOM diff that caused this change, if any.
|
|
/// </summary>
|
|
[JsonPropertyName("caused_by_diff")]
|
|
public WhatIfSbomDiff? CausedByDiff { get; init; }
|
|
|
|
/// <summary>
|
|
/// Explanation for the change.
|
|
/// </summary>
|
|
[JsonPropertyName("explanation")]
|
|
public WhatIfExplanation? Explanation { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A decision in what-if simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfDecision(
|
|
[property: JsonPropertyName("status")] string Status,
|
|
[property: JsonPropertyName("severity")] string? Severity,
|
|
[property: JsonPropertyName("rule_name")] string? RuleName,
|
|
[property: JsonPropertyName("priority")] int? Priority,
|
|
[property: JsonPropertyName("exception_applied")] bool ExceptionApplied);
|
|
|
|
/// <summary>
|
|
/// Explanation for a what-if decision.
|
|
/// </summary>
|
|
public sealed record WhatIfExplanation
|
|
{
|
|
/// <summary>
|
|
/// Rules that matched.
|
|
/// </summary>
|
|
[JsonPropertyName("matched_rules")]
|
|
public ImmutableArray<string> MatchedRules { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
/// <summary>
|
|
/// Key factors in the decision.
|
|
/// </summary>
|
|
[JsonPropertyName("factors")]
|
|
public ImmutableArray<string> Factors { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
/// <summary>
|
|
/// VEX evidence considered.
|
|
/// </summary>
|
|
[JsonPropertyName("vex_evidence")]
|
|
public string? VexEvidence { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reachability state.
|
|
/// </summary>
|
|
[JsonPropertyName("reachability")]
|
|
public string? Reachability { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Summary of what-if simulation results.
|
|
/// </summary>
|
|
public sealed record WhatIfSummary
|
|
{
|
|
/// <summary>
|
|
/// Total components evaluated.
|
|
/// </summary>
|
|
[JsonPropertyName("total_evaluated")]
|
|
public int TotalEvaluated { get; init; }
|
|
|
|
/// <summary>
|
|
/// Components with changed decisions.
|
|
/// </summary>
|
|
[JsonPropertyName("total_changed")]
|
|
public int TotalChanged { get; init; }
|
|
|
|
/// <summary>
|
|
/// Components newly affected.
|
|
/// </summary>
|
|
[JsonPropertyName("newly_affected")]
|
|
public int NewlyAffected { get; init; }
|
|
|
|
/// <summary>
|
|
/// Components no longer affected.
|
|
/// </summary>
|
|
[JsonPropertyName("no_longer_affected")]
|
|
public int NoLongerAffected { get; init; }
|
|
|
|
/// <summary>
|
|
/// Status changes by type.
|
|
/// </summary>
|
|
[JsonPropertyName("status_changes")]
|
|
public required ImmutableDictionary<string, int> StatusChanges { get; init; }
|
|
|
|
/// <summary>
|
|
/// Severity changes by type (e.g., "low_to_high").
|
|
/// </summary>
|
|
[JsonPropertyName("severity_changes")]
|
|
public required ImmutableDictionary<string, int> SeverityChanges { get; init; }
|
|
|
|
/// <summary>
|
|
/// Impact assessment.
|
|
/// </summary>
|
|
[JsonPropertyName("impact")]
|
|
public required WhatIfImpact Impact { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Impact assessment from what-if simulation.
|
|
/// </summary>
|
|
public sealed record WhatIfImpact(
|
|
[property: JsonPropertyName("risk_delta")] string RiskDelta, // increased, decreased, unchanged
|
|
[property: JsonPropertyName("blocked_count_delta")] int BlockedCountDelta,
|
|
[property: JsonPropertyName("warning_count_delta")] int WarningCountDelta,
|
|
[property: JsonPropertyName("recommendation")] string? Recommendation);
|