feat: Implement IsolatedReplayContext for deterministic audit replay

- Added IsolatedReplayContext class to provide an isolated environment for replaying audit bundles without external calls.
- Introduced methods for initializing the context, verifying input digests, and extracting inputs for policy evaluation.
- Created supporting interfaces and options for context configuration.

feat: Create ReplayExecutor for executing policy re-evaluation and verdict comparison

- Developed ReplayExecutor class to handle the execution of replay processes, including input verification and verdict comparison.
- Implemented detailed drift detection and error handling during replay execution.
- Added interfaces for policy evaluation and replay execution options.

feat: Add ScanSnapshotFetcher for fetching scan data and snapshots

- Introduced ScanSnapshotFetcher class to retrieve necessary scan data and snapshots for audit bundle creation.
- Implemented methods to fetch scan metadata, advisory feeds, policy snapshots, and VEX statements.
- Created supporting interfaces for scan data, feed snapshots, and policy snapshots.
This commit is contained in:
StellaOps Bot
2025-12-23 07:46:34 +02:00
parent e47627cfff
commit 7e384ab610
77 changed files with 153346 additions and 209 deletions

View File

@@ -0,0 +1,292 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4100_0004_0001 - Security State Delta & Verdict
// Task: T6 - Add Delta API endpoints
using System.ComponentModel.DataAnnotations;
using StellaOps.Policy.Deltas;
namespace StellaOps.Policy.Gateway.Contracts;
/// <summary>
/// Request to compute a security state delta.
/// </summary>
public sealed record ComputeDeltaRequest
{
/// <summary>
/// Artifact digest (required).
/// </summary>
[Required]
public required string ArtifactDigest { get; init; }
/// <summary>
/// Artifact name (optional).
/// </summary>
public string? ArtifactName { get; init; }
/// <summary>
/// Artifact tag (optional).
/// </summary>
public string? ArtifactTag { get; init; }
/// <summary>
/// Target snapshot ID (required).
/// </summary>
[Required]
public required string TargetSnapshotId { get; init; }
/// <summary>
/// Explicit baseline snapshot ID (optional).
/// If not provided, baseline selection strategy is used.
/// </summary>
public string? BaselineSnapshotId { get; init; }
/// <summary>
/// Baseline selection strategy (optional, defaults to LastApproved).
/// Values: PreviousBuild, LastApproved, ProductionDeployed, BranchBase
/// </summary>
public string? BaselineStrategy { get; init; }
}
/// <summary>
/// Response from computing a security state delta.
/// </summary>
public sealed record ComputeDeltaResponse
{
/// <summary>
/// The computed delta ID.
/// </summary>
public required string DeltaId { get; init; }
/// <summary>
/// Baseline snapshot ID used.
/// </summary>
public required string BaselineSnapshotId { get; init; }
/// <summary>
/// Target snapshot ID.
/// </summary>
public required string TargetSnapshotId { get; init; }
/// <summary>
/// When the delta was computed.
/// </summary>
public required DateTimeOffset ComputedAt { get; init; }
/// <summary>
/// Summary statistics.
/// </summary>
public required DeltaSummaryDto Summary { get; init; }
/// <summary>
/// Number of drivers identified.
/// </summary>
public int DriverCount { get; init; }
}
/// <summary>
/// Summary statistics DTO.
/// </summary>
public sealed record DeltaSummaryDto
{
public int TotalChanges { get; init; }
public int RiskIncreasing { get; init; }
public int RiskDecreasing { get; init; }
public int Neutral { get; init; }
public decimal RiskScore { get; init; }
public required string RiskDirection { get; init; }
public static DeltaSummaryDto FromModel(DeltaSummary summary) => new()
{
TotalChanges = summary.TotalChanges,
RiskIncreasing = summary.RiskIncreasing,
RiskDecreasing = summary.RiskDecreasing,
Neutral = summary.Neutral,
RiskScore = summary.RiskScore,
RiskDirection = summary.RiskDirection
};
}
/// <summary>
/// Full delta response DTO.
/// </summary>
public sealed record DeltaResponse
{
public required string DeltaId { get; init; }
public required DateTimeOffset ComputedAt { get; init; }
public required string BaselineSnapshotId { get; init; }
public required string TargetSnapshotId { get; init; }
public required ArtifactRefDto Artifact { get; init; }
public required SbomDeltaDto Sbom { get; init; }
public required ReachabilityDeltaDto Reachability { get; init; }
public required VexDeltaDto Vex { get; init; }
public required PolicyDeltaDto Policy { get; init; }
public required UnknownsDeltaDto Unknowns { get; init; }
public required IReadOnlyList<DeltaDriverDto> Drivers { get; init; }
public required DeltaSummaryDto Summary { get; init; }
public static DeltaResponse FromModel(SecurityStateDelta delta) => new()
{
DeltaId = delta.DeltaId,
ComputedAt = delta.ComputedAt,
BaselineSnapshotId = delta.BaselineSnapshotId,
TargetSnapshotId = delta.TargetSnapshotId,
Artifact = ArtifactRefDto.FromModel(delta.Artifact),
Sbom = SbomDeltaDto.FromModel(delta.Sbom),
Reachability = ReachabilityDeltaDto.FromModel(delta.Reachability),
Vex = VexDeltaDto.FromModel(delta.Vex),
Policy = PolicyDeltaDto.FromModel(delta.Policy),
Unknowns = UnknownsDeltaDto.FromModel(delta.Unknowns),
Drivers = delta.Drivers.Select(DeltaDriverDto.FromModel).ToList(),
Summary = DeltaSummaryDto.FromModel(delta.Summary)
};
}
public sealed record ArtifactRefDto
{
public required string Digest { get; init; }
public string? Name { get; init; }
public string? Tag { get; init; }
public static ArtifactRefDto FromModel(ArtifactRef artifact) => new()
{
Digest = artifact.Digest,
Name = artifact.Name,
Tag = artifact.Tag
};
}
public sealed record SbomDeltaDto
{
public int PackagesAdded { get; init; }
public int PackagesRemoved { get; init; }
public int PackagesModified { get; init; }
public static SbomDeltaDto FromModel(SbomDelta sbom) => new()
{
PackagesAdded = sbom.PackagesAdded,
PackagesRemoved = sbom.PackagesRemoved,
PackagesModified = sbom.PackagesModified
};
}
public sealed record ReachabilityDeltaDto
{
public int NewReachable { get; init; }
public int NewUnreachable { get; init; }
public int ChangedReachability { get; init; }
public static ReachabilityDeltaDto FromModel(ReachabilityDelta reach) => new()
{
NewReachable = reach.NewReachable,
NewUnreachable = reach.NewUnreachable,
ChangedReachability = reach.ChangedReachability
};
}
public sealed record VexDeltaDto
{
public int NewVexStatements { get; init; }
public int RevokedVexStatements { get; init; }
public int CoverageIncrease { get; init; }
public int CoverageDecrease { get; init; }
public static VexDeltaDto FromModel(VexDelta vex) => new()
{
NewVexStatements = vex.NewVexStatements,
RevokedVexStatements = vex.RevokedVexStatements,
CoverageIncrease = vex.CoverageIncrease,
CoverageDecrease = vex.CoverageDecrease
};
}
public sealed record PolicyDeltaDto
{
public int NewViolations { get; init; }
public int ResolvedViolations { get; init; }
public int PolicyVersionChanged { get; init; }
public static PolicyDeltaDto FromModel(PolicyDelta policy) => new()
{
NewViolations = policy.NewViolations,
ResolvedViolations = policy.ResolvedViolations,
PolicyVersionChanged = policy.PolicyVersionChanged
};
}
public sealed record UnknownsDeltaDto
{
public int NewUnknowns { get; init; }
public int ResolvedUnknowns { get; init; }
public int TotalBaselineUnknowns { get; init; }
public int TotalTargetUnknowns { get; init; }
public static UnknownsDeltaDto FromModel(UnknownsDelta unknowns) => new()
{
NewUnknowns = unknowns.NewUnknowns,
ResolvedUnknowns = unknowns.ResolvedUnknowns,
TotalBaselineUnknowns = unknowns.TotalBaselineUnknowns,
TotalTargetUnknowns = unknowns.TotalTargetUnknowns
};
}
public sealed record DeltaDriverDto
{
public required string Type { get; init; }
public required string Severity { get; init; }
public required string Description { get; init; }
public string? CveId { get; init; }
public string? Purl { get; init; }
public static DeltaDriverDto FromModel(DeltaDriver driver) => new()
{
Type = driver.Type,
Severity = driver.Severity.ToString().ToLowerInvariant(),
Description = driver.Description,
CveId = driver.CveId,
Purl = driver.Purl
};
}
/// <summary>
/// Request to evaluate a delta verdict.
/// </summary>
public sealed record EvaluateDeltaRequest
{
/// <summary>
/// Exception IDs to apply.
/// </summary>
public IReadOnlyList<string>? Exceptions { get; init; }
}
/// <summary>
/// Delta verdict response DTO.
/// </summary>
public sealed record DeltaVerdictResponse
{
public required string VerdictId { get; init; }
public required string DeltaId { get; init; }
public required DateTimeOffset EvaluatedAt { get; init; }
public required string Status { get; init; }
public required string RecommendedGate { get; init; }
public int RiskPoints { get; init; }
public required IReadOnlyList<DeltaDriverDto> BlockingDrivers { get; init; }
public required IReadOnlyList<DeltaDriverDto> WarningDrivers { get; init; }
public required IReadOnlyList<string> AppliedExceptions { get; init; }
public string? Explanation { get; init; }
public required IReadOnlyList<string> Recommendations { get; init; }
public static DeltaVerdictResponse FromModel(DeltaVerdict verdict) => new()
{
VerdictId = verdict.VerdictId,
DeltaId = verdict.DeltaId,
EvaluatedAt = verdict.EvaluatedAt,
Status = verdict.Status.ToString().ToLowerInvariant(),
RecommendedGate = verdict.RecommendedGate.ToString(),
RiskPoints = verdict.RiskPoints,
BlockingDrivers = verdict.BlockingDrivers.Select(DeltaDriverDto.FromModel).ToList(),
WarningDrivers = verdict.WarningDrivers.Select(DeltaDriverDto.FromModel).ToList(),
AppliedExceptions = verdict.AppliedExceptions.ToList(),
Explanation = verdict.Explanation,
Recommendations = verdict.Recommendations.ToList()
};
}