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 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-23 16:09:36 -05:00
parent 06480db417
commit 6406fccd19
2 changed files with 3 additions and 14 deletions

View file

@ -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 {

View file

@ -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()
}
}