Skip to main content

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​

ContextContentsAvailable when
envReferenced process environment valuesWhen a command uses the value
factsInspected subjects by path or selection name, with their app, package or other fieldsAfter software preparation
evidenceNamespaced metadata supplied with the prepared artifactDestination metadata
inputsNamed builder inputs: version, filename, sha256, size, format, evidence, factsBuilder 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 plan or apply publishes;
  • destination connection settings when plan or apply connects;
  • source-control settings when stemma reconcile connects.

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.