5 Commits

Author SHA1 Message Date
2b244348ca chore: bump version to 1.6.2, update CHANGELOG 2026-04-05 12:45:25 +01:00
de66fef2d6 feat(wfe-core): add add_step_typed() and make wire_outcome public
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.
2026-04-05 12:44:00 +01:00
6c16c89379 fix: add version + registry to wfe-server path deps for publishing 2026-04-05 12:01:02 +01:00
e515ffbe0c chore: bump version to 1.6.1, update CHANGELOG 2026-04-05 11:55:42 +01:00
978109d3fc feat(wfe-core): add step config API for attaching runtime JSON config
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.
2026-04-05 11:52:40 +01:00
7 changed files with 125 additions and 16 deletions

View File

@@ -2,6 +2,19 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [1.6.2] - 2026-04-05
### Added
- **wfe-core**: `WorkflowBuilder::add_step_typed()` for adding named, configured steps in parallel branch closures
- **wfe-core**: `WorkflowBuilder::wire_outcome()` now public for custom graph wiring
## [1.6.1] - 2026-04-05
### Added
- **wfe-core**: `StepBuilder::config()` for attaching arbitrary JSON configuration to individual steps, readable at runtime via `context.step.step_config`
## [1.6.0] - 2026-04-01 ## [1.6.0] - 2026-04-01
### Added ### Added

View File

@@ -3,7 +3,7 @@ members = ["wfe-core", "wfe-sqlite", "wfe-postgres", "wfe-opensearch", "wfe-valk
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "1.6.0" version = "1.6.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://src.sunbeam.pt/studio/wfe" repository = "https://src.sunbeam.pt/studio/wfe"
@@ -38,15 +38,15 @@ redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
opensearch = "2" opensearch = "2"
# Internal crates # Internal crates
wfe-core = { version = "1.6.0", path = "wfe-core", registry = "sunbeam" } wfe-core = { version = "1.6.2", path = "wfe-core", registry = "sunbeam" }
wfe-sqlite = { version = "1.6.0", path = "wfe-sqlite", registry = "sunbeam" } wfe-sqlite = { version = "1.6.2", path = "wfe-sqlite", registry = "sunbeam" }
wfe-postgres = { version = "1.6.0", path = "wfe-postgres", registry = "sunbeam" } wfe-postgres = { version = "1.6.2", path = "wfe-postgres", registry = "sunbeam" }
wfe-opensearch = { version = "1.6.0", path = "wfe-opensearch", registry = "sunbeam" } wfe-opensearch = { version = "1.6.2", path = "wfe-opensearch", registry = "sunbeam" }
wfe-valkey = { version = "1.6.0", path = "wfe-valkey", registry = "sunbeam" } wfe-valkey = { version = "1.6.2", path = "wfe-valkey", registry = "sunbeam" }
wfe-yaml = { version = "1.6.0", path = "wfe-yaml", registry = "sunbeam" } wfe-yaml = { version = "1.6.2", path = "wfe-yaml", registry = "sunbeam" }
wfe-buildkit = { version = "1.6.0", path = "wfe-buildkit", registry = "sunbeam" } wfe-buildkit = { version = "1.6.2", path = "wfe-buildkit", registry = "sunbeam" }
wfe-containerd = { version = "1.6.0", path = "wfe-containerd", registry = "sunbeam" } wfe-containerd = { version = "1.6.2", path = "wfe-containerd", registry = "sunbeam" }
wfe-rustlang = { version = "1.6.0", path = "wfe-rustlang", registry = "sunbeam" } wfe-rustlang = { version = "1.6.2", path = "wfe-rustlang", registry = "sunbeam" }
# YAML # YAML
serde_yaml = "0.9" serde_yaml = "0.9"

View File

@@ -16,7 +16,7 @@ async-trait = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
wfe-buildkit-protos = { version = "1.6.0", path = "../wfe-buildkit-protos", registry = "sunbeam" } wfe-buildkit-protos = { version = "1.6.2", path = "../wfe-buildkit-protos", registry = "sunbeam" }
tonic = "0.14" tonic = "0.14"
tower = { version = "0.4", features = ["util"] } tower = { version = "0.4", features = ["util"] }
hyper-util = { version = "0.1", features = ["tokio"] } hyper-util = { version = "0.1", features = ["tokio"] }

View File

@@ -9,7 +9,7 @@ description = "containerd container runner executor for WFE"
[dependencies] [dependencies]
wfe-core = { workspace = true } wfe-core = { workspace = true }
wfe-containerd-protos = { version = "1.6.0", path = "../wfe-containerd-protos", registry = "sunbeam" } wfe-containerd-protos = { version = "1.6.2", path = "../wfe-containerd-protos", registry = "sunbeam" }
tokio = { workspace = true } tokio = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

View File

@@ -43,6 +43,14 @@ impl<D: WorkflowData> StepBuilder<D> {
self self
} }
/// Attach arbitrary JSON configuration to this step.
///
/// The step can read it at runtime via `context.step.step_config`.
pub fn config(mut self, config: serde_json::Value) -> Self {
self.builder.steps[self.step_id].step_config = Some(config);
self
}
/// Add a compensation step for saga rollback. /// Add a compensation step for saga rollback.
pub fn compensate_with<C: StepBody + Default + 'static>(mut self) -> Self { pub fn compensate_with<C: StepBody + Default + 'static>(mut self) -> Self {
let comp_id = self.builder.add_step(std::any::type_name::<C>()); let comp_id = self.builder.add_step(std::any::type_name::<C>());

View File

@@ -61,8 +61,23 @@ impl<D: WorkflowData> WorkflowBuilder<D> {
id id
} }
/// Add a typed step with an optional name and config.
/// Convenience for use inside `parallel` branch closures.
pub fn add_step_typed<S: StepBody + Default + 'static>(
&mut self,
name: &str,
config: Option<serde_json::Value>,
) -> usize {
let id = self.add_step(std::any::type_name::<S>());
self.steps[id].name = Some(name.to_string());
if let Some(cfg) = config {
self.steps[id].step_config = Some(cfg);
}
id
}
/// Wire an outcome from `from_step` to `to_step`. /// Wire an outcome from `from_step` to `to_step`.
pub(crate) fn wire_outcome(&mut self, from_step: usize, to_step: usize, value: Option<serde_json::Value>) { pub fn wire_outcome(&mut self, from_step: usize, to_step: usize, value: Option<serde_json::Value>) {
if let Some(step) = self.steps.get_mut(from_step) { if let Some(step) = self.steps.get_mut(from_step) {
step.outcomes.push(StepOutcome { step.outcomes.push(StepOutcome {
next_step: to_step, next_step: to_step,
@@ -341,6 +356,79 @@ mod tests {
assert!(def.steps[1].step_type.contains("StepB")); assert!(def.steps[1].step_type.contains("StepB"));
} }
#[test]
fn config_sets_step_config() {
let cfg = serde_json::json!({"namespace": "ory", "timeout": 30});
let def = WorkflowBuilder::<TestData>::new()
.start_with::<StepA>()
.config(cfg.clone())
.end_workflow()
.build("test", 1);
assert_eq!(def.steps[0].step_config, Some(cfg));
}
#[test]
fn config_chains_with_name() {
let cfg = serde_json::json!({"namespace": "data"});
let def = WorkflowBuilder::<TestData>::new()
.start_with::<StepA>()
.name("apply-data")
.config(cfg.clone())
.then::<StepB>()
.end_workflow()
.build("test", 1);
assert_eq!(def.steps[0].name, Some("apply-data".into()));
assert_eq!(def.steps[0].step_config, Some(cfg));
assert_eq!(def.steps[0].outcomes[0].next_step, 1);
}
#[test]
fn config_on_multiple_steps_of_same_type() {
let cfg_a = serde_json::json!({"namespace": "ory"});
let cfg_b = serde_json::json!({"namespace": "data"});
let def = WorkflowBuilder::<TestData>::new()
.start_with::<StepA>()
.name("apply-ory")
.config(cfg_a.clone())
.then::<StepA>()
.name("apply-data")
.config(cfg_b.clone())
.end_workflow()
.build("test", 1);
assert_eq!(def.steps[0].step_config, Some(cfg_a));
assert_eq!(def.steps[1].step_config, Some(cfg_b));
// Both are StepA
assert_eq!(def.steps[0].step_type, def.steps[1].step_type);
}
#[test]
fn add_step_typed_sets_name_and_config() {
let cfg = serde_json::json!({"namespace": "ory"});
let mut builder = WorkflowBuilder::<TestData>::new();
let id = builder.add_step_typed::<StepA>("apply-ory", Some(cfg.clone()));
assert_eq!(builder.steps[id].name, Some("apply-ory".into()));
assert_eq!(builder.steps[id].step_config, Some(cfg));
assert!(builder.steps[id].step_type.contains("StepA"));
}
#[test]
fn add_step_typed_without_config() {
let mut builder = WorkflowBuilder::<TestData>::new();
let id = builder.add_step_typed::<StepB>("my-step", None);
assert_eq!(builder.steps[id].name, Some("my-step".into()));
assert_eq!(builder.steps[id].step_config, None);
}
#[test]
fn wire_outcome_connects_steps() {
let mut builder = WorkflowBuilder::<TestData>::new();
let id0 = builder.add_step_typed::<StepA>("first", None);
let id1 = builder.add_step_typed::<StepB>("second", None);
builder.wire_outcome(id0, id1, None);
assert_eq!(builder.steps[id0].outcomes.len(), 1);
assert_eq!(builder.steps[id0].outcomes[0].next_step, id1);
}
#[test] #[test]
fn inline_step_via_then_fn() { fn inline_step_via_then_fn() {
let def = WorkflowBuilder::<TestData>::new() let def = WorkflowBuilder::<TestData>::new()

View File

@@ -14,9 +14,9 @@ path = "src/main.rs"
[dependencies] [dependencies]
# Internal # Internal
wfe-core = { workspace = true, features = ["test-support"] } wfe-core = { workspace = true, features = ["test-support"] }
wfe = { path = "../wfe" } wfe = { version = "1.6.2", path = "../wfe", registry = "sunbeam" }
wfe-yaml = { path = "../wfe-yaml", features = ["rustlang", "buildkit", "containerd"] } wfe-yaml = { version = "1.6.2", path = "../wfe-yaml", registry = "sunbeam", features = ["rustlang", "buildkit", "containerd"] }
wfe-server-protos = { path = "../wfe-server-protos" } wfe-server-protos = { version = "1.6.2", path = "../wfe-server-protos", registry = "sunbeam" }
wfe-sqlite = { workspace = true } wfe-sqlite = { workspace = true }
wfe-postgres = { workspace = true } wfe-postgres = { workspace = true }
wfe-valkey = { workspace = true } wfe-valkey = { workspace = true }