pub struct QedStep {Show 32 fields
pub name: String,
pub argv: Vec<String>,
pub cwd: Option<String>,
pub env: HashMap<String, String>,
pub timeout: Option<u64>,
pub on_fail: OnFail,
pub produces: Vec<ProducedArtifact>,
pub runtime: Option<TaskRuntime>,
pub kind: StepKind,
pub image: Option<String>,
pub tag: Option<String>,
pub push: bool,
pub platforms: Vec<String>,
pub binary_path: Option<String>,
pub triple: Option<String>,
pub package: Option<String>,
pub context: Option<PathBuf>,
pub load: bool,
pub sub_pipeline: Option<SubPipelineConfig>,
pub outputs: Vec<OutputDecl>,
pub gha_workflow: Option<GhaWorkflowConfig>,
pub import: Option<ImportConfig>,
pub matrix: Option<MatrixSpec>,
pub enabled: bool,
pub activation: StepActivation,
pub if_cond: Option<String>,
pub background: bool,
pub background_until: Option<String>,
pub wait_for: Option<WaitForConfig>,
pub manifest_stitch: Option<ManifestStitchConfig>,
pub platform: Option<PlatformSpec>,
pub toolchain: Option<ToolchainSpec>,
}Fields§
§name: String§argv: Vec<String>§cwd: Option<String>§env: HashMap<String, String>§timeout: Option<u64>Per-step budget in seconds (R603-B6). Every pipeline TOML has always
written seconds (timeout = 1800 for a 30-minute cargo check,
timeout = 9000 for the 2.5h rusty-v8 build), but the runner used to
lower this with Millis::from_ms, reading 9000 as 9 milliseconds-worth
of seconds — i.e. 9s. That stayed invisible for local steps (the local
driver never enforces spec.timeout) and silently killed every long
REMOTE step at 1/1000th of its budget. Lower it with
[Millis::from_secs], never from_ms.
on_fail: OnFail§produces: Vec<ProducedArtifact>Release artifacts this step builds, declared so an Outcome::Publish
can collect + upload them into the R2 release channel (R330-F3). Only
the artifacts of successful steps are collected. Defaults to empty —
most steps (check, typecheck) produce nothing publishable.
runtime: Option<TaskRuntime>How this step is sandboxed. None defers to the pipeline default
(resolved from --where: local ⇒ Native, remote ⇒ Container). Setting
it explicitly in TOML pins the runtime regardless of where the
pipeline runs — used by build-image steps that must always be
containerised.
kind: StepKindWhich step variant this is. Defaults to StepKind::Subprocess —
existing TOML and Rust literals omit the field. Set to
StepKind::BuildImage to build an image instead of running argv.
image: Option<String>Catalog entry name resolved by the image catalog loader (R381-T1).
Required when kind = build-image; used by Subprocess only as a
nominal hint until the per-step image-override path is wired through
(R381 follow-up — see runner notes).
tag: Option<String>Output tag when kind = build-image. Defaults to the step’s name.
push: boolWhen kind = build-image, push the resulting image to its registry
after a successful build. Ignored for other kinds.
platforms: Vec<String>For kind = build-image: docker --platform values the image is built
for (e.g. ["linux/amd64"]). Empty (the default) means host-native —
buildx picks the daemon’s own platform, which is what every pre-existing
build-image step got.
This is the image platform, distinct from platform.target (the Rust
triple a build produces). A foreign-arch entry here does NOT authorize
emulation: the runner refuses to build a foreign platform on a local
docker daemon (that is QEMU by another name) unless the step also
declares platform = { native = true, … }, which routes it to an
arch-matched build-worker instead.
binary_path: Option<String>For kind = package-native-tarball (R407-T2): filesystem path to the
static musl Rust binary produced by an earlier build step. Resolved
relative to the camp root.
triple: Option<String>For kind = package-native-tarball (R407-T2): target-triple shorthand
(e.g. x86_64-unknown-linux-musl) baked into the tarball stem and the
emitted manifest. None resolves to the build host’s triple at
packaging time.
package: Option<String>For kind = musl-static-preflight (R407-T3): workspace member name
to gate (e.g. yubaba, yah). The runner walks its transitive dep
closure and fails if any crate in
crate::preflight::KNOWN_GLIBC_ONLY_CRATES appears.
context: Option<PathBuf>For kind = build-image: docker build context directory, resolved
relative to the camp root. Defaults to . (camp root itself) when
absent — the same behaviour as before this field existed. Use this
to point at a staging directory assembled by an earlier subprocess
step (e.g. context = "target/yah-yubaba-ctx").
load: boolFor kind = build-image: load the finished image into the local
docker daemon with --load instead of writing an OCI archive.
Use in dev pipelines where the image must be immediately runnable.
Mutually exclusive with multi-platform builds; ignored when
push = true.
sub_pipeline: Option<SubPipelineConfig>For kind = sub-pipeline (W201-F1): the target to resolve as a child
pipeline, params to forward, and what to roll up into the parent. The
runner recurses into the resolved child as a nested QedRun parented
to the caller; ProducedArtifacts and named outputs flow back per
SubPipelineCollect.
outputs: Vec<OutputDecl>Named outputs this step may emit (W201-F4). Subprocess steps write
KEY=VALUE\n lines to $YAH_OUTPUTS; the runner captures them in
StepStatus::outputs for downstream sibling substitution via
${{ steps.<name>.outputs.<key> }}. Declaring outputs here is
advisory — undeclared keys are captured too.
gha_workflow: Option<GhaWorkflowConfig>For kind = gha-workflow (W200-F9): path to a
.github/workflows/*.yml, with optional event + dispatch inputs.
Resolved relative to the camp root. Required when kind = gha-workflow;
validate() rejects misconfiguration at parse time the same way
sub_pipeline does.
import: Option<ImportConfig>For kind = import (W224, R533-F1): the imported workflow.yml source,
its pinned blake3 content hash, and the virtual/materialize toggle.
Required when kind = import; validate() rejects misconfiguration at
parse time the same way gha_workflow / sub_pipeline do. The runner
re-reads the source, recomputes its hash, and expands it into the native
subgraph at plan time (crate::import).
matrix: Option<MatrixSpec>Step-level matrix (R505). When present, crate::matrix::plan fans
this single step out into N step instances within the parent job, each
carrying its row’s coord substituted into argv / env / cwd and
its name suffixed with the coord pairs.
enabled: boolDeclarative on/off switch (R506). When false, the runner skips the
step at plan-time: a StepStatus with RunStatus::Skipped is still
emitted so the dashboard renders the row, but no subprocess / container
/ sub-pipeline is launched. Defaults to true. Orthogonal to
Self::activation — enabled = false means “I explicitly want this
off for this run”; status = "stubbed" means “this is a planned but
not-yet-implemented surface”. The runner treats both as skip; the
dashboard renders them distinctly.
activation: StepActivationDeclarative lifecycle state (R506). active (the default) runs the
step normally; stubbed marks the step as a visible-but-skipped row
— typically a planned target (e.g. ios-device, rpi0) that hasn’t
been wired up yet but should still appear in the dashboard so bit-rot
is observable. The runner skips stubbed steps the same way it skips
enabled = false steps; the on-demand --include-stubbed override
runs them.
if_cond: Option<String>Runtime conditional (R506) — a ${{ <expr> }}-style expression
evaluated against the W201-F4 context (matrix coords, env, prior
steps.<X>.outputs.<Y>, plus success() / failure()). When the
expression evaluates to a falsy value the step is skipped at
dispatch-time with RunStatus::Skipped. Bare expressions without
${{ }} delimiters are evaluated as implicit-expression bodies (GHA
semantics). Layered above Self::enabled / Self::activation:
a step that is enabled = false is skipped before if is consulted.
Layered above Self::on_fail: this gate is pre-execution, while
on_fail is post-failure propagation.
background: boolRun this step as a long-lived sidecar (R513-F2, W207 Gap #4). A
background step is spawned — run() emits its StepStarted, kicks
the subprocess onto its own task, and immediately advances to the next
step instead of awaiting completion. The classic case is a server a
later step talks to: yah-camp, vite preview, a mock auth broker.
Without this every such step would block the pipeline forever.
Lifecycle: the sidecar lives until it is reaped. With
Self::background_until unset it is reaped at the end of the step
loop (after the last foreground step, before terminal outcomes); with
background_until = "<step>" it is reaped the moment that named step
finishes. Reaping a still-running sidecar kills it (kill_on_drop) and
records RunStatus::Success — a healthy server torn down on schedule
is the expected path, not a failure. A sidecar that exits on its own
before reap surfaces its real exit status: clean → Success, non-zero
→ Failed (a sidecar that crashes mid-pipeline is a genuine problem and
flips the run to Failed so on_fail fires).
Log story: a background step’s stdout/stderr keep streaming as
StepOutput events tagged with the step index, identical to a
foreground step — a misbehaving sidecar’s logs are exactly what you want
when triaging, so v1 never silences them; collapsing a chatty sidecar’s
pane is a consumer concern.
v1 scope: background is only valid on StepKind::Subprocess steps run
locally (the [crate::ForgeExecutor] spawn path). validate() rejects
other kinds; the runner rejects --where=remote background steps
(yubaba-supervised remote sidecars are a separate lifecycle). Defaults
to false — omitted from every existing pipeline.
background_until: Option<String>Reap this background step right after the named step finishes, rather
than at the end of the pipeline (R513-F2). Implies Self::background.
The named step must appear after this one in the pipeline — the runner
rejects a forward-reference to a missing or earlier step at run start, so
a typo fails loudly instead of silently deferring the reap to pipeline
end. None (the default) ⇒ reap at end of the step loop.
wait_for: Option<WaitForConfig>For kind = wait-for (R513-F3, W207 Gap #5): the network endpoint to
poll and the timeout/interval budget. Required when kind = wait-for;
validate() rejects misconfiguration (missing block, no target, both
targets) at parse time the same way sub_pipeline / gha_workflow do.
None for every other step kind.
manifest_stitch: Option<ManifestStitchConfig>For kind = manifest-stitch (R590-F2): the arch-agnostic target tag and
the per-arch source tags to fold into a multi-arch manifest list.
Required when kind = manifest-stitch; validate() rejects a missing
block / empty target / no sources at parse time the same way wait_for
does. None for every other step kind.
platform: Option<PlatformSpec>Structured platform intent (R531-F2, W222): what target this step
produces and the arch of the base image it pulls. host is not
declared here — it’s self-detected per runner (R531-T1) and composed
in at plan time via crate::platform::Platform::compose. None (the
default, omitted from every existing pipeline file) means host-native /
no foreign-arch container — the common case. An explicit
platform.target overrides the legacy per-kind triple field as the
composed target.
toolchain: Option<ToolchainSpec>Per-step toolchain pin overrides (R507, W208 pillar 3). An inline table
toolchain.<tool> = "..." whose entries crate::toolchain::effective_pins
overlays on the pipeline-level [pipeline.toolchain] — so a single
build-android step can pin ndk = "r26d" while the pipeline pins
r27. None (the default) ⇒ the step inherits the pipeline pins
unchanged. See crate::toolchain.
Implementations§
Source§impl QedStep
impl QedStep
Sourcepub fn is_background(&self) -> bool
pub fn is_background(&self) -> bool
true when this step runs as a long-lived sidecar (R513-F2) — either
background = true or a background_until target is set. See
Self::background for the lifecycle.
Sourcepub fn validate(&self) -> Result<(), StepValidationError>
pub fn validate(&self) -> Result<(), StepValidationError>
Validate kind-specific invariants. Called by the TOML loader
(PipelineLoader::load_from_file) before the pipeline reaches the
runner — fail loudly at parse time, not at execution time.
Sourcepub fn validate_finally(&self) -> Result<(), StepValidationError>
pub fn validate_finally(&self) -> Result<(), StepValidationError>
Validate a step that lives in a pipeline’s [[finally]] teardown block
(R513-F4). Runs the normal kind-specific Self::validate first, then
enforces the v1 finally-only constraint: teardown is a plain
StepKind::Subprocess and never a background sidecar (a detached
teardown step has no one to reap it). Composite / image / sub-pipeline
teardown is a documented follow-up.
Trait Implementations§
Source§impl Default for QedStep
Deliberately not #[derive(Default)].
impl Default for QedStep
Deliberately not #[derive(Default)].
enabled carries #[serde(default = "default_enabled")] = true, and a
derived Default would give it false — so QedStep { name, argv, ..Default::default() } would build a step that the runner silently skips,
and a pipeline made only of such steps reports Success having run nothing.
(R633 hit exactly that: a synthesized image build “succeeded” in 40 ms.)
Round-tripping serde’s own defaults makes the two definitions the same
definition, so a future #[serde(default = …)] on some other field cannot
reintroduce the divergence. name is the only field without a serde default.
Source§impl<'de> Deserialize<'de> for QedStep
impl<'de> Deserialize<'de> for QedStep
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for QedStep
impl RefUnwindSafe for QedStep
impl Send for QedStep
impl Sync for QedStep
impl Unpin for QedStep
impl UnsafeUnpin for QedStep
impl UnwindSafe for QedStep
Blanket Implementations§
impl<T> Allocation for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<T> Fruit for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more