release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,651 @@
|
||||
// <copyright file="EvidenceThreadEndpoints.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Platform.WebService.Services;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Export;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Models;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Services;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Transcript;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// REST endpoints for Evidence Thread operations.
|
||||
/// </summary>
|
||||
public static class EvidenceThreadEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps Evidence Thread API endpoints.
|
||||
/// </summary>
|
||||
public static IEndpointRouteBuilder MapEvidenceThreadEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var evidence = app.MapGroup("/api/v1/evidence")
|
||||
.WithTags("Evidence Thread");
|
||||
|
||||
// GET /api/v1/evidence/{artifactDigest} - Get evidence thread for artifact
|
||||
evidence.MapGet("/{artifactDigest}", GetEvidenceThread)
|
||||
.WithName("GetEvidenceThread")
|
||||
.WithSummary("Get evidence thread for an artifact")
|
||||
.WithDescription("Retrieves the full evidence thread graph for an artifact by its digest.")
|
||||
.Produces<EvidenceThreadResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
// POST /api/v1/evidence/{artifactDigest}/export - Export thread as DSSE bundle
|
||||
evidence.MapPost("/{artifactDigest}/export", ExportEvidenceThread)
|
||||
.WithName("ExportEvidenceThread")
|
||||
.WithSummary("Export evidence thread as DSSE bundle")
|
||||
.WithDescription("Exports the evidence thread as a signed DSSE envelope for offline verification.")
|
||||
.Produces<EvidenceExportResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
// POST /api/v1/evidence/{artifactDigest}/transcript - Generate transcript
|
||||
evidence.MapPost("/{artifactDigest}/transcript", GenerateTranscript)
|
||||
.WithName("GenerateEvidenceTranscript")
|
||||
.WithSummary("Generate natural language transcript")
|
||||
.WithDescription("Generates a natural language transcript explaining the evidence thread.")
|
||||
.Produces<EvidenceTranscriptResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
// GET /api/v1/evidence/{artifactDigest}/nodes - Get evidence nodes
|
||||
evidence.MapGet("/{artifactDigest}/nodes", GetEvidenceNodes)
|
||||
.WithName("GetEvidenceNodes")
|
||||
.WithSummary("Get evidence nodes for an artifact")
|
||||
.WithDescription("Retrieves all evidence nodes in the thread.")
|
||||
.Produces<EvidenceNodeListResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
// GET /api/v1/evidence/{artifactDigest}/links - Get evidence links
|
||||
evidence.MapGet("/{artifactDigest}/links", GetEvidenceLinks)
|
||||
.WithName("GetEvidenceLinks")
|
||||
.WithSummary("Get evidence links for an artifact")
|
||||
.WithDescription("Retrieves all evidence links in the thread.")
|
||||
.Produces<EvidenceLinkListResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
// POST /api/v1/evidence/{artifactDigest}/collect - Trigger evidence collection
|
||||
evidence.MapPost("/{artifactDigest}/collect", CollectEvidence)
|
||||
.WithName("CollectEvidence")
|
||||
.WithSummary("Collect evidence for an artifact")
|
||||
.WithDescription("Triggers collection of all available evidence for an artifact.")
|
||||
.Produces<EvidenceCollectionResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetEvidenceThread(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService service,
|
||||
string artifactDigest,
|
||||
[FromQuery] bool? includeContent,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
var options = new EvidenceThreadOptions
|
||||
{
|
||||
IncludeContent = includeContent ?? true
|
||||
};
|
||||
|
||||
var graph = await service.GetThreadGraphAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
options,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
if (graph is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "thread_not_found", artifactDigest });
|
||||
}
|
||||
|
||||
return Results.Ok(new EvidenceThreadResponse
|
||||
{
|
||||
ThreadId = graph.Thread.Id,
|
||||
TenantId = graph.Thread.TenantId,
|
||||
ArtifactDigest = graph.Thread.ArtifactDigest,
|
||||
ArtifactName = graph.Thread.ArtifactName,
|
||||
Status = graph.Thread.Status.ToString(),
|
||||
Verdict = graph.Thread.Verdict?.ToString(),
|
||||
RiskScore = graph.Thread.RiskScore,
|
||||
ReachabilityMode = graph.Thread.ReachabilityMode?.ToString(),
|
||||
NodeCount = graph.Nodes.Length,
|
||||
LinkCount = graph.Links.Length,
|
||||
CreatedAt = graph.Thread.CreatedAt,
|
||||
UpdatedAt = graph.Thread.UpdatedAt
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> ExportEvidenceThread(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService threadService,
|
||||
IDsseThreadExporter exporter,
|
||||
string artifactDigest,
|
||||
[FromBody] EvidenceExportRequest? request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
var graph = await threadService.GetThreadGraphAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
null,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
if (graph is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "thread_not_found", artifactDigest });
|
||||
}
|
||||
|
||||
var options = new ThreadExportOptions
|
||||
{
|
||||
Format = ParseExportFormat(request?.Format),
|
||||
IncludeTranscript = request?.IncludeTranscript ?? true,
|
||||
Sign = request?.Sign ?? true,
|
||||
SigningKeyId = request?.SigningKeyId
|
||||
};
|
||||
|
||||
var result = await exporter.ExportAsync(graph, options, ct).ConfigureAwait(false);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return Results.BadRequest(new
|
||||
{
|
||||
error = result.ErrorCode,
|
||||
message = result.ErrorMessage
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(new EvidenceExportResponse
|
||||
{
|
||||
ThreadId = result.ThreadId,
|
||||
Format = result.Format.ToString(),
|
||||
ContentDigest = result.ContentDigest,
|
||||
SizeBytes = result.SizeBytes,
|
||||
SigningKeyId = result.SigningKeyId,
|
||||
ExportedAt = result.ExportedAt,
|
||||
DurationMs = (long)result.Duration.TotalMilliseconds,
|
||||
Envelope = result.Envelope is not null
|
||||
? new DsseEnvelopeResponse
|
||||
{
|
||||
PayloadType = result.Envelope.PayloadType,
|
||||
Payload = result.Envelope.Payload,
|
||||
PayloadDigest = result.Envelope.PayloadDigest,
|
||||
Signatures = result.Envelope.Signatures.Select(s => new DsseSignatureResponse
|
||||
{
|
||||
KeyId = s.KeyId,
|
||||
Sig = s.Sig,
|
||||
Algorithm = s.Algorithm
|
||||
}).ToList()
|
||||
}
|
||||
: null
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> GenerateTranscript(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService threadService,
|
||||
ITranscriptGenerator transcriptGenerator,
|
||||
string artifactDigest,
|
||||
[FromBody] EvidenceTranscriptRequest? request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
var graph = await threadService.GetThreadGraphAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
null,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
if (graph is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "thread_not_found", artifactDigest });
|
||||
}
|
||||
|
||||
var options = new TranscriptOptions
|
||||
{
|
||||
Type = ParseTranscriptType(request?.Type),
|
||||
IncludeLlmRationale = request?.IncludeLlmRationale ?? true,
|
||||
RationalePromptHint = request?.RationalePromptHint,
|
||||
MaxLength = request?.MaxLength
|
||||
};
|
||||
|
||||
var transcript = await transcriptGenerator.GenerateTranscriptAsync(graph, options, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new EvidenceTranscriptResponse
|
||||
{
|
||||
TranscriptId = transcript.Id,
|
||||
ThreadId = transcript.ThreadId,
|
||||
Type = transcript.TranscriptType.ToString(),
|
||||
TemplateVersion = transcript.TemplateVersion,
|
||||
LlmModel = transcript.LlmModel,
|
||||
Content = transcript.Content,
|
||||
AnchorCount = transcript.Anchors.Length,
|
||||
GeneratedAt = transcript.GeneratedAt
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetEvidenceNodes(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService service,
|
||||
string artifactDigest,
|
||||
[FromQuery] string? kind,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
var filterKinds = string.IsNullOrWhiteSpace(kind)
|
||||
? null
|
||||
: kind.Split(',')
|
||||
.Select(k => Enum.TryParse<EvidenceNodeKind>(k.Trim(), true, out var nk) ? nk : (EvidenceNodeKind?)null)
|
||||
.Where(k => k.HasValue)
|
||||
.Select(k => k!.Value)
|
||||
.ToList();
|
||||
|
||||
var options = new EvidenceThreadOptions
|
||||
{
|
||||
IncludeContent = true,
|
||||
NodeKinds = filterKinds
|
||||
};
|
||||
|
||||
var graph = await service.GetThreadGraphAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
options,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
if (graph is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "thread_not_found", artifactDigest });
|
||||
}
|
||||
|
||||
var nodes = graph.Nodes.Select(n => new EvidenceNodeResponse
|
||||
{
|
||||
Id = n.Id,
|
||||
Kind = n.Kind.ToString(),
|
||||
RefId = n.RefId,
|
||||
RefDigest = n.RefDigest,
|
||||
Title = n.Title,
|
||||
Summary = n.Summary,
|
||||
Confidence = n.Confidence,
|
||||
AnchorCount = n.Anchors.Length,
|
||||
CreatedAt = n.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
return Results.Ok(new EvidenceNodeListResponse
|
||||
{
|
||||
ThreadId = graph.Thread.Id,
|
||||
ArtifactDigest = artifactDigest,
|
||||
Nodes = nodes,
|
||||
TotalCount = nodes.Count
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetEvidenceLinks(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService service,
|
||||
string artifactDigest,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
var graph = await service.GetThreadGraphAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
null,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
if (graph is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "thread_not_found", artifactDigest });
|
||||
}
|
||||
|
||||
var links = graph.Links.Select(l => new EvidenceLinkResponse
|
||||
{
|
||||
Id = l.Id,
|
||||
SrcNodeId = l.SrcNodeId,
|
||||
DstNodeId = l.DstNodeId,
|
||||
Relation = l.Relation.ToString(),
|
||||
Weight = l.Weight,
|
||||
CreatedAt = l.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
return Results.Ok(new EvidenceLinkListResponse
|
||||
{
|
||||
ThreadId = graph.Thread.Id,
|
||||
ArtifactDigest = artifactDigest,
|
||||
Links = links,
|
||||
TotalCount = links.Count
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> CollectEvidence(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
IEvidenceThreadService threadService,
|
||||
IEvidenceNodeCollector collector,
|
||||
string artifactDigest,
|
||||
[FromBody] EvidenceCollectionRequest? request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artifactDigest))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact_digest_required" });
|
||||
}
|
||||
|
||||
// Get or create the thread
|
||||
var thread = await threadService.GetOrCreateThreadAsync(
|
||||
requestContext!.TenantId,
|
||||
artifactDigest,
|
||||
artifactName: null,
|
||||
ct).ConfigureAwait(false);
|
||||
|
||||
var options = new EvidenceCollectionOptions
|
||||
{
|
||||
BaseArtifactDigest = request?.BaseArtifactDigest,
|
||||
CollectSbomDiff = request?.CollectSbomDiff ?? true,
|
||||
CollectReachability = request?.CollectReachability ?? true,
|
||||
CollectVex = request?.CollectVex ?? true,
|
||||
CollectAttestations = request?.CollectAttestations ?? true
|
||||
};
|
||||
|
||||
var result = await collector.CollectAllAsync(thread.Id, artifactDigest, options, ct).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new EvidenceCollectionResponse
|
||||
{
|
||||
ThreadId = thread.Id,
|
||||
ArtifactDigest = artifactDigest,
|
||||
NodesCollected = result.Nodes.Count,
|
||||
LinksCreated = result.Links.Count,
|
||||
ErrorCount = result.Errors.Count,
|
||||
Errors = result.Errors.Select(e => new EvidenceCollectionErrorResponse
|
||||
{
|
||||
Source = e.Source,
|
||||
Message = e.Message,
|
||||
ExceptionType = e.ExceptionType
|
||||
}).ToList(),
|
||||
DurationMs = result.DurationMs
|
||||
});
|
||||
}
|
||||
|
||||
private static ThreadExportFormat ParseExportFormat(string? format) => format?.ToLowerInvariant() switch
|
||||
{
|
||||
"dsse" => ThreadExportFormat.Dsse,
|
||||
"json" => ThreadExportFormat.Json,
|
||||
"markdown" => ThreadExportFormat.Markdown,
|
||||
"pdf" => ThreadExportFormat.Pdf,
|
||||
_ => ThreadExportFormat.Dsse
|
||||
};
|
||||
|
||||
private static TranscriptType ParseTranscriptType(string? type) => type?.ToLowerInvariant() switch
|
||||
{
|
||||
"summary" => TranscriptType.Summary,
|
||||
"detailed" => TranscriptType.Detailed,
|
||||
"audit" => TranscriptType.Audit,
|
||||
_ => TranscriptType.Detailed
|
||||
};
|
||||
|
||||
private static bool TryResolveContext(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
out PlatformRequestContext? requestContext,
|
||||
out IResult? failure)
|
||||
{
|
||||
if (resolver.TryResolve(context, out requestContext, out var error))
|
||||
{
|
||||
failure = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#region Request/Response DTOs
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence thread query.
|
||||
/// </summary>
|
||||
public sealed record EvidenceThreadResponse
|
||||
{
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? TenantId { get; init; }
|
||||
public string? ArtifactDigest { get; init; }
|
||||
public string? ArtifactName { get; init; }
|
||||
public string? Status { get; init; }
|
||||
public string? Verdict { get; init; }
|
||||
public decimal? RiskScore { get; init; }
|
||||
public string? ReachabilityMode { get; init; }
|
||||
public int NodeCount { get; init; }
|
||||
public int LinkCount { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public DateTimeOffset UpdatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for evidence export.
|
||||
/// </summary>
|
||||
public sealed record EvidenceExportRequest
|
||||
{
|
||||
public string? Format { get; init; }
|
||||
public bool? IncludeTranscript { get; init; }
|
||||
public bool? Sign { get; init; }
|
||||
public string? SigningKeyId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence export.
|
||||
/// </summary>
|
||||
public sealed record EvidenceExportResponse
|
||||
{
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? Format { get; init; }
|
||||
public string? ContentDigest { get; init; }
|
||||
public long SizeBytes { get; init; }
|
||||
public string? SigningKeyId { get; init; }
|
||||
public DateTimeOffset ExportedAt { get; init; }
|
||||
public long DurationMs { get; init; }
|
||||
public DsseEnvelopeResponse? Envelope { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE envelope response DTO.
|
||||
/// </summary>
|
||||
public sealed record DsseEnvelopeResponse
|
||||
{
|
||||
public string? PayloadType { get; init; }
|
||||
public string? Payload { get; init; }
|
||||
public string? PayloadDigest { get; init; }
|
||||
public List<DsseSignatureResponse>? Signatures { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DSSE signature response DTO.
|
||||
/// </summary>
|
||||
public sealed record DsseSignatureResponse
|
||||
{
|
||||
public string? KeyId { get; init; }
|
||||
public string? Sig { get; init; }
|
||||
public string? Algorithm { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for transcript generation.
|
||||
/// </summary>
|
||||
public sealed record EvidenceTranscriptRequest
|
||||
{
|
||||
public string? Type { get; init; }
|
||||
public bool? IncludeLlmRationale { get; init; }
|
||||
public string? RationalePromptHint { get; init; }
|
||||
public int? MaxLength { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for transcript generation.
|
||||
/// </summary>
|
||||
public sealed record EvidenceTranscriptResponse
|
||||
{
|
||||
public Guid TranscriptId { get; init; }
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? Type { get; init; }
|
||||
public string? TemplateVersion { get; init; }
|
||||
public string? LlmModel { get; init; }
|
||||
public string? Content { get; init; }
|
||||
public int AnchorCount { get; init; }
|
||||
public DateTimeOffset GeneratedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence node query.
|
||||
/// </summary>
|
||||
public sealed record EvidenceNodeResponse
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public string? Kind { get; init; }
|
||||
public string? RefId { get; init; }
|
||||
public string? RefDigest { get; init; }
|
||||
public string? Title { get; init; }
|
||||
public string? Summary { get; init; }
|
||||
public decimal? Confidence { get; init; }
|
||||
public int AnchorCount { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence node list.
|
||||
/// </summary>
|
||||
public sealed record EvidenceNodeListResponse
|
||||
{
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? ArtifactDigest { get; init; }
|
||||
public List<EvidenceNodeResponse>? Nodes { get; init; }
|
||||
public int TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence link query.
|
||||
/// </summary>
|
||||
public sealed record EvidenceLinkResponse
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public Guid SrcNodeId { get; init; }
|
||||
public Guid DstNodeId { get; init; }
|
||||
public string? Relation { get; init; }
|
||||
public decimal? Weight { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence link list.
|
||||
/// </summary>
|
||||
public sealed record EvidenceLinkListResponse
|
||||
{
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? ArtifactDigest { get; init; }
|
||||
public List<EvidenceLinkResponse>? Links { get; init; }
|
||||
public int TotalCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for evidence collection.
|
||||
/// </summary>
|
||||
public sealed record EvidenceCollectionRequest
|
||||
{
|
||||
public string? BaseArtifactDigest { get; init; }
|
||||
public bool? CollectSbomDiff { get; init; }
|
||||
public bool? CollectReachability { get; init; }
|
||||
public bool? CollectVex { get; init; }
|
||||
public bool? CollectAttestations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response for evidence collection.
|
||||
/// </summary>
|
||||
public sealed record EvidenceCollectionResponse
|
||||
{
|
||||
public Guid ThreadId { get; init; }
|
||||
public string? ArtifactDigest { get; init; }
|
||||
public int NodesCollected { get; init; }
|
||||
public int LinksCreated { get; init; }
|
||||
public int ErrorCount { get; init; }
|
||||
public List<EvidenceCollectionErrorResponse>? Errors { get; init; }
|
||||
public long DurationMs { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error response for evidence collection.
|
||||
/// </summary>
|
||||
public sealed record EvidenceCollectionErrorResponse
|
||||
{
|
||||
public string? Source { get; init; }
|
||||
public string? Message { get; init; }
|
||||
public string? ExceptionType { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
Reference in New Issue
Block a user