332 lines
16 KiB
Gherkin
332 lines
16 KiB
Gherkin
Feature: Error recovery patterns and CLI hints
|
|
As a plan executor
|
|
I want structured error recovery with retry logic and actionable hints
|
|
So that failed plans can be recovered automatically or with clear guidance
|
|
|
|
Background:
|
|
Given an error recovery test environment
|
|
|
|
# -- Error classification ------------------------------------------------
|
|
|
|
Scenario: Classify transient error by message
|
|
When I classify the error message "Connection timed out after 30s"
|
|
Then the classified error category should be "transient"
|
|
|
|
Scenario: Classify validation error by message
|
|
When I classify the error message "Schema validation failed for field X"
|
|
Then the classified error category should be "validation"
|
|
|
|
Scenario: Classify configuration error by message
|
|
When I classify the error message "Missing configuration for provider"
|
|
Then the classified error category should be "configuration"
|
|
|
|
Scenario: Classify merge conflict error by message
|
|
When I classify the error message "Merge conflict in src/main.py"
|
|
Then the classified error category should be "merge_conflict"
|
|
|
|
Scenario: Classify authentication error by message
|
|
When I classify the error message "Authentication failed: invalid token"
|
|
Then the classified error category should be "authentication"
|
|
|
|
Scenario: Classify provider error by message
|
|
When I classify the error message "Model not available: gpt-4-turbo"
|
|
Then the classified error category should be "provider"
|
|
|
|
Scenario: Classify internal error by message
|
|
When I classify the error message "Unexpected internal error in pipeline"
|
|
Then the classified error category should be "internal"
|
|
|
|
Scenario: Classify unknown error by message
|
|
When I classify the error message "Something completely novel happened"
|
|
Then the classified error category should be "unknown"
|
|
|
|
Scenario: Classify error by exception type
|
|
When I classify error text "failed" using exception type "RateLimitError"
|
|
Then the classified error category should be "transient"
|
|
|
|
Scenario: Classify error by exception type overrides message
|
|
When I classify error text "validation issue" using exception type "NetworkError"
|
|
Then the classified error category should be "transient"
|
|
|
|
# -- Recovery hints ------------------------------------------------------
|
|
|
|
Scenario: Transient error recovery hints include retry command
|
|
When I get recovery hints for category "transient" and plan "01PLAN001"
|
|
Then the first recovery hint action should be "retry"
|
|
And the first recovery hint should have CLI command containing "01PLAN001"
|
|
|
|
Scenario: Validation error recovery hints include correct command
|
|
When I get recovery hints for category "validation" and plan "01PLAN001"
|
|
Then the first recovery hint action should be "correct"
|
|
And the first recovery hint should have CLI command containing "correct"
|
|
|
|
Scenario: Configuration error recovery hints suggest cancel
|
|
When I get recovery hints for category "configuration" and plan "01PLAN001"
|
|
Then the first recovery hint action should be "cancel"
|
|
|
|
Scenario: Merge conflict hints suggest revert to strategize
|
|
When I get recovery hints for category "merge_conflict" and plan "01PLAN001"
|
|
Then the first recovery hint action should be "revert_strategize"
|
|
|
|
# -- Error recording -----------------------------------------------------
|
|
|
|
Scenario: Record error creates error record with metadata
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Timeout talking to provider"
|
|
Then the recorded error should have category "transient"
|
|
And the recorded error should have phase "execute"
|
|
And the recorded error should have recovery hints
|
|
And the error history should have 1 record
|
|
|
|
Scenario: Record error with explicit category
|
|
Given a plan in errored state for error recovery
|
|
When I record a categorized error with phase "apply" and message "custom issue" and category "merge_conflict"
|
|
Then the recorded error should have category "merge_conflict"
|
|
|
|
Scenario: Record multiple errors tracks retry count
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Timeout 1"
|
|
And I record an error with phase "execute" and message "Timeout 2"
|
|
Then the error history should have 2 records
|
|
And the latest error retry count should be 1
|
|
|
|
# -- Retry policy --------------------------------------------------------
|
|
|
|
Scenario: Transient error with auto-retry threshold 0 is retriable
|
|
Given a plan in errored state for error recovery
|
|
And the auto retry threshold is 0.0
|
|
When I record an error with phase "execute" and message "Connection timed out"
|
|
Then the service should recommend retry
|
|
|
|
Scenario: Transient error with auto-retry threshold 1 requires escalation
|
|
Given a plan in errored state for error recovery
|
|
And the auto retry threshold is 1.0
|
|
When I record an error with phase "execute" and message "Connection timed out"
|
|
Then the service should recommend escalation
|
|
|
|
Scenario: Configuration error is never retriable
|
|
Given a plan in errored state for error recovery
|
|
And the auto retry threshold is 0.0
|
|
When I record an error with phase "strategize" and message "Missing configuration"
|
|
Then the service should recommend escalation
|
|
|
|
Scenario: Retry exhaustion after max retries
|
|
Given a plan in errored state for error recovery
|
|
And the max retries is 2
|
|
When I record an error with phase "execute" and message "Timeout 1"
|
|
And I record an error with phase "execute" and message "Timeout 2"
|
|
And I record an error with phase "execute" and message "Timeout 3"
|
|
Then the latest error should have retries exhausted
|
|
And the service should recommend escalation
|
|
|
|
# -- Error history -------------------------------------------------------
|
|
|
|
Scenario: Error history shows category breakdown
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Timeout occurred"
|
|
And I record an error with phase "apply" and message "Merge conflict in file.py"
|
|
Then the error history category breakdown should include "transient"
|
|
And the error history category breakdown should include "merge_conflict"
|
|
|
|
# -- CLI output formatting -----------------------------------------------
|
|
|
|
Scenario: Format error output as plain text
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Provider timed out"
|
|
And I format the error output as "plain"
|
|
Then the error recovery output should contain "transient"
|
|
And the error recovery output should contain "Provider timed out"
|
|
And the error recovery output should contain "execute"
|
|
|
|
Scenario: Format error output as JSON
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Provider timed out"
|
|
And I format the error output as "json"
|
|
Then the error recovery output should contain "transient"
|
|
And the error recovery output should contain "plan_id"
|
|
|
|
Scenario: Format empty error history
|
|
When I format error output as "plain" for separate plan "01EMPTYPLAN00000000001"
|
|
Then the error recovery output should contain "No errors recorded"
|
|
|
|
# -- ErrorRecord model checks --------------------------------------------
|
|
|
|
Scenario: ErrorRecord is_retriable for transient errors
|
|
Given an error record with category "transient" and retry count 0 and max retries 3
|
|
Then the error record should be retriable
|
|
|
|
Scenario: ErrorRecord is not retriable when exhausted
|
|
Given an error record with category "transient" and retry count 3 and max retries 3
|
|
Then the error record should not be retriable
|
|
And the error record should have retries exhausted
|
|
|
|
Scenario: ErrorRecord is not retriable for configuration errors
|
|
Given an error record with category "configuration" and retry count 0 and max retries 3
|
|
Then the error record should not be retriable
|
|
|
|
Scenario: ErrorRecord to_cli_dict has expected fields
|
|
Given an error recovery test environment
|
|
Given an error record with category "transient" and retry count 1 and max retries 3
|
|
Then the error record CLI dict should have key "error_id"
|
|
And the error record CLI dict should have key "category"
|
|
And the error record CLI dict should have key "is_retriable"
|
|
|
|
# -- Exception-based recording -------------------------------------------
|
|
|
|
Scenario: Record error with actual exception captures stack trace
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with exception for phase "execute" and message "timeout"
|
|
Then the recorded error should have a stack summary
|
|
|
|
# -- Edge cases -----------------------------------------------------------
|
|
|
|
Scenario: should_retry returns false when no errors exist
|
|
Given a plan in errored state for error recovery
|
|
Then the service should not recommend retry for a clean plan
|
|
|
|
Scenario: should_escalate returns false when no errors exist
|
|
Given a plan in errored state for error recovery
|
|
Then the service should not recommend escalation for a clean plan
|
|
|
|
Scenario: Format recovery hints for CLI
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "execute" and message "Connection timed out"
|
|
Then the CLI recovery hints should contain "transient"
|
|
|
|
# -- ErrorRecoveryPolicy formatting --------------------------------------
|
|
|
|
Scenario: Policy formats output with actor and tool info
|
|
Given a plan in errored state for error recovery
|
|
When I record an actor-error in phase "execute" with message "Timeout" actor "test-actor" tool "test-tool"
|
|
And I format the error output as "plain"
|
|
Then the error recovery output should contain "test-actor"
|
|
And the error recovery output should contain "test-tool"
|
|
|
|
Scenario: Policy format shows automatic retry available
|
|
Given a plan in errored state for error recovery
|
|
And the auto retry threshold is 0.0
|
|
When I record an error with phase "execute" and message "Connection timed out"
|
|
And I format the error output as "plain"
|
|
Then the error recovery output should contain "Automatic retry available"
|
|
|
|
Scenario: Policy format shows human intervention required
|
|
Given a plan in errored state for error recovery
|
|
And the auto retry threshold is 1.0
|
|
When I record an error with phase "execute" and message "Connection timed out"
|
|
And I format the error output as "plain"
|
|
Then the error recovery output should contain "Human intervention required"
|
|
|
|
Scenario: Policy format shows retries exhausted message
|
|
Given a plan in errored state for error recovery
|
|
And the max retries is 1
|
|
When I record an error with phase "execute" and message "Timeout 1"
|
|
And I record an error with phase "execute" and message "Timeout 2"
|
|
And I format the error output as "plain"
|
|
Then the error recovery output should contain "retry attempts exhausted"
|
|
|
|
# -- ErrorHistory with no retriable errors --------------------------------
|
|
|
|
Scenario: Error history has_retriable is false when only non-retriable
|
|
Given a plan in errored state for error recovery
|
|
When I record an error with phase "strategize" and message "Missing configuration"
|
|
Then the error history should not have retriable errors
|
|
|
|
# -- ErrorRecord with actor and tool for CLI dict -------------------------
|
|
|
|
Scenario: ErrorRecord to_cli_dict includes actor and tool when present
|
|
Given an error record with actor "my-actor" and tool "my-tool"
|
|
Then the error record CLI dict should have key "actor"
|
|
And the error record CLI dict should have key "tool_call"
|
|
|
|
# -- Recovery hints for empty plan ----------------------------------------
|
|
|
|
Scenario: Format recovery hints returns empty for plan with no errors
|
|
Given a plan in errored state for error recovery
|
|
Then the CLI recovery hints for a clean plan should be empty
|
|
|
|
# -- ErrorRecord to_cli_dict with stack summary ---------------------------
|
|
|
|
Scenario: ErrorRecord to_cli_dict includes stack summary
|
|
Given an error record with stack summary "File test.py line 10"
|
|
Then the error record CLI dict should have key "stack_summary"
|
|
|
|
# -- ErrorRecoveryPolicy should_retry on non-retriable --------------------
|
|
|
|
Scenario: Policy should_retry returns false for non-retriable category
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And an error record with category "configuration" and retry count 0 and max retries 3
|
|
Then the policy should not recommend retry
|
|
|
|
Scenario: Policy should_retry returns false for exhausted retries
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And an error record with category "transient" and retry count 3 and max retries 3
|
|
Then the policy should not recommend retry
|
|
|
|
# -- T1: Persistence failure path ----------------------------------------
|
|
|
|
Scenario: Persist error metadata handles lifecycle errors gracefully
|
|
Given a plan in errored state for error recovery
|
|
And the lifecycle service update_error_details raises an error
|
|
When I record an error with phase "execute" and message "transient failure"
|
|
Then the error should still be recorded in memory
|
|
|
|
# -- T2: Branch coverage for policy/format --------------------------------
|
|
|
|
Scenario: Policy should_escalate returns true for exhausted retries
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And an error record with category "transient" and retry count 3 and max retries 3
|
|
Then the policy should recommend escalation
|
|
|
|
Scenario: Policy should_escalate returns true for non-retriable category
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And an error record with category "configuration" and retry count 0 and max retries 3
|
|
Then the policy should recommend escalation
|
|
|
|
Scenario: Format recovery output includes hints with CLI commands
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And a hinted error record with category "transient" and retry count 0 and max retries 3
|
|
Then the formatted recovery output should contain CLI command text
|
|
|
|
Scenario: Format recovery output shows exhausted message
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And a hinted error record with category "transient" and retry count 3 and max retries 3
|
|
Then the formatted recovery output should contain "exhausted"
|
|
|
|
Scenario: Format recovery output shows automatic retry available
|
|
Given a policy with threshold 0.0 and max retries 3
|
|
And an error record with category "transient" and retry count 0 and max retries 3
|
|
Then the formatted recovery output should contain "Automatic retry"
|
|
|
|
Scenario: Policy should_retry returns false when threshold is 1.0
|
|
Given a policy with threshold 1.0 and max retries 3
|
|
And an error record with category "transient" and retry count 0 and max retries 3
|
|
Then the policy should not recommend retry
|
|
|
|
# -- T4: Integration test with executor retry loop ------------------------
|
|
|
|
Scenario: Executor retries execute phase on recoverable errors
|
|
Given a plan executor with error recovery service
|
|
And the execute actor fails twice then succeeds
|
|
When I run the executor for the plan
|
|
Then the execute should complete after retries
|
|
And the error recovery service should have recorded 2 errors
|
|
|
|
Scenario: Executor fails after exhausting retries
|
|
Given a plan executor with error recovery service max retries 2
|
|
And the execute actor always fails with "persistent failure"
|
|
When I run the executor expecting failure
|
|
Then the lifecycle should record the plan as failed
|
|
And the error recovery service should have recorded 3 errors
|
|
|
|
# -- B5: Bare conflict no longer matches MERGE_CONFLICT -------------------
|
|
|
|
Scenario: Classify resource conflict does not match merge_conflict
|
|
When I classify the error message "resource conflict detected"
|
|
Then the classified error category should not be "merge_conflict"
|
|
|
|
# -- S1: Format string safe substitution ----------------------------------
|
|
|
|
Scenario: Recovery hints handle plan_id with braces safely
|
|
When I get recovery hints for category "transient" with plan_id "{evil_id}"
|
|
Then the hints should contain "{evil_id}" in CLI commands
|