wfectl is a command-line client for wfe-server with 17 subcommands
covering the full workflow lifecycle:
* Auth: login (OAuth2 PKCE via Ory Hydra), logout, whoami
* Definitions: register (YAML → gRPC), validate (local compile),
definitions list
* Instances: run, get, list, cancel, suspend, resume
* Events: publish
* Streaming: watch (lifecycle), logs, search-logs (full-text)
Key design points:
* `validate` compiles YAML locally via `wfe-yaml::load_workflow_from_str`
with the full executor feature set enabled — instant feedback, no
server round-trip, no auth required. Uses the same compile path as
the server's `register` RPC so what passes validation is guaranteed
to register.
* Lookup commands accept either UUID or human name; the server
resolves the identifier for us. Display tables show both columns.
* `run --name <N>` lets users override the auto-generated
`{def_id}-{N}` instance name when they want a sticky reference.
* Table and JSON output formats, shared bearer-token or cached-login
auth path, direct token injection via `WFECTL_TOKEN`.
* 5 new unit tests for the validate command cover happy path, unknown
step type rejection, and missing file handling.
Dockerfile.ci ships the prebuilt image used as the `image:` for
kubernetes CI steps: rust stable, cargo-nextest, cargo-llvm-cov,
sccache (configured via WFE_SCCACHE_* env), buildctl for in-cluster
buildkitd, kubectl, tea for Gitea releases, and git. Published to
`src.sunbeam.pt/studio/wfe-ci:latest`.
Proto changes:
* Add `name` to `WorkflowInstance`, `WorkflowSearchResult`,
`RegisteredDefinition`, and `DefinitionSummary` messages.
* Add optional `name` override to `StartWorkflowRequest` and echo the
assigned name back in `StartWorkflowResponse`.
* Document that `GetWorkflowRequest.workflow_id` accepts UUID or
human name.
gRPC handler changes:
* `start_workflow` honors the optional name override and reads the
instance back to return the assigned name to clients.
* `get_workflow` flows through `WorkflowHost::get_workflow`, which
already falls back from UUID to name lookup.
* `stream_logs`, `watch_lifecycle`, and `search_logs` resolve
name-or-UUID up front so the LogStore/lifecycle bus (keyed by
UUID) subscribe to the right instance.
* `register_workflow` propagates the definition's display name into
`RegisteredDefinition.name`.
Crate build changes:
* Enable the full executor feature set on wfe-yaml —
`rustlang,buildkit,containerd,kubernetes,deno` — so the shipped
binary recognizes every step type users can write.
* Dockerfile switched from `rust:alpine` to `rust:1-bookworm` +
`debian:bookworm-slim` runtime. `deno_core` bundles a v8 binary
that only ships glibc; alpine/musl can't link it without building
v8 from source.
Add an optional `name` field to `WorkflowSpec` so YAML authors can
declare a human-friendly display name alongside the existing slug
`id`. The compiler copies it through to `WorkflowDefinition.name`,
which surfaces in definitions listings, run tables, and JSON output.
Slug `id` remains the primary lookup key.
Also adds a small smoke test for the schema generators to catch
regressions in `generate_json_schema` / `generate_yaml_schema`.
Three related host.rs changes that together make the 1.9 name support
end-to-end functional.
1. `WorkflowHost::start()` now calls `persistence.ensure_store_exists()`.
The method existed on the trait and was implemented by every
provider but nothing ever invoked it, so the Postgres/SQLite schema
was never auto-created on startup — deployments failed on first
persist with `relation "wfc.workflows" does not exist`.
2. New `start_workflow_with_name` entry point accepting an optional
caller-supplied name override. The normal `start_workflow` is now a
thin wrapper that passes `None` (auto-assign). The default path
calls `next_definition_sequence(definition_id)` and formats the
result as `{definition_id}-{N}` before persisting. Sub-workflow
children also get auto-assigned names via HostContextImpl.
3. `get_workflow`/`suspend_workflow`/`resume_workflow`/
`terminate_workflow` now accept either a UUID or a human-friendly
name. `get_workflow` tries the UUID index first, then falls back to
name lookup. A new `resolve_workflow_id` helper returns the
canonical UUID so the gRPC log/lifecycle streams (which are keyed
by UUID internally) can translate before subscribing.
Land the `name` field and `next_definition_sequence` counter in the
two real persistence backends. Both providers:
* Add `name TEXT NOT NULL UNIQUE` to the `workflows` table.
* Add a `definition_sequences` table (`definition_id, next_num`) with
an atomic UPSERT + RETURNING to give the host a race-free monotonic
counter for `{def_id}-{N}` name generation.
* INSERT/UPDATE queries now include `name`; SELECT row parsers hydrate
it back onto `WorkflowInstance`.
* New `get_workflow_instance_by_name` method for name-based lookups
used by grpc handlers.
Postgres includes a DO-block migration that back-fills `name` from
`id` on pre-existing deployments so the NOT NULL + UNIQUE invariant
holds retroactively; callers can overwrite with a real name on the
next persist.
Add a `name` field to both `WorkflowDefinition` (optional display name
declared in YAML, e.g. "Continuous Integration") and `WorkflowInstance`
(required, unique alongside the UUID primary key). Instance names are
auto-assigned as `{definition_id}-{N}` via a per-definition monotonic
counter so the 42nd run of `ci` becomes `ci-42`.
Persistence trait gains two methods:
* `get_workflow_instance_by_name` — name-based lookup for Get/Cancel/
Suspend/Resume/Watch/Logs RPCs so callers can address instances
interchangeably as either UUID or human name.
* `next_definition_sequence` — atomic per-definition counter used by
the host at start time to allocate the next N.
This commit wires the in-memory test provider and touches the deno
bridge test helper; the real postgres/sqlite impls follow in the next
commit. UUIDs remain the primary key throughout — names are a second
unique index, never a replacement.
Kubernetes step jobs with a `run:` block were invoked via
`/bin/sh -c <script>`. On debian-family base images that resolves to
dash, which rejects `set -o pipefail` ("Illegal option") and other
bashisms (arrays, process substitution, `{1..10}`). The first line of
nearly every real CI script relies on `set -euo pipefail`, so the
steps were failing with exit code 2 before running a single command.
Switch to `/bin/bash -c` so `run:` scripts can rely on the bash
feature set. Containers that lack bash should use the explicit
`command:` form instead.
Pure formatting pass from `cargo fmt --all`. No logic changes. Separating
this out so the 1.9 release feature commits that follow show only their
intentional edits.
SubWorkflowStep was hard-coding `inputs: serde_json::Value::Null` from
the YAML compiler, so every `type: workflow` step kicked off a child
instance with an empty data object. Scripts in child workflows then
saw empty `$REPO_URL`, `$COMMIT_SHA`, etc. and failed immediately.
Now: when no explicit inputs are set, the child inherits the parent
workflow's data (when it's an object). Scripts in child workflows can
reference the same top-level inputs the parent was started with without
every `type: workflow` step needing to re-declare them.
Local dev runs with the SQLite backend leave `wfe.db{,-shm,-wal}` files
in the repo root, and `workflows.schema.yaml` is a generated artifact
we prefer to fetch from the running server's `/schema/workflow.yaml`
endpoint rather than checking in.
- tonic-reflection for gRPC service discovery
- /schema/workflow.json (JSON Schema from schemars derives)
- /schema/workflow.yaml (same schema in YAML)
- /schema/workflow.proto (raw proto file)
- Multi-stage alpine Dockerfile with all executor features
- Comprehensive configuration reference (wfe-server/README.md)
- Release script (scripts/release.sh)
- Bumped to 1.8.1
Sets step_name on execution pointers when advancing to next steps,
compensation steps, and parallel branch children so that runtime
consumers can identify steps by name without lookup.
Adds WorkflowBuilder::add_step_typed<S>() for adding named, configured
steps directly — needed for parallel branch closures in the CLI.
Makes wire_outcome() public so callers can wire custom step graphs.
Adds StepBuilder::config() to attach arbitrary JSON configuration to
individual steps, readable at runtime via context.step.step_config.
Bumps version to 1.6.1.
Add wfe-server-protos and wfe-server to workspace members.
Update StepExecutionContext constructions with log_sink: None
in buildkit and containerd test files.
Shell step streaming: when LogSink is present, uses cmd.spawn() with
tokio::select! to interleave stdout/stderr line-by-line. Respects
timeout_ms with child.kill() on timeout. Falls back to buffered mode
when no LogSink.
Security: block sensitive env var overrides (PATH, LD_PRELOAD, etc.)
from workflow data injection. Proper error handling for pipe capture.
4 LogSink regression tests + 2 env var security regression tests.
LogSink trait for real-time step output streaming. Added to
StepExecutionContext as optional field (backward compatible).
Threaded through WorkflowExecutor and WorkflowHostBuilder.
Wired LifecyclePublisher.publish() into executor at 5 points:
StepStarted, StepCompleted, Error, Completed, Terminated.
Also added lifecycle events to host start/suspend/resume/terminate.
Full changelog covering v1.0.0, v1.4.0, and v1.5.0 releases.
Also fix containerd integration test default address to handle
Lima socket forwarding gracefully.
879 tests passing. 88.8% coverage on wfe-rustlang.