From 1a7d815ec36d7378899c5251c30f0bd659120591 Mon Sep 17 00:00:00 2001 From: Kuntal Pal Date: Sat, 16 May 2026 22:34:46 -0700 Subject: [PATCH] fix: detect forecaster-chain as invalid in sequential pipeline validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ENSEMBLE composition rule (forecasting → forecasting, position="any") was matching during validate_pipeline(), causing the explicit forecaster-chain error to be silently skipped. Two forecasters in a sequential pipeline are always invalid — ENSEMBLE composition is parallel, not sequential. Fixes: forecaster → forecaster pair returns Valid: True in Step 5 of examples/01_forecasting_workflow.py when it should return Valid: False. --- src/sktime_mcp/composition/validator.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/sktime_mcp/composition/validator.py b/src/sktime_mcp/composition/validator.py index fac94f9a..2ae099dc 100644 --- a/src/sktime_mcp/composition/validator.py +++ b/src/sktime_mcp/composition/validator.py @@ -259,6 +259,17 @@ def _check_pair_compatibility( errors = [] warnings = [] + # Forecasters cannot be chained sequentially regardless of composition rules + if first.task == "forecasting" and second.task == "forecasting": + errors.append( + f"Cannot chain forecasters '{first.name}' → '{second.name}' directly. " + "Use an ensemble or multiplexer instead." + ) + tag_errors, tag_warnings = self._check_tag_compatibility(first, second) + errors.extend(tag_errors) + warnings.extend(tag_warnings) + return False, errors, warnings + # Find applicable rule applicable_rule = None for rule in self.COMPOSITION_RULES: @@ -266,18 +277,14 @@ def _check_pair_compatibility( rule.source_task == first.task and rule.target_task == second.task and rule.position in ("before", "any") + and rule.composition_type != CompositionType.ENSEMBLE ): applicable_rule = rule break if applicable_rule is None: # No rule found - check if it's an obvious error - if first.task == second.task == "forecasting": - errors.append( - f"Cannot chain forecasters '{first.name}' → '{second.name}' directly. " - "Use an ensemble or multiplexer instead." - ) - elif first.task in ("classification", "regression") and second.task != first.task: + if first.task in ("classification", "regression") and second.task != first.task: errors.append( f"Invalid composition: {first.task} '{first.name}' → {second.task} '{second.name}'" )