diff --git a/docs/implplan/SPRINT_20260323_002_ElkSharp_bounded_edge_refinement.md b/docs/implplan/SPRINT_20260323_002_ElkSharp_bounded_edge_refinement.md
new file mode 100644
index 000000000..ed544ac82
--- /dev/null
+++ b/docs/implplan/SPRINT_20260323_002_ElkSharp_bounded_edge_refinement.md
@@ -0,0 +1,75 @@
+# Sprint 20260323-002 - ElkSharp Bounded Edge Refinement
+
+## Topic & Scope
+- Add a bounded deterministic edge-refinement stage to ElkSharp without replacing the existing channel and dummy-edge routing model.
+- Preserve orthogonal output, backward corridor behavior, sink corridor behavior, and target anchor heuristics.
+- Working directory: `src/__Libraries/StellaOps.ElkSharp/`
+- Expected evidence: targeted renderer tests, direct geometry assertions, and workflow docs updated for the new layout option.
+
+## Dependencies & Concurrency
+- Depends on the current ElkSharp routing pipeline in `src/__Libraries/StellaOps.ElkSharp/`.
+- Safe cross-module edits for this sprint are limited to:
+ - `src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/`
+ - `docs/workflow/`
+
+## Documentation Prerequisites
+- `docs/code-of-conduct/CODE_OF_CONDUCT.md`
+- `docs/code-of-conduct/TESTING_PRACTICES.md`
+- `docs/workflow/ENGINE.md`
+- `src/__Libraries/StellaOps.ElkSharp/ElkSharpLayeredLayoutEngine.cs`
+- `src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouter.cs`
+- `src/__Libraries/StellaOps.ElkSharp/ElkEdgePostProcessor.cs`
+
+## Delivery Tracker
+
+### TASK-001 - Add module-local ElkSharp guidance and option scaffolding
+Status: DONE
+Dependency: none
+Owners: Implementer
+Task description:
+Create a module-local `AGENTS.md` for ElkSharp and extend the layout option model with bounded refinement settings that default safely and deterministically.
+
+Completion criteria:
+- [x] `src/__Libraries/StellaOps.ElkSharp/AGENTS.md` exists with local routing rules
+- [x] `ElkLayoutOptions` exposes bounded refinement options without changing workflow request contracts
+
+### TASK-002 - Implement bounded orthogonal edge refinement
+Status: DONE
+Dependency: TASK-001
+Owners: Implementer
+Task description:
+Add an internal refinement stage that scores routed output, detects crossing-prone edges, and tries a small fixed set of orthogonal reroute strategies while preserving corridor and anchor semantics.
+
+Completion criteria:
+- [x] Refinement is deterministic and bounded by explicit pass and trial limits
+- [x] Refinement runs only for `LeftToRight` and preserves existing corridor and port-sensitive edges
+- [x] Existing simplify and tighten passes still run after refinement
+
+### TASK-003 - Add regression tests and docs
+Status: DONE
+Dependency: TASK-002
+Owners: Implementer
+Task description:
+Add regression tests covering deterministic output and option gating, and update workflow documentation to note the bounded ElkSharp refinement behavior.
+
+Completion criteria:
+- [x] Targeted workflow renderer tests cover refinement determinism and `TopToBottom` stability
+- [x] Workflow docs mention the bounded refinement behavior for ElkSharp best-effort layout
+- [x] Sprint execution log records validation results
+
+## Execution Log
+| Date (UTC) | Update | Owner |
+| --- | --- | --- |
+| 2026-03-23 | Sprint created and work started for bounded deterministic ElkSharp edge refinement. | Implementer |
+| 2026-03-23 | Added module-local ElkSharp guidance, implemented bounded orthogonal refinement, updated `docs/workflow/ENGINE.md`, and passed `dotnet test src/Workflow/__Tests/StellaOps.Workflow.Renderer.Tests/StellaOps.Workflow.Renderer.Tests.csproj --filter "FullyQualifiedName~ElkSharp" -v minimal` (15/15). | Implementer |
+
+## Decisions & Risks
+- There was no module-local `AGENTS.md` under `src/__Libraries/StellaOps.ElkSharp/`; this sprint adds one before code changes so the module is no longer undocumented.
+- Cross-module edits are limited to workflow renderer tests and workflow engine docs because the implementation changes a shared library used by those surfaces.
+- The refinement stage must remain deterministic and must not introduce random strategy generation or diagonal output.
+- Updated docs: `docs/workflow/ENGINE.md`
+- Module-local guidance added: `src/__Libraries/StellaOps.ElkSharp/AGENTS.md`
+
+## Next Checkpoints
+- After TASK-002: targeted `dotnet test` run for ElkSharp renderer tests
+- After TASK-003: update sprint statuses and execution log with concrete command results
diff --git a/docs/workflow/ENGINE.md b/docs/workflow/ENGINE.md
index ccb70c1a9..ded1a7a40 100644
--- a/docs/workflow/ENGINE.md
+++ b/docs/workflow/ENGINE.md
@@ -920,7 +920,7 @@ The engine can render workflow definitions as visual diagrams.
| Engine | Description |
|--------|-------------|
-| **ElkSharp** | Port of Eclipse Layout Kernel (default) |
+| **ElkSharp** | Port of Eclipse Layout Kernel (default). In `Best` effort mode it now runs a bounded deterministic orthogonal edge-refinement pass after base routing; `Draft` and `Balanced` keep the base route unless library callers opt in through `ElkLayoutOptions.EdgeRefinement`. |
| **ElkJS** | JavaScript-based ELK via Node.js |
| **MSAGL** | Microsoft Automatic Graph Layout |
@@ -1020,4 +1020,3 @@ docs/decompiled-samples/
csharp/ 177 .cs files (Roslyn-formatted C# with typed request models)
json/ 177 .json files (indented canonical definitions with JSON Schema)
```
-
diff --git a/src/JobEngine/StellaOps.JobEngine/StellaOps.JobEngine.WebService/Endpoints/DeploymentEndpoints.cs b/src/JobEngine/StellaOps.JobEngine/StellaOps.JobEngine.WebService/Endpoints/DeploymentEndpoints.cs
new file mode 100644
index 000000000..d2611a906
--- /dev/null
+++ b/src/JobEngine/StellaOps.JobEngine/StellaOps.JobEngine.WebService/Endpoints/DeploymentEndpoints.cs
@@ -0,0 +1,463 @@
+using Microsoft.AspNetCore.Mvc;
+using StellaOps.Auth.ServerIntegration.Tenancy;
+
+namespace StellaOps.JobEngine.WebService.Endpoints;
+
+///
+/// Deployment monitoring endpoints for the Orchestrator service.
+/// Provides lifecycle operations, log streaming, event tracking, and metrics
+/// for individual deployment runs.
+/// Routes: /api/release-orchestrator/deployments
+///
+public static class DeploymentEndpoints
+{
+ public static IEndpointRouteBuilder MapDeploymentEndpoints(this IEndpointRouteBuilder app)
+ {
+ MapDeploymentGroup(app, "/api/release-orchestrator/deployments", includeRouteNames: true);
+ MapDeploymentGroup(app, "/api/v1/release-orchestrator/deployments", includeRouteNames: false);
+
+ return app;
+ }
+
+ private static void MapDeploymentGroup(
+ IEndpointRouteBuilder app,
+ string prefix,
+ bool includeRouteNames)
+ {
+ var group = app.MapGroup(prefix)
+ .WithTags("Deployments")
+ .RequireAuthorization(JobEnginePolicies.ReleaseRead)
+ .RequireTenant();
+
+ // --- Read endpoints ---
+
+ var list = group.MapGet(string.Empty, ListDeployments)
+ .WithDescription("Return a paginated list of deployments for the calling tenant, optionally filtered by status, environment, and release. Each deployment record includes its current status, target environment, strategy, and lifecycle timestamps.");
+ if (includeRouteNames)
+ {
+ list.WithName("Deployment_List");
+ }
+
+ var detail = group.MapGet("/{id}", GetDeployment)
+ .WithDescription("Return the full deployment record for the specified ID including status, target environment, deployment strategy, target health, and progress details. Returns 404 when the deployment does not exist in the tenant.");
+ if (includeRouteNames)
+ {
+ detail.WithName("Deployment_Get");
+ }
+
+ var logs = group.MapGet("/{id}/logs", GetDeploymentLogs)
+ .WithDescription("Return the aggregated log entries for the specified deployment across all targets. Entries are ordered chronologically and include severity level, source target, and message content.");
+ if (includeRouteNames)
+ {
+ logs.WithName("Deployment_GetLogs");
+ }
+
+ var targetLogs = group.MapGet("/{id}/targets/{targetId}/logs", GetTargetLogs)
+ .WithDescription("Return log entries for a specific target within the deployment. Useful for diagnosing issues on an individual host or container instance. Returns 404 when the deployment or target does not exist.");
+ if (includeRouteNames)
+ {
+ targetLogs.WithName("Deployment_GetTargetLogs");
+ }
+
+ var events = group.MapGet("/{id}/events", GetDeploymentEvents)
+ .WithDescription("Return the chronological event stream for the specified deployment including status transitions, health check results, target progress updates, and rollback triggers.");
+ if (includeRouteNames)
+ {
+ events.WithName("Deployment_GetEvents");
+ }
+
+ var metrics = group.MapGet("/{id}/metrics", GetDeploymentMetrics)
+ .WithDescription("Return real-time and historical metrics for the specified deployment including duration, error rates, resource utilisation, and target-level health indicators.");
+ if (includeRouteNames)
+ {
+ metrics.WithName("Deployment_GetMetrics");
+ }
+
+ // --- Mutation endpoints ---
+
+ var pause = group.MapPost("/{id}/pause", PauseDeployment)
+ .WithDescription("Pause the specified in-progress deployment, halting further target rollouts while keeping already-deployed targets running. Returns 409 if the deployment is not in a pausable state.")
+ .RequireAuthorization(JobEnginePolicies.ReleaseWrite);
+ if (includeRouteNames)
+ {
+ pause.WithName("Deployment_Pause");
+ }
+
+ var resume = group.MapPost("/{id}/resume", ResumeDeployment)
+ .WithDescription("Resume a previously paused deployment, continuing the rollout to remaining targets from where it was halted. Returns 409 if the deployment is not currently paused.")
+ .RequireAuthorization(JobEnginePolicies.ReleaseWrite);
+ if (includeRouteNames)
+ {
+ resume.WithName("Deployment_Resume");
+ }
+
+ var cancel = group.MapPost("/{id}/cancel", CancelDeployment)
+ .WithDescription("Cancel the specified deployment, stopping all in-progress rollouts and marking the deployment as cancelled. Already-deployed targets are not rolled back. Returns 409 if the deployment is already in a terminal state.")
+ .RequireAuthorization(JobEnginePolicies.ReleaseWrite);
+ if (includeRouteNames)
+ {
+ cancel.WithName("Deployment_Cancel");
+ }
+
+ var rollback = group.MapPost("/{id}/rollback", RollbackDeployment)
+ .WithDescription("Initiate a rollback of the specified deployment, reverting all targets to the previous stable version. The rollback is audited and creates corresponding events. Returns 409 if the deployment is not in a rollbackable state.")
+ .RequireAuthorization(JobEnginePolicies.ReleaseApprove);
+ if (includeRouteNames)
+ {
+ rollback.WithName("Deployment_Rollback");
+ }
+
+ var retryTarget = group.MapPost("/{id}/targets/{targetId}/retry", RetryTarget)
+ .WithDescription("Retry the deployment to a specific failed target within the deployment. Only targets in failed or error state can be retried. Returns 404 when the deployment or target does not exist; 409 when the target is not in a retryable state.")
+ .RequireAuthorization(JobEnginePolicies.ReleaseWrite);
+ if (includeRouteNames)
+ {
+ retryTarget.WithName("Deployment_RetryTarget");
+ }
+ }
+
+ // ---- Handlers ----
+
+ private static IResult ListDeployments(
+ [FromQuery] string? status,
+ [FromQuery] string? environment,
+ [FromQuery] string? releaseId,
+ [FromQuery] string? sortField,
+ [FromQuery] string? sortOrder,
+ [FromQuery] int? page,
+ [FromQuery] int? pageSize)
+ {
+ var deployments = SeedData.Deployments.AsEnumerable();
+
+ if (!string.IsNullOrWhiteSpace(status))
+ {
+ var statusList = status.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ deployments = deployments.Where(d => statusList.Contains(d.Status, StringComparer.OrdinalIgnoreCase));
+ }
+
+ if (!string.IsNullOrWhiteSpace(environment))
+ {
+ deployments = deployments.Where(d =>
+ string.Equals(d.Environment, environment, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (!string.IsNullOrWhiteSpace(releaseId))
+ {
+ deployments = deployments.Where(d =>
+ string.Equals(d.ReleaseId, releaseId, StringComparison.OrdinalIgnoreCase));
+ }
+
+ var sorted = (sortField?.ToLowerInvariant(), sortOrder?.ToLowerInvariant()) switch
+ {
+ ("status", "asc") => deployments.OrderBy(d => d.Status),
+ ("status", _) => deployments.OrderByDescending(d => d.Status),
+ ("environment", "asc") => deployments.OrderBy(d => d.Environment),
+ ("environment", _) => deployments.OrderByDescending(d => d.Environment),
+ (_, "asc") => deployments.OrderBy(d => d.StartedAt),
+ _ => deployments.OrderByDescending(d => d.StartedAt),
+ };
+
+ var all = sorted.ToList();
+ var effectivePage = Math.Max(page ?? 1, 1);
+ var effectivePageSize = Math.Clamp(pageSize ?? 20, 1, 100);
+ var items = all.Skip((effectivePage - 1) * effectivePageSize).Take(effectivePageSize).ToList();
+
+ return Results.Ok(new
+ {
+ items,
+ totalCount = all.Count,
+ page = effectivePage,
+ pageSize = effectivePageSize,
+ });
+ }
+
+ private static IResult GetDeployment(string id)
+ {
+ var deployment = SeedData.Deployments.FirstOrDefault(d => d.Id == id);
+ return deployment is not null ? Results.Ok(deployment) : Results.NotFound();
+ }
+
+ private static IResult GetDeploymentLogs(
+ string id,
+ [FromQuery] string? level,
+ [FromQuery] int? limit)
+ {
+ if (!SeedData.Deployments.Any(d => d.Id == id))
+ return Results.NotFound();
+
+ return Results.Ok(new { entries = Array.Empty