From ed38caecec6f888126342c0f9ee6260787450e55 Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Thu, 26 Mar 2026 23:18:48 +0000 Subject: [PATCH] fix(wfe-core): resolve .outputs. paths flat and pass empty object to child workflows --- wfe-core/src/executor/condition.rs | 22 +++++++++++++++++++--- wfe-core/src/primitives/sub_workflow.rs | 9 ++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/wfe-core/src/executor/condition.rs b/wfe-core/src/executor/condition.rs index 3071622..449ce9f 100644 --- a/wfe-core/src/executor/condition.rs +++ b/wfe-core/src/executor/condition.rs @@ -92,11 +92,28 @@ fn resolve_field_path<'a>( } let segments: Vec<&str> = path.split('.').collect(); + + // Try resolving the full path first (for nested data like {"outputs": {"x": 1}}). + // If the first segment is "outputs"/"inputs" and doesn't exist as a key, + // strip it and resolve flat (for workflow data where outputs merge flat). + if segments.len() >= 2 + && (segments[0] == "outputs" || segments[0] == "inputs") + && data.get(segments[0]).is_none() + { + return walk_segments(&segments[1..], data); + } + + walk_segments(&segments, data) +} + +fn walk_segments<'a>( + segments: &[&str], + data: &'a serde_json::Value, +) -> Result<&'a serde_json::Value, EvalError> { let mut current = data; - for segment in &segments { + for segment in segments { if let Ok(idx) = segment.parse::() { - // Try array index access. match current.as_array() { Some(arr) => { current = arr.get(idx).ok_or(EvalError::FieldNotPresent)?; @@ -106,7 +123,6 @@ fn resolve_field_path<'a>( } } } else { - // Object field access. match current.as_object() { Some(obj) => { current = obj.get(*segment).ok_or(EvalError::FieldNotPresent)?; diff --git a/wfe-core/src/primitives/sub_workflow.rs b/wfe-core/src/primitives/sub_workflow.rs index e5bdfac..b3de2a1 100644 --- a/wfe-core/src/primitives/sub_workflow.rs +++ b/wfe-core/src/primitives/sub_workflow.rs @@ -110,8 +110,15 @@ impl StepBody for SubWorkflowStep { ) })?; + // Use inputs if set, otherwise pass an empty object so the child + // workflow has a valid JSON object for storing step outputs. + let child_data = if self.inputs.is_null() { + serde_json::json!({}) + } else { + self.inputs.clone() + }; let child_instance_id = host - .start_workflow(&self.workflow_id, self.version, self.inputs.clone()) + .start_workflow(&self.workflow_id, self.version, child_data) .await?; Ok(ExecutionResult::wait_for_event(