doctor enhancements, setup, enhancements, ui functionality and design consolidation and , test projects fixes , product advisory attestation/rekor and delta verfications enhancements

This commit is contained in:
master
2026-01-19 09:02:59 +02:00
parent 8c4bf54aed
commit 17419ba7c4
809 changed files with 170738 additions and 12244 deletions

View File

@@ -99,9 +99,188 @@ public static class GreyQueueEndpoints
.WithSummary("Get grey queue summary statistics")
.WithDescription("Returns summary counts by status, reason, and performance metrics.");
// Sprint: SPRINT_20260118_018 (UQ-005) - New state transitions
group.MapPost("/{id:guid}/assign", AssignForReview)
.WithName("AssignGreyQueueEntry")
.WithSummary("Assign entry for review")
.WithDescription("Assigns an entry to a reviewer, transitioning to UnderReview state.");
group.MapPost("/{id:guid}/escalate", EscalateEntry)
.WithName("EscalateGreyQueueEntry")
.WithSummary("Escalate entry to security team")
.WithDescription("Escalates an entry to the security team, transitioning to Escalated state.");
group.MapPost("/{id:guid}/reject", RejectEntry)
.WithName("RejectGreyQueueEntry")
.WithSummary("Reject a grey queue entry")
.WithDescription("Marks an entry as rejected (invalid or not actionable).");
group.MapPost("/{id:guid}/reopen", ReopenEntry)
.WithName("ReopenGreyQueueEntry")
.WithSummary("Reopen a closed entry")
.WithDescription("Reopens a rejected, failed, or dismissed entry back to pending.");
group.MapGet("/{id:guid}/transitions", GetValidTransitions)
.WithName("GetValidTransitions")
.WithSummary("Get valid state transitions")
.WithDescription("Returns the valid next states for an entry based on current state.");
return routes;
}
// Sprint: SPRINT_20260118_018 (UQ-005) - Assign for review
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound, BadRequest<string>>> AssignForReview(
Guid id,
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
[FromBody] AssignForReviewRequest request,
IGreyQueueRepository repository = null!,
INotificationPublisher? notificationPublisher = null,
CancellationToken ct = default)
{
var entry = await repository.GetByIdAsync(tenantId, id, ct);
if (entry is null)
{
return TypedResults.NotFound();
}
try
{
GreyQueueStateMachine.ValidateUnderReviewTransition(entry.Status, request.Assignee);
}
catch (InvalidOperationException ex)
{
return TypedResults.BadRequest(ex.Message);
}
var updated = await repository.TransitionToUnderReviewAsync(
tenantId, id, request.Assignee, ct);
return TypedResults.Ok(MapToDto(updated));
}
// Sprint: SPRINT_20260118_018 (UQ-005) - Escalate to security team
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound, BadRequest<string>>> EscalateEntry(
Guid id,
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
[FromBody] EscalateRequest request,
IGreyQueueRepository repository = null!,
INotificationPublisher? notificationPublisher = null,
CancellationToken ct = default)
{
var entry = await repository.GetByIdAsync(tenantId, id, ct);
if (entry is null)
{
return TypedResults.NotFound();
}
try
{
GreyQueueStateMachine.ValidateTransition(entry.Status, GreyQueueStatus.Escalated);
}
catch (InvalidOperationException ex)
{
return TypedResults.BadRequest(ex.Message);
}
var updated = await repository.TransitionToEscalatedAsync(
tenantId, id, request.Reason, ct);
// Notify security team
if (notificationPublisher != null)
{
await notificationPublisher.PublishAsync(new EscalationNotification
{
EntryId = id,
BomRef = entry.BomRef,
Reason = request.Reason,
EscalatedAt = DateTimeOffset.UtcNow
}, ct);
}
return TypedResults.Ok(MapToDto(updated));
}
// Sprint: SPRINT_20260118_018 (UQ-005) - Reject entry
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound, BadRequest<string>>> RejectEntry(
Guid id,
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
[FromBody] RejectRequest request,
IGreyQueueRepository repository = null!,
CancellationToken ct = default)
{
var entry = await repository.GetByIdAsync(tenantId, id, ct);
if (entry is null)
{
return TypedResults.NotFound();
}
try
{
GreyQueueStateMachine.ValidateTransition(entry.Status, GreyQueueStatus.Rejected);
}
catch (InvalidOperationException ex)
{
return TypedResults.BadRequest(ex.Message);
}
var updated = await repository.TransitionToRejectedAsync(
tenantId, id, request.Reason, request.RejectedBy, ct);
return TypedResults.Ok(MapToDto(updated));
}
// Sprint: SPRINT_20260118_018 (UQ-005) - Reopen entry
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound, BadRequest<string>>> ReopenEntry(
Guid id,
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
[FromBody] ReopenRequest request,
IGreyQueueRepository repository = null!,
CancellationToken ct = default)
{
var entry = await repository.GetByIdAsync(tenantId, id, ct);
if (entry is null)
{
return TypedResults.NotFound();
}
try
{
GreyQueueStateMachine.ValidateTransition(entry.Status, GreyQueueStatus.Pending);
}
catch (InvalidOperationException ex)
{
return TypedResults.BadRequest(ex.Message);
}
var updated = await repository.ReopenAsync(tenantId, id, request.Reason, ct);
return TypedResults.Ok(MapToDto(updated));
}
// Sprint: SPRINT_20260118_018 (UQ-005) - Get valid transitions
private static async Task<Results<Ok<ValidTransitionsResponse>, NotFound>> GetValidTransitions(
Guid id,
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
IGreyQueueRepository repository = null!,
CancellationToken ct = default)
{
var entry = await repository.GetByIdAsync(tenantId, id, ct);
if (entry is null)
{
return TypedResults.NotFound();
}
var validStates = GreyQueueStateMachine.GetValidNextStates(entry.Status);
var response = new ValidTransitionsResponse
{
CurrentState = entry.Status.ToString(),
ValidNextStates = validStates.Select(s => s.ToString()).ToList()
};
return TypedResults.Ok(response);
}
// List entries with pagination
private static async Task<Ok<GreyQueueListResponse>> ListEntries(
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
@@ -580,3 +759,57 @@ public sealed record ExpireResultResponse
{
public required int ExpiredCount { get; init; }
}
// Sprint: SPRINT_20260118_018 (UQ-005) - New DTOs for state transitions
public sealed record AssignForReviewRequest
{
/// <summary>Required: The assignee for review.</summary>
public required string Assignee { get; init; }
/// <summary>Optional notes for the reviewer.</summary>
public string? Notes { get; init; }
}
public sealed record EscalateRequest
{
/// <summary>Reason for escalation.</summary>
public required string Reason { get; init; }
}
public sealed record RejectRequest
{
/// <summary>Reason for rejection.</summary>
public required string Reason { get; init; }
/// <summary>Who rejected the entry.</summary>
public required string RejectedBy { get; init; }
}
public sealed record ReopenRequest
{
/// <summary>Reason for reopening.</summary>
public required string Reason { get; init; }
}
public sealed record ValidTransitionsResponse
{
/// <summary>Current state of the entry.</summary>
public required string CurrentState { get; init; }
/// <summary>Valid next states from current state.</summary>
public required List<string> ValidNextStates { get; init; }
}
public sealed record EscalationNotification
{
public required Guid EntryId { get; init; }
public required string BomRef { get; init; }
public required string Reason { get; init; }
public DateTimeOffset EscalatedAt { get; init; }
}
// Interface for notification publishing
public interface INotificationPublisher
{
Task PublishAsync<T>(T notification, CancellationToken ct = default);
}