notify doctors work, audit work, new product advisory sprints
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// GreyQueueEndpoints.cs
|
||||
// Description: Minimal API endpoints for Grey Queue management.
|
||||
// Implements signed, replayable evidence pipeline for ambiguous unknowns.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Unknowns.Core.Models;
|
||||
using StellaOps.Unknowns.Core.Repositories;
|
||||
|
||||
namespace StellaOps.Unknowns.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal API endpoints for Grey Queue service.
|
||||
/// </summary>
|
||||
public static class GreyQueueEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps all Grey Queue endpoints.
|
||||
/// </summary>
|
||||
public static IEndpointRouteBuilder MapGreyQueueEndpoints(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
var group = routes.MapGroup("/api/grey-queue")
|
||||
.WithTags("GreyQueue")
|
||||
.WithOpenApi();
|
||||
|
||||
// List and query
|
||||
group.MapGet("/", ListEntries)
|
||||
.WithName("ListGreyQueueEntries")
|
||||
.WithSummary("List grey queue entries with pagination")
|
||||
.WithDescription("Returns paginated list of grey queue entries. Supports filtering by status and reason.");
|
||||
|
||||
group.MapGet("/{id:guid}", GetEntryById)
|
||||
.WithName("GetGreyQueueEntry")
|
||||
.WithSummary("Get grey queue entry by ID")
|
||||
.WithDescription("Returns a single grey queue entry with full evidence bundle.");
|
||||
|
||||
group.MapGet("/by-unknown/{unknownId:guid}", GetByUnknownId)
|
||||
.WithName("GetGreyQueueByUnknownId")
|
||||
.WithSummary("Get grey queue entry by unknown ID")
|
||||
.WithDescription("Returns the grey queue entry for a specific unknown.");
|
||||
|
||||
group.MapGet("/ready", GetReadyForProcessing)
|
||||
.WithName("GetReadyForProcessing")
|
||||
.WithSummary("Get entries ready for processing")
|
||||
.WithDescription("Returns entries that are ready to be processed (pending, not exhausted, past next processing time).");
|
||||
|
||||
// Triggers
|
||||
group.MapGet("/triggers/feed/{feedId}", GetByFeedTrigger)
|
||||
.WithName("GetByFeedTrigger")
|
||||
.WithSummary("Get entries triggered by feed update")
|
||||
.WithDescription("Returns entries that should be reprocessed due to a feed update.");
|
||||
|
||||
group.MapGet("/triggers/tool/{toolId}", GetByToolTrigger)
|
||||
.WithName("GetByToolTrigger")
|
||||
.WithSummary("Get entries triggered by tool update")
|
||||
.WithDescription("Returns entries that should be reprocessed due to a tool update.");
|
||||
|
||||
group.MapGet("/triggers/cve/{cveId}", GetByCveTrigger)
|
||||
.WithName("GetByCveTrigger")
|
||||
.WithSummary("Get entries triggered by CVE update")
|
||||
.WithDescription("Returns entries that should be reprocessed due to a CVE update.");
|
||||
|
||||
// Actions
|
||||
group.MapPost("/", EnqueueEntry)
|
||||
.WithName("EnqueueGreyQueueEntry")
|
||||
.WithSummary("Enqueue a new grey queue entry")
|
||||
.WithDescription("Creates a new grey queue entry with evidence bundle and trigger conditions.");
|
||||
|
||||
group.MapPost("/{id:guid}/process", StartProcessing)
|
||||
.WithName("StartGreyQueueProcessing")
|
||||
.WithSummary("Mark entry as processing")
|
||||
.WithDescription("Marks an entry as currently being processed.");
|
||||
|
||||
group.MapPost("/{id:guid}/result", RecordResult)
|
||||
.WithName("RecordGreyQueueResult")
|
||||
.WithSummary("Record processing result")
|
||||
.WithDescription("Records the result of a processing attempt.");
|
||||
|
||||
group.MapPost("/{id:guid}/resolve", ResolveEntry)
|
||||
.WithName("ResolveGreyQueueEntry")
|
||||
.WithSummary("Resolve a grey queue entry")
|
||||
.WithDescription("Marks an entry as resolved with resolution type and reference.");
|
||||
|
||||
group.MapPost("/{id:guid}/dismiss", DismissEntry)
|
||||
.WithName("DismissGreyQueueEntry")
|
||||
.WithSummary("Dismiss a grey queue entry")
|
||||
.WithDescription("Manually dismisses an entry from the queue.");
|
||||
|
||||
// Maintenance
|
||||
group.MapPost("/expire", ExpireOldEntries)
|
||||
.WithName("ExpireGreyQueueEntries")
|
||||
.WithSummary("Expire old entries")
|
||||
.WithDescription("Expires entries that have exceeded their TTL.");
|
||||
|
||||
// Statistics
|
||||
group.MapGet("/summary", GetSummary)
|
||||
.WithName("GetGreyQueueSummary")
|
||||
.WithSummary("Get grey queue summary statistics")
|
||||
.WithDescription("Returns summary counts by status, reason, and performance metrics.");
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
// List entries with pagination
|
||||
private static async Task<Ok<GreyQueueListResponse>> ListEntries(
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 50,
|
||||
[FromQuery] GreyQueueStatus? status = null,
|
||||
[FromQuery] GreyQueueReason? reason = null,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
IReadOnlyList<GreyQueueEntry> entries;
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
entries = await repository.GetByStatusAsync(tenantId, status.Value, take, skip, ct);
|
||||
}
|
||||
else if (reason.HasValue)
|
||||
{
|
||||
entries = await repository.GetByReasonAsync(tenantId, reason.Value, take, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
entries = await repository.GetByStatusAsync(tenantId, GreyQueueStatus.Pending, take, skip, ct);
|
||||
}
|
||||
|
||||
var total = await repository.CountPendingAsync(tenantId, ct);
|
||||
|
||||
var response = new GreyQueueListResponse
|
||||
{
|
||||
Items = entries.Select(MapToDto).ToList(),
|
||||
Total = total,
|
||||
Skip = skip,
|
||||
Take = take
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Get entry by ID
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> GetEntryById(
|
||||
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();
|
||||
}
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
|
||||
// Get by unknown ID
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> GetByUnknownId(
|
||||
Guid unknownId,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var entry = await repository.GetByUnknownIdAsync(tenantId, unknownId, ct);
|
||||
if (entry is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
|
||||
// Get ready for processing
|
||||
private static async Task<Ok<GreyQueueListResponse>> GetReadyForProcessing(
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromQuery] int limit = 50,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var entries = await repository.GetReadyForProcessingAsync(tenantId, limit, ct);
|
||||
|
||||
var response = new GreyQueueListResponse
|
||||
{
|
||||
Items = entries.Select(MapToDto).ToList(),
|
||||
Total = entries.Count,
|
||||
Skip = 0,
|
||||
Take = limit
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Get by feed trigger
|
||||
private static async Task<Ok<GreyQueueListResponse>> GetByFeedTrigger(
|
||||
string feedId,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromQuery] string? version = null,
|
||||
[FromQuery] int limit = 50,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var entries = await repository.GetByFeedTriggerAsync(tenantId, feedId, version, limit, ct);
|
||||
|
||||
var response = new GreyQueueListResponse
|
||||
{
|
||||
Items = entries.Select(MapToDto).ToList(),
|
||||
Total = entries.Count,
|
||||
Skip = 0,
|
||||
Take = limit
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Get by tool trigger
|
||||
private static async Task<Ok<GreyQueueListResponse>> GetByToolTrigger(
|
||||
string toolId,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromQuery] string? version = null,
|
||||
[FromQuery] int limit = 50,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var entries = await repository.GetByToolTriggerAsync(tenantId, toolId, version, limit, ct);
|
||||
|
||||
var response = new GreyQueueListResponse
|
||||
{
|
||||
Items = entries.Select(MapToDto).ToList(),
|
||||
Total = entries.Count,
|
||||
Skip = 0,
|
||||
Take = limit
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Get by CVE trigger
|
||||
private static async Task<Ok<GreyQueueListResponse>> GetByCveTrigger(
|
||||
string cveId,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromQuery] int limit = 50,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var entries = await repository.GetByCveTriggerAsync(tenantId, cveId, limit, ct);
|
||||
|
||||
var response = new GreyQueueListResponse
|
||||
{
|
||||
Items = entries.Select(MapToDto).ToList(),
|
||||
Total = entries.Count,
|
||||
Skip = 0,
|
||||
Take = limit
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Enqueue new entry
|
||||
private static async Task<Created<GreyQueueEntryDto>> EnqueueEntry(
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromBody] EnqueueGreyQueueRequest request,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var evidence = request.Evidence is not null ? new GreyQueueEvidenceBundle
|
||||
{
|
||||
SbomSliceJson = request.Evidence.SbomSliceJson,
|
||||
AdvisorySnippetJson = request.Evidence.AdvisorySnippetJson,
|
||||
VexEvidenceJson = request.Evidence.VexEvidenceJson,
|
||||
DiffTracesJson = request.Evidence.DiffTracesJson,
|
||||
ReachabilityEvidenceJson = request.Evidence.ReachabilityEvidenceJson
|
||||
} : null;
|
||||
|
||||
var triggers = request.Triggers is not null ? new GreyQueueTriggers
|
||||
{
|
||||
Feeds = request.Triggers.Feeds?.Select(f => new FeedTrigger(f.FeedId, f.MinVersion)).ToList() ?? [],
|
||||
Tools = request.Triggers.Tools?.Select(t => new ToolTrigger(t.ToolId, t.MinVersion)).ToList() ?? [],
|
||||
CveIds = request.Triggers.CveIds ?? [],
|
||||
PurlPatterns = request.Triggers.PurlPatterns ?? []
|
||||
} : null;
|
||||
|
||||
var entry = await repository.EnqueueAsync(
|
||||
tenantId,
|
||||
request.UnknownId,
|
||||
request.Reason,
|
||||
request.ReasonDetail,
|
||||
evidence,
|
||||
triggers,
|
||||
request.Priority,
|
||||
request.CreatedBy,
|
||||
request.CorrelationId,
|
||||
ct);
|
||||
|
||||
return TypedResults.Created($"/api/grey-queue/{entry.Id}", MapToDto(entry));
|
||||
}
|
||||
|
||||
// Start processing
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> StartProcessing(
|
||||
Guid id,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = await repository.StartProcessingAsync(tenantId, id, ct);
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Record result
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> RecordResult(
|
||||
Guid id,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromBody] RecordResultRequest request,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = await repository.RecordProcessingResultAsync(
|
||||
tenantId,
|
||||
id,
|
||||
request.Success,
|
||||
request.Result,
|
||||
request.NextProcessingAt,
|
||||
ct);
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve entry
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> ResolveEntry(
|
||||
Guid id,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromBody] ResolveEntryRequest request,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = await repository.ResolveAsync(
|
||||
tenantId,
|
||||
id,
|
||||
request.Resolution,
|
||||
request.ResolutionRef,
|
||||
ct);
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss entry
|
||||
private static async Task<Results<Ok<GreyQueueEntryDto>, NotFound>> DismissEntry(
|
||||
Guid id,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
[FromBody] DismissEntryRequest request,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = await repository.DismissAsync(
|
||||
tenantId,
|
||||
id,
|
||||
request.DismissedBy,
|
||||
request.Reason,
|
||||
ct);
|
||||
return TypedResults.Ok(MapToDto(entry));
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
// Expire old entries
|
||||
private static async Task<Ok<ExpireResultResponse>> ExpireOldEntries(
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var count = await repository.ExpireOldEntriesAsync(tenantId, ct);
|
||||
return TypedResults.Ok(new ExpireResultResponse { ExpiredCount = count });
|
||||
}
|
||||
|
||||
// Get summary
|
||||
private static async Task<Ok<GreyQueueSummaryDto>> GetSummary(
|
||||
[FromHeader(Name = "X-Tenant-Id")] string tenantId,
|
||||
IGreyQueueRepository repository = null!,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var summary = await repository.GetSummaryAsync(tenantId, ct);
|
||||
|
||||
var response = new GreyQueueSummaryDto
|
||||
{
|
||||
Total = summary.Total,
|
||||
Pending = summary.Pending,
|
||||
Processing = summary.Processing,
|
||||
Retrying = summary.Retrying,
|
||||
Resolved = summary.Resolved,
|
||||
Failed = summary.Failed,
|
||||
Expired = summary.Expired,
|
||||
Dismissed = summary.Dismissed,
|
||||
ByReason = summary.ByReason,
|
||||
AvgAttemptsToResolve = summary.AvgAttemptsToResolve,
|
||||
AvgHoursToResolve = summary.AvgHoursToResolve,
|
||||
OldestPendingHours = summary.OldestPendingHours
|
||||
};
|
||||
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
|
||||
// Mapping helpers
|
||||
private static GreyQueueEntryDto MapToDto(GreyQueueEntry e) => new()
|
||||
{
|
||||
Id = e.Id,
|
||||
TenantId = e.TenantId,
|
||||
UnknownId = e.UnknownId,
|
||||
Fingerprint = e.Fingerprint,
|
||||
Status = e.Status.ToString(),
|
||||
Priority = e.Priority,
|
||||
Reason = e.Reason.ToString(),
|
||||
ReasonDetail = e.ReasonDetail,
|
||||
HasSbomSlice = e.SbomSlice is not null,
|
||||
HasAdvisorySnippet = e.AdvisorySnippet is not null,
|
||||
HasVexEvidence = e.VexEvidence is not null,
|
||||
HasDiffTraces = e.DiffTraces is not null,
|
||||
HasReachabilityEvidence = e.ReachabilityEvidence is not null,
|
||||
ProcessingAttempts = e.ProcessingAttempts,
|
||||
MaxAttempts = e.MaxAttempts,
|
||||
LastProcessedAt = e.LastProcessedAt,
|
||||
LastProcessingResult = e.LastProcessingResult,
|
||||
NextProcessingAt = e.NextProcessingAt,
|
||||
TriggerCveIds = e.TriggerOnCveUpdate.ToList(),
|
||||
TriggerPurlPatterns = e.TriggerOnPurlMatch.ToList(),
|
||||
CreatedAt = e.CreatedAt,
|
||||
UpdatedAt = e.UpdatedAt,
|
||||
ExpiresAt = e.ExpiresAt,
|
||||
ResolvedAt = e.ResolvedAt,
|
||||
Resolution = e.Resolution?.ToString(),
|
||||
ResolutionRef = e.ResolutionRef,
|
||||
IsPending = e.IsPending,
|
||||
IsExhausted = e.IsExhausted,
|
||||
IsReadyForProcessing = e.IsReadyForProcessing
|
||||
};
|
||||
}
|
||||
|
||||
// DTOs
|
||||
|
||||
public sealed record GreyQueueListResponse
|
||||
{
|
||||
public required IReadOnlyList<GreyQueueEntryDto> Items { get; init; }
|
||||
public required long Total { get; init; }
|
||||
public required int Skip { get; init; }
|
||||
public required int Take { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GreyQueueEntryDto
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required string TenantId { get; init; }
|
||||
public required Guid UnknownId { get; init; }
|
||||
public required string Fingerprint { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required int Priority { get; init; }
|
||||
public required string Reason { get; init; }
|
||||
public string? ReasonDetail { get; init; }
|
||||
public required bool HasSbomSlice { get; init; }
|
||||
public required bool HasAdvisorySnippet { get; init; }
|
||||
public required bool HasVexEvidence { get; init; }
|
||||
public required bool HasDiffTraces { get; init; }
|
||||
public required bool HasReachabilityEvidence { get; init; }
|
||||
public required int ProcessingAttempts { get; init; }
|
||||
public required int MaxAttempts { get; init; }
|
||||
public DateTimeOffset? LastProcessedAt { get; init; }
|
||||
public string? LastProcessingResult { get; init; }
|
||||
public DateTimeOffset? NextProcessingAt { get; init; }
|
||||
public required IReadOnlyList<string> TriggerCveIds { get; init; }
|
||||
public required IReadOnlyList<string> TriggerPurlPatterns { get; init; }
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
public required DateTimeOffset UpdatedAt { get; init; }
|
||||
public DateTimeOffset? ExpiresAt { get; init; }
|
||||
public DateTimeOffset? ResolvedAt { get; init; }
|
||||
public string? Resolution { get; init; }
|
||||
public string? ResolutionRef { get; init; }
|
||||
public required bool IsPending { get; init; }
|
||||
public required bool IsExhausted { get; init; }
|
||||
public required bool IsReadyForProcessing { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GreyQueueSummaryDto
|
||||
{
|
||||
public required long Total { get; init; }
|
||||
public required long Pending { get; init; }
|
||||
public required long Processing { get; init; }
|
||||
public required long Retrying { get; init; }
|
||||
public required long Resolved { get; init; }
|
||||
public required long Failed { get; init; }
|
||||
public required long Expired { get; init; }
|
||||
public required long Dismissed { get; init; }
|
||||
public required IReadOnlyDictionary<string, long> ByReason { get; init; }
|
||||
public double AvgAttemptsToResolve { get; init; }
|
||||
public double AvgHoursToResolve { get; init; }
|
||||
public double OldestPendingHours { get; init; }
|
||||
}
|
||||
|
||||
public sealed record EnqueueGreyQueueRequest
|
||||
{
|
||||
public required Guid UnknownId { get; init; }
|
||||
public required GreyQueueReason Reason { get; init; }
|
||||
public string? ReasonDetail { get; init; }
|
||||
public EvidenceBundleDto? Evidence { get; init; }
|
||||
public TriggersDto? Triggers { get; init; }
|
||||
public int Priority { get; init; } = 100;
|
||||
public required string CreatedBy { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
}
|
||||
|
||||
public sealed record EvidenceBundleDto
|
||||
{
|
||||
public string? SbomSliceJson { get; init; }
|
||||
public string? AdvisorySnippetJson { get; init; }
|
||||
public string? VexEvidenceJson { get; init; }
|
||||
public string? DiffTracesJson { get; init; }
|
||||
public string? ReachabilityEvidenceJson { get; init; }
|
||||
}
|
||||
|
||||
public sealed record TriggersDto
|
||||
{
|
||||
public IReadOnlyList<FeedTriggerDto>? Feeds { get; init; }
|
||||
public IReadOnlyList<ToolTriggerDto>? Tools { get; init; }
|
||||
public IReadOnlyList<string>? CveIds { get; init; }
|
||||
public IReadOnlyList<string>? PurlPatterns { get; init; }
|
||||
}
|
||||
|
||||
public sealed record FeedTriggerDto
|
||||
{
|
||||
public required string FeedId { get; init; }
|
||||
public string? MinVersion { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ToolTriggerDto
|
||||
{
|
||||
public required string ToolId { get; init; }
|
||||
public string? MinVersion { get; init; }
|
||||
}
|
||||
|
||||
public sealed record RecordResultRequest
|
||||
{
|
||||
public required bool Success { get; init; }
|
||||
public required string Result { get; init; }
|
||||
public DateTimeOffset? NextProcessingAt { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ResolveEntryRequest
|
||||
{
|
||||
public required GreyQueueResolution Resolution { get; init; }
|
||||
public string? ResolutionRef { get; init; }
|
||||
}
|
||||
|
||||
public sealed record DismissEntryRequest
|
||||
{
|
||||
public required string DismissedBy { get; init; }
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ExpireResultResponse
|
||||
{
|
||||
public required int ExpiredCount { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user