Skip to main content

Environment promotion

Promotion order (preview, then staging, then production) is a standard a platform declares once, in intent.yaml. Within a single plan orun compiles it into DAG edges the runner enforces. Across separate plans it compiles into evidence gates that are recorded on the plan but not yet enforced; see standards.

Environment promotion dependencies define ordering and gating relationships between environments. They express the deployment pipeline flow — for example, staging must succeed before production can run.

This is separate from component-level dependsOn, which expresses service-to-service dependencies within an environment.

Syntax​

Add promotion.dependsOn to any environment that requires prior environment success:

environments:
preview:
activation:
triggerRefs:
- github-pull-request
parameterDefaults:
"*":
lane: verify

staging:
activation:
triggerRefs:
- github-push-main
promotion:
dependsOn:
- environment: preview
parameterDefaults:
"*":
lane: verify

production:
activation:
triggerRefs:
- github-tag-release
promotion:
dependsOn:
- environment: staging
parameterDefaults:
"*":
lane: release

With defaults applied, each entry is equivalent to:

promotion:
dependsOn:
- environment: staging
strategy: same-component
condition: success
satisfy: same-plan-or-previous-success
match:
revision: source

How It Compiles​

The behavior depends on whether the dependency environment is active in the same plan.

Same-plan (both environments active)​

When both environments are activated by the same trigger (or selected with --env), promotion dependencies compile into normal DAG edges:

web-app.dev.deploy → web-app.staging.deploy
api.dev.deploy → api.staging.deploy

The plan output uses standard dependsOn:

{
"id": "web-app.staging.deploy",
"dependsOn": ["web-app.dev.deploy"]
}

Cross-plan (dependency environment not active)​

When environments are activated by different triggers (e.g., PR activates preview, push activates staging), the dependency environment won't be in the same plan. In this case, promotion compiles into gates: a record of the prior success the job is meant to require.

{
"id": "web-app.production.release",
"dependsOn": [],
"gates": [
{
"type": "environment-promotion",
"environment": "staging",
"component": "web-app",
"condition": "success",
"match": { "revision": "source" }
}
]
}

Gates encode that the same component should have succeeded in the referenced environment for the same source revision.

Gates are advisory today

Cross-plan promotion gates are recorded in the plan but not yet enforced at run time — cross-pipeline (cross-invocation) gating is deferred (the env-scoping "Z" model calls this "Option C"). In-plan dependsOn ordering is the only enforced promotion mechanism. orun plan/run print a one-line notice when a plan carries gates, so you know they are not gating execution. To order a promotion chain end-to-end today, include the related environments in one plan/run (e.g. --all-envs). See the env-scoping spec.

Strategies​

same-component (default)​

Each component in the target environment depends only on the same component in the dependency environment:

web-app@dev → web-app@staging
api@dev → api@staging

Components not subscribed to both environments are gracefully skipped.

environment-barrier​

Every job in the target environment waits for every job in the dependency environment:

web-app@dev ─┬─→ web-app@staging
api@dev ─┘ ┌→ api@staging
web-app@dev ───┘
api@dev ────→ api@staging

Use this for strict release-train semantics where the entire environment must be green before promotion.

Satisfy Modes​

The satisfy field controls how the dependency is checked:

ModeBehavior
same-planUses in-plan DAG ordering when both environments are in the plan. If the dependency environment is not in the (scoped) plan, the edge is pruned with a warning and the selected environment runs standalone (env-scoping "Z" model — earlier releases errored here).
previous-successRecords a prior-evidence gate. Gates are advisory today (see the note above) — not enforced at run time.
same-plan-or-previous-successUses in-plan DAG edges when both are in the plan; records an (advisory) gate otherwise. (default)

Match​

The match field controls how prior success evidence is validated:

match:
revision: source

revision: source means the gate checks that the same source commit (or the commit pointed to by a tag) succeeded in the prior environment.

Interaction with Trigger Bindings​

Promotion dependencies work alongside trigger bindings and environment activation:

  1. Trigger activation decides which environments are active in a given plan
  2. Promotion dependencies decide ordering and gating between those environments
  3. Component dependencies decide ordering within the active set

A typical setup:

automation:
triggerBindings:
github-pull-request:
on:
provider: github
event: pull_request
plan:
scope: changed

github-push-main:
on:
provider: github
event: push
branches: [main]
plan:
scope: changed

github-tag-release:
on:
provider: github
event: push
tags: ["v*"]
plan:
scope: full

environments:
preview:
activation:
triggerRefs: [github-pull-request]
parameterDefaults:
"*":
lane: verify

staging:
activation:
triggerRefs: [github-push-main]
promotion:
dependsOn:
- environment: preview
parameterDefaults:
"*":
lane: verify

production:
activation:
triggerRefs: [github-tag-release]
promotion:
dependsOn:
- environment: staging
parameterDefaults:
"*":
lane: release

This gives you:

  • PR → preview only (validate changes)
  • Push to main → staging only (a recorded gate: preview should have passed for this commit)
  • Tag → production only (a recorded gate: staging should have passed for this commit)

The gates are recorded, not enforced (see the note above). To make the order binding today, plan the environments together, for example with --all-envs.

Validation​

Orun validates promotion dependencies at normalization time:

  • Referenced environments must exist
  • Self-references are rejected
  • Cycles are detected (e.g., A → B → A)