Implement missing backend endpoints for release orchestration

TASK-002: 11 deployment monitoring endpoints in JobEngine
  (list, get, logs, events, metrics, pause/resume/cancel/rollback/retry)
TASK-003: 6 evidence management endpoints in JobEngine
  (list, get, verify, export, raw, timeline)
TASK-005: 3 release dashboard endpoints in JobEngine
  (dashboard summary, approve/reject promotion)
TASK-006: 2 registry image search endpoints in Scanner
  (search with 9 mock images, digests lookup)

All endpoints return seed/mock data for testing. Auth policies
match existing patterns. Dual route registration on both
/api/ and /api/v1/ prefixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-23 15:52:20 +02:00
parent d3353e9d16
commit dd29786e38
17 changed files with 2066 additions and 26 deletions

View File

@@ -0,0 +1,193 @@
namespace StellaOps.ElkSharp;
internal static class ElkEdgeRoutingGeometry
{
private const double CoordinateTolerance = 0.5d;
internal static IReadOnlyList<RoutedEdgeSegment> FlattenSegments(IReadOnlyCollection<ElkRoutedEdge> edges)
{
var segments = new List<RoutedEdgeSegment>();
foreach (var edge in edges)
{
segments.AddRange(FlattenSegments(edge));
}
return segments;
}
internal static IReadOnlyList<RoutedEdgeSegment> FlattenSegments(ElkRoutedEdge edge)
{
var segments = new List<RoutedEdgeSegment>();
foreach (var section in edge.Sections)
{
var points = new List<ElkPoint> { section.StartPoint };
points.AddRange(section.BendPoints);
points.Add(section.EndPoint);
for (var i = 0; i < points.Count - 1; i++)
{
segments.Add(new RoutedEdgeSegment(edge.Id, points[i], points[i + 1]));
}
}
return segments;
}
internal static double ComputePathLength(ElkRoutedEdge edge)
{
return FlattenSegments(edge).Sum(segment => ComputeSegmentLength(segment.Start, segment.End));
}
internal static double ComputeSegmentLength(ElkPoint start, ElkPoint end)
{
var dx = end.X - start.X;
var dy = end.Y - start.Y;
return Math.Sqrt((dx * dx) + (dy * dy));
}
internal static bool SegmentsIntersect(ElkPoint a1, ElkPoint a2, ElkPoint b1, ElkPoint b2)
{
if (ShareEndpoint(a1, a2, b1, b2))
{
return false;
}
if (AreCollinearAndOverlapping(a1, a2, b1, b2))
{
return false;
}
if (IsHorizontal(a1, a2) && IsVertical(b1, b2))
{
return IntersectsOrthogonal(a1, a2, b1, b2);
}
if (IsVertical(a1, a2) && IsHorizontal(b1, b2))
{
return IntersectsOrthogonal(b1, b2, a1, a2);
}
return SegmentsIntersectGeneral(a1, a2, b1, b2);
}
internal static bool AreParallelAndClose(
ElkPoint a1,
ElkPoint a2,
ElkPoint b1,
ElkPoint b2,
double clearance)
{
if (IsHorizontal(a1, a2) && IsHorizontal(b1, b2))
{
return Math.Abs(a1.Y - b1.Y) <= clearance
&& OverlapLength(Math.Min(a1.X, a2.X), Math.Max(a1.X, a2.X), Math.Min(b1.X, b2.X), Math.Max(b1.X, b2.X)) > 1d;
}
if (IsVertical(a1, a2) && IsVertical(b1, b2))
{
return Math.Abs(a1.X - b1.X) <= clearance
&& OverlapLength(Math.Min(a1.Y, a2.Y), Math.Max(a1.Y, a2.Y), Math.Min(b1.Y, b2.Y), Math.Max(b1.Y, b2.Y)) > 1d;
}
return false;
}
internal static bool AreCollinearAndOverlapping(ElkPoint a1, ElkPoint a2, ElkPoint b1, ElkPoint b2)
{
if (IsHorizontal(a1, a2) && IsHorizontal(b1, b2) && Math.Abs(a1.Y - b1.Y) <= CoordinateTolerance)
{
return OverlapLength(Math.Min(a1.X, a2.X), Math.Max(a1.X, a2.X), Math.Min(b1.X, b2.X), Math.Max(b1.X, b2.X)) > 1d;
}
if (IsVertical(a1, a2) && IsVertical(b1, b2) && Math.Abs(a1.X - b1.X) <= CoordinateTolerance)
{
return OverlapLength(Math.Min(a1.Y, a2.Y), Math.Max(a1.Y, a2.Y), Math.Min(b1.Y, b2.Y), Math.Max(b1.Y, b2.Y)) > 1d;
}
return false;
}
internal static ElkPoint ResolveApproachPoint(ElkRoutedEdge edge)
{
var lastSection = edge.Sections.Last();
if (lastSection.BendPoints.Count > 0)
{
return lastSection.BendPoints.Last();
}
return lastSection.StartPoint;
}
internal static bool PointsEqual(ElkPoint left, ElkPoint right)
{
return Math.Abs(left.X - right.X) <= CoordinateTolerance
&& Math.Abs(left.Y - right.Y) <= CoordinateTolerance;
}
private static bool IsHorizontal(ElkPoint start, ElkPoint end) => Math.Abs(start.Y - end.Y) <= CoordinateTolerance;
private static bool IsVertical(ElkPoint start, ElkPoint end) => Math.Abs(start.X - end.X) <= CoordinateTolerance;
private static bool IntersectsOrthogonal(ElkPoint horizontalStart, ElkPoint horizontalEnd, ElkPoint verticalStart, ElkPoint verticalEnd)
{
var minHorizontalX = Math.Min(horizontalStart.X, horizontalEnd.X);
var maxHorizontalX = Math.Max(horizontalStart.X, horizontalEnd.X);
var minVerticalY = Math.Min(verticalStart.Y, verticalEnd.Y);
var maxVerticalY = Math.Max(verticalStart.Y, verticalEnd.Y);
return verticalStart.X > minHorizontalX + CoordinateTolerance
&& verticalStart.X < maxHorizontalX - CoordinateTolerance
&& horizontalStart.Y > minVerticalY + CoordinateTolerance
&& horizontalStart.Y < maxVerticalY - CoordinateTolerance;
}
private static bool ShareEndpoint(ElkPoint a1, ElkPoint a2, ElkPoint b1, ElkPoint b2)
{
return PointsEqual(a1, b1)
|| PointsEqual(a1, b2)
|| PointsEqual(a2, b1)
|| PointsEqual(a2, b2);
}
private static bool SegmentsIntersectGeneral(ElkPoint a1, ElkPoint a2, ElkPoint b1, ElkPoint b2)
{
var o1 = Orientation(a1, a2, b1);
var o2 = Orientation(a1, a2, b2);
var o3 = Orientation(b1, b2, a1);
var o4 = Orientation(b1, b2, a2);
if (o1 != o2 && o3 != o4)
{
return true;
}
return o1 == 0 && OnSegment(a1, b1, a2)
|| o2 == 0 && OnSegment(a1, b2, a2)
|| o3 == 0 && OnSegment(b1, a1, b2)
|| o4 == 0 && OnSegment(b1, a2, b2);
}
private static int Orientation(ElkPoint start, ElkPoint middle, ElkPoint end)
{
var value = ((middle.Y - start.Y) * (end.X - middle.X)) - ((middle.X - start.X) * (end.Y - middle.Y));
if (Math.Abs(value) <= CoordinateTolerance)
{
return 0;
}
return value > 0 ? 1 : 2;
}
private static bool OnSegment(ElkPoint start, ElkPoint point, ElkPoint end)
{
return point.X <= Math.Max(start.X, end.X) + CoordinateTolerance
&& point.X >= Math.Min(start.X, end.X) - CoordinateTolerance
&& point.Y <= Math.Max(start.Y, end.Y) + CoordinateTolerance
&& point.Y >= Math.Min(start.Y, end.Y) - CoordinateTolerance;
}
private static double OverlapLength(double firstMin, double firstMax, double secondMin, double secondMax)
{
return Math.Min(firstMax, secondMax) - Math.Max(firstMin, secondMin);
}
}