Files
git.stella-ops.org/src/Scanner/StellaOps.Scanner.WebService/Services/IHumanApprovalAttestationService.cs
StellaOps Bot 5fc469ad98 feat: Add VEX Status Chip component and integration tests for reachability drift detection
- Introduced `VexStatusChipComponent` to display VEX status with color coding and tooltips.
- Implemented integration tests for reachability drift detection, covering various scenarios including drift detection, determinism, and error handling.
- Enhanced `ScannerToSignalsReachabilityTests` with a null implementation of `ICallGraphSyncService` for better test isolation.
- Updated project references to include the new Reachability Drift library.
2025-12-20 01:26:42 +02:00

207 lines
6.3 KiB
C#

// -----------------------------------------------------------------------------
// IHumanApprovalAttestationService.cs
// Sprint: SPRINT_3801_0001_0004_human_approval_attestation (APPROVE-001)
// Description: Interface for creating human approval attestations.
// -----------------------------------------------------------------------------
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Domain;
namespace StellaOps.Scanner.WebService.Services;
/// <summary>
/// Creates DSSE attestations for human approval decisions.
/// </summary>
/// <remarks>
/// <para>
/// Human approvals record decisions made by authorized personnel to
/// accept, defer, reject, suppress, or escalate security findings.
/// </para>
/// <para>
/// These attestations have a 30-day default TTL to force periodic
/// re-review of risk acceptance decisions.
/// </para>
/// </remarks>
public interface IHumanApprovalAttestationService
{
/// <summary>
/// Creates a human approval attestation.
/// </summary>
/// <param name="input">The approval input parameters.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>
/// A <see cref="HumanApprovalAttestationResult"/> containing the
/// attestation statement and content-addressed attestation ID.
/// </returns>
Task<HumanApprovalAttestationResult> CreateAttestationAsync(
HumanApprovalAttestationInput input,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets an existing approval attestation.
/// </summary>
/// <param name="scanId">The scan ID.</param>
/// <param name="findingId">The finding ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The attestation result if found, null otherwise.</returns>
Task<HumanApprovalAttestationResult?> GetAttestationAsync(
ScanId scanId,
string findingId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets all active approval attestations for a scan.
/// </summary>
/// <param name="scanId">The scan ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>List of active approval attestations.</returns>
Task<IReadOnlyList<HumanApprovalAttestationResult>> GetApprovalsByScanAsync(
ScanId scanId,
CancellationToken cancellationToken = default);
/// <summary>
/// Revokes an existing approval attestation.
/// </summary>
/// <param name="scanId">The scan ID.</param>
/// <param name="findingId">The finding ID.</param>
/// <param name="revokedBy">Who revoked the approval.</param>
/// <param name="reason">Reason for revocation.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>True if revoked, false if not found.</returns>
Task<bool> RevokeApprovalAsync(
ScanId scanId,
string findingId,
string revokedBy,
string reason,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Input for creating a human approval attestation.
/// </summary>
public sealed record HumanApprovalAttestationInput
{
/// <summary>
/// The scan ID.
/// </summary>
public required ScanId ScanId { get; init; }
/// <summary>
/// The finding ID (e.g., CVE identifier).
/// </summary>
public required string FindingId { get; init; }
/// <summary>
/// The approval decision.
/// </summary>
public required ApprovalDecision Decision { get; init; }
/// <summary>
/// The approver's user ID.
/// </summary>
public required string ApproverUserId { get; init; }
/// <summary>
/// The approver's display name.
/// </summary>
public string? ApproverDisplayName { get; init; }
/// <summary>
/// The approver's role.
/// </summary>
public string? ApproverRole { get; init; }
/// <summary>
/// Justification for the decision.
/// </summary>
public required string Justification { get; init; }
/// <summary>
/// Optional custom TTL for the approval.
/// </summary>
public TimeSpan? ApprovalTtl { get; init; }
/// <summary>
/// Reference to the policy decision attestation.
/// </summary>
public string? PolicyDecisionRef { get; init; }
/// <summary>
/// Optional restrictions on the approval scope.
/// </summary>
public ApprovalRestrictions? Restrictions { get; init; }
/// <summary>
/// Optional prior approval being superseded.
/// </summary>
public string? Supersedes { get; init; }
/// <summary>
/// Optional metadata.
/// </summary>
public IDictionary<string, string>? Metadata { get; init; }
}
/// <summary>
/// Result of creating a human approval attestation.
/// </summary>
public sealed record HumanApprovalAttestationResult
{
/// <summary>
/// Whether the attestation was created successfully.
/// </summary>
public required bool Success { get; init; }
/// <summary>
/// The human approval statement.
/// </summary>
public HumanApprovalStatement? Statement { get; init; }
/// <summary>
/// The content-addressed attestation ID.
/// </summary>
public string? AttestationId { get; init; }
/// <summary>
/// The DSSE envelope (if signing is enabled).
/// </summary>
public string? DsseEnvelope { get; init; }
/// <summary>
/// Error message if creation failed.
/// </summary>
public string? Error { get; init; }
/// <summary>
/// Whether the approval has been revoked.
/// </summary>
public bool IsRevoked { get; init; }
/// <summary>
/// Creates a successful result.
/// </summary>
public static HumanApprovalAttestationResult Succeeded(
HumanApprovalStatement statement,
string attestationId,
string? dsseEnvelope = null)
=> new()
{
Success = true,
Statement = statement,
AttestationId = attestationId,
DsseEnvelope = dsseEnvelope
};
/// <summary>
/// Creates a failed result.
/// </summary>
public static HumanApprovalAttestationResult Failed(string error)
=> new()
{
Success = false,
Error = error
};
}