OSOP Expression Language

March 31, 2026 ยท View on GitHub

Version: 1.0.0-draft


Overview

OSOP uses CEL (Common Expression Language) as its expression language for all when conditions on edges, dynamic string evaluation, and runtime logic. CEL was chosen because it is fast, safe, well-specified, and already adopted by Kubernetes, Google Cloud IAM, and Firebase.

CEL expressions in OSOP are always pure โ€” they cannot produce side effects, make network calls, or modify state. They evaluate to a value (typically boolean for when conditions) based on the available context variables.

Where Expressions Are Used

LocationTypePurpose
edges[].whenbooleanDetermine whether an edge is traversed
triggers[].config.filterbooleanFilter which events activate a trigger
Variable interpolationstringDynamic values in ${...} blocks

Available Context Variables

CEL expressions in OSOP have access to the following context:

inputs

Workflow-level input values.

inputs.environment == "production"
inputs.version.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$")
inputs.tags.exists(t, t == "critical")

outputs.<node_id>

Output values from a completed node. Only nodes that have already executed (upstream in the graph) are available.

outputs.build.digest != ""
outputs.test.passed == true
outputs.test.coverage >= 80.0
outputs.security_scan.vulnerabilities.size() == 0

metadata

Execution metadata provided by the engine.

metadata.run_id != ""
metadata.attempt_number <= 3
metadata.triggered_by == "schedule"

env

Environment variables available to the engine.

env.DEPLOY_ENV == "production"
has(env.FEATURE_FLAG_CANARY)

CEL Type System

OSOP uses the standard CEL type system:

CEL TypeJSON Schema EquivalentExample
boolbooleantrue, false
intinteger42, -1
uintinteger (minimum: 0)42u
doublenumber3.14
stringstring"hello"
bytesstring (format: byte)b"data"
listarray[1, 2, 3]
mapobject{"key": "value"}
null_typenullnull
timestampstring (format: date-time)timestamp("2026-03-31T00:00:00Z")
durationstring (format: duration)duration("5m")

Operators

Comparison

inputs.count == 5
inputs.count != 0
inputs.count > 10
inputs.count >= 10
inputs.count < 100
inputs.count <= 100

Logical

inputs.env == "prod" && outputs.test.passed == true
inputs.env == "staging" || inputs.force_deploy == true
!(outputs.scan.has_vulnerabilities)

Arithmetic

outputs.test.coverage + outputs.integration_test.coverage > 150.0
metadata.attempt_number * 2

String Operations

inputs.branch.startsWith("release/")
inputs.email.endsWith("@example.com")
inputs.name.contains("admin")
inputs.version.matches("[0-9]+\\.[0-9]+\\.[0-9]+")
inputs.message.size() <= 280

List Operations

inputs.tags.size() > 0
inputs.tags.exists(t, t == "urgent")
inputs.tags.all(t, t.size() > 0)
inputs.approvers.exists_one(a, a == "security-lead")
[1, 2, 3].map(x, x * 2)
[1, 2, 3].filter(x, x > 1)

Map Operations

has(outputs.build.metadata.commit_sha)
outputs.build.metadata["commit_sha"]

Ternary (Conditional)

inputs.environment == "production" ? "prod-cluster" : "staging-cluster"

Standard Functions

OSOP engines MUST support the following CEL standard functions:

FunctionDescriptionExample
size()Length of string, list, or mapinputs.name.size()
contains()String contains substringinputs.url.contains("https")
startsWith()String starts with prefixinputs.branch.startsWith("feat/")
endsWith()String ends with suffixinputs.file.endsWith(".yaml")
matches()Regex matchinputs.version.matches("^v[0-9]+")
has()Field existence checkhas(outputs.build.artifact)
type()Runtime type checktype(inputs.count) == int
int()Convert to integerint(inputs.count_str)
uint()Convert to unsigned integeruint(inputs.port)
double()Convert to doubledouble(inputs.threshold)
string()Convert to stringstring(inputs.count)
timestamp()Parse timestamptimestamp("2026-01-01T00:00:00Z")
duration()Parse durationduration("5m30s")

OSOP-Specific Functions

In addition to standard CEL, OSOP defines these extension functions:

FunctionDescriptionExample
status(node_id)Get execution status of a nodestatus("build") == "success"
duration_of(node_id)Get execution duration of a nodeduration_of("build") < duration("10m")
error_of(node_id)Get error message of a failed nodeerror_of("deploy").contains("timeout")
attempt()Current retry attempt numberattempt() <= 3
now()Current timestampnow() - timestamp(inputs.deadline) < duration("1h")

Examples by Edge Mode

Conditional Edge

edges:
  - from: "test"
    to: "deploy-staging"
    mode: "conditional"
    when: "outputs.test.passed == true && inputs.environment == 'staging'"

  - from: "test"
    to: "deploy-production"
    mode: "conditional"
    when: >
      outputs.test.passed == true
      && inputs.environment == 'production'
      && outputs.security_scan.vulnerabilities.size() == 0

Loop Edge

edges:
  - from: "poll-status"
    to: "poll-status"
    mode: "loop"
    when: "outputs.poll_status.state == 'in_progress' && attempt() <= 30"

Event Edge

edges:
  - from: "wait-for-approval"
    to: "deploy"
    mode: "event"
    when: "event.type == 'approval' && event.data.decision == 'approved'"

Error Edge

edges:
  - from: "deploy"
    to: "rollback"
    mode: "error"
    when: "error_of('deploy').contains('OOMKilled') || status('deploy') == 'timeout'"

Expression Validation

OSOP validators SHOULD statically check CEL expressions at validation time:

  1. Parse check: The expression must be syntactically valid CEL.
  2. Type check: Variables referenced must exist in the available context (inputs, upstream outputs).
  3. Return type check: when expressions must return bool.
  4. Safety check: Expressions must not reference undefined variables or use unsupported functions.

Example validator output:

Edge "test -> deploy" expression error:
  when: "outputs.test.passsed == true"
                       ^^^^^^^ Unknown field 'passsed' on outputs.test.
                       Did you mean 'passed'?

Performance Requirements

CEL expressions in OSOP MUST evaluate within 10 milliseconds under normal conditions. Engines SHOULD reject or warn about expressions that are computationally expensive (e.g., deeply nested list comprehensions over large datasets).

Engines MAY cache compiled CEL programs for repeated evaluation.


See SPEC.md for the full protocol specification. See the CEL specification for the complete language reference.