# Certified Promotions & Rollout (/docs/operations/promotions-and-rollout)



This is the change-governance plane for **artifacts** — routing tables,
workflows, connector configs, and strategies — not for the binary or the
cluster. A promotion moves an immutable artifact through preflight, guardrails,
and a canary into activation, with a rollback handle at every step. Rollout
evidence and runtime conformance are the proof that authorizes each move.

<Callout type="warn" title="This is not Kubernetes deployment">
  Promotions govern **config/strategy artifact** releases inside a running
  Ductor tenant. Rolling out a new *binary* or *chart* is a different plane —
  see [Production deployment](/docs/deployment/production) and
  [Kubernetes / Helm](/docs/deployment/kubernetes-helm). The two are
  independent: you promote artifacts on a cluster that is already deployed.
</Callout>

## Promotions: the candidate lifecycle [#promotions-the-candidate-lifecycle]

A promotion candidate wraps one or more immutable **routing, workflow,
connector, or strategy artifacts** and carries them through a fixed lifecycle.
Every stage is a separate, authenticated call under
`/api/promotions/candidates`, so an operator (or an automated release pipeline)
advances the candidate deliberately and can stop at any gate.

```mermaid
flowchart LR
    Create["create<br/>candidate"] --> Preflight["preflight<br/>impact check"]
    Preflight --> Guardrails["guardrails/<br/>evaluate"]
    Guardrails --> CanaryStart["canary/start"]
    CanaryStart --> CanaryStop["canary/stop"]
    CanaryStop --> Approve["approve"]
    Approve --> Promote["promote"]
    Promote --> Rollback["rollback"]
    Preflight --> Abort["abort"]
```

| Stage        | Endpoint                                        | What it does                                                                               |
| ------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Create       | `POST /api/promotions/candidates`               | Registers a governed candidate for immutable routing/workflow/connector/strategy artifacts |
| Inspect      | `GET /api/promotions/candidates/{candidate_id}` | Returns the candidate, active canary state, check results, and approval evidence           |
| Preflight    | `POST …/{candidate_id}/preflight`               | Runs impact checks **without** live connector actions or external side effects             |
| Guardrails   | `POST …/{candidate_id}/guardrails/evaluate`     | Evaluates guardrails against durable evidence and low-cardinality metrics                  |
| Canary start | `POST …/{candidate_id}/canary/start`            | Starts a deterministic sticky canary run                                                   |
| Canary stop  | `POST …/{candidate_id}/canary/stop`             | Stops new canary assignment while preserving recorded canary evidence                      |
| Approve      | `POST …/{candidate_id}/approve`                 | Records an authenticated approval decision                                                 |
| Promote      | `POST …/{candidate_id}/promote`                 | Activates artifacts through their artifact-specific activation paths                       |
| Rollback     | `POST …/{candidate_id}/rollback`                | Runs rollback through artifact-specific rollback handles                                   |
| Abort        | `POST …/{candidate_id}/abort`                   | Aborts the candidate and stops any active canary assignment                                |

<Steps>
  <Step>
    ### Create the candidate [#create-the-candidate]

    ```bash
    curl -X POST http://localhost:8080/api/promotions/candidates \
      -H "X-Tenant-ID: $DUCTOR_TENANT_ID" \
      -H "Content-Type: application/json" \
      -d '{ "environment_id": "prod", "artifacts": [ ... ] }'
    ```

    The candidate binds the exact artifact versions you intend to release. Artifacts
    are immutable — a new version is a new candidate.
  </Step>

  <Step>
    ### Preflight before anything touches production [#preflight-before-anything-touches-production]

    `preflight` computes per-artifact impacts and blocking reasons over the
    candidate's artifacts and their dependents, and runs **without** executing live
    connector actions or external side effects. Treat a non-empty set of blocking
    reasons as a hard stop.
  </Step>

  <Step>
    ### Evaluate guardrails, then canary [#evaluate-guardrails-then-canary]

    `guardrails/evaluate` scores the candidate against durable evidence and
    low-cardinality metrics. `canary/start` then routes a deterministic, sticky
    slice of traffic to the new artifacts so the same key always lands on the same
    side of the canary.
  </Step>

  <Step>
    ### Approve and promote — or roll back [#approve-and-promote--or-roll-back]

    `approve` records the authenticated decision; `promote` activates the artifacts
    through their activation paths. If anything regresses, `rollback` reverses the
    promotion through each artifact's rollback handle.
  </Step>
</Steps>

## Rollout evidence: what authorizes a promotion [#rollout-evidence-what-authorizes-a-promotion]

A promotion should not be approved on trust. A **rollout-evidence pack** is the
governed, tenant/environment-scoped bundle of proof — attached artifacts and
typed receipts — that a gate evaluates before a candidate is allowed forward.
Packs and their gate are the machine-checkable answer to "is this promotion
allowed to proceed?"

<Callout type="warn" title="Advanced / enterprise surface">
  Rollout evidence is an advanced governance surface for organizations that
  require an auditable, redacted record for every promotion. Most tenants can
  use the standard promotion lifecycle without evidence packs.
</Callout>

A pack moves through its own lifecycle, all under
`/api/rollout-evidence/packs`:

| Operation                              | Endpoint                                                                                    |
| -------------------------------------- | ------------------------------------------------------------------------------------------- |
| Create / list packs                    | `POST` · `GET /api/rollout-evidence/packs`                                                  |
| Get / update a pack                    | `GET` · `PUT /api/rollout-evidence/packs/{environment_id}/{pack_id}`                        |
| Attach a hash-addressed artifact       | `POST …/{pack_id}/artifacts`                                                                |
| Attach one typed receipt               | `POST …/{pack_id}/receipts`                                                                 |
| Start / get / heartbeat / cancel a run | `POST …/{pack_id}/runs` · `GET`/`:heartbeat`/`:cancel` on `/runs/{environment_id}/{run_id}` |
| Evaluate the gate                      | `POST …/{pack_id}/gate:evaluate`                                                            |
| Explain failures                       | `POST …/{pack_id}:explain`                                                                  |
| Expand requirements                    | `POST /api/rollout-evidence/requirements:expand`                                            |
| Export redacted manifest               | `POST …/{pack_id}:export`                                                                   |
| Archive                                | `POST …/{pack_id}:archive`                                                                  |
| Attach to a promotion                  | `POST …/{pack_id}:attach-to-promotion`                                                      |

Receipts are strictly typed — exactly one of a scenario, connector, strategy,
runtime, graph, or canary receipt per attachment. The gate evaluates the full
set for **missing, failed, blocked, stale, expired, wrong-environment, and
wrong-candidate** evidence; `:explain` returns the tenant-scoped reason set when
the gate is not green.

```mermaid
flowchart LR
    Pack["evidence pack"] --> Artifacts["attach artifacts<br/>+ typed receipts"]
    Artifacts --> Runs["evidence runs<br/>start/heartbeat/cancel"]
    Runs --> Gate["gate:evaluate"]
    Gate -->|green| AttachProm["attach-to-promotion"]
    Gate -->|not green| Explain[":explain"]
    AttachProm --> Promote["promotion promote"]
```

<Callout type="info" title="Evidence runs are leased">
  A run is started in an allowed environment mode and holds a lease you renew
  with `:heartbeat`. Heartbeats never extend a run beyond its absolute deadline,
  and `:cancel` stops a pending or running run. Exports are redacted — no
  secrets, storage refs, raw provider payloads, or unrestricted PII leave the
  pack.
</Callout>

### Runtime conformance: gating the environment itself [#runtime-conformance-gating-the-environment-itself]

Where evidence packs prove a *candidate* is safe, **runtime conformance** proves
the *environment* is. A conformance suite runs against a tenant environment and
produces a hash-addressed **coverage report**; that report is what gates whether
the environment is fit to receive a promotion.

<Callout type="warn" title="Advanced / enterprise surface">
  Runtime conformance is an advanced enterprise gate rather than a required
  step in the standard promotion lifecycle.
</Callout>

| Operation          | Endpoint                                                                              |
| ------------------ | ------------------------------------------------------------------------------------- |
| List / get suites  | `GET /api/runtime-conformance/suites` · `GET …/suites/{suite_id}`                     |
| Run a suite        | `POST …/suites/{suite_id}:run`                                                        |
| List / get reports | `GET /api/runtime-conformance/reports` · `GET …/reports/{environment_id}/{report_id}` |
| Explain coverage   | `POST …/reports/{environment_id}:explain`                                             |

Suites cover the runtime, bridge, worker-protocol, MCP, connector-lifecycle, and
admission surfaces with stable case IDs. A run executes in a sandbox or dry-run
mode — running a suite against a **live** environment without dry-run is
rejected — and returns a hash-addressed report. `:explain` answers whether the
latest report covers the cases required for promotion or rollout evidence.

## Related [#related]

<Cards>
  <Card title="Environments" href="/docs/management/environments">
    The tenant environment a promotion targets and a conformance suite runs
    against.
  </Card>

  <Card title="Governance strategies" href="/docs/strategies/governance">
    Certification and optimization evidence that feeds promotion guardrails.
  </Card>

  <Card title="Production deployment" href="/docs/deployment/production">
    The distinct plane that rolls out the binary and chart.
  </Card>

  <Card title="Enterprise activation" href="/docs/operations/enterprise-activation">
    The fail-closed security posture for enterprise deployments.
  </Card>
</Cards>
