CEL EXPRESSIONS
Swamp uses CEL (Common Expression Language) for inline expressions in model definitions, workflow configurations, and data queries.
Expression Syntax
Embed CEL expressions in YAML values with the ${{ }} wrapper:
globalArguments:
greeting: "${{ 'Hello, ' + self.name }}"
vpc_id: "${{ model.my_vpc.resource.result.result.attributes.vpcId }}"When the entire YAML value is a single ${{ }} expression, the evaluated result
preserves its type (number, boolean, list, map). When mixed with surrounding
text, the result is coerced to a string.
Where Expressions Are Evaluated
Each location has access to a different set of context variables:
| Location | Available Context |
|---|---|
Model globalArguments |
model, self, env, vault, data, file |
Model inputs defaults |
model, self, env, vault, data, file |
Workflow step task.inputs |
model, self, inputs, env, vault, data, file |
Workflow step forEach.in |
model, self, inputs, env, vault, data, file |
| Data output overrides | model, self, env, vault, data, file |
swamp data query predicate |
DataRecord fields only (see Query Fields) |
swamp data query --select |
DataRecord fields only |
Context Variables
self
The current model definition being evaluated.
| Field | Type | Description |
|---|---|---|
self.id |
string |
Definition UUID |
self.name |
string |
Model name |
self.version |
int |
Definition version number |
self.tags |
map<string, string> |
Definition tags |
self.globalArguments |
map<string, dyn> |
Evaluated global arguments |
When forEach is active, the iteration variable is available as a field on
self. For example, forEach: { item: "env", in: [...] } adds self.env.
model
A map of all models in the repository, indexed by name and UUID.
model.<name>.input
| Field | Type | Description |
|---|---|---|
id |
string |
Definition UUID |
name |
string |
Model name |
version |
int |
Definition version |
tags |
map<string, string> |
Definition tags |
globalArguments |
map<string, dyn> |
Global arguments |
model.<name>.definition
| Field | Type | Description |
|---|---|---|
id |
string |
Definition UUID |
name |
string |
Model name |
version |
int |
Definition version |
tags |
map<string, string> |
Definition tags |
globalArguments |
map<string, dyn> |
Global arguments |
inputs |
map<string, dyn> |
Inputs schema |
model.<name>.resource.<specName>.<instanceName>
| Field | Type | Description |
|---|---|---|
attributes |
map<string, dyn> |
Parsed JSON content |
tags |
map<string, string> |
Metadata tags |
version |
int |
Version number |
model.<name>.file.<specName>.<instanceName>
| Field | Type | Description |
|---|---|---|
path |
string |
File path on disk |
size |
int |
File size in bytes |
contentType |
string |
MIME type |
model.<name>.execution
| Field | Type | Description |
|---|---|---|
id |
string |
Execution ID |
methodName |
string |
Method that was executed |
status |
string |
succeeded, failed, etc. |
startedAt |
string |
ISO 8601 timestamp |
completedAt |
string |
ISO 8601 timestamp (if done) |
durationMs |
int |
Execution duration |
error |
map |
Error details (if failed) |
Hyphenated Model Names
CEL interprets hyphens as subtraction. Swamp automatically transforms
model.deploy-vpc.resource to model["deploy-vpc"].resource. Both forms work
in expressions. Use bracket notation explicitly when needed:
model["deploy-vpc"].resource.result.result.attributes.vpcIdinputs
Input values provided when instantiating a definition. Available in workflow step task inputs.
inputs.vpc_id
inputs["hyphenated-name"]env
The full process environment as a flat string map.
env.API_ENDPOINT
env.HOMECaution
Values accessed via env are persisted to .swamp/data/ on disk. Use
vault.get() for sensitive values.
vault
Secure secret access. Secrets are fetched at runtime and never written to model output data. Vault expressions are resolved before CEL evaluation — the secret value is substituted into the expression before it runs.
vault.get("my-vault", "api-key")
vault.get("infra", "Client ID")data
Namespace for querying versioned data artifacts. See Data Functions below.
file
Namespace for reading file contents. See File Functions below.
workflow
Reserved for workflow context data. Not yet populated in the current version of swamp.
DataRecord Fields
Data functions and query predicates operate on records with these fields:
| Field | Type | Description |
|---|---|---|
id |
string |
Record UUID |
name |
string |
Data item name |
version |
int |
Version number |
createdAt |
string |
ISO 8601 timestamp |
attributes |
map<string, dyn> |
Parsed JSON content (resource data) |
tags |
map<string, string> |
Metadata tags |
modelName |
string |
Owning model name |
modelType |
string |
Model type (e.g., command/shell) |
specName |
string |
Output spec name |
dataType |
string |
resource or file |
contentType |
string |
MIME type |
lifetime |
string |
Data lifetime |
ownerType |
string |
Owner type |
streaming |
bool |
Whether data was streamed |
size |
int |
Content size in bytes |
content |
string |
Raw text content |
Data Functions
All data functions are methods on the data namespace. They return DataRecord
objects or arrays of them.
data.latest(modelName, dataName)
Returns the latest version of a named data item, or null.
data.latest("scanner", "result")
data.latest("scanner", "result").attributes.findingsdata.latest(modelName, dataName, varyValues)
Returns the latest version for a specific vary dimension.
data.latest("scanner", "result", ["dev"])
data.latest("scanner", "result", ["us-east-1", "prod"])data.version(modelName, dataName, version)
Returns a specific version of a data item, or null.
data.version("scanner", "result", 1)
data.version("scanner", "result", 3).attributes.statusdata.version(modelName, dataName, varyValues, version)
Returns a specific version for a vary dimension.
data.version("scanner", "result", ["dev"], 2)data.listVersions(modelName, dataName)
Returns all version numbers for a data item, in ascending order.
data.listVersions("scanner", "result")With vary values:
data.listVersions("scanner", "result", ["prod"])data.findByTag(tagKey, tagValue)
Returns all data records (latest version) matching a tag.
data.findByTag("env", "production")data.findBySpec(modelName, specName)
Returns all data records for a model matching a given output spec name. Inside a workflow run, results are scoped to data produced during that run.
data.findBySpec("scanner", "result")data.query(predicate)
Runs a CEL predicate against all data artifacts. Returns matching records.
data.query("modelName == \"scanner\" && size > 1000")data.query(predicate, select)
Runs a predicate with a projection expression that transforms each matching row into the specified shape.
data.query("modelName == \"scanner\"", "attributes.status")
data.query("size > 1000", "{\"name\": name, \"size\": size}")File Functions
file.contents(modelName, specName)
Returns the text content of a file data item, or null.
file.contents("config-gen", "output")Data Query Predicates
The data.query() function and swamp data query command evaluate CEL
predicates against data artifacts. Each row exposes the DataRecord fields as
top-level variables.
Available Query Fields
id, name, version, createdAt, attributes, tags, modelName,
modelType, specName, dataType, contentType, lifetime, ownerType,
streaming, size, content
When using data.query(), the attributes and content fields are lazy-loaded
only when referenced in the predicate or projection. Other data functions
(data.latest(), data.version(), etc.) load these fields eagerly.
Projections
A projection transforms each matching row into the shape specified by the
expression. The second argument to data.query() (or --select flag on the
CLI) is evaluated against each matching row, and the result replaces the full
DataRecord in the output.
data.query("modelName == \"hello-world\"", "attributes.stdout")
data.query("size > 1000", "{\"name\": name, \"size\": size}")Types
Primitives
| Type | CEL Literal | Description |
|---|---|---|
int |
42, 0xFF |
64-bit signed integer |
uint |
42u |
64-bit unsigned integer |
double |
3.14, 3e2 |
64-bit floating-point |
string |
"hello", 'hello' |
Unicode text |
bool |
true, false |
Boolean |
bytes |
b"data" |
Binary data |
null |
null |
Null value |
Aggregates
| Type | Literal | Description |
|---|---|---|
list<T> |
[1, 2, 3] |
Ordered collection |
map<K, V> |
{"key": value} |
Key-value mapping |
Strings
Triple-quoted strings span multiple lines. Raw strings (r"...") disable escape
processing.
"""multi
line"""
r"no\escape"Operators
Arithmetic
| Operator | Types | Result |
|---|---|---|
+ |
int, double, string, list, bytes |
Addition, concatenation |
- |
int, double |
Subtraction |
* |
int, double |
Multiplication |
/ |
int, double |
Division |
% |
int, uint |
Remainder |
- |
int, double (unary) |
Negation |
Arithmetic operators work across int and double without explicit conversion.
This covers +, -, *, /, and %.
Comparison
| Operator | Description |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
Logical
| Operator | Description |
|---|---|
&& |
Logical AND (short-circuit) |
|| |
Logical OR (short-circuit) |
! |
Logical NOT |
Membership
| Expression | Description |
|---|---|
value in list |
true if list contains value |
key in map |
true if map has key |
For string containment, use the .contains() method:
"hello world".contains("world") // trueAccess
| Syntax | Description |
|---|---|
obj.field |
Field access |
obj[key] |
Index / bracket access |
cond ? a : b |
Conditional (ternary) |
Precedence (Highest to Lowest)
(),.,[]-- grouping, field access, indexing-(unary),!-- negation*,/,%-- multiplicative+,--- additive==,!=,<,>,<=,>=,in-- comparison&&-- logical AND||-- logical OR? :-- conditional
Built-in Functions
Type Conversion
| Function | Description |
|---|---|
int(x) |
Convert to int from double, string |
uint(x) |
Convert to uint |
double(x) |
Convert to double from int, string |
string(x) |
Convert to string from int, double, bool, bytes |
bool(x) |
Parse boolean from string |
bytes(x) |
UTF-8 encode string |
type(x) |
Returns type of value |
dyn(x) |
Wrap as dynamic type |
Size
size("hello") // 5
size([1, 2, 3]) // 3
size({"a": 1}) // 1
"hello".size() // 5 (method form)String Methods
| Method | Returns | Description |
|---|---|---|
s.contains(sub) |
bool |
Substring check |
s.startsWith(prefix) |
bool |
Prefix check |
s.endsWith(suffix) |
bool |
Suffix check |
s.matches(regex) |
bool |
Regex match |
s.lowerAscii() |
string |
Lowercase |
s.upperAscii() |
string |
Uppercase |
s.trim() |
string |
Trim whitespace |
s.indexOf(sub) |
int |
First occurrence (-1 if absent) |
s.indexOf(sub, offset) |
int |
From offset |
s.lastIndexOf(sub) |
int |
Last occurrence |
s.lastIndexOf(sub, offset) |
int |
Backward from offset |
s.substring(start) |
string |
From start to end |
s.substring(start, end) |
string |
From start to end index |
s.split(sep) |
list |
Split by separator |
s.split(sep, limit) |
list |
Split with max parts |
List/String Join
["a", "b", "c"].join() // "abc"
["a", "b", "c"].join(", ") // "a, b, c"Timestamp and Duration
Constructors
| Function | Returns | Description |
|---|---|---|
timestamp(string) |
Timestamp |
Parse ISO 8601 string |
duration(string) |
Duration |
Parse Go-style duration (e.g., "1h30m", "300ms") |
Valid duration units: ns, us, ms, s, m, h.
Timestamp Methods
All accept an optional timezone string (e.g., "America/New_York"). Default is
UTC.
| Method | Returns | Description |
|---|---|---|
t.getFullYear() |
int |
Four-digit year |
t.getMonth() |
int |
Month (0-based, 0 = January) |
t.getDate() |
int |
Day of month (1-based) |
t.getDayOfWeek() |
int |
Day of week (0 = Sunday) |
t.getDayOfYear() |
int |
Day of year (0-based) |
t.getHours() |
int |
Hour (0-23) |
t.getMinutes() |
int |
Minute (0-59) |
t.getSeconds() |
int |
Second (0-59) |
t.getMilliseconds() |
int |
Millisecond (0-999) |
timestamp("2024-01-15T10:30:00Z").getHours("America/New_York") // 5Duration Methods
| Method | Returns | Description |
|---|---|---|
d.getHours() |
int |
Total hours |
d.getMinutes() |
int |
Total minutes |
d.getSeconds() |
int |
Total seconds |
d.getMilliseconds() |
int |
Total milliseconds |
Arithmetic
timestamp("2024-01-15T10:00:00Z") + duration("1h") // Timestamp
timestamp("2024-01-16T00:00:00Z") - timestamp("2024-01-15T00:00:00Z") // Duration
duration("1h") + duration("30m") // DurationMacros
has()
Tests whether a field exists without throwing on missing properties.
has(model.scanner.resource)
has(attributes.optional_field)all()
True if all elements satisfy the predicate. True for empty lists.
[1, 2, 3].all(x, x > 0) // trueexists()
True if any element satisfies the predicate.
[1, 2, 3].exists(x, x > 2) // trueexists_one()
True if exactly one element satisfies the predicate.
[1, 2, 3].exists_one(x, x > 2) // truefilter()
Returns elements that satisfy the predicate.
[1, 2, 3, 4].filter(x, x > 2) // [3, 4]map()
Transforms each element. With three arguments, filters then transforms.
[1, 2, 3].map(x, x * 2) // [2, 4, 6]
[1, 2, 3, 4].map(x, x > 2, x * 10) // [30, 40]