Skip to main content

Trigger bindings

Which events may touch which environments is a platform standard: pull requests may validate, merges to main may verify staging, only a release tag may touch production. Trigger bindings write that standard down in intent.yaml, so it is one reviewed declaration instead of if: expressions scattered across CI files. The plan records which binding activated each environment.

Trigger bindings map CI provider events to environment activation. They let you declare which environments should be planned for each type of event — pull requests activate development, pushes to main activate staging, release tags activate production.

How it works​

Provider event → TriggerBinding match → Environment activation → Plan

When orun plan runs with a trigger context (--trigger or --from-ci), it:

  1. Normalizes the raw provider event into a standard format
  2. Matches the event against declared trigger bindings
  3. Activates only environments that reference the matched triggers
  4. Plans only components subscribed to those active environments

Without a trigger context, all environments are planned as before.

Declaring trigger bindings​

Trigger bindings live under automation.triggerBindings in your intent file:

automation:
triggerBindings:
github-pull-request:
description: Pull request validation
on:
provider: github
event: pull_request
actions:
- opened
- synchronize
- reopened
baseBranches:
- main
plan:
scope: changed
base: pull_request.base.sha
head: pull_request.head.sha

github-push-main:
description: Main branch verification
on:
provider: github
event: push
branches:
- main
plan:
scope: changed
base: before
head: after

github-tag-release:
description: Release planning
on:
provider: github
event: push
tags:
- "v*"
plan:
scope: full

Activating environments​

Environments reference trigger bindings through activation.triggerRefs:

environments:
development:
activation:
triggerRefs:
- github-pull-request
parameterDefaults:
"*":
namespacePrefix: dev-

staging:
activation:
triggerRefs:
- github-push-main
parameterDefaults:
"*":
namespacePrefix: stg-

production:
activation:
triggerRefs:
- github-tag-release
parameterDefaults:
"*":
namespacePrefix: prod-

When github-pull-request fires, only development is activated. Components subscribed to development are planned; components only subscribed to staging or production are skipped.

Event matching​

Each trigger binding declares match criteria under on:

FieldPurpose
providerEvent provider (required, e.g. github)
eventEvent name (required, e.g. push, pull_request)
actionsOptional action filter (e.g. opened, synchronize)
branchesOptional source branch filter with glob support
baseBranchesOptional base branch filter (for PRs) with glob support
tagsOptional tag filter with glob support

All specified fields must match for the trigger to fire. Unspecified fields are not checked.

Glob patterns​

Branch and tag filters support simple glob patterns:

branches:
- main # exact match
- release/* # prefix match (release/1.0, release/hotfix)
- feature/** # same as feature/*

tags:
- "v*" # matches v1.0.0, v2.3.1-rc1
- "*" # matches any non-empty value

Plan scope​

Each trigger binding can set plan.scope to control how the plan is scoped:

ScopeBehavior
fullPlan all components in activated environments
changedPlan only changed components (enables --changed mode)
selectedPlan exactly the components and environments the event names

When scope: changed is set, the trigger also needs base and head to determine the diff range. These are dot-paths into the raw event payload:

plan:
scope: changed
base: pull_request.base.sha # resolves from event JSON
head: pull_request.head.sha

For push events, the standard paths are before and after.

Selected plans: an event that names what to plan​

A workflow_dispatch event — a person or a platform asking for one component in one environment — carries its selection in its inputs. scope: selected makes that selection the whole plan, so the same plan command serves pull requests, pushes and dispatches:

automation:
triggerBindings:
github-dispatch:
description: A deploy asked for by a person
on:
provider: github
event: workflow_dispatch
branches: [main]
plan:
scope: selected
components: inputs.component # dot-path: "api, web" or ["api", "web"]
environments: inputs.environment # dot-path: "stage"
autopush: false # no plan-time catalog push for these plans
# one command for every event; a dispatch plans its inputs, not a diff
orun plan --from-ci github --event-file "$GITHUB_EVENT_PATH" --changed --output plan.json
  • components and environments are dot-paths into the event payload, resolved like base and head. A value is a comma-separated string (items trimmed) or a list of strings. Both are required with scope: selected and invalid with any other scope.
  • The selection is exact and never empty. The plan fails, naming the cause, on a missing or empty value, an unknown component, a wildcard in a name, an environment the trigger does not activate, or a component that does not subscribe to a selected environment.
  • --changed and --files are ignored for a selected plan (orun says so on stderr): the request is the scope.
  • Under --trigger github-dispatch there is no event, so the selection comes from --env (required) and --component: orun plan --trigger github-dispatch --env stage --component api.
  • autopush: false skips the best-effort catalog push that execution.state.autopushCatalog would otherwise make after the plan; it is valid on any binding. --push-catalog still pushes.
  • The plan records the trigger's scope as selected; the object model records the revision as full.

Opt-in lanes: requireRule​

With plan.requireRule: true, a lane (one component in one environment) is planned under the binding only if one of its profileRules names the binding. The lane opts in, and the rule's profile is what it runs — deploy in stage, verify in dev (Profile rules → Opting a lane in).

github-dispatch:
on: { provider: github, event: workflow_dispatch, branches: [main] }
plan:
scope: selected
components: inputs.component
environments: inputs.environment
requireRule: true
  • Under scope: selected it is strict: every selected lane must have opted in, or the plan fails naming each lane that did not and the fix (not enabled for github-dispatch: web.stage — add a profileRule with when.triggerRef: github-dispatch to the lane's subscription).
  • Under another scope it drops the lanes that did not opt in, and fails only when none is left.
  • Unset (the default) keeps today's behaviour: a lane with no rule naming the binding runs its default profile.
  • The catalog records each opted-in lane as spec.environments.<env>.manual: { trigger, profile } (the first requireRule binding by name, with its rule's profile). A component that opts nothing in keeps its manifest hash. Inline intent components do not carry profile rules into the catalog, so they get no manual field.

CLI usage​

Named trigger simulation (local development)​

orun plan --trigger github-pull-request

This activates the named trigger directly without requiring a real event. Useful for local reproduction of CI behavior.

Provider event file (CI)​

orun plan \
--from-ci github \
--event-file "$GITHUB_EVENT_PATH"

This reads the raw event JSON, normalizes it, matches against all declared trigger bindings, and activates the corresponding environments.

Combining with --env​

When both a trigger and --env are provided, the result is the intersection:

orun plan --trigger github-pull-request --env development

If the specified environment is not activated by the trigger, planning fails with an error.

Multiple matched triggers​

When an event matches multiple trigger bindings, orun:

  1. Collects all matched trigger names (sorted alphabetically)
  2. Activates the union of all referenced environments
  3. Checks for conflicting plan.scope values — if triggers disagree, planning fails with a clear error

Plan metadata​

When a trigger is used, the resulting plan includes trigger metadata:

{
"metadata": {
"trigger": {
"mode": "event-file",
"provider": "github",
"event": "pull_request",
"action": "synchronize",
"matchedBindings": ["github-pull-request"],
"activeEnvironments": ["development"],
"scope": "changed",
"base": "abc123",
"head": "def456"
}
}
}

Validation​

Static validation (orun validate) checks:

  • Trigger bindings have required on.provider and on.event fields
  • plan.scope is full, changed or selected (if set); plan.components and plan.environments are required with selected and invalid with any other scope, and each is a dot-path with no empty segment (also checked by orun plan)
  • Environment triggerRefs point to existing trigger bindings
  • No duplicate refs within an environment

A warning (not error) is produced for trigger bindings that no environment references.

Backwards compatibility​

Existing intent files without automation or activation continue to work unchanged. The trigger system is opt-in:

  • orun plan without --trigger or --from-ci ignores all activation rules
  • Environments without activation.triggerRefs are never activated by triggers (but are always included in non-trigger plans)