Feature: Correction attempt persistence via CorrectionAttemptRepository As a developer I want correction attempt records to persist correctly So that decision correction workflows are durable across operations Background: Given a fresh in-memory correction attempt database And a prerequisite action "local/correction-action" exists for correction attempts And a prerequisite plan exists for correction attempts And a prerequisite root decision exists for correction attempts # ------------------------------------------------------------------ # Create and retrieve # ------------------------------------------------------------------ Scenario: Create a correction attempt and retrieve it by ID Given a new correction attempt in revert mode When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt mode should be "revert" And the persisted correction attempt state should be "pending" Scenario: Create an append-mode correction attempt Given a new correction attempt in append mode When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt mode should be "append" Scenario: Persisted correction attempt preserves all fields Given a new correction attempt with all fields populated When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt guidance should match And the persisted correction attempt plan_id should match And the persisted correction attempt original_decision_id should match # ------------------------------------------------------------------ # List by plan # ------------------------------------------------------------------ Scenario: List correction attempts by plan returns ordered results Given 3 persisted correction attempts for the same plan When I list correction attempts by plan ID Then I should get 3 correction attempts in creation order # ------------------------------------------------------------------ # Update state # ------------------------------------------------------------------ Scenario: Update correction attempt state to executing Given a persisted correction attempt in pending state When I update the correction attempt state to "executing" Then the correction attempt state should be "executing" Scenario: Update correction attempt state to complete with timestamp Given a persisted correction attempt in executing state When I update the correction attempt to complete with timestamp Then the correction attempt state should be "complete" And the correction attempt completed_at should be set Scenario: Invalid state transition from pending to complete is rejected Given a persisted correction attempt in pending state When I try to update the correction attempt state to "complete" Then an InvalidCorrectionStateTransitionError should be raised Scenario: Update correction attempt with new decision ID Given a persisted correction attempt in pending state When I update the correction attempt with a new decision ID Then the correction attempt new_decision_id should be set Scenario: Update correction attempt with archived artifacts path Given a persisted correction attempt in pending state When I update the correction attempt with archived artifacts path Then the correction attempt archived_artifacts_path should be set # ------------------------------------------------------------------ # Delete # ------------------------------------------------------------------ Scenario: Delete a correction attempt by ID Given a persisted correction attempt in pending state When I delete the correction attempt Then the correction attempt should no longer exist Scenario: Delete a non-existent correction attempt returns false When I try to delete a non-existent correction attempt Then the correction attempt delete result should be false # ------------------------------------------------------------------ # Duplicate detection # ------------------------------------------------------------------ Scenario: Creating a duplicate correction attempt raises error Given a persisted correction attempt in pending state When I try to persist the same correction attempt again Then a DuplicateCorrectionAttemptError should be raised # ------------------------------------------------------------------ # Cascade deletion # ------------------------------------------------------------------ Scenario: Deleting a plan cascades to its correction attempts Given a persisted correction attempt in pending state When the parent plan is deleted Then the correction attempt should have been cascade deleted # ------------------------------------------------------------------ # Not found # ------------------------------------------------------------------ Scenario: Getting a non-existent correction attempt raises error When I try to get a non-existent correction attempt Then a CorrectionAttemptNotFoundError should be raised # ------------------------------------------------------------------ # Edge cases # ------------------------------------------------------------------ Scenario: List correction attempts for a plan with no attempts When I list correction attempts for a plan with no correction attempts Then I should get 0 correction attempts # ------------------------------------------------------------------ # Additional state transition coverage # ------------------------------------------------------------------ Scenario: Update correction attempt state to failed with timestamp Given a persisted correction attempt in executing state When I update the correction attempt to failed with timestamp Then the correction attempt state should be "failed" And the correction attempt completed_at should be set Scenario: Transition from complete state is rejected Given a persisted correction attempt in complete state When I try to update the correction attempt state to "executing" Then an InvalidCorrectionStateTransitionError should be raised Scenario: Transition from failed state is rejected Given a persisted correction attempt in failed state When I try to update the correction attempt state to "pending" Then an InvalidCorrectionStateTransitionError should be raised Scenario: Setting completed_at on non-terminal transition is rejected Given a persisted correction attempt in pending state When I try to update the correction attempt to executing with completed_at Then an InvalidCorrectionStateTransitionError should be raised # ------------------------------------------------------------------ # Domain model guidance validation # ------------------------------------------------------------------ Scenario: Empty guidance is rejected at domain model level When I try to create a correction attempt with empty guidance Then a guidance validation error should be raised Scenario: Whitespace-only guidance is rejected at domain model level When I try to create a correction attempt with whitespace-only guidance Then a guidance validation error should be raised # ------------------------------------------------------------------ # Additional test coverage # ------------------------------------------------------------------ Scenario: Updating state of a non-existent correction attempt raises error When I try to update the state of a non-existent correction attempt Then a CorrectionAttemptNotFoundError should be raised Scenario: Updating with non-existent new_decision_id raises FK error Given a persisted correction attempt in pending state When I try to update the correction attempt with a non-existent decision ID Then a DatabaseError should be raised Scenario: Guidance at max length is accepted Given a new correction attempt with guidance at max length When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID Scenario: Guidance exceeding max length is rejected at domain model level When I try to create a correction attempt with guidance exceeding max length Then a guidance validation error should be raised # ------------------------------------------------------------------ # Timezone normalization coverage (M3) # ------------------------------------------------------------------ Scenario: Non-UTC timezone datetimes are normalised to UTC on persist Given a new correction attempt with non-UTC timezone timestamps When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt created_at should be in UTC # ------------------------------------------------------------------ # archived_artifacts_path round-trip on create (M4) # ------------------------------------------------------------------ Scenario: Correction attempt with archived_artifacts_path round-trips on create Given a new correction attempt with archived_artifacts_path set When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt archived_artifacts_path should match # ------------------------------------------------------------------ # Minimum-boundary guidance (L7) # ------------------------------------------------------------------ Scenario: Single-character guidance is accepted Given a new correction attempt with single-character guidance When I persist the correction attempt via the repository Then I can retrieve the correction attempt by its ID And the persisted correction attempt guidance should be a single character # ------------------------------------------------------------------ # Delete in non-pending state (L4) # ------------------------------------------------------------------ Scenario: Delete a correction attempt in complete state Given a persisted correction attempt in complete state When I delete the correction attempt Then the correction attempt should no longer exist # ------------------------------------------------------------------ # Cross-plan isolation for list_by_plan (L5) # ------------------------------------------------------------------ Scenario: list_by_plan returns only attempts for the specified plan Given a persisted correction attempt in pending state When I list correction attempts for a plan with no correction attempts Then I should get 0 correction attempts # ------------------------------------------------------------------ # Auto-set completed_at for terminal states (T-1) # ------------------------------------------------------------------ Scenario: Terminal transition auto-sets completed_at when not provided Given a persisted correction attempt in executing state When I update the correction attempt state to "complete" without completed_at Then the correction attempt state should be "complete" And the correction attempt completed_at should be set Scenario: Terminal transition to failed auto-sets completed_at when not provided Given a persisted correction attempt in executing state When I update the correction attempt state to "failed" without completed_at Then the correction attempt state should be "failed" And the correction attempt completed_at should be set # ------------------------------------------------------------------ # FK violation on create (T-2) # ------------------------------------------------------------------ Scenario: Creating a correction attempt with non-existent plan_id raises error When I try to create a correction attempt with a non-existent plan_id Then a DatabaseError should be raised Scenario: Creating a correction attempt with non-existent original_decision_id raises error When I try to create a correction attempt with a non-existent original_decision_id Then a DatabaseError should be raised # ------------------------------------------------------------------ # Invalid mode at domain model level (T-3) # ------------------------------------------------------------------ Scenario: Invalid mode value is rejected at domain model level When I try to create a correction attempt with an invalid mode Then a mode validation error should be raised # ------------------------------------------------------------------ # Empty/whitespace new_decision_id via update_state (M-1) # ------------------------------------------------------------------ Scenario: Whitespace-only new_decision_id is rejected in update_state Given a persisted correction attempt in pending state When I try to update the correction attempt with whitespace-only new_decision_id Then a correction attempt ValueError should be raised Scenario: Empty new_decision_id is rejected in update_state Given a persisted correction attempt in pending state When I try to update the correction attempt with empty new_decision_id Then a correction attempt ValueError should be raised # ------------------------------------------------------------------ # Combined field update (L-5) # ------------------------------------------------------------------ Scenario: Update with both new_decision_id and archived_artifacts_path Given a persisted correction attempt in pending state When I update the correction attempt with new_decision_id and archived_artifacts_path Then the correction attempt new_decision_id should be set And the correction attempt archived_artifacts_path should be set # ------------------------------------------------------------------ # Self-transition rejection (L-3) # ------------------------------------------------------------------ Scenario: Self-transition from pending to pending is rejected Given a persisted correction attempt in pending state When I try to update the correction attempt state to "pending" Then an InvalidCorrectionStateTransitionError should be raised Scenario: Self-transition from executing to executing is rejected Given a persisted correction attempt in executing state When I try to update the correction attempt state to "executing" Then an InvalidCorrectionStateTransitionError should be raised # ------------------------------------------------------------------ # Empty/whitespace archived_artifacts_path via update_state (L-2) # ------------------------------------------------------------------ Scenario: Empty archived_artifacts_path is rejected in update_state Given a persisted correction attempt in pending state When I try to update the correction attempt with empty archived_artifacts_path Then a correction attempt ValueError should be raised Scenario: Whitespace-only archived_artifacts_path is rejected in update_state Given a persisted correction attempt in pending state When I try to update the correction attempt with whitespace-only archived_artifacts_path Then a correction attempt ValueError should be raised # ------------------------------------------------------------------ # Whitespace-padded archived_artifacts_path stripping (L-6) # ------------------------------------------------------------------ Scenario: Whitespace-padded archived_artifacts_path is stripped on update Given a persisted correction attempt in pending state When I update the correction attempt with whitespace-padded archived_artifacts_path Then the correction attempt archived_artifacts_path should be stripped # ------------------------------------------------------------------ # RESTRICT FK on original_decision_id (R-1) # ------------------------------------------------------------------ Scenario: Deleting a decision referenced by original_decision_id is blocked Given a persisted correction attempt in pending state When I try to delete the original decision Then a database integrity error should be raised for the decision And the correction attempt should still exist # ------------------------------------------------------------------ # Stronger cross-plan isolation (I-1) # ------------------------------------------------------------------ Scenario: list_by_plan returns only first plan's attempts Given a second prerequisite plan exists for correction attempts And a persisted correction attempt for the first plan And a persisted correction attempt for the second plan When I list correction attempts for the first plan Then I should get exactly 1 correction attempt for the first plan Scenario: list_by_plan returns only second plan's attempts Given a second prerequisite plan exists for correction attempts And a persisted correction attempt for the first plan And a persisted correction attempt for the second plan When I list correction attempts for the second plan Then I should get exactly 1 correction attempt for the second plan # ------------------------------------------------------------------ # Defensive to_domain() coercion for corrupted DB data (DC-1) # ------------------------------------------------------------------ Scenario: to_domain coerces unknown mode to revert Given a raw correction attempt row with corrupted mode "unknown_mode" When I retrieve the corrupted correction attempt by ID Then the retrieved correction attempt mode should be "revert" Scenario: to_domain coerces unknown state to failed Given a raw correction attempt row with corrupted state "unknown_state" When I retrieve the corrupted correction attempt by ID Then the retrieved correction attempt state should be "failed" Scenario: to_domain coerces empty guidance to corrupted marker Given a raw correction attempt row with empty guidance When I retrieve the corrupted correction attempt by ID Then the retrieved correction attempt guidance should be "[corrupted]" # ------------------------------------------------------------------ # format_sqlite_timestamp naive datetime rejection (TS-1) # ------------------------------------------------------------------ Scenario: format_sqlite_timestamp rejects naive datetimes When I call format_sqlite_timestamp with a naive datetime Then a ValueError should be raised for naive datetime # ------------------------------------------------------------------ # Domain model naive datetime normalisation (TS-2) # ------------------------------------------------------------------ Scenario: CorrectionAttemptRecord normalizes naive created_at to UTC When I create a CorrectionAttemptRecord with naive created_at Then the record created_at should have UTC timezone Scenario: CorrectionAttemptRecord normalizes naive completed_at to UTC When I create a CorrectionAttemptRecord with naive completed_at Then the record completed_at should have UTC timezone # ------------------------------------------------------------------ # Corrupted DB state in update_state (DC-2) # ------------------------------------------------------------------ Scenario: update_state treats corrupted DB state as terminal failed Given a raw correction attempt row with corrupted state "garbage_state" When I try to update the corrupted correction attempt state to "executing" Then an InvalidCorrectionStateTransitionError should be raised