Expressions
Use {{ expression }} in YAML values to select data, supply environment values or
compose text. Expressions use CEL, with no filesystem, network or process access.
Quote expressions in YAML:
client_secret: "{{ env.CLIENT_SECRET }}"
version: "{{ facts.app.app.version }}-1"
description: "Vendor release {{ evidence['vendor.release'].version }}"
Values and text
An expression occupying the whole YAML value retains its result type: string, boolean, number, null, list or object. Surrounding text produces a string; embedded expressions must return strings, booleans or numbers. An embedded null, list or object is an error.
enabled: "{{ env.CHANNEL == 'production' }}"
keep: "{{ int(env.RETAIN_VERSIONS) }}"
description: "Release {{ evidence['vendor.release'].version }}"
Environment values are always strings. Convert them explicitly when the target field needs another type. Expressions do not run again on their results.
Escape a literal opener with a backslash. YAML single quotes or block text retain that backslash:
description: '\{{ not_an_expression }}'
This publishes the literal text {{ not_an_expression }}. Ordinary shell
substitutions inside script text remain shell syntax.
Available data
| Context | Contents | Available when |
|---|---|---|
env | Referenced process environment values | When a command uses the value |
facts | Inspected subjects by path or selection name, with their app, package or other fields | After software preparation |
evidence | Namespaced metadata supplied with the prepared artifact | Destination metadata |
inputs | Named builder inputs: version, filename, sha256, size, format, evidence, facts | Builder preparation |
evidence.source.version is the version the source states for its input, when
it states one: a Homebrew cask's, a release tag or a match pattern's version
group.
Names containing punctuation use brackets, such as
evidence['vendor.release'].version. Builder inputs expose portable metadata,
not runner paths. A downloaded input may have no declared version.
Software kinds expose inspected subjects and can name selections for destination
metadata. Their fields retain the
inspection model's meanings: for example, app.version is the application's
short version and app.build is its separate build version.
app.architectures lists the CPU architectures of the application's main
executable, such as arm64 and x86_64. It is absent when that file is not a
Mach-O binary, as with a script launcher, which says nothing about where the
application runs. No destination field is set from it.
An inspected subject is addressed by its path, such as
facts['Core.pkg/PackageInfo']. subjects names one by what identifies it instead:
kind, path, installed_path, an application's bundle_id or a component
package's package_id. Each selector must match exactly one subject.
subjects:
core:
package_id: com.vendor.suite.core
destinations:
repository:
pkginfo:
version: "{{ facts.core.package.version }}"
A package with no product version whose components carry different versions has no version of its own; this takes one component's.
Missing and null values
A required lookup fails if its key is absent. Use CEL optional selection when an absent value has a deliberate fallback:
developer: "{{ evidence.?vendor.name.orValue('Unknown') }}"
An explicit null is present and remains null; orValue does not replace it. Where
null also needs a fallback, test it explicitly:
developer: "{{ evidence.vendor.name == null ? 'Unknown' : evidence.vendor.name }}"
Evaluation boundaries
Plugin declarations resolve while loading, because the plugins define the contracts the rest of the catalog is checked against. Every other value resolves when a command uses it, so a missing environment variable fails only the commands that need it:
- a resource's source and preparation fields when a run acquires or prepares it;
- destination metadata after preparation, and its environment values only when
planorapplypublishes; - destination connection settings when
planorapplyconnects; - source-control settings when
stemma reconcileconnects.
BuildMacPkg resolves package fields, payload and script entries after acquiring
its inputs. Facts cannot select what must be acquired before those facts exist.
stemma validate reads no environment values. It checks declaration structure,
expression syntax and the literal parts of each field. Once data is available, the
resolved values must satisfy the field's native schema and semantic validation.
stemma validate --resolved evaluates every environment value, as runs do.
Document identities, object keys, $input names, resolver and operation names,
component references and resource references remain literal, as do the source,
inputs and files that hold them, so no value can change what a resource consumes.
Destination reports identify expression-supplied fields with origin expression.
Expressions apply to YAML values, including multiline content and
destination scripts. Files selected with $input, such as a repository
Scripts/postinstall, are copied unchanged. Stemma never executes installer or
policy scripts while evaluating expressions or building packages.
CEL's string functions can encode text for its destination format. For a shell argument, surround the result with single quotes and escape embedded single quotes:
postinstall_script: |-
#!/bin/sh
/usr/local/bin/example --token '{{ env.EXAMPLE_TOKEN.replace("'", "'\\''") }}'
Values containing dollar signs, command substitutions or newlines remain one literal shell argument. Stemma's interpolation alone does not provide shell, XML or other destination-specific escaping.