Add conditional stages to TaskPipeline

KN
2d ago
+914-19

What type of PR is this?

/kind api

What this PR does / why we need it:

Adds conditional stages to TaskPipeline, as discussed on #1761: rather than
a new TaskRouter CRD, routing becomes a pipeline whose first stage decides
and whose later stages are gated on that decision.

- name: incident
  conditions:
    - input: '{{index .Stages "route" 0 "Results" "response" | b64dec}}'
      operator: In          # In | NotIn
      values: [incident]
  • spec.stages[].conditions (0–8, ANDed). Each input is a Go template
    rendered with .Stages, trimmed, and compared exactly against values.
  • Conditions are evaluated when the stage would otherwise start. A stage whose
    conditions are not met is Skipped: it creates no Tasks, reports Total: 0,
    shows up as an empty list under .Stages.<name>, and counts as complete for
    the next stage and for pipeline success. The skip is recorded in status and
    never re-evaluated.
  • A condition input that cannot be rendered fails the stage after the existing
    output-capture grace period. It never silently skips.
  • Pipeline templates gain b64dec, since the agent's free-form answer only
    reaches templates as the base64 response result.

This is the first of three stacked PRs:

  1. This PR: conditional stages.
  2. TaskSpawner pipelineTemplate, so a Slack message can start a TaskPipeline.
  3. Propagate the initiator's Slack reporting onto stage Tasks, so results return to the thread.

Which issue(s) this PR is related to:

Part of #1760 (replaces the approach in #1761).

Special notes for your reviewer:

  • Ordering is unchanged. Stages still run one at a time in declaration
    order. Mutually exclusive branches therefore run only the selected stage.
    Non-exclusive branches serialize; running stages as a DAG would be a
    separate change.
  • Skipped is a stage-only phase. It is a TaskPhase value used only in
    PipelineStageStatus.phase, which has no enum marker, so the schema is
    unchanged. Tasks never report it.
  • Values are a closed set. An agent answer can only select a stage whose
    condition lists that value, so a prompt-injected request costs at most the
    wrong branch on the operator's own menu. docs/reference.md says this.
  • Why a list, not a single condition. conditions is a list from the
    start, because changing a single field to a list later would break existing
    manifests.

Testing:

  • make verify and make test are clean.
  • New unit tests cover:
    • the routed happy path (In selects one branch, the others are Skipped, and the pipeline succeeds);
    • the NotIn fallback;
    • ANDed conditions;
    • a skip staying final after results change;
    • render failure after the grace period;
    • a skipped leading stage;
    • template validation;
    • b64dec errors.
  • New integration specs cover operator, values and input validation, plus a
    dry-run of the new example.
  • A new e2e case routes on a real agent reply.

Does this PR introduce a user-facing change?

TaskPipeline stages accept `conditions`, which gate a stage on earlier stages'
results using `In`/`NotIn` comparisons. A stage whose conditions are not met is
reported as `Skipped` and does not block later stages. Pipeline templates gain
a `b64dec` function for reading the base64-encoded `response` result.

🤖 Generated with Claude Code


Summary by cubic

Adds conditional stages to TaskPipeline, so one pipeline can route between branches at runtime based on earlier stages' results — replacing the TaskRouter CRD approach in #1761 as part of #1760.

A stage declares 0–8 conditions, each rendering a Go template input against .Stages and comparing the trimmed result against values with In or NotIn. A stage whose conditions aren't met is reported Skipped: it creates no Tasks, counts as complete for later stages and pipeline success, and is never re-evaluated. A condition input that cannot be rendered fails the stage after the existing output-capture grace period rather than silently skipping. Pipeline templates gain b64dec, since agent responses only reach templates as the base64 response result.

Behavior

  • Stages still run one at a time in declaration order, so mutually exclusive branches run only the selected stage and non-exclusive branches serialize.
  • Skipped is a TaskPhase value used only in stage status, so the CRD schema is unchanged and Tasks never report it.
  • Condition values are a closed set, so a prompt-injected reply can only select a branch the operator listed.

Notes

  • This is the first of three stacked PRs; the next adds TaskSpawner pipelineTemplate, and the last propagates initiator reporting to stage Tasks.
  • New unit, integration, and e2e coverage; make verify and make test pass.