Feature: Cross-plan correction cascading When a correction's affected subtree includes child plans, the child plan's state determines the cascade behaviour: cancel, cancel with sandbox rollback, or reject the correction entirely. # ───────────────────────────────────────────────────────────────── # Not-yet-started child plans → cancel only # ───────────────────────────────────────────────────────────────── Scenario: Correction cascades to not-started child plan — cancel Given a cross-plan correction service And the child plan "CP1" has state "not_started" When I execute a cascade correction for correction "C1" with child plans "CP1" Then the cascade should succeed And the cancelled child plans should contain "CP1" And the rolled back child plans should be empty # ───────────────────────────────────────────────────────────────── # In-progress child plans → cancel + sandbox rollback # ───────────────────────────────────────────────────────────────── Scenario: Correction cascades to in-progress child plan — cancel and rollback Given a cross-plan correction service And the child plan "CP1" has state "in_progress" When I execute a cascade correction for correction "C1" with child plans "CP1" Then the cascade should succeed And the cancelled child plans should contain "CP1" And the rolled back child plans should contain "CP1" # ───────────────────────────────────────────────────────────────── # Completed-unapplied child plans → cancel + rollback # ───────────────────────────────────────────────────────────────── Scenario: Correction cascades to completed-unapplied child plan — cancel and rollback Given a cross-plan correction service And the child plan "CP1" has state "completed_unapplied" When I execute a cascade correction for correction "C1" with child plans "CP1" Then the cascade should succeed And the cancelled child plans should contain "CP1" And the rolled back child plans should contain "CP1" # ───────────────────────────────────────────────────────────────── # Already-applied child plans → rejection # ───────────────────────────────────────────────────────────────── Scenario: Correction cascades to already-applied child plan — rejection with CorrectionRejection Given a cross-plan correction service And the child plan "CP1" has state "applied" When I execute a cascade correction for correction "C1" with child plans "CP1" Then the cascade should be rejected And the rejection reason should mention "already applied" And the rejection should list applied child plan "CP1" # ───────────────────────────────────────────────────────────────── # Mixed child plan states # ───────────────────────────────────────────────────────────────── Scenario: Mixed child plan states — reject if any applied even if others are cancellable Given a cross-plan correction service And the child plan "CP1" has state "not_started" And the child plan "CP2" has state "in_progress" And the child plan "CP3" has state "applied" When I execute a cascade correction for correction "C1" with child plans "CP1,CP2,CP3" Then the cascade should be rejected And the rejection should list applied child plan "CP3" And the cancelled child plans should be empty # ───────────────────────────────────────────────────────────────── # Atomic cascade: failure during one cancel rolls back all previous # ───────────────────────────────────────────────────────────────── Scenario: Atomic cascade — failure during one cancel rolls back all previous cancels Given a cross-plan correction service with failing canceller on "CP2" And the child plan "CP1" has state "not_started" And the child plan "CP2" has state "not_started" When I try to execute a cascade correction for correction "C1" with child plans "CP1,CP2" Then the cascade should raise an error And the cascade error should mention "CP2" # ───────────────────────────────────────────────────────────────── # Evaluation (dry-run) scenarios # ───────────────────────────────────────────────────────────────── Scenario: Evaluate cascade without executing — no child plans Given a cross-plan correction service When I evaluate a cascade for correction "C1" with no child plans Then the cascade evaluation should succeed And the cascade actions should be empty Scenario: Evaluate cascade — mixed states preview Given a cross-plan correction service And the child plan "CP1" has state "not_started" And the child plan "CP2" has state "in_progress" When I evaluate a cascade for correction "C1" with child plans "CP1,CP2" Then the cascade evaluation should succeed And the cascade action for "CP1" should be "cancel" And the cascade action for "CP2" should be "cancel_and_rollback" # ───────────────────────────────────────────────────────────────── # Validation # ───────────────────────────────────────────────────────────────── Scenario: Empty correction_id raises validation error Given a cross-plan correction service When I try to evaluate a cascade with empty correction_id Then a cross-plan validation error should be raised # ───────────────────────────────────────────────────────────────── # Multiple not-started child plans — all cancelled # ───────────────────────────────────────────────────────────────── Scenario: Multiple not-started child plans all cancelled Given a cross-plan correction service And the child plan "CP1" has state "not_started" And the child plan "CP2" has state "not_started" And the child plan "CP3" has state "not_started" When I execute a cascade correction for correction "C1" with child plans "CP1,CP2,CP3" Then the cascade should succeed And the cancelled child plans should contain "CP1" And the cancelled child plans should contain "CP2" And the cancelled child plans should contain "CP3" And the rolled back child plans should be empty # ───────────────────────────────────────────────────────────────── # Integration with CorrectionImpact — execute_correction_with_cascade # ───────────────────────────────────────────────────────────────── Scenario: Execute correction with cascade — no child plans returns result Given a cross-plan correction service When I execute a correction with cascade for correction "C1" with no child plans in revert mode Then the correction with cascade result should be applied Scenario: Execute correction with cascade — applied child plan returns rejection Given a cross-plan correction service And the child plan "CP1" has state "applied" When I execute a correction with cascade for correction "C1" with child plan "CP1" in revert mode Then the correction with cascade result should be a rejection Scenario: Execute correction with cascade — cancellable child plan succeeds Given a cross-plan correction service And the child plan "CP1" has state "not_started" When I execute a correction with cascade for correction "C1" with child plan "CP1" in revert mode Then the correction with cascade result should be applied # ───────────────────────────────────────────────────────────────── # classify_cascade_action unit-level checks # ───────────────────────────────────────────────────────────────── Scenario: classify_cascade_action for not_started returns cancel When I classify cascade action for state "not_started" Then the classified action should be "cancel" Scenario: classify_cascade_action for in_progress returns cancel_and_rollback When I classify cascade action for state "in_progress" Then the classified action should be "cancel_and_rollback" Scenario: classify_cascade_action for completed_unapplied returns cancel_and_rollback When I classify cascade action for state "completed_unapplied" Then the classified action should be "cancel_and_rollback" Scenario: classify_cascade_action for applied returns reject When I classify cascade action for state "applied" Then the classified action should be "reject" # ───────────────────────────────────────────────────────────────── # Constructor validation # ───────────────────────────────────────────────────────────────── Scenario: Constructor rejects None plan_lookup When I try to create a cross-plan service with None plan_lookup Then a cross-plan validation error should be raised Scenario: Constructor rejects None plan_canceller When I try to create a cross-plan service with None plan_canceller Then a cross-plan validation error should be raised Scenario: Constructor rejects None sandbox_rollbacker When I try to create a cross-plan service with None sandbox_rollbacker Then a cross-plan validation error should be raised # ───────────────────────────────────────────────────────────────── # CorrectionRejection model validation # ───────────────────────────────────────────────────────────────── Scenario: CorrectionRejection model has correct fields When I create a CorrectionRejection with correction_id "C1" and reason "test" and plans "CP1,CP2" Then the rejection correction_id should be "C1" And the rejection reason should be "test" And the rejection affected plans should contain "CP1" And the rejection affected plans should contain "CP2" # ───────────────────────────────────────────────────────────────── # CascadeAction model validation # ───────────────────────────────────────────────────────────────── Scenario: CascadeAction with invalid action raises error When I try to create a CascadeAction with invalid action "destroy" Then a cross-plan pydantic validation error should be raised # ───────────────────────────────────────────────────────────────── # CorrectionStatus REJECTED # ───────────────────────────────────────────────────────────────── Scenario: CorrectionStatus includes REJECTED Then the CorrectionStatus enum should include "rejected" # ───────────────────────────────────────────────────────────────── # ChildPlanState enum completeness # ───────────────────────────────────────────────────────────────── Scenario: ChildPlanState enum has all four states Then the ChildPlanState enum should have 4 members And the ChildPlanState enum should include "not_started" And the ChildPlanState enum should include "in_progress" And the ChildPlanState enum should include "completed_unapplied" And the ChildPlanState enum should include "applied" # ───────────────────────────────────────────────────────────────── # Validation — execute_cascade with empty correction_id # ───────────────────────────────────────────────────────────────── Scenario: execute_cascade with empty correction_id raises validation error Given a cross-plan correction service When I try to execute a cascade with empty correction_id Then a cross-plan validation error should be raised # ───────────────────────────────────────────────────────────────── # Validation — execute_correction_with_cascade with empty id # ───────────────────────────────────────────────────────────────── Scenario: execute_correction_with_cascade with empty correction_id raises validation error Given a cross-plan correction service When I try to execute a correction with cascade with empty correction_id Then a cross-plan validation error should be raised # ───────────────────────────────────────────────────────────────── # Append mode — no child plans # ───────────────────────────────────────────────────────────────── Scenario: Execute correction with cascade in append mode — no child plans returns result with empty lists Given a cross-plan correction service When I execute a correction with cascade for correction "C1" with no child plans in append mode Then the correction with cascade result should be applied And the correction result reverted decisions should be empty And the correction result archived artifacts should be empty # ───────────────────────────────────────────────────────────────── # Append mode — cancellable child plan # ───────────────────────────────────────────────────────────────── Scenario: Execute correction with cascade in append mode — cancellable child plan succeeds with empty lists Given a cross-plan correction service And the child plan "CP1" has state "not_started" When I execute a correction with cascade for correction "C1" with child plan "CP1" in append mode Then the correction with cascade result should be applied And the correction result reverted decisions should be empty And the correction result archived artifacts should be empty # ───────────────────────────────────────────────────────────────── # CorrectionImpact model validation — invalid rollback_tier # ───────────────────────────────────────────────────────────────── Scenario: CorrectionImpact rejects invalid rollback_tier When I try to create a CorrectionImpact with rollback_tier "invalid_tier" Then a cross-plan pydantic validation error should be raised # ───────────────────────────────────────────────────────────────── # CorrectionImpact model validation — invalid risk_level # ───────────────────────────────────────────────────────────────── Scenario: CorrectionImpact rejects invalid risk_level When I try to create a CorrectionImpact with risk_level "catastrophic" Then a cross-plan pydantic validation error should be raised # ───────────────────────────────────────────────────────────────── # Protocol runtime checks # ───────────────────────────────────────────────────────────────── Scenario: Mock plan lookup satisfies ChildPlanLookup protocol Given a cross-plan correction service Then the plan lookup should satisfy the ChildPlanLookup protocol Scenario: Mock plan canceller satisfies ChildPlanCanceller protocol Given a cross-plan correction service Then the plan canceller should satisfy the ChildPlanCanceller protocol Scenario: Mock sandbox rollbacker satisfies SandboxRollbacker protocol Given a cross-plan correction service Then the sandbox rollbacker should satisfy the SandboxRollbacker protocol # ───────────────────────────────────────────────────────────────── # Whitespace-only correction_id treated as empty # ───────────────────────────────────────────────────────────────── Scenario: execute_cascade with whitespace-only correction_id raises validation error Given a cross-plan correction service When I try to execute a cascade with whitespace-only correction_id Then a cross-plan validation error should be raised Scenario: execute_correction_with_cascade with whitespace-only correction_id raises validation error Given a cross-plan correction service When I try to execute a correction with cascade with whitespace-only correction_id Then a cross-plan validation error should be raised