Profile rules
Lane policy is a standard: a pull request may only plan, a merge to main may apply. Profile rules declare that policy on the component's subscription, as a choice among the profiles its golden path offers, and the plan records which rule chose the profile. orun validate rejects a malformed rule before anything is planned.
Profile rules provide conditional execution profile selection based on which trigger fired. They allow a single component subscription to behave differently depending on the CI event — for example, running plan-only on pull requests but apply on merge to main.
Motivation
Without profile rules, you would need separate environments or external logic to control profile selection. Profile rules keep this decision declarative and co-located with the subscription.
Syntax
subscribe:
environments:
- name: dev-preview
profile: plan-only
profileRules:
- profile: apply
when:
triggerRef: github-push-main
The profile field is the fallback/default — it is used when no rule matches (including when no trigger is active, such as local orun plan runs).
profileRules is an ordered list of conditional overrides evaluated top-to-bottom (first-match-wins).
How it works
- TriggerBinding decides: should this environment participate in this plan?
- Profile rule decides: if this component is in this environment, which profile should it use?
These are separate decisions. Profile rules do not affect environment activation — they only influence behavior within an already-activated environment.
Evaluation flow
--trigger github-push-main
→ activates dev-preview (via environment activation.triggerRefs)
→ component subscribes to dev-preview
→ profileRule matches triggerRef: github-push-main
→ selected profile: apply
--trigger github-pull-request
→ activates dev-preview
→ component subscribes to dev-preview
→ no profileRule matches
→ selected profile: plan-only (fallback)
Full example
apiVersion: sourceplane.io/v1
kind: Component
metadata:
name: network-foundation
spec:
type: terraform
domain: platform-foundation
subscribe:
environments:
- name: dev-preview
profile: plan-only
profileRules:
- profile: apply
when:
triggerRef: github-push-main
- name: stage-preview
profile: plan-only
profileRules:
- profile: apply
when:
triggerRef: github-tag-release
Plan output
When a profile rule fires, the plan records both the selected profile and the trigger that caused it:
{
"profile": "terraform.apply",
"profileSource": "subscription-rule",
"profileRuleTriggerRef": "github-push-main"
}
The DAG view also annotates this:
├─ network-foundation
│ └─ dev-preview [terraform.apply via github-push-main]
│ └─ apply
Opting a lane in to a trigger
A trigger binding with plan.requireRule: true plans a lane only if one of the
lane's profile rules names that trigger. The rule is both the switch and the
behaviour: it says the lane may run under the trigger, and which profile it runs.
A lane with no such rule is never planned by that trigger, whatever its default
profile is.
# a person may run the dev check and deploy to stage; prod deploys on merge only
subscribe:
environments:
- name: dev
profile: verify
profileRules:
- profile: verify
when: { triggerRef: github-dispatch }
- name: stage
profile: verify
profileRules:
- profile: deploy
when: { triggerRef: github-push-main }
- profile: deploy
when: { triggerRef: github-dispatch }
- name: prod
profile: verify
profileRules:
- profile: deploy
when: { triggerRef: github-push-main }
The catalog records each opted-in lane as spec.environments.<env>.manual: { trigger, profile }, so a backend can offer a manual run only where a lane
opted in. See Trigger bindings → Opt-in lanes.
Validation rules
| Rule | Reason |
|---|---|
profile is required when profileRules exists | Always have a deterministic fallback |
profileRules[].profile must exist in the composition | Prevent runtime surprises |
when.triggerRef must exist in automation.triggerBindings | Avoid typo-based silent behavior |
| First-match-wins (order matters) | Deterministic evaluation |
Conditions
For v1, only triggerRef is supported in the when block:
when:
triggerRef: github-push-main
The triggerRef must reference a key declared in automation.triggerBindings.
When not to use profile rules
- If you need different environments per trigger, use environment activation (
activation.triggerRefs) instead - If you want all components in an environment to use the same profile, set it as the composition's
defaultProfile - Profile rules are best for cases where a single environment needs different behavior depending on the triggering event
- To change whether a
dependsOnedge blocks execution (e.g. parallel PR plans), use Dependency rules — they operate on the DAG, not the steps