7fb3fc76c8
- What was implemented
- Added PLAN_ROLLED_BACK event type to the EventType enum at src/cleveragents/infrastructure/events/types.py to properly represent successful rollbacks in the domain model.
- Implemented rollback_plan(plan_id: str, checkpoint_id: str) -> RollbackResult in PlanLifecycleService (src/cleveragents/application/services/plan_lifecycle_service.py) with:
- Plan state validation: rejects rollback when the plan is in terminal APPLIED or CANCELLED states.
- Delegation to CheckpointService.selective_rollback() to perform the actual rollback logic and obtain a RollbackResult.
- Emission of PLAN_ROLLED_BACK as a domain event to reflect the completed rollback.
- checkpoint_service is accepted as an optional constructor parameter; if not provided, a PlanError is raised to preserve backward compatibility.
- Updated CLI behavior in src/cleveragents/cli/commands/plan.py so agents plan rollback routes through PlanLifecycleService.rollback_plan() rather than calling CheckpointService.selective_rollback() directly.
- Updated PlanLifecycleService module docstring to include rollback_plan in the documented API.
- Added Behave feature file features/plan_lifecycle_rollback.feature with 11 scenarios covering state validation, domain events, and delegation.
- Added step implementations in features/steps/plan_lifecycle_rollback_steps.py to support the new scenarios.
- Key design decisions
- rollback_plan returns RollbackResult (the same result type produced by CheckpointService.selective_rollback) so the CLI can display rollback details consistently.
- Terminal states APPLIED and CANCELLED are disallowed for rollback to prevent inconsistent or invalid state transitions.
- checkpoint_service is optional in the PlanLifecycleService constructor; when omitted (None), a PlanError is raised to retain backward compatibility while signaling explicit dependency requirements.
- CLI UI remains powered by CheckpointService for metadata enrichment (e.g., confirmation prompts), but the actual rollback action is performed via PlanLifecycleService to ensure proper domain workflow and event emission.
- Technical implications
- All rollback logic now flows through the domain service layer (PlanLifecycleService) to preserve invariants and emit domain events, rather than allowing ad-hoc UI routes to bypass service validation.
- The UI can still retrieve checkpoint metadata for user confirmation, but the operation that modifies state uses the new rollback_plan pathway.
- Tests and behavior coverage were expanded via the new Behave feature and step implementations to validate state handling, events, and delegation.
- Affected modules/components
- src/cleveragents/infrastructure/events/types.py
- src/cleveragents/application/services/plan_lifecycle_service.py
- src/cleveragents/cli/commands/plan.py
- PlanLifecycleService module docstring
- features/plan_lifecycle_rollback.feature
- features/steps/plan_lifecycle_rollback_steps.py
ISSUES CLOSED: #3677
94 lines
5.5 KiB
Gherkin
94 lines
5.5 KiB
Gherkin
@phase2 @plan_lifecycle @rollback
|
|
Feature: PlanLifecycleService rollback_plan method
|
|
As a developer using the plan lifecycle service
|
|
I want a rollback_plan method on PlanLifecycleService
|
|
So that rollback operations go through the service layer with proper
|
|
state validation and domain event emission
|
|
|
|
# ───────────────────────────────────────────────────────────
|
|
# Service method existence and basic delegation
|
|
# ───────────────────────────────────────────────────────────
|
|
|
|
Scenario: rollback_plan delegates to CheckpointService and returns RollbackResult
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in Execute phase with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
And plr-the result should be a RollbackResult
|
|
And plr-the checkpoint service selective_rollback should have been called
|
|
|
|
Scenario: rollback_plan raises PlanError when checkpoint_service is not configured
|
|
Given plr-a plan lifecycle service without a checkpoint service
|
|
And plr-a plan in Execute phase
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-a PlanError should be raised with "requires a CheckpointService"
|
|
|
|
# ───────────────────────────────────────────────────────────
|
|
# State validation
|
|
# ───────────────────────────────────────────────────────────
|
|
|
|
Scenario: rollback_plan rejects a plan in APPLIED terminal state
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in APPLIED terminal state
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-a PlanError should be raised with "terminal state"
|
|
And plr-the checkpoint service selective_rollback should NOT have been called
|
|
|
|
Scenario: rollback_plan rejects a plan in CANCELLED terminal state
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in CANCELLED terminal state
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-a PlanError should be raised with "terminal state"
|
|
And plr-the checkpoint service selective_rollback should NOT have been called
|
|
|
|
Scenario: rollback_plan accepts a plan in Execute/PROCESSING state
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in Execute/PROCESSING state with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
|
|
Scenario: rollback_plan accepts a plan in Execute/QUEUED state
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in Execute/QUEUED state with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
|
|
Scenario: rollback_plan accepts a plan in Strategize phase
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
And plr-a plan in Strategize/QUEUED state with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
|
|
# ───────────────────────────────────────────────────────────
|
|
# Domain event emission
|
|
# ───────────────────────────────────────────────────────────
|
|
|
|
Scenario: rollback_plan emits PLAN_ROLLED_BACK domain event on success
|
|
Given plr-a plan lifecycle service with a mock checkpoint service and event bus
|
|
And plr-a plan in Execute phase with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
And plr-a PLAN_ROLLED_BACK domain event should have been emitted
|
|
And plr-the event should contain the checkpoint_id
|
|
|
|
Scenario: rollback_plan does not emit events when event_bus is None
|
|
Given plr-a plan lifecycle service with a mock checkpoint service but no event bus
|
|
And plr-a plan in Execute phase with a sandbox
|
|
And plr-a checkpoint exists for the plan
|
|
When plr-I call rollback_plan with the plan id and checkpoint id
|
|
Then plr-the rollback should succeed
|
|
|
|
# ───────────────────────────────────────────────────────────
|
|
# NotFoundError propagation
|
|
# ───────────────────────────────────────────────────────────
|
|
|
|
Scenario: rollback_plan raises NotFoundError for non-existent plan
|
|
Given plr-a plan lifecycle service with a mock checkpoint service
|
|
When plr-I call rollback_plan with a non-existent plan id
|
|
Then plr-a NotFoundError should be raised
|