First half of the slice that runs a job through the control plane: without
this the plan has no way onto the wire, so it lands with the protocol rather
than ahead of it.
The plan travels as itself
D018 settled that the protocol carries the compiled plan, not a parallel set
of DTOs. The plan model therefore carries serde and check-architecture.sh
stops rejecting it — while still rejecting serde_json, prost, tokio, reqwest, gix and bollard, because choosing an encoding and owning a
transport remain someone else's job.
Types that validate on construction deserialize through those constructors:
identifiers via TryFrom<String>, PositiveDuration as whole minutes, and RunnerSelection through try_new, so a plan cannot arrive naming neither a
group nor a label. RunnerSelection serializes by reference rather than
through Into, which keeps a Clone bound out of every node holding one.
Expressions
ExpressionProgram now keeps the source it parsed and renders it through Display. A condition that inherits an implicit status check rewrites the
source (success() && (…)) instead of splicing nodes, so the program still
parses back to exactly the tree it carries. Serialization is that source; no
syntax tree reaches the wire.
Schema version
VersionedPlan pairs a plan with PlanSchemaVersion, which decodes only the
version this build speaks and fails with PlanSchemaError::Unsupported
otherwise. Internal contract for 0.3, versioned from the first day per D018.
tests/plan_wire.rs compiles a workflow with a container, a matrix strategy, a
timeout, an if:, an env template and an action step, then asserts the plan
round-trips unchanged, that the schema version is on the wire and an unknown one
is refused, that the condition source appears while no syntax tree does, and
that a forged job key fails to decode.
First half of the slice that runs a job through the control plane: without
this the plan has no way onto the wire, so it lands with the protocol rather
than ahead of it.
## The plan travels as itself
`D018` settled that the protocol carries the compiled plan, not a parallel set
of DTOs. The plan model therefore carries `serde` and `check-architecture.sh`
stops rejecting it — while still rejecting `serde_json`, `prost`, `tokio`,
`reqwest`, `gix` and `bollard`, because choosing an encoding and owning a
transport remain someone else's job.
Types that validate on construction deserialize through those constructors:
identifiers via `TryFrom<String>`, `PositiveDuration` as whole minutes, and
`RunnerSelection` through `try_new`, so a plan cannot arrive naming neither a
group nor a label. `RunnerSelection` serializes by reference rather than
through `Into`, which keeps a `Clone` bound out of every node holding one.
## Expressions
`ExpressionProgram` now keeps the source it parsed and renders it through
`Display`. A condition that inherits an implicit status check rewrites the
source (`success() && (…)`) instead of splicing nodes, so the program still
parses back to exactly the tree it carries. Serialization is that source; no
syntax tree reaches the wire.
## Schema version
`VersionedPlan` pairs a plan with `PlanSchemaVersion`, which decodes only the
version this build speaks and fails with `PlanSchemaError::Unsupported`
otherwise. Internal contract for 0.3, versioned from the first day per D018.
## Verification
`cargo fmt --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`,
`cargo test --workspace --all-targets --all-features`, `check-architecture.sh`,
`check-rust-loc.sh`.
`tests/plan_wire.rs` compiles a workflow with a container, a matrix strategy, a
timeout, an `if:`, an env template and an action step, then asserts the plan
round-trips unchanged, that the schema version is on the wire and an unknown one
is refused, that the condition source appears while no syntax tree does, and
that a forged job key fails to decode.
The plan model gains serde, so the plan the control plane compiles is the plan
the runner decodes rather than a copy of it converted at a boundary. Validated
types deserialize through their own constructors, expressions travel as source
and are rebuilt by parsing, and VersionedPlan refuses a schema version this
build does not speak.
A plan that travels needs the same two things a workflow file needs: which
dialect produced it and the bytes. Keeping the payload opaque lets a plan be
moved without knowing which expression type it holds.
domain_map! is exported and used by consumers whose maps hold types that do not
serialize. Only DynamicObject travels inside a plan, so only DynamicObject gets
the impls.
dtolnay/rust-toolchain publishes versions as branches whose heads move, so a
commit pinned today is unreachable from any ref once the branch advances and a
fresh mirror no longer carries it. The branch is what a mirror can serve.
The runner on production cannot resolve an action reference named like a
version, so dtolnay/rust-toolchain is unusable there until it ships the fix.
The toolchain is pinned by image digest instead, which the job container
resolves without any action at all.
The compiler accepted only an already-specialised matrix, because the forge
expanded it before the runner ever saw it. The control plane compiles the
workflow as written, so a dimension now holds all its values and expansion
turns one plan into one plan per combination.
A node is still only ever handed a plan with a single value per dimension:
expansion happens before assignment, never on the node.
day01
merged commit 3336ee3889 into %!s()2026-08-05 15:31:59 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First half of the slice that runs a job through the control plane: without
this the plan has no way onto the wire, so it lands with the protocol rather
than ahead of it.
The plan travels as itself
D018settled that the protocol carries the compiled plan, not a parallel setof DTOs. The plan model therefore carries
serdeandcheck-architecture.shstops rejecting it — while still rejecting
serde_json,prost,tokio,reqwest,gixandbollard, because choosing an encoding and owning atransport remain someone else's job.
Types that validate on construction deserialize through those constructors:
identifiers via
TryFrom<String>,PositiveDurationas whole minutes, andRunnerSelectionthroughtry_new, so a plan cannot arrive naming neither agroup nor a label.
RunnerSelectionserializes by reference rather thanthrough
Into, which keeps aClonebound out of every node holding one.Expressions
ExpressionProgramnow keeps the source it parsed and renders it throughDisplay. A condition that inherits an implicit status check rewrites thesource (
success() && (…)) instead of splicing nodes, so the program stillparses back to exactly the tree it carries. Serialization is that source; no
syntax tree reaches the wire.
Schema version
VersionedPlanpairs a plan withPlanSchemaVersion, which decodes only theversion this build speaks and fails with
PlanSchemaError::Unsupportedotherwise. Internal contract for 0.3, versioned from the first day per D018.
Verification
cargo fmt --check,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo test --workspace --all-targets --all-features,check-architecture.sh,check-rust-loc.sh.tests/plan_wire.rscompiles a workflow with a container, a matrix strategy, atimeout, an
if:, an env template and an action step, then asserts the planround-trips unchanged, that the schema version is on the wire and an unknown one
is refused, that the condition source appears while no syntax tree does, and
that a forged job key fails to decode.