From 9f9b4619711d82a50bb74b9c85f7604a31da5d5f Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 23 Feb 2026 16:12:45 -0500 Subject: [PATCH] Restore exactly-one terminal node validation per spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec says "exactly one" exit node in the shape table (line 184), exit handler docs (line 648), and test criteria (line 1834). The lint rule table (line 1437) says "at least one" but is the minority. The previous commit incorrectly reverted this — the terminal node change was not causing the test failures (all three were from retry-on-Fail). Co-Authored-By: Claude Opus 4.6 --- crates/attractor/src/validation/rules.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/attractor/src/validation/rules.rs b/crates/attractor/src/validation/rules.rs index e45df775b..971eb34fe 100644 --- a/crates/attractor/src/validation/rules.rs +++ b/crates/attractor/src/validation/rules.rs @@ -94,12 +94,24 @@ impl LintRule for TerminalNodeRule { return vec![Diagnostic { rule: self.name().to_string(), severity: Severity::Error, - message: "Pipeline must have at least one terminal node (shape=Msquare or id exit/end)".to_string(), + message: "Pipeline must have exactly 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() } }