Skip to content

Feat: $( ) conditions in a step's if, and a step's status under steps - #272

Merged
briankane merged 8 commits into
kubevela:mainfrom
briankane:feat/cel-conditions
Oct 15, 2026
Merged

briankane merged 8 commits into
kubevela:mainfrom
briankane:feat/cel-conditions

Conversation

@briankane

@briankane briankane commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Description of your changes

Merge after #271, which this branch sits on. Only the last three commits are this PR's.

A step's if is CUE today. With this PR, an if that is exactly one $( ) expression is CEL, evaluated with the same roots as the step's properties (#271), in a run that resolves expressions.

steps:
  - name: notify
    type: notification
    if: '$(steps.build.failed && context.name != "dev")'
  - name: cleanup
    type: cleanup
    if: status.build.failed            # CUE, as before
  • Nothing that works changes. Only a value that is entirely one $( ) is CEL. always, an empty if, any CUE expression, and a $( inside a CUE string are unchanged. A whole $(...) never worked as CUE ($ is an identifier, so $(x) is a call to something undefined), and in a run without expressions enabled a $( ) if still fails as it does today.
  • Roots: context, steps, each and the host's roots, as in properties. A condition must be a bool.
  • steps.<name> now offers a finished step's status: phase, message, reason, succeeded, failed, skipped, timeout, failedAfterRetries, terminate, as status.<name> does in a CUE if. A read waits while the step has still to finish; it works in properties too.
  • Ordering: a condition's reads of steps are implied dependsOn, as a property's are (Feat: $( ) expressions in workflow step properties, resolved when each step runs #271), so if: $(steps.build.failed) waits for build without a written dependsOn.
  • Loops: a loop step's own if applies to the loop as a whole, decided once, so it cannot read each; a looping group's sub-steps' if is decided each pass, with that pass's each.
  • Waiting: a read still to be answered holds the step back, running, rather than skipping it.
  • Admission: the webhook checks a $( ) if as it does properties: roots, the steps and outputs it reads, the order it can wait in, cycles, each placement.
  • The executor's pre-check hook cannot reach the evaluator, so the runners (a step, a step group, a loop) pass the condition in PreCheckOptions.Condition; PreCheckResult.Wait carries the wait. PropertyEvaluator gains Condition.

I have:

  • Read and followed KubeVela's pull request process.
  • All commits are signed off (DCO).
  • Related Docs updated properly: to follow.
  • Run make reviewable to ensure this PR is ready for review: golangci-lint and staticcheck with Go 1.27, 0 issues.

How has this code been tested

  • Unit: condition detection; Condition (true, false, every root, a host root given the run, not a bool, waiting); each status field in each finished phase, waiting while running or retrying, a loop's status from outside and inside, a step reading its own status; the executor's decision (true, false, wait, error, CUE left to CUE, no condition offered); a group's and a loop's condition (skip, wait, CUE, no expressions; a loop's decided once); validation of conditions and their implied dependencies.
  • e2e (e2e/expressions_test.go) on k3d: a WorkflowRun where a true condition runs its step, a false one skips it, a looping group's sub-step is decided each pass, a CUE if beside them is unchanged, and the reads are recorded as dependencies. With the condition switched off in the executor, the spec fails.

Summary by cubic

Step properties, forEach.items, and a step's if can now be $( ) CEL expressions, resolved when the step runs with the same roots as its properties (context, steps, the host's roots, and each inside a loop pass).

  • steps.<name>.outputs.<output> reads an output the step declares; steps.<name> exposes a finished step's status (phase, succeeded, failed, skipped, timeout, and so on). A read waits while the step has still to finish; a failed step fails the read, and a skipped step is read as skipped — its status never changes, and its outputs read as absent.
  • Reads of steps are implied dependsOn, which—unlike a written one—does not skip the reader when the step read is skipped. The run status records what each step waits on and whether dependsOn or an expression implied it; restarting a step also restarts every step that reads it.
  • A whole $( ) if is CEL only when the run opts in via the EnableCelExpressions feature gate and the workflowrun.oam.dev/cel-expressions annotation; any other if, including $( ) inside CUE text, stays CUE. An escaped $( still counts as an expression.
  • A loop step's own if is decided once for the loop and cannot read each; a looping group's sub-step if is decided each pass, and a group whose condition errors runs none of its sub-steps.
  • Admission checks every expression without evaluating it and refuses a read that does not compile, names a step or output that does not exist, uses each outside a forEach body, or could never be answered (the reader itself, a later step in order, a cycle), plus reads below a status field. forEach.items must be one sole expression; text around it is refused.

Written for commit 2443bd6. Summary will update on new commits.

View guided diff

briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.99145% with 41 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.56%. Comparing base (d7db9c4) to head (1e4ff70).
⚠️ Report is 34 commits behind head on main.

Files with missing lines Patch % Lines
pkg/executor/workflow.go 71.11% 13 Missing ⚠️
pkg/expressions/conditions.go 74.19% 8 Missing ⚠️
pkg/expressions/expressions.go 97.22% 4 Missing ⚠️
pkg/expressions/dependencies.go 97.63% 3 Missing ⚠️
pkg/expressions/evaluator.go 88.46% 3 Missing ⚠️
pkg/tasks/builtin/for_each.go 88.00% 3 Missing ⚠️
pkg/tasks/custom/task.go 93.18% 3 Missing ⚠️
pkg/expressions/validation.go 97.75% 2 Missing ⚠️
pkg/generator/generator.go 90.90% 1 Missing ⚠️
pkg/tasks/builtin/for_each_validation.go 88.88% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main     #272       +/-   ##
===========================================
+ Coverage   62.49%   79.56%   +17.07%     
===========================================
  Files          62       82       +20     
  Lines        4415     6275     +1860     
===========================================
+ Hits         2759     4993     +2234     
+ Misses       1324     1282       -42     
+ Partials      332        0      -332     
Flag Coverage Δ
e2etests 38.27% <67.69%> (?)
unit-test 73.22% <91.28%> (+10.73%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 43 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread pkg/tasks/builtin/for_each.go
Comment thread pkg/expressions/dependencies.go
Comment thread pkg/webhook/v1alpha1/workflowrun/validation.go
Comment thread pkg/expressions/expressions.go Outdated
Comment thread charts/vela-workflow/README.md Outdated
Comment thread pkg/tasks/builtin/step_group.go
Comment thread pkg/features/controller_features.go Outdated
Comment thread pkg/tasks/builtin/for_each_validation.go Outdated
Comment thread pkg/types/types.go Outdated
Comment thread api/v1alpha1/types.go Outdated
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
@briankane
briankane force-pushed the feat/cel-conditions branch 2 times, most recently from c14b311 to 1e4ff70 Compare October 15, 2026 10:01
An if that is exactly one $( ) expression is evaluated as CEL when the run
resolves expressions, reading what the step's properties may: context, steps,
each and the host's roots. Any other if is CUE as before, and so is a $( ) if
in a run that does not resolve expressions, so no working condition changes.
A condition must be a bool; a read still to be answered holds the step back
rather than skipping it.

steps.<name> now offers the step's status once it has finished, in any phase:
phase, message, reason, and succeeded, failed, skipped, timeout,
failedAfterRetries and terminate, as status.<name> does in a CUE if.

The executor's pre-check cannot reach the evaluator, so the runners pass the
condition in PreCheckOptions.Condition; a loop's own if is decided once for
the loop.

Signed-off-by: Brian Kane <briankane1@gmail.com>
Admission checks a $( ) if as it does a step's properties: the roots offered
where it sits, the steps and outputs it reads, and the order it can wait in.
A loop step's own if is decided before any pass, so it cannot read each; a
looping group's sub-steps' if can. Its reads of steps are implied dependsOn,
as a property's are. A CUE if is not read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
A condition true for its run runs the step and a false one skips it; a
looping group's sub-step's condition is decided each pass with that pass's
each; a CUE if beside them behaves as before; and the conditions' reads are
recorded as dependencies.

Signed-off-by: Brian Kane <briankane1@gmail.com>
Over every phase and reason a step can end in, each status field, and inputs
against the outputs they read, a condition and its CUE equivalent decide the
same; a condition waits for a step still to finish. The one difference: CUE's
status leaves out an empty reason, so a CUE if reading it fails, where a
condition reads it as "".

Signed-off-by: Brian Kane <briankane1@gmail.com>
…OnFailure

Under that gate a failed step may be resumed, so only a succeeded one counted as
finished, and a read of a skipped step's status waited for ever. A skipped step
never runs, so its status is read as it is.

Signed-off-by: Brian Kane <briankane1@gmail.com>
The group was marked skipped but the skip was not recorded where its sub-steps
look for it, so they ran. And a loop's condition is now tested over a run that
starts its passes, so being decided once is shown, not only a false one.

Signed-off-by: Brian Kane <briankane1@gmail.com>
A status field is a value, so steps.build.succeeded.foo can never resolve;
admission now says so instead of the run failing.

Signed-off-by: Brian Kane <briankane1@gmail.com>
The feature gate, its chart value, ExpressionOptions and the expression
dependency source all cover a step's if as well as its properties.

Signed-off-by: Brian Kane <briankane1@gmail.com>
@briankane
briankane force-pushed the feat/cel-conditions branch from 1e4ff70 to 2443bd6 Compare October 15, 2026 10:15
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
@briankane
briankane merged commit 3fc7db6 into kubevela:main Oct 15, 2026
18 checks passed
briankane added a commit to briankane/kubevela that referenced this pull request Oct 15, 2026
Build against kubevela/workflow 3fc7db6 (kubevela/workflow#272), which decides a
step by a $( ) condition.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ela fork

Pins github.com/oam-dev/kubevela to briankane/kubevela a6aacd0b (feat/cel-conditions,
on feat/typed-reads), which exports the kinds admission gives component and loop
reads and the loaders it types them with, and checks a step's $( ) condition.
Requires github.com/kubevela/workflow at 3fc7db63 (kubevela/workflow#272, merged), as
that commit does.

Never merge: swap the kubevela pin for an upstream version once kubevela#7464 and
kubevela#7465 merge.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/kubevela that referenced this pull request Oct 15, 2026
Build against kubevela/workflow 3fc7db6 (kubevela/workflow#272), which decides a
step by a $( ) condition.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/kubevela that referenced this pull request Oct 15, 2026
Build against kubevela/workflow 3fc7db6 (kubevela/workflow#272), which decides a
step by a $( ) condition.

Signed-off-by: Brian Kane <briankane1@gmail.com>
anoop2811 pushed a commit to kubevela/kubevela that referenced this pull request Oct 15, 2026
#7465)

* Chore: bump kubevela/workflow to the step conditions change

Build against kubevela/workflow 3fc7db6 (kubevela/workflow#272), which decides a
step by a $( ) condition.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat(webhook): check a step's $( ) condition at admission

A step's if that is a $( ) condition is checked as its properties are: the
roots a workflow step offers, so not component; the source bindings it
reads; and its type, which must be a bool. A sub-step of a looping group
types each from the loop's items. A refusal of a whole-value expression,
forEach.items included, shows the expression as the value.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Test(e2e): an Application's workflow decided by $( ) conditions

Conditions read context, a source, a step's status and outputs, and the
loop's index: a true one runs its step, a false one skips it, a looping
group's sub-step is decided each pass, a CUE if beside them is unchanged,
and a condition's reads are recorded as what its step waits on. One that is
not a bool is refused at admission.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Test(webhook): a condition on a field some loop items leave out

$(each.item.wave > 0) over items not all of which have wave is refused
unguarded, since it would fail on those items at run time, and admitted
behind has().

Signed-off-by: Brian Kane <briankane1@gmail.com>

---------

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
Builds against KubeVela 6c4bab5 (kubevela/kubevela#7464 and #7465), which export
the kinds admission gives component and loop reads, the loaders it types them
with, and check a step's $( ) condition; and kubevela/workflow 3fc7db6
(kubevela/workflow#272), as that commit requires.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 15, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 16, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to briankane/velaux that referenced this pull request Oct 16, 2026
…ssion

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>
briankane added a commit to kubevela/velaux that referenced this pull request Oct 16, 2026
…d $( ) conditions in the studio (#979)

* Chore: bump KubeVela to the typed reads, step conditions and forEach key changes

Builds against KubeVela 28372bb (kubevela/kubevela#7464, #7465 and #7468), which
export the kinds admission gives component and loop reads, each.key among them,
the loaders it types them with, and check a step's $( ) condition.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Refactor: follow KubeVela's move of template parsing to kubevela/pkg

KubeVela now parses $( ) values with github.com/kubevela/pkg/cel/template, and its
propexpr package keeps the reads' roots as functions over template.Reference.
ListApplicationResourceTrackers moved from pkg/resourcetracker to pkg/appkeeper.
The calls follow; nothing changes in what they do.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat(expressions): type component reads in the expression check

The check took a value's type from CEL, which has component.<name> as dyn, so
"$(component.db.output.data.host)" in an int parameter passed with a warning while
the same read of a source was refused. KubeVela's admission now types a component
read by what its producer declares, and refuses the mismatch.

A value that is exactly one component read now takes that type, from KubeVela's
schema.ProducedFor, the loader admission types it with: the component's definition
in its namespace then vela-system, at the revision a type@vN names and over its
extends chain, the resources its traits add, an untyped workload under a trait
that patches it, untyped resources under one with patchOutputs, and a trait that
cannot be read treated as doing both. A read the schema has no kind for, such as a
status field or one the template does not write, fits anything. Interpolated text
is still a string, and an expression doing more than reading keeps its CEL type.
Completion and the variables panel still read the schema ConfigMaps.

Source schemas come from schema.SourceSchemaText in place of a direct Get, so a
pinned source such as atlas@v1 is typed by that revision's schema, and editor and
admission find the same definition.

The warning on a dyn value said the webhook refuses it until it is converted. It
admits it against any parameter; the warning now says the value is not checked and
a wrong one fails at render.

each reads stay dyn: the check is not given the step's forEach items.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat(workflow): step expressions in the studio: steps and each, typed like admission

KubeVela resolves a workflow step's $( ) values as each step runs (#7458), so a
step reads steps.<name>.outputs.<output>, an earlier step's declared output, and,
in a forEach body, each.item and each.index. The studio's step forms took no
expressions, and the check let either root through anywhere as dyn.

Step properties now take expressions in an application's workflow. The env and
check requests carry where the step sits, all optional: the step, whether it is a
loop body (a step with forEach, or a sub-step of a group with one), the loop's
forEach items as written, and the workflow's steps with their declared outputs.
Completion lists the other steps' outputs and, in a loop body, each.item with the
fields its items have and each.index.

The check refuses what admission refuses, in its words: steps or each off a
workflow step, each outside a forEach body, and a read of a step or output the
workflow does not declare. A value that is one each read is typed as admission
types it, with schema.EachReadKind over schema.LoopItemKind: a literal list as
written, items read from a source by its schema. A steps read stays dyn, since an
output declares no type. component stays refused on a step.

The loop's items editor sends the workflow's steps too, so a steps read there is
checked. The inputs-based loop wiring is unchanged.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat(workflow): $( ) step conditions in the studio, checked like admission

A step's if that is exactly one $( ) expression is now a CEL condition
(kubevela/workflow#272): it reads what the step's properties read, must give a
bool, and admission types it so (kubevela/kubevela#7465). Any other if is CUE,
as it always was. steps.<name> also offers a finished step's status: phase,
message, reason, succeeded, failed, skipped, timeout, failedAfterRetries and
terminate.

The studio's If field opens in the mode its value is in. Where the application
reads expressions, a toggle switches between CUE and a $( ) expression, with a
hint that a whole $( ) makes it CEL; the expression is edited with completion
and checked as it is written. An if is saved exactly as written either way, so
existing CUE ifs display and save as before, and a pipeline's if stays CUE.

The check takes a condition flag. A condition is typed as admission types it,
against a required bool with the has() rule for a read that may be absent, in
admission's words; any other if is CUE and is not read. A top-level step's own
if is decided once, before its loop, so it reads no each; a looping group's
sub-step decides its if per pass and reads each over the group's items.

Completion offers every other step's status fields, and the check accepts them
wherever step reads are allowed, properties included, with the workflow's hint
for any other steps.<name> read.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): name a map or struct value object, as admission does

The check named a value read as a map, or a component's struct, "map", where
admission names both "object": "$(source.cfg.labels)" in a string parameter read
"this value is map" in the editor and "is object" at deploy. Both now say object.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): refuse component reads as admission does, clusters included

The check refused a component read of itself, of a component the application
does not have, or on a surface that reads none in its own words, and let a read
at an unregistered cluster through, which admission refuses.

It now builds the reading component and asks KubeVela's ValidateComponentReads,
so those refusals, and a misplaced placement, read as admission's. A read at a
cluster is refused when the cluster is not registered, through the same lookup
admission makes. A component read on a surface that reads none gets admission's
roots refusal, and a hyphenated name read with a dot names the whole expression
in index form, as celexpr.Explain does, keeping its quick fix.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): judge source reads by admission's rules

The check left a source read to CEL, so every refusal read "undefined field",
and two that admission makes were missed: a definition whose consumableFrom
leaves out the surface, and a definition that cannot be read.

A source read is now judged as admission judges it, in its words: a binding the
application does not declare, a source reading one not declared before it, a
definition that cannot be read, a consumableFrom that leaves out the surface,
and a path the schema does not declare, walked as admission walks it (optional
fields, keys of an open map, anything below a value of any type). A chained
read's consumableFrom is left to admission, which knows where the outer source
is consumed.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): refuse a read that may be absent feeding a required parameter

Admission refuses a read that may be missing at render, an optional field or a
key of an open map, where it feeds a parameter that must be set, unless has()
guards it. The check only did that for a step's $( ) if, so the editor passed
"$(source.cfg.labels.team)" in a required parameter that admission refused.

The check request takes whether the parameter is required, which the form sends
from the parameter's schema and a save works out from the definition's. A read
that may be absent feeding one is refused with admission's has() advice. The if
and a required parameter share one pass over KubeVela's CheckTypes.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(workflow): judge a step's reads with the workflow's own validation

The check judged steps and each reads by hand from a flat list of steps, so it
missed what the workflow refuses about order: in a workflow run in order, a step
reading a later one ("use runs after first, and a workflow run in order cannot
wait for it"), a read closing a dependsOn cycle, and an output two steps both
declare.

The studio now sends the workflow as it runs: each step's outputs, dependsOn and
whether it loops, a group's steps under it with their mode, and the workflow's
modes. The check builds that workflow with the value in the step being edited,
its if or its properties, and refuses what the workflow's Validate refuses there,
the call admission makes, so the order rules, the self-read and each outside a
loop body come out in the workflow's words. A request without the step or the
list keeps the checks it had. The loop items editor sends the list without a
step, since its value sits in no step's properties.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): declare a source binding whose definition has no schema

A binding was left out of the typed environment when its SourceDefinition
declared no schema or could not be loaded, so a read of it was refused as an
undefined field, beside the source rules' own refusal of a definition that
cannot be loaded. Admission now declares such a binding of any type, and so does
the check: its reads fit anything, and a missing definition is reported once.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Fix(expressions): judge an each.item read that may be absent as admission does

Admission's has() rule now treats an each.item read as possibly absent where
some of the loop's literal items leave the field out, have a shorter list, or
hold an open map, and, for items read from a source, as the source's schema
says. The check judged each reads as always present, so "$(each.item.wave > 0)"
passed the editor in a step's if, or in a required parameter, where admission
refused it.

The check now judges each reads with KubeVela's EachMayBeAbsent over
LoopItemMayBeAbsent, composed as admission composes them, for both the if and a
required parameter.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Test(expressions): equivalence of the check with KubeVela's admission

One table of 100 cases, each run through the expression check, with the step
scope the studio sends, and through KubeVela's ValidateSources and
ValidateWorkflow on the Application the case builds, over one fake client of
definitions, KubeVela's shipped daemon, addon and module among them. Both must
agree on whether the value is refused, and VelaUX's message must carry
admission's. Where the words legitimately differ, the case says why and what both
still say: a type mismatch, which the editor words for the field and both name by
the type found and wanted, a hyphenated name, and the has() rule for a required
parameter. Policies, which VelaUX's forms do not check, record admission's answer
only.

The cases cover component, source, each and steps reads, an each.item read some
items lack, $( ) conditions and CUE ifs, and the roots each surface offers.

Signed-off-by: Brian Kane <briankane1@gmail.com>

* Feat(workflow): offer each.key in a loop body's expressions

A loop body's expression env lists each.key beside each.item and each.index: the
item's key from forEach.key, or its index as text. KubeVela types it as a
string, so the check refuses it where a number is expected.

Signed-off-by: Brian Kane <briankane1@gmail.com>

---------

Signed-off-by: Brian Kane <briankane1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants