diff --git a/crates/taurus-core/src/runtime/engine.rs b/crates/taurus-core/src/runtime/engine.rs index 4f3e286..b173288 100644 --- a/crates/taurus-core/src/runtime/engine.rs +++ b/crates/taurus-core/src/runtime/engine.rs @@ -268,12 +268,14 @@ mod tests { use crate::handler::argument::Argument; use crate::handler::registry::{FunctionRegistration, FunctionStore, ThunkRunner}; use crate::runtime::execution::value_store::ValueStore; + use crate::runtime::remote::{RemoteExecution, RemoteRuntime}; use crate::types::exit_reason::ExitReason; + use async_trait::async_trait; use std::cell::RefCell; use std::time::Duration; use tucana::shared::{ - InputType, ListValue, NodeParameter, NodeValue, ReferenceValue, Struct, SubFlow, - SubFlowSetting, Value, node_execution_result, node_value, reference_value, + InputType, ListValue, NodeExecutionResult, NodeParameter, NodeValue, ReferenceValue, + Struct, SubFlow, SubFlowSetting, Value, node_execution_result, node_value, reference_value, sub_flow::ExecutionReference, value::Kind, }; @@ -418,6 +420,22 @@ mod tests { Signal::Success(null_value()) } + #[derive(Clone)] + struct StubRemoteRuntime { + result: NodeExecutionResult, + } + + #[async_trait] + impl RemoteRuntime for StubRemoteRuntime { + async fn execute_remote( + &self, + _execution: RemoteExecution, + ) -> Result + { + Ok(self.result.clone()) + } + } + fn input_type_ref_param( database_id: i64, runtime_parameter_id: &str, @@ -938,6 +956,49 @@ mod tests { )); } + #[test] + fn remote_execution_report_converts_missing_outcome_to_node_error() { + let engine = ExecutionEngine::new(); + let remote = StubRemoteRuntime { + result: NodeExecutionResult { + node_id: 99, + started_at: 1, + finished_at: 2, + parameter_results: Vec::new(), + result: None, + }, + }; + let mut remote_node = node( + 1, + "remote::missing_outcome", + vec![literal_param(100, "payload", int_value(20))], + None, + ); + remote_node.definition_source = Some("remote-service".to_string()); + + let report = + engine.execute_graph_report(1, vec![remote_node], None, Some(&remote), None, false); + + assert_eq!(report.exit_reason, ExitReason::Failure); + match report.signal { + Signal::Failure(err) => assert_eq!(err.code, "T-CORE-000006"), + other => panic!("expected missing-outcome failure, got {:?}", other), + } + assert_eq!(report.node_execution_results.len(), 1); + + let node_result = &report.node_execution_results[0]; + assert_eq!(node_result.node_id, 1); + assert_eq!(node_result.parameter_results.len(), 1); + assert_eq!(node_result.parameter_results[0].value, Some(int_value(20))); + match node_result.result.as_ref() { + Some(node_execution_result::Result::Error(error)) => { + assert_eq!(error.code, "T-CORE-000006"); + assert_eq!(error.category, "NodeExecutionResultMissingOutcome"); + } + other => panic!("expected node error result, got {:?}", other), + } + } + #[test] fn node_execution_result_tracks_actual_node_duration() { let mut handlers = FunctionStore::new(); diff --git a/crates/taurus-core/src/runtime/engine/executor.rs b/crates/taurus-core/src/runtime/engine/executor.rs index 48254a1..05aa1ec 100644 --- a/crates/taurus-core/src/runtime/engine/executor.rs +++ b/crates/taurus-core/src/runtime/engine/executor.rs @@ -415,9 +415,14 @@ impl<'a> EngineExecutor<'a> { target_service: service.to_string(), request, })) { - Ok(result) => { - self.commit_remote_result(node.id, result, parameter_results, value_store) - } + Ok(result) => self.commit_remote_result( + node.id, + result, + parameter_results, + started_at, + now_unix_micros(), + value_store, + ), Err(err) => self.commit_result( node.id, Signal::Failure(err), @@ -747,22 +752,37 @@ impl<'a> EngineExecutor<'a> { node_id: i64, mut result: TucanaNodeExecutionResult, parameter_results: Vec, + started_at: i64, + finished_at: i64, value_store: &mut ValueStore, ) -> Signal { if result.parameter_results.is_empty() { result.parameter_results = parameter_results; } - value_store.insert_node_result(node_id, result.clone()); - match result.result { - Some(TucanaNodeResult::Success(value)) => Signal::Success(value), + match result.result.clone() { + Some(TucanaNodeResult::Success(value)) => { + value_store.insert_node_result(node_id, result); + Signal::Success(value) + } Some(TucanaNodeResult::Error(error)) => { + value_store.insert_node_result(node_id, result); Signal::Failure(RuntimeError::from_tucana_error(&error)) } - None => Signal::Failure(RuntimeError::new( - "T-CORE-000006", - "NodeExecutionResultMissingOutcome", - "Remote node execution result is missing success/error outcome", - )), + None => { + let runtime_error = RuntimeError::new( + "T-CORE-000006", + "NodeExecutionResultMissingOutcome", + "Remote node execution result is missing success/error outcome", + ); + value_store.insert_error_with_timing( + node_id, + runtime_error.clone(), + result.parameter_results, + started_at, + finished_at, + ); + Signal::Failure(runtime_error) + } } }