Files
cleveragents-core/features/fix_then_revalidate_coverage_boost.feature
Luis Mendes 3cf3f1f69e
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 15s
CI / lint (pull_request) Successful in 3m40s
CI / quality (pull_request) Successful in 4m13s
CI / security (pull_request) Successful in 4m16s
CI / typecheck (pull_request) Successful in 4m16s
CI / integration_tests (pull_request) Successful in 9m11s
CI / unit_tests (pull_request) Successful in 9m23s
CI / docker (pull_request) Successful in 1m18s
CI / coverage (pull_request) Successful in 11m48s
CI / status-check (pull_request) Successful in 7s
CI / e2e_tests (pull_request) Successful in 8m24s
CI / lint (push) Successful in 3m18s
CI / build (push) Failing after 13s
CI / typecheck (push) Successful in 3m54s
CI / benchmark-regression (push) Has been skipped
CI / quality (push) Successful in 4m15s
CI / security (push) Successful in 4m39s
CI / integration_tests (push) Successful in 7m21s
CI / unit_tests (push) Successful in 7m44s
CI / docker (push) Successful in 1m9s
CI / e2e_tests (push) Successful in 11m9s
CI / coverage (push) Successful in 13m41s
CI / status-check (push) Successful in 2s
CI / benchmark-regression (pull_request) Successful in 48m49s
CI / benchmark-publish (push) Successful in 26m25s
feat(validation): implement Fix-then-Revalidate orchestration loop for required validations
Implemented FixThenRevalidateOrchestrator with full diagnosis → self-fix →
re-validation → retry limit → strategy revision → user escalation → terminal
failure flow. Added retry counting per validation per plan, configurable retry
limit (default 3), auto_strategy_revision flag support, and validation_fix_history
recording in plan execution metadata.

Review fixes applied (round 1):
- Added try/except around fix_callback and revalidate_callback (spec: validation
  errors treated as required failure regardless of mode)
- Wired optional EventBus for VALIDATION_FIX_ATTEMPTED/SUCCEEDED/EXHAUSTED events
- Fixed total_attempts derivation from fix_history length instead of cumulative
  retry counts
- Updated module docstring to reflect implemented vs caller-responsible steps
- Added exhausted-retry logging on re-invocation
- Replaced bare assert in Robot helper with explicit _check() function
- Added 10 new Behave scenarios covering exception paths, multi-failure, reset,
  field validation, boundary values, event_bus property, and exhausted re-invocation

Review fixes applied (round 2 — PR #711):
- B1+B7: Fixed TOCTOU race in _fix_single_validation; atomic claim-per-iteration
  under RLock; eliminated defaultdict auto-vivification with .get() reads
- B2: Added validation_name mismatch check on revalidate_callback return
- B3: Added fix_description truncation to 2000 chars before FixAttemptRecord
- T1: Switched threading.Lock to threading.RLock for defensive reentrancy
- A1: Added model_validator preventing escalated+terminal_failure both True
- R1: Added max_length=255 to FixAttemptRecord.validation_name
- S7: Fixed misleading docstring about automation_profile integration
- D4: Fixed timestamp field description to specify UTC
- D1: Renamed misleading benchmark time_fix_after_two_retries
- Added 5 new Behave scenarios for truncation, model constraints, name mismatch

ISSUES CLOSED: #583
2026-03-24 22:01:48 +00:00

349 lines
21 KiB
Gherkin

Feature: Fix-then-Revalidate coverage boost
As a developer
I want to cover edge cases and validation paths in FixThenRevalidateOrchestrator
So that code coverage meets the 97% threshold
# Covers: __init__ validation
Scenario: Orchestrator rejects None validation_pipeline
When the fix-revalidate orchestrator is created with None pipeline
Then a fix-revalidate validation error should be raised with "validation_pipeline must not be None"
# Covers: __init__ validation
Scenario: Orchestrator rejects non-integer max_retries
When the fix-revalidate orchestrator is created with non-integer max_retries
Then a fix-revalidate validation error should be raised with "max_retries must be an integer"
# Covers: __init__ validation
Scenario: Orchestrator rejects max_retries below minimum
When the fix-revalidate orchestrator is created with max_retries -1
Then a fix-revalidate validation error should be raised with "max_retries must be between"
# Covers: __init__ validation
Scenario: Orchestrator rejects max_retries above maximum
When the fix-revalidate orchestrator is created with max_retries 101
Then a fix-revalidate validation error should be raised with "max_retries must be between"
# Covers: max_retries property
Scenario: Orchestrator exposes max_retries property
Given a fix-revalidate coverage orchestrator with max_retries 5
Then the fix-revalidate max_retries property should return 5
# Covers: auto_strategy_revision property
Scenario: Orchestrator exposes auto_strategy_revision property
Given a fix-revalidate coverage orchestrator with auto_strategy_revision enabled
Then the fix-revalidate auto_strategy_revision property should be 0.0
# Covers: get_retry_count empty plan_id
Scenario: get_retry_count rejects empty plan_id
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate get_retry_count is called with empty plan_id
Then a fix-revalidate validation error should be raised with "plan_id must not be empty"
# Covers: get_retry_count empty validation_name
Scenario: get_retry_count rejects empty validation_name
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate get_retry_count is called with empty validation_name
Then a fix-revalidate validation error should be raised with "validation_name must not be empty"
# Covers: get_retry_count returns count
Scenario: get_retry_count returns zero for unseen validation
Given a fix-revalidate coverage orchestrator with max_retries 3
Then the fix-revalidate get_retry_count for plan "P1" validation "check-a" should be 0
# Covers: reset_retry_counts empty plan_id
Scenario: reset_retry_counts rejects empty plan_id
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate reset_retry_counts is called with empty plan_id
Then a fix-revalidate validation error should be raised with "plan_id must not be empty"
# Covers: run_fix_loop empty plan_id
Scenario: run_fix_loop rejects empty plan_id
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with empty plan_id
Then a fix-revalidate validation error should be raised with "plan_id must not be empty"
# Covers: run_fix_loop None failed_results
Scenario: run_fix_loop rejects None failed_results
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with None failed_results
Then a fix-revalidate validation error should be raised with "failed_results must not be None"
# Covers: run_fix_loop None fix_callback
Scenario: run_fix_loop rejects None fix_callback
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with None fix_callback
Then a fix-revalidate validation error should be raised with "fix_callback must not be None"
# Covers: run_fix_loop None revalidate_callback
Scenario: run_fix_loop rejects None revalidate_callback
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with None revalidate_callback
Then a fix-revalidate validation error should be raised with "revalidate_callback must not be None"
# Covers: fix_callback exception handling
Scenario: fix_callback exception is treated as failed attempt
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-syntax" that fails
And a fix-revalidate coverage fix callback that raises an exception
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-EXC1"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
And the fix-revalidate coverage validation fix history should have 3 records
And each fix-revalidate coverage validation fix history record should have success False
# Covers: revalidate_callback exception handling
Scenario: revalidate_callback exception is treated as failed validation
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-lint" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that raises an exception
When the fix-revalidate coverage loop runs for plan "P-EXC2"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
And the fix-revalidate coverage validation fix history should have 3 records
# Covers: multiple simultaneous validation failures
Scenario: Multiple validations fail simultaneously with mixed outcomes
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-a" that fails
And a required coverage validation "check-b" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback where check-a passes after 1 and check-b never passes
When the fix-revalidate coverage loop runs for plan "P-MULTI"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
And the fix-revalidate coverage validation fix history should have 4 records
# Covers: reset_retry_counts then re-run
Scenario: Reset retry counts allows fresh retry budget
Given a fix-revalidate coverage orchestrator with max_retries 2
And a required coverage validation "check-retry" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-RESET"
And the fix-revalidate coverage retry counts are reset for plan "P-RESET"
And a fix-revalidate coverage revalidate callback that passes after 1
And the fix-revalidate coverage loop runs again for plan "P-RESET" with validation "check-retry"
Then the fix-revalidate coverage result should have final_passed True
And the fix-revalidate coverage result should have validation_attempts 1
# Covers: validation fix history record field validation
Scenario: Fix history records contain correct field values
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-fields" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 2
When the fix-revalidate coverage loop runs for plan "P-FIELDS"
Then the fix-revalidate coverage validation fix history should have 2 records
And fix-revalidate coverage validation fix history record 1 should have validation_name "check-fields"
And fix-revalidate coverage validation fix history record 1 should have attempt_number 1
And fix-revalidate coverage validation fix history record 1 should have success False
And fix-revalidate coverage validation fix history record 2 should have success True
# Covers: max_retries=1 boundary
Scenario: Fix loop works with max_retries 1
Given a fix-revalidate coverage orchestrator with max_retries 1
And a required coverage validation "check-bound1" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 1
When the fix-revalidate coverage loop runs for plan "P-BOUND1"
Then the fix-revalidate coverage result should have final_passed True
And the fix-revalidate coverage result should have validation_attempts 1
# Covers: max_retries=10 boundary
Scenario: Fix loop works with max_retries 10
Given a fix-revalidate coverage orchestrator with max_retries 10
And a required coverage validation "check-bound10" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 9
When the fix-revalidate coverage loop runs for plan "P-BOUND10"
Then the fix-revalidate coverage result should have final_passed True
And the fix-revalidate coverage result should have validation_attempts 9
# Covers: event_bus property
Scenario: Orchestrator exposes event_bus property
Given a fix-revalidate coverage orchestrator with max_retries 3
Then the fix-revalidate event_bus property should be None
# Covers: exhausted re-invocation with auto-reset
Scenario: Second invocation for exhausted validation logs warning
Given a fix-revalidate coverage orchestrator with max_retries 1
And a required coverage validation "check-exhausted" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-EXHAUST"
And the fix-revalidate coverage loop runs again for plan "P-EXHAUST" with validation "check-exhausted"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should have validation_attempts 1
And the fix-revalidate coverage result should need user escalation
# Covers: bool type rejected for max_retries
Scenario: Orchestrator rejects boolean max_retries True
When the fix-revalidate orchestrator is created with boolean max_retries True
Then a fix-revalidate validation error should be raised with "max_retries must be an integer"
Scenario: Orchestrator rejects boolean max_retries False
When the fix-revalidate orchestrator is created with boolean max_retries False
Then a fix-revalidate validation error should be raised with "max_retries must be an integer"
# Covers: EventBus domain event emission
Scenario: EventBus receives VALIDATION_FIX_ATTEMPTED and SUCCEEDED events on fix success
Given a fix-revalidate coverage orchestrator with max_retries 3 and a mock event bus
And a required coverage validation "check-events" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 1
When the fix-revalidate coverage loop runs for plan "P-EVENTS"
Then the mock event bus should have received 2 events
And mock event bus event 1 should have event_type "validation.fix_attempted"
And mock event bus event 2 should have event_type "validation.fix_succeeded"
Scenario: EventBus receives VALIDATION_FIX_EXHAUSTED event on exhaustion
Given a fix-revalidate coverage orchestrator with max_retries 1 and a mock event bus
And a required coverage validation "check-exhaust-evt" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-EXHEVT"
Then the fix-revalidate coverage result should need user escalation
And the mock event bus should have received 2 events
And mock event bus event 1 should have event_type "validation.fix_attempted"
And mock event bus event 2 should have event_type "validation.fix_exhausted"
Scenario: EventBus emit exception does not crash the fix loop
Given a fix-revalidate coverage orchestrator with max_retries 1 and a failing event bus
And a required coverage validation "check-bus-err" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 1
When the fix-revalidate coverage loop runs for plan "P-BUSERR"
Then the fix-revalidate coverage result should have final_passed True
# Covers: FixAttemptRecord Pydantic field constraints
Scenario: FixAttemptRecord rejects attempt_number below 1
When a fix-revalidate FixAttemptRecord is created with attempt_number 0
Then a fix-revalidate pydantic validation error should be raised
Scenario: FixAttemptRecord rejects whitespace-only validation_name
When a fix-revalidate FixAttemptRecord is created with whitespace validation_name
Then a fix-revalidate pydantic validation error should be raised
# Covers: fix_description truncation to 2000 chars
Scenario: Fix callback returning over-length description is truncated
Given a fix-revalidate coverage orchestrator with max_retries 1
And a required coverage validation "check-trunc" that fails
And a fix-revalidate coverage fix callback that returns a 3000 char description
And a fix-revalidate coverage revalidate callback that passes after 1
When the fix-revalidate coverage loop runs for plan "P-TRUNC"
Then the fix-revalidate coverage result should have final_passed True
And the fix-revalidate coverage validation fix history should have 1 records
And fix-revalidate coverage validation fix history record 1 fix_description should be at most 2000 chars
# Covers: FixThenRevalidateResult model_validator
Scenario: FixThenRevalidateResult rejects escalated and terminal_failure both True
When a fix-revalidate FixThenRevalidateResult is created with escalated True and terminal_failure True
Then a fix-revalidate pydantic validation error should be raised
# Covers: validation_name max_length on FixAttemptRecord
Scenario: FixAttemptRecord rejects validation_name exceeding 255 chars
When a fix-revalidate FixAttemptRecord is created with validation_name exceeding 255 chars
Then a fix-revalidate pydantic validation error should be raised
# Covers: revalidate callback returning mismatched validation_name
Scenario: Revalidate callback returning wrong validation_name is treated as failure
Given a fix-revalidate coverage orchestrator with max_retries 2
And a required coverage validation "check-mismatch" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that returns a different validation_name
When the fix-revalidate coverage loop runs for plan "P-MISMATCH"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
# Covers: auto_strategy_revision type validation
Scenario: Orchestrator rejects boolean auto_strategy_revision
When the fix-revalidate orchestrator is created with boolean auto_strategy_revision
Then a fix-revalidate validation error should be raised with "auto_strategy_revision must be a float"
# Covers: auto_strategy_revision range validation
Scenario: Orchestrator rejects auto_strategy_revision out of range
When the fix-revalidate orchestrator is created with auto_strategy_revision 1.5
Then a fix-revalidate validation error should be raised with "auto_strategy_revision must be between"
# Covers: auto_validation_fix type validation
Scenario: Orchestrator rejects boolean auto_validation_fix
When the fix-revalidate orchestrator is created with boolean auto_validation_fix
Then a fix-revalidate validation error should be raised with "auto_validation_fix must be a float"
# Covers: auto_validation_fix range validation
Scenario: Orchestrator rejects auto_validation_fix out of range
When the fix-revalidate orchestrator is created with auto_validation_fix -0.1
Then a fix-revalidate validation error should be raised with "auto_validation_fix must be between"
# Covers: event_bus type validation
Scenario: Orchestrator rejects non-EventBus event_bus
When the fix-revalidate orchestrator is created with invalid event_bus type
Then a fix-revalidate validation error should be raised with "event_bus must be an EventBus"
# Covers: callable() validation on fix_callback
Scenario: run_fix_loop rejects non-callable fix_callback
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with non-callable fix_callback
Then a fix-revalidate validation error should be raised with "fix_callback must be callable"
# Covers: callable() validation on revalidate_callback
Scenario: run_fix_loop rejects non-callable revalidate_callback
Given a fix-revalidate coverage orchestrator with max_retries 3
When the fix-revalidate run_fix_loop is called with non-callable revalidate_callback
Then a fix-revalidate validation error should be raised with "revalidate_callback must be callable"
# Covers: fix_callback returning None signals unfixable
Scenario: Fix callback returning None escalates immediately
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-unfixable" that fails
And a fix-revalidate coverage fix callback that returns None
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-UNFIXABLE"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
And the fix-revalidate coverage validation fix history should have 1 records
# Covers: passed=True with error treated as failure
Scenario: Revalidate returning passed True with error is treated as failure
Given a fix-revalidate coverage orchestrator with max_retries 1
And a required coverage validation "check-contradictory" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that returns passed with error
When the fix-revalidate coverage loop runs for plan "P-CONTRADICT"
Then the fix-revalidate coverage result should have final_passed False
And the fix-revalidate coverage result should need user escalation
# Covers: final_passed=True with escalated=True rejected
Scenario: FixThenRevalidateResult rejects final_passed True with escalated True
When a fix-revalidate FixThenRevalidateResult is created with final_passed True and escalated True
Then a fix-revalidate pydantic validation error should be raised
# Covers: auto_validation_fix property accessor
Scenario: Orchestrator exposes auto_validation_fix property
Given a fix-revalidate coverage orchestrator with max_retries 3
Then the fix-revalidate auto_validation_fix property should be 0.0
# Covers: EventBus receives VALIDATION_FIX_EXHAUSTED on escalation path
Scenario: EventBus receives VALIDATION_FIX_EXHAUSTED event on escalation
Given a fix-revalidate coverage orchestrator with max_retries 1 and mock event bus and escalation
And a required coverage validation "check-esc-evt" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that never passes
When the fix-revalidate coverage loop runs for plan "P-ESCEVT"
Then the mock event bus should have received 2 events
And mock event bus event 1 should have event_type "validation.fix_attempted"
And mock event bus event 2 should have event_type "validation.fix_exhausted"
# Covers: mixed required and informational failures
Scenario: Mixed required and informational failures only fix required
Given a fix-revalidate coverage orchestrator with max_retries 3
And a required coverage validation "check-req" that fails
And an informational coverage validation "check-info" that fails
And a fix-revalidate coverage fix callback that always succeeds
And a fix-revalidate coverage revalidate callback that passes after 1
When the fix-revalidate coverage loop runs for plan "P-MIXED"
Then the fix-revalidate coverage result should have final_passed True
And the fix-revalidate coverage result should have validation_attempts 1