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/*.cs and the UI routes in src/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

ActorTypeRole
Release operatorUserRegisters environments/targets, composes releases, requests promotion, triggers deploy (console or stella CLI)
ApproverUserDecides pending promotion approvals in the decision cockpit
Release Orchestrator WebApiServiceOwns environments, targets, agents, releases, approvals, gate decisions, deployments, and evidence (release_orchestrator.* schema)
Deployment agentServiceStellaOps.Agent.Host process on/near the target; polls for tasks over mTLS and executes docker/compose/ssh/winrm capabilities
GatewayServiceAPI entry point; UI and CLI traffic flows through it
Console UITool/releases, /releases/promotions, /releases/approvals, /releases/deployments

Prerequisites

NOT IMPLEMENTED (CLI): there is no wired stella agent command group — enrollment via stella agent bootstrap is 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 are stella 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-orchestrator with /environments and /targets sub-groups. Mutations require orch:operate; reads require orch: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) and AgentRuntimeEndpoints.cs (/api/v1/release-orchestrator/agent-runtime).

  1. Operator issues a one-time registration token: POST /api/v1/release-orchestrator/agents/registration-tokens (orch:operate; tokens are masked in listings).
  2. 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.
  3. 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
  1. 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 require release: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 native release:publish or 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 codeMeaning
release_not_readyRelease status is not ready
target_environment_missingNo target environment supplied/resolved
evidence_missingNo evidence packets exist for the release
evidence_unverifiedNo 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):

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

5. Gates + approval decision

Source: ApprovalEndpoints.cs — groups at /api/{v1/}release-orchestrator/approvals. Decisions require release: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-policiesrelease.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):

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), then DeploymentEndpoints.cs for 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 codeMeaning
target_environment_missingRelease has no target environment
approval_missingNo approval decision exists for (release, target environment)
approval_not_approvedLatest approval is not approved
approval_quorum_missingcurrentApprovals < requiredApprovals
gate_decision_waiting / gate_decision_failed / gate_decision_missing / gate_decision_not_passingGate truth is waiting, absent, or not passing
gate_evidence_incompleteAn advisory/skipped result is visible but is not a completed allow decision
gate_exception_invalid_or_expiredAn 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_unverifiedA protected or signing-required target lacks a verified durable decision basis
evidence_missing / evidence_unverifiedSame 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):

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.

FailureHTTPBehavior
Missing scope / share grant on promote/deploy/rollback403{ error: "forbidden", message: "release:publish or an Operate & Deploy share grant is required." }
Truth-guard block (see reason-code tables above)409Fail-closed; nothing dispatched
Policy denies promotion409blocked: true + denying policyId, approval row persisted as denied
Duplicate release name+version409On POST /releases
Unknown release/approval/deployment id404Tenant-scoped lookups
Agent task failureDeployment 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