Release Deployment Golden Path Flow
Overview
The Release Deployment Golden Path is the flagship end-to-end journey of the Release Orchestrator: an operator registers a deployment environment and target, enrolls a deployment agent, composes a digest-pinned release, requests promotion into an environment, clears the gate + approval decision, watches the deployment execute on the target, and finally inspects the sealed, hash-verified evidence trail that proves what happened.
Audience: release operators and platform engineers running promotions through the Stella Ops console or the stella CLI, and integrators wiring external systems against the /api/v1/release-orchestrator/* API.
Business Value: every environment change is policy-gated, human-approvable, executed against a pinned sha256 digest (never a mutable tag), and backed by verifiable evidence — so a release decision can be audited and replayed after the fact.
Source of truth: the runtime surfaces below are implemented by
src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/Endpoints/*.csand the UI routes insrc/Web/StellaOps.Web/src/app/routes/releases.routes.ts. Most release-scoped route groups are mapped under both/api/release-orchestrator/*(legacy, named routes) and/api/v1/release-orchestrator/*(e.g.ReleaseEndpoints.cs,ApprovalEndpoints.cs,DeploymentEndpoints.cs,EvidenceEndpoints.cs); the environment/target/agent administration groups exist only under/api/v1/release-orchestrator/*. This doc quotes the/api/v1/form. As-built status per API group: promotion runtime gap closure plan.
Actors
| Actor | Type | Role |
|---|---|---|
| Release operator | User | Registers environments/targets, composes releases, requests promotion, triggers deploy (console or stella CLI) |
| Approver | User | Decides pending promotion approvals in the decision cockpit |
| Release Orchestrator WebApi | Service | Owns environments, targets, agents, releases, approvals, gate decisions, deployments, and evidence (release_orchestrator.* schema) |
| Deployment agent | Service | StellaOps.Agent.Host process on/near the target; polls for tasks over mTLS and executes docker/compose/ssh/winrm capabilities |
| Gateway | Service | API entry point; UI and CLI traffic flows through it |
| Console UI | Tool | /releases, /releases/promotions, /releases/approvals, /releases/deployments |
Prerequisites
- Scopes (policy constants in
src/ReleaseOrchestrator/__Apps/StellaOps.ReleaseOrchestrator.WebApi/ReleaseOrchestratorPolicies.cs):orch:read/orch:operate— read / mutate environments, targets, agents, registration tokensrelease:read— read releases, approvals, deployments, evidencerelease:write— create/update releases, components, evidence packetsrelease:publish— promote, deploy, rollback, approve/reject (promote/deploy/rollback also accept a resource-scoped Operate & Deploy share grant in place of the native scope; the check is enforced in-handler byShareGrantAccessGuard)
- A running deployment agent host (or an SSH/WinRM-reachable target for agentless transports — see agentless deployment)
- Component images pushed to a registry, addressable by sha256 digest
NOT IMPLEMENTED (CLI): there is no wired
stella agentcommand group — enrollment viastella agent bootstrapis a draft UX contract only (see the status box in Agent Operations Quick Start). Agent enrollment happens through the API/console path in Step 2. The wired CLI verbs for this flow arestella release approve|reject|promote|deploy|rollback(src/Cli/StellaOps.Cli/Commands/ReleaseOperatorDecisionCommandGroup.cs).
Flow Diagram
┌──────────────────────────────────────────────────────────────────────────────┐
│ Release Deployment Golden Path │
└──────────────────────────────────────────────────────────────────────────────┘
Operator WebApi Approver Agent Target
│ │ │ │ │
│ 1. POST /environments │ │ │
│ POST /environments/{id}/targets │ │ │
│────────────────>│ │ │ │
│ │ │ │ │
│ 2. POST /agents/registration-tokens │ │ │
│────────────────>│ │ │ │
│ (run agent host with token) │ │ │
│ │<───── register ────────────────────│ │
│ │<──── mTLS poll/heartbeat ──────────│ │
│ │ │ │ │
│ 3. POST /releases (+components w/ digests) │ │
│ POST /releases/{id}/ready │ │ │
│ POST /evidence (recorded; verify step may sign) │ │
│────────────────>│ │ │ │
│ │ │ │ │
│ 4. POST /releases/{id}/promote │ │ │
│────────────────>│ │ │ │
│ │ truth guard: ready? evidence verified? │
│ │ gate evaluation → approval row │ │
│ │ (auto-approve │ manual │ deny) │ │
│ │ │ │ │
│ │ 5. pending approval│ │ │
│ │───────────────────>│ │ │
│ │ POST /approvals/{id}/approve │ │
│ │<───────────────────│ │ │
│ │ │ │ │
│ 6. POST /releases/{id}/deploy │ │ │
│────────────────>│ │ │ │
│ │ truth guard: approval quorum? gates passed? │
│ │ evidence verified? → 202 Accepted │ │
│ │──── deployment task ───────────────>│ │
│ │ │ │─── execute ───>│
│ │<─── signed result ─────────────────│ │
│ │ │ │ │
│ 7. GET /evidence?releaseId=… │ │ │
│ POST /evidence/{id}/verify │ │ │
│────────────────>│ │ │ │
│ signature + payload binding checked → recorded/verified/failed │ │
Step-by-Step
The canonical subject and evidence-spine contract governs every identity handoff in this flow. Artifact continuity is the exact lowercase OCI digest; release, approval, deployment, evidence packet, EvidenceLocker record, and Attestor receipt keep their own owner ids. Console handoffs to Chain of Custody use ?subject=...; owner detail routes use their path ids. Global Signing readiness is a key-inventory capability and must never be cited as record-level evidence verification.
1. Register a deployment environment and target
Source:
ReleaseOrchestratorEnvironmentEndpoints.cs— root group/api/v1/release-orchestratorwith/environmentsand/targetssub-groups. Mutations requireorch:operate; reads requireorch:read.
POST /api/v1/release-orchestrator/environments # create environment
POST /api/v1/release-orchestrator/environments/{id}/targets # create target in env
POST /api/v1/release-orchestrator/targets/{id}/health-check # probe target health
PUT /api/v1/release-orchestrator/targets/{id}/agent # bind an enrolled agent
Supporting reads: GET /environments, GET /environments/{id}/targets, GET /environments/{id}/targets/healthy, GET /environments/{id}/next-promotion-target. Environments also own freeze windows (GET/POST /environments/{id}/freeze-windows, GET .../is-frozen) which block promotions during change freezes.
An environment can be flagged requires human approval — promotions into it never auto-approve, regardless of the matched approval policy (checked in ReleaseEndpoints.RequestPromotion).
UI: /releases/environments (release-scoped environment management) and /environments (topology overview).
2. Enroll a deployment agent
Source:
AgentRegistrationEndpoints.cs(/api/v1/release-orchestrator/agents) andAgentRuntimeEndpoints.cs(/api/v1/release-orchestrator/agent-runtime).
- Operator issues a one-time registration token:
POST /api/v1/release-orchestrator/agents/registration-tokens(orch:operate; tokens are masked in listings). - The agent host (
src/ReleaseOrchestrator/__Agents/StellaOps.Agent.Host) presents that token to register:POST /api/v1/release-orchestrator/agents. Registration issues internal CA-signed client-certificate material. - The registered agent then polls for work over mTLS:
POST /api/v1/release-orchestrator/agent-runtime/agents/{agentId}/tasks:poll
POST /api/v1/release-orchestrator/agent-runtime/agents/{agentId}/tasks/{taskId}/result
POST /api/v1/release-orchestrator/agent-runtime/agents/{agentId}/heartbeat
- Bind the agent to the target from Step 1 (
PUT /targets/{id}/agent) so deployments to that target dispatch to this agent.
Operator maintenance surfaces: POST /agents/{id}/refresh-capabilities, POST /agents/{id}/execute-capability (audited ad-hoc dispatch), DELETE /agents/{id} (revoke).
UI: /ops/operations/agents (agent fleet; topology-agents-page.component).
3. Compose a digest-pinned release
Source:
ReleaseEndpoints.cs— groups at/api/{v1/}release-orchestrator/releases. Create/update requirerelease:write.
POST /api/v1/release-orchestrator/releases # Draft release (name+version unique → 409 on dupe)
POST /api/v1/release-orchestrator/releases/{releaseId}/components # add component: name, version, digest
POST /api/v1/release-orchestrator/evidence # submit evidence packet (hash recomputed server-side)
POST /api/v1/release-orchestrator/releases/{id}/ready # Draft → Ready
Components carry the sha256 digest of the artifact; deploys later pull by that digest, not by tag. Evidence packets (EvidenceEndpoints.cs, release:write to create) store canonical content whose hash the service recomputes before storing (an integrity precondition, not a verification claim). A POST lands the packet as recorded; a packet only becomes verifiedthrough the real verification step (below), and a packet whose verification runs and fails is verification_failed(Sprint 20260704_002, EVH-1/EVH-2).
There is also a one-shot idempotent aggregate for automation: POST /api/v1/release-orchestrator/releases/seal (ReleaseSealAggregateEndpoints.cs, release:publish) — creates or re-reads a sealed release by aggregating creation, component registration, ready transition, recorded seal evidence, approval gate truth, and deployment dispatch in one call.
UI: /releases (unified release grid), /releases/new (release flow launchpad), /releases/versions/new (create version), /releases/detail/{releaseId}.
4. Request promotion
Source:
ReleaseEndpoints.RequestPromotion(ReleaseEndpoints.cs) +Services/ReleaseTruthGuard.cs. Requires nativerelease:publishor an active accepted deploy share grant for (this release, this target environment).
POST /api/v1/release-orchestrator/releases/{id}/promote
{ "targetEnvironment": "production", "justification": "…", "urgency": "normal" }
The request fails closed (HTTP 409 with { blocked, reason, message }) before anything is persisted when release truth is incomplete:
| Reason code | Meaning |
|---|---|
release_not_ready | Release status is not ready |
target_environment_missing | No target environment supplied/resolved |
evidence_missing | No evidence packets exist for the release |
evidence_unverified | No acceptable evidence packet. Under the default advisory posture a content-intact recorded packet passes (a warning is logged when nothing is genuinely verified); under blocking posture (per-env or blanket, ReleaseOrchestrator:EvidenceVerification) a genuinely EVH-2-verified packet is required (Sprint 20260704_002, EVH-3) |
When truth passes, the endpoint creates (or idempotently returns) a pending approval row, then PromotionRequestProcessor evaluates the gate/approval policy and branches into one of three outcomes (the persisted gate decision row records which):
- Auto-approved — policy allows, no human required (never happens when the target environment requires human approval)
- Manual / waiting — approval remains
pendingin the queue for a human decision (also used while waiting on SBOM readiness — 409 withwaiting: trueand the gate results) - Denied — 409 with the denying policy id and reason
Each transition emits granular audit events (promotion_requested, promotion_blocked, promotion_auto_approved) alongside the HTTP-level promote_release audit row.
UI: /releases/promotions (list), /releases/promotions/create (wizard: pick bundle version
- target environment),
/releases/promotions/{promotionId}(detail with run timeline). CLI:stella release promote <release-id> --env <target>.
5. Gates + approval decision
Source:
ApprovalEndpoints.cs— groups at/api/{v1/}release-orchestrator/approvals. Decisions requirerelease:publish; decision application is idempotent per actor.
GET /api/v1/release-orchestrator/approvals # queue (release:read)
GET /api/v1/release-orchestrator/approvals/{id} # detail incl. gate results
POST /api/v1/release-orchestrator/approvals/{id}/approve
POST /api/v1/release-orchestrator/approvals/{id}/reject
POST /api/v1/release-orchestrator/approvals/batch-approve | /batch-reject
The approval row carries the quorum counters (currentApprovals / requiredApprovals), the gatesPassed flag, and the persisted per-gate results that the deploy step later re-checks. All four approval surfaces (normal, batch, v2, and dashboard) cross the same ApprovalSafetyGuard before quorum mutation. waiting, empty, advisory, or skipped gate truth cannot be approved. A time-limited gate override is a separate signed GateException action with a durable evidence reference and explicit missing-evidence list; the normal Approve action cannot act as an implicit override.
Approval policies themselves are DB-backed and operator-managed (/releases/policies UI, /api/v1/release-orchestrator/approval-policies — release.policy.read/.manage scopes).
Gate decision basis + deploy-time attestation gate (Sprint 20260704_006)
Every gate decision now records the basis it was computed from — not just the outcome — into release_orchestrator.gate_decisions.decision_basis, and DSSE-signs the canonical (outcome + basis) into the existing decision_envelope column (service key by default; unsigned + a loud log if signing is unavailable — advisory posture). The basis captures, all real or the explicit absent sentinel (never fabricated — no more date-synthesized policy version, C4):
- policy-pack digest + pack id/version (from the Policy.Engine evaluate response),
- verdict attestation ref (
attestationRef: status / verdictId / determinismHash) from the policy-verdict attestation contract (Sprint 20260704_005), - call-graph digest (from the reachability witness) and per-digest findings hash (sha256 over the sorted CVE set the decision used); vuln-db / feed snapshot digests are recorded
absentat this layer today, - evidence packet refs (id + content hash + honest status),
- the components actually evaluated (image digests — killing the old empty-components hole),
- a signed gate-exception record when a C9 bypass applied (exception id, signer key id, scope), so replay/audit sees the exception inside the signed basis.
A new deploy-time attestation gate (attestation-gate) runs on the live gate chain: for every component digest it queries the Attestor for a scan/SBOM attestation and confirms subject binding + signer-key trust against the scanner scan-attestation JWKS (GET /api/v1/scan-attestation/keys). It is advisory by default (records attestation: verified | missing | invalid | unavailable per component in the basis + a gate row) and flips to blocking per environment via environment_policy_bindings.attestation_blocking / ReleaseOrchestrator:AttestationGate:BlockingEnvironments (the DTC-10 reachability-gate rollout pattern). A transient Attestor/scanner outage (unavailable) does not block unless BlockOnUnavailable is set.
UI: /releases/approvals (queue) → /releases/approvals/{id} — the decision cockpit with Overview / Gates / Security / Reachability / Ops-Data / Evidence / Replay / History tabs; Approve / Reject / Defer act from there. The Gate Evaluation section now shows a Decision basis summary (pack digest, verdict attestation status, per-component attestation status). CLI: stella release approve <approval-id> --comment "…" / stella release reject …; stella release promote … prints the same Decision basis summary from the response.
6. Deployment execution
Source:
ReleaseEndpoints.Deploy+ReleaseTruthGuard.ValidateDeploymentRequest(fail-closed), thenDeploymentEndpoints.csfor the deployment resource.
POST /api/v1/release-orchestrator/releases/{id}/deploy
Deploy re-validates the full truth chain and fails closed (409) on any gap:
| Reason code | Meaning |
|---|---|
target_environment_missing | Release has no target environment |
approval_missing | No approval decision exists for (release, target environment) |
approval_not_approved | Latest approval is not approved |
approval_quorum_missing | currentApprovals < requiredApprovals |
gate_decision_waiting / gate_decision_failed / gate_decision_missing / gate_decision_not_passing | Gate truth is waiting, absent, or not passing |
gate_evidence_incomplete | An advisory/skipped result is visible but is not a completed allow decision |
gate_exception_invalid_or_expired | An exception is missing signed evidence/missing-evidence enumeration or has expired |
gate_decision_basis_missing / gate_decision_basis_mismatch / gate_decision_unsigned / gate_decision_evidence_unverified | A protected or signing-required target lacks a verified durable decision basis |
evidence_missing / evidence_unverified | Same evidence checks as promote |
On success it returns 202 Accepted with Location: /api/v1/release-orchestrator/deployments/{id} and creates a pending persisted deployment record — only target execution may advance it to success. The bound agent picks the task up on its next mTLS poll and executes it with the matching capability (docker / compose / native, or the SSH/WinRM agentless transports), pulling the component by digest.
Observe and control the running deployment:
GET /api/v1/release-orchestrator/deployments # list
GET /api/v1/release-orchestrator/deployments/{id} # status
GET /api/v1/release-orchestrator/deployments/{id}/logs # aggregated logs
GET /api/v1/release-orchestrator/deployments/{id}/targets/{targetId}/logs
GET /api/v1/release-orchestrator/deployments/{id}/events | /metrics
POST /api/v1/release-orchestrator/deployments/{id}/pause | /resume | /cancel | /rollback
POST /api/v1/release-orchestrator/deployments/{id}/targets/{targetId}/retry
Pause/resume/cancel/retry require release:write, rollback requires release:publish (both also honor a per-deployment operate share grant, enforced in-handler).
UI: /releases/deployments (history list) → /releases/deployments/{id} (live deployment monitor). CLI: stella release deploy <release-id> / stella release rollback <release-id>.
7. View sealed evidence
Source:
EvidenceEndpoints.cs— groups at/api/{v1/}release-orchestrator/evidence.
GET /api/v1/release-orchestrator/evidence?releaseId={id} # packets for the release
GET /api/v1/release-orchestrator/evidence/{id} # packet detail (id, type, hash, timestamps)
POST /api/v1/release-orchestrator/evidence/{id}/verify # recompute stored-content hash, compare
GET /api/v1/release-orchestrator/evidence/{id}/export # export packet
GET /api/v1/release-orchestrator/evidence/{id}/raw # canonical stored content
GET /api/v1/release-orchestrator/evidence/{id}/timeline # evidence timeline
Verification (Sprint 20260704_002, EVH-2) first recomputes sha256:<hex> over the stored canonical content as an integrity precondition, then — when the packet carries a DSSE envelope — validates the signature against a configured trust anchor via the Attestor DSSE verification engine and binds the signed payload to the stored content. A packet reaches verifiedonly when signature + binding pass; a present-but-invalid signature yields verification_failed; an unsigned (or trust-anchorless) packet stays recorded. The persisted check list records exactly what was proven. This is the same evidence the promote/deploy truth guards consult — the evidence you view is the evidence that gated the release.
UI: the decision cockpit’s Evidence tab (/releases/approvals/{id}) and the run workspace evidence tab (/releases/runs/{runId}/evidence).
Data Contracts
Key response/record shapes (see the endpoint files for the full DTOs):
- Release (
ReleaseEndpoints.ManagedReleaseDto): id, name, version,status(draft → ready → …),targetEnvironment,currentEnvironment,deploymentStrategy, components[ { name, version, digest } ], lifecycle timestamps. - Approval (
ApprovalEndpoints.ApprovalDto): id, releaseId/name/version, source/target environment, requestedBy/At, urgency, justification,status(pending/approved/rejected),currentApprovals/requiredApprovals,gatesPassed,gateResults [ { gateId, type, status } ],expiresAt, release component summaries. - Evidence packet (
EvidenceEndpoints.EvidencePacketDto): id, releaseId, type,hash(sha256:<hex>),algorithm(SHA-256),status(recorded|verified|verification_failed),verificationReason,verifiedKeyId,verificationChecks[], stored canonical content (base64). - Deployment: id, releaseId, environment,
strategy, status, per-target task state; created viaCreateDeploymentRequest { releaseId, environmentId, strategy, packageType, packageRef… }. - Blocked response (promote/deploy 409):
{ "blocked": true, "reason": "<code>", "message": "…" }(waiting variant addswaiting: true,approvalId,policyId,gateResults).
Error Handling
Use the contract’s state and recovery table before retrying a decision. In particular, missing asks the owning producer to create/re-ingest a record, unavailable asks the operator to restore/read the owner, recorded/signed asks for trust material plus verification, and verification_failed/tampered asks for quarantine and a producer-owned rebuild. None is interchangeable with a passing gate.
| Failure | HTTP | Behavior |
|---|---|---|
| Missing scope / share grant on promote/deploy/rollback | 403 | { error: "forbidden", message: "release:publish or an Operate & Deploy share grant is required." } |
| Truth-guard block (see reason-code tables above) | 409 | Fail-closed; nothing dispatched |
| Policy denies promotion | 409 | blocked: true + denying policyId, approval row persisted as denied |
| Duplicate release name+version | 409 | On POST /releases |
| Unknown release/approval/deployment id | 404 | Tenant-scoped lookups |
| Agent task failure | — | Deployment record stays/ends non-success; per-target retry, whole-deployment rollback available |
All lifecycle mutations are audited (.Audited(...) on each route — create/promote/deploy/ rollback/approve/reject/pause/resume/cancel/retry, agent registration and revocation).
Observability
- Metrics (
DeploymentEndpoints.cs): meterStellaOps.ReleaseOrchestrator.Deployments—release_orchestrator_deployment_lifecycle_total(counter, per lifecycle operation) andrelease_orchestrator_deployment_lifecycle_duration_seconds(histogram). - Audit: HTTP-level audit rows for every mutation plus granular promotion events (
promotion_requested,promotion_blocked,promotion_auto_approved) carrying the approval id and gate-decision extras (PromotionAuditEmitter). - Deployment events/logs:
GET /deployments/{id}/events,/logs, per-target logs — the same feeds the deployment monitor UI renders.
Related Flows
- CI/CD Gate Flow — how the scan/gate verdicts that feed release gates are produced in pipelines
- Evidence Bundle Export Flow — packaging evidence for auditors
- Exception Approval Workflow — policy exceptions referenced by gate decisions
- Module docs: Release Orchestrator · promotion runtime gap closure plan · agentless deployment (SSH/WinRM)
