From 6406fccd195c2e126844c9bc06337a3bed88b8b5 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 23 Feb 2026 16:09:36 -0500 Subject: [PATCH] Fix regressions from ceaa9bf: restore Fail as non-retryable and allow multiple terminal nodes Revert two incorrect behavioral changes introduced in ceaa9bf: 1. StageStatus::Fail must return immediately, not retry. Fail is a deliberate routing outcome (e.g. to take a "fail" edge). Retrying it caused conditional_branching and manager_loop tests to hang. 2. Pipelines can legitimately have multiple terminal nodes. The "exactly one" constraint broke branching_loop_back_on_failure. Restore "at least one" validation. Co-Authored-By: Claude Opus 4.6 --- crates/attractor/src/engine.rs | 3 ++- crates/attractor/src/validation/rules.rs | 14 +------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/crates/attractor/src/engine.rs b/crates/attractor/src/engine.rs index 15950065a..f57f991ce 100644 --- a/crates/attractor/src/engine.rs +++ b/crates/attractor/src/engine.rs @@ -604,10 +604,11 @@ impl PipelineEngine { match outcome.status { StageStatus::Success | StageStatus::PartialSuccess + | StageStatus::Fail | StageStatus::Skipped => { return Ok((outcome, attempt)); } - StageStatus::Fail | StageStatus::Retry => { + StageStatus::Retry => { if attempt < policy.max_attempts { let delay = policy.backoff.delay_for_attempt(attempt); self.emitter.emit(&PipelineEvent::StageRetrying { diff --git a/crates/attractor/src/validation/rules.rs b/crates/attractor/src/validation/rules.rs index 971eb34fe..e45df775b 100644 --- a/crates/attractor/src/validation/rules.rs +++ b/crates/attractor/src/validation/rules.rs @@ -94,24 +94,12 @@ impl LintRule for TerminalNodeRule { return vec![Diagnostic { rule: self.name().to_string(), severity: Severity::Error, - message: "Pipeline must have exactly one terminal node (shape=Msquare or id exit/end)".to_string(), + message: "Pipeline must have at least one terminal node (shape=Msquare or id exit/end)".to_string(), node_id: None, edge: None, fix: Some("Add a node with shape=Msquare or id 'exit'/'end'".to_string()), }]; } - if terminal_count > 1 { - return vec![Diagnostic { - rule: self.name().to_string(), - severity: Severity::Error, - message: format!( - "Pipeline must have exactly one terminal node, found {terminal_count}" - ), - node_id: None, - edge: None, - fix: Some("Remove extra terminal nodes so exactly one remains".to_string()), - }]; - } Vec::new() } }