Let Slack TaskSpawners create TaskPipelines
KN
2d ago+16930+16930+01234567890123456789012345678901234567890123456789-547-547-012345678901234567890123456789
What type of PR is this?
/kind api
What this PR does / why we need it:
Stacked on #1853. Only the top commit is new; review that one. It lets a
Slack TaskSpawner start a TaskPipeline. Combined with #1853's conditional
stages, one Slack front door can route each request to the right agent without
a new CRD, as discussed on #1761.
kind: TaskSpawner
spec:
when:
slack:
channels: ["C0123456789"]
pipelineTemplate:
stages:
- name: route
taskTemplate:
workerPoolRef: {name: router-pool}
prompt: "Reply docs or none: {{.Params.Body}}"
- name: docs
conditions:
- input: '{{index .Stages "route" 0 "Results" "response" | b64dec}}'
operator: In
values: [docs]
taskTemplate: {...}
TaskSpawner.spec.pipelineTemplate.stagescreates a TaskPipeline for each
matching message instead of a Task.- The pipeline gets the same name and dedupe behavior a Task would.
- It carries the spawner label, a controller owner reference, and the Slack
reporting annotations. - It counts toward
status.activeTasksandmaxConcurrency. The
TaskSpawner controller watches TaskPipelines so the count updates.
TaskPipeline.spec.paramsis an immutablemap[string]string, exposed
as.Paramsto stage prompt, branch, and condition templates. The spawner
fills it from the work item's string template variables (for Slack:ID,
Title,Body,URL,Kind). This sidesteps a template clash: the
spawner can't fill in request fields at creation time while leaving
{{.Stages...}}for the pipeline to fill in later.taskTemplatebecomes optional. CEL requires exactly one of
taskTemplateorpipelineTemplate, so every existing manifest still
applies unchanged.
This is the second of three stacked PRs:
- #1853: conditional stages.
- This PR: TaskSpawner
pipelineTemplate. - 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. Depends on #1853.
Special notes for your reviewer:
- Scope is Slack only, by CEL.
pipelineTemplateis rejected with any
other source and withspec.credentials. Each stage sets its own
worker/workerPoolRef.- The polling spawner, webhook handlers, and manual runs each have their own
Task-creation and dedupe logic. Wiring them is follow-up work, and lifting
the restriction later only loosens validation. kelos run --fromand Console manual runs return an error naming the
spawner instead of building a broken Task.
- The polling spawner, webhook handlers, and manual runs each have their own
- How
taskTemplatebecame optional. I usedjson:"taskTemplate,omitzero"
with+optional, keeping the value type. Converting it to a pointer would
touch about 98 references. Withomitzero, a typed Go client omits a zero
taskTemplate, which the integration specs exercise with the typed client.
The three existing spec-level rules that readself.taskTemplateare now
guarded with!has(self.taskTemplate) || .... One spec checks that the
inline-credentials rule still fires for ataskTemplatespawner. - v1alpha1 round-trip.
pipelineTemplateis kept in a
kelos.dev/v1alpha2-pipeline-templateannotation, following the existing
preservation pattern. v1alpha1 always serializestaskTemplate, including
its non-pointercredentials. On the way back up, that empty template is
dropped so the exactly-one rule still holds. - CRD size.
PipelineStageembeds the fullWorkerSpecschema, so the
TaskSpawner CRD grows from about 844 KB to about 1.09 MB of compact JSON.
That is under etcd's 1.5 MiB object limit, but worth knowing.
TestHelmReleaseSecretFitsKubernetesLimitpasses. - Slack server RBAC gains
create/getontaskpipelines.
Testing:
make verify,make test, andmake test-integrationare clean
(179/179 specs, plus 12/12 install specs).- New unit tests:
- Slack happy path: a TaskPipeline with params, stages, owner, and reporting
annotations is created, and no Task is created; - duplicate delivery dedupe;
- a name collision with an unowned pipeline;
BuildTaskPipeline, including a deep-copy check;- ownership checks against a recreated spawner;
- manual-run rejection;
- active-pipeline counting and watch mapping;
.Paramsin prompts and conditions;- conversion round-trip, malformed annotation, and no-annotation cases.
- Slack happy path: a TaskPipeline with params, stages, owner, and reporting
- New integration specs:
- exactly-one-template in both directions;
- the Slack-only source rule;
- the
credentialsrule; - duplicate stage names;
- the existing rule still enforced;
paramsimmutability.
Does this PR introduce a user-facing change?
TaskSpawner accepts `spec.pipelineTemplate` for the Slack source, creating a
TaskPipeline for each matching message instead of a Task. TaskPipeline accepts
`spec.params`, exposed to stage templates as `.Params`; pipeline spawners fill
it from the message. `spec.taskTemplate` is now optional, with exactly one of
`taskTemplate` or `pipelineTemplate` required.
🤖 Generated with Claude Code
Summary by cubic
Lets Slack TaskSpawners create TaskPipelines instead of Tasks, so a single Slack front door can route each message to the right agent using conditional stages.
spec.pipelineTemplate.stagesdefines the pipeline for each matching message; the pipeline keeps Task naming, dedupe, spawner label, owner reference, and Slack reporting annotations, and counts towardmaxConcurrencyandstatus.activeTasks.- TaskPipeline gains
spec.params, an immutable string map exposed to stage templates as.Params; the spawner fills it from the message's string template variables. spec.taskTemplateis now optional — exactly one oftaskTemplateorpipelineTemplateis required via CEL, so existing manifests still apply unchanged.pipelineTemplateis Slack-only (rejected with other sources and withspec.credentials); manual runs (kelos run --from, Console) return an error naming the spawner.- Stages can declare
conditionsthat render an earlier result and compare withIn/NotIn; templates gainb64decto decode base64 results.
Notes
pipelineTemplatesurvives a v1alpha1 round-trip via a preservation annotation; v1alpha1 always serializes ataskTemplate, which is dropped on conversion back so the exactly-one rule holds.- The TaskSpawner CRD grows to about 1.09 MB of compact JSON, still within etcd's 1.5 MiB object limit; Slack server RBAC gains
create/getontaskpipelines.