Key points
- Pipeline
variablesis a declaration of what the run requires, not an assignment. Values resolve from the run scope. - A declared variable that cannot be resolved fails the run before execution rather than starting with a missing value.
- A bare name resolves in the run scope;
scope/path:NAMEresolves in that scope and is injected under the bare name. - Names must match
^[A-Za-z0-9_.-]+$, and the same runtime name may not resolve from two different scopes in one place. - Step
variablesapply to every task in the step; taskvariablesare applied afterwards and win. - Scope is not team path. Scope decides which values resolve; team path decides ownership and notification.
Examples
variables:
- RELEASE_CHANNEL
- platform/shared:ARTIFACT_BUCKETname: release-service
description: Build, verify, and publish the payments service.
container_image: alpine:3.20
working_directory: /workspace
timeout: 45m
variables:
- RELEASE_CHANNEL
- platform/shared:ARTIFACT_BUCKET
steps:
- name: build
script: |
make build
echo "1.0.$(date +%s)" > /nopsai/outputs/BUILD_TAG
outputs:
- name: BUILD_TAG
- name: verify
image: golang:1.24
depends_on: [build]
runtime_pool: ci
volumes:
- build-cache:/root/.cache
script: |
go vet ./...
go test ./...
- name: checks
depends_on: [verify]
tasks:
- name: licenses
script: |
./scripts/license-check.sh
- name: sbom
script: |
./scripts/sbom.sh > /workspace/sbom.json
sha256sum /workspace/sbom.json | cut -d' ' -f1 > /nopsai/outputs/SBOM_DIGEST
outputs:
- name: SBOM_DIGEST
- name: report
depends_on: [checks.licenses, checks.sbom]
variables:
SBOM_DIGEST: $steps.checks.sbom.outputs.SBOM_DIGEST
script: |
echo "checks complete"
echo "sbom $SBOM_DIGEST"
- name: package
depends_on: [checks]
variables:
BUILD_TAG: $steps.build.outputs.BUILD_TAG
script: |
./scripts/package.sh "$BUILD_TAG"
echo "channel $RELEASE_CHANNEL, bucket $ARTIFACT_BUCKET"
./scripts/mint-token.sh > /nopsai/outputs/PUBLISH_TOKEN
outputs:
- name: PUBLISH_TOKEN
sensitive: truevariables:
- platform/shared:ARTIFACT_BUCKET
- platform/staging:ARTIFACT_BUCKETField reference
variablespipelinestring[]OptionalNone
Runtime variables the pipeline requires. A bare name resolves in the run scope; scope/path:NAME resolves an explicit scope and injects it as NAME.
variables:
- REGISTRY_HOST
- platform/shared:ARTIFACT_BUCKET- Names must match ^[A-Za-z0-9_.-]+$.
- The same runtime name may not be declared twice, even from different scopes.
- Scope segments may not be absolute or contain
./...
services/nopsai/pkg/validation/pipeline.go
How it works
The two variables directives are different things that share a name. At the top level it is a list of requirements. On a step or task it is a map of assignments, which is also where runtime outputs are consumed.
Cross-scope use is always explicit. Nothing resolves from another scope unless the reference says so, which keeps a pipeline honest about what it reaches for.
A run can also override variables at start time, and the values a run actually resolved are visible on the run record, which is usually faster than reasoning about scope inheritance.
Implementation evidence
pkg/models/runtime_refs.goScoped reference parsing and name validation.
services/nopsai/pkg/validation/pipeline.goRules the validator enforces on this directive set.

