@phase2 @checkpoint @rollback
Feature: Checkpoint and rollback
  As an operator
  I want to create checkpoints during plan execution and rollback to them
  So that I can recover from mistakes without losing all progress

  # ───────────────────────────────────────────────────────────
  # Domain model scenarios
  # ───────────────────────────────────────────────────────────

  Scenario: Checkpoint model validates ULID fields
    Given a valid checkpoint with plan_id "01ARZ3NDEKTSV4RRFFQ69G5FAV" and sandbox_ref "abc123"
    Then the checkpoint plan_id should be "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And the checkpoint sandbox_ref should be "abc123"

  Scenario: Checkpoint metadata stores reason and source tool
    Given a checkpoint with reason "pre-execution" and source_tool "lint-check" and phase "execute"
    Then the checkpoint metadata reason should be "pre-execution"
    And the checkpoint metadata source_tool should be "lint-check"
    And the checkpoint metadata phase should be "execute"

  Scenario: Checkpoint retention policy defaults
    Given a default retention policy
    Then the max_checkpoints should be 50
    And auto_prune should be true

  Scenario: Checkpoint retention policy custom values
    Given a retention policy with max_checkpoints 5 and auto_prune false
    Then the max_checkpoints should be 5
    And auto_prune should be false

  Scenario: RollbackResult model stores restored file count
    Given a rollback result with 3 restored files and checkpoint "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    Then the restored_files_count should be 3
    And the from_checkpoint_id should be "01ARZ3NDEKTSV4RRFFQ69G5FAV"

  # ───────────────────────────────────────────────────────────
  # CheckpointService scenarios
  # ───────────────────────────────────────────────────────────

  Scenario: Create a checkpoint
    Given a checkpoint service
    When I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    Then the checkpoint should be created successfully
    And the checkpoint plan_id should match "01ARZ3NDEKTSV4RRFFQ69G5FAV"

  Scenario: List checkpoints for a plan
    Given a checkpoint service
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-1"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-2"
    When I list checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    Then I should see 2 checkpoints

  Scenario: Rollback to checkpoint succeeds
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a checkpoint is created from the sandbox HEAD for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a file is modified in the sandbox after the checkpoint
    When I rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should succeed

  Scenario: Rollback rejected when plan is applied
    Given a checkpoint service
    And plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" is marked as applied
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "plan is already applied"

  Scenario: Rollback rejected when sandbox is missing
    Given a checkpoint service
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "sandbox is missing"

  Scenario: Rollback with non-existent checkpoint
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to checkpoint "01NONEXISTENT0000000000000"
    Then the rollback should fail with not found error

  Scenario: Delete a checkpoint
    Given a checkpoint service
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    When I delete the created checkpoint
    Then the checkpoint should be deleted

  Scenario: Prune enforces retention policy
    Given a checkpoint service
    And I create 5 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I prune checkpoints with max 3
    Then 2 checkpoints should be pruned
    And 3 checkpoint snapshots should remain

  Scenario: Auto-prune on create with retention policy
    Given a checkpoint service
    And I create 10 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I create a checkpoint with retention policy max 5
    Then 6 checkpoints should have been pruned on creation
    And 5 checkpoint snapshots should remain

  Scenario: Create checkpoint with empty plan_id raises error
    Given a checkpoint service
    When I attempt to create a checkpoint with empty plan_id
    Then a checkpoint validation error should be raised

  Scenario: Create checkpoint with empty sandbox_ref raises error
    Given a checkpoint service
    When I attempt to create a checkpoint with empty sandbox_ref
    Then a checkpoint validation error should be raised

  Scenario: Get checkpoint by ID
    Given a checkpoint service
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-xyz"
    When I get the checkpoint by its ID
    Then the checkpoint should be returned successfully

  Scenario: Get non-existent checkpoint raises error
    Given a checkpoint service
    When I attempt to get checkpoint "01NONEXISTENT0000000000000"
    Then a checkpoint not found error should be raised

  # ───────────────────────────────────────────────────────────
  # Correction service rollback integration
  # ───────────────────────────────────────────────────────────

  Scenario: Correction service accepts checkpoint_service parameter
    Given a correction service with checkpoint support
    Then the correction service should have checkpoint support

  Scenario: Checkpoint metadata reason stored for auditability
    Given a checkpoint service
    When I create a checkpoint with reason "pre-tool-run" and source_tool "format-code" and phase "execute"
    Then the stored checkpoint metadata should have reason "pre-tool-run"
    And the stored checkpoint metadata should have source_tool "format-code"
    And the stored checkpoint metadata should have phase "execute"

  # ───────────────────────────────────────────────────────────
  # Spec-aligned field scenarios (decision_id, checkpoint_type, etc.)
  # ───────────────────────────────────────────────────────────

  Scenario: Checkpoint stores decision_id when aligned to a decision
    Given a checkpoint service
    When I create a checkpoint aligned to decision "01DEC1S10N0000000000000000"
    Then the stored checkpoint decision_id should be "01DEC1S10N0000000000000000"

  Scenario: Checkpoint stores checkpoint_type
    Given a checkpoint service
    When I create a pre_write checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    Then the stored checkpoint_type should be "pre_write"

  Scenario: Checkpoint type validation rejects invalid values
    Given a checkpoint service
    When I attempt to create a checkpoint with type "invalid_type"
    Then a checkpoint validation error should be raised

  Scenario: Checkpoint stores resource_id
    Given a checkpoint service
    When I create a checkpoint with resource "01RES0URCE0000000000000000"
    Then the stored checkpoint resource_id should be "01RES0URCE0000000000000000"

  Scenario: Checkpoint stores filesystem_path and size_bytes
    Given a checkpoint service
    When I create a checkpoint with path "snapshots/cp001" and size 4096
    Then the stored checkpoint filesystem_path should be "snapshots/cp001"
    And the stored checkpoint size_bytes should be 4096

  Scenario: Prune preserves first and most recent checkpoints
    Given a checkpoint service
    And I create 5 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I prune checkpoints with max 2
    Then 3 checkpoints should be pruned
    And 2 checkpoint snapshots should remain
    And the first checkpoint should survive pruning
    And the most recent checkpoint should survive pruning

  # ───────────────────────────────────────────────────────────
  # Persistent guard scenarios (via PlanLifecycleService)
  # ───────────────────────────────────────────────────────────

  Scenario: Rollback rejected via lifecycle service when plan is applied
    Given a checkpoint service backed by a lifecycle service with an applied plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "plan is already applied"

  Scenario: Rollback rejected via lifecycle service when sandbox refs are empty
    Given a checkpoint service backed by a lifecycle service with no sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "sandbox is missing"

  Scenario: Rollback succeeds via lifecycle service when plan has sandbox
    Given a checkpoint service backed by a lifecycle service with real sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a checkpoint is created from the sandbox HEAD for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a file is modified in the sandbox after the checkpoint
    When I rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should succeed

  # ───────────────────────────────────────────────────────────
  # Default retention policy scenario
  # ───────────────────────────────────────────────────────────

  Scenario: Default retention policy applied when none specified
    Given a checkpoint service
    And I create 55 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-final"
    Then at most 50 checkpoint snapshots should remain for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"

  Scenario: Rollback rejected when checkpoint belongs to different plan
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01KJGSHD2JKTFZGGBT6FRQD2W2" with sandbox_ref "commit-other"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "does not belong to plan"

  Scenario: Unregister sandbox prevents rollback
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-abc"
    And the sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" is unregistered
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "sandbox is missing"

  Scenario: Prune with auto_prune disabled returns empty
    Given a checkpoint service
    And I create 5 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I prune checkpoints with auto_prune disabled
    Then 0 checkpoints should be pruned

  # ───────────────────────────────────────────────────────────
  # Repository-backed service scenarios
  # ───────────────────────────────────────────────────────────

  Scenario: Repository-backed create persists via repository
    Given a checkpoint service backed by a stub repository
    When I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-repo"
    Then the checkpoint should be created successfully
    And the stub repository should have 1 checkpoint stored

  Scenario: Repository-backed list delegates to repository
    Given a checkpoint service backed by a stub repository
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-r1"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-r2"
    When I list checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    Then I should see 2 checkpoints

  Scenario: Repository-backed delete removes via repository
    Given a checkpoint service backed by a stub repository
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-del"
    When I delete the created checkpoint
    Then the checkpoint should be deleted

  Scenario: Repository-backed delete raises error when not found
    Given a checkpoint service backed by a stub repository
    When I attempt to delete checkpoint "01NONEXISTENT0000000000000"
    Then a checkpoint not found error should be raised

  Scenario: Repository-backed prune delegates to repository
    Given a checkpoint service backed by a stub repository
    And I create 5 checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    When I prune checkpoints with max 3
    Then 2 checkpoints should be pruned

  Scenario: Repository-backed get retrieves via repository
    Given a checkpoint service backed by a stub repository
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "commit-get"
    When I get the checkpoint by its ID
    Then the checkpoint should be returned successfully

  Scenario: Repository-backed get raises not found for missing checkpoint
    Given a checkpoint service backed by a stub repository
    When I attempt to get checkpoint "01NONEXISTENT0000000000000"
    Then a checkpoint not found error should be raised

  # ───────────────────────────────────────────────────────────
  # In-memory edge case scenarios
  # ───────────────────────────────────────────────────────────

  Scenario: Delete non-existent checkpoint from in-memory store raises error
    Given a checkpoint service
    When I attempt to delete checkpoint "01NONEXISTENT0000000000000"
    Then a checkpoint not found error should be raised

  Scenario: List checkpoints tolerates orphaned index entries
    Given a checkpoint service with an orphaned plan index entry
    When I list checkpoints for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    Then I should see 1 checkpoints

  Scenario: Delete checkpoint whose plan index is missing skips cleanup
    Given a checkpoint service with a checkpoint missing from plan index
    When I delete the created checkpoint
    Then the checkpoint should be deleted

  # ───────────────────────────────────────────────────────────
  # Domain model validator edge cases
  # ───────────────────────────────────────────────────────────

  Scenario: Checkpoint ULID validator rejects empty string
    When I invoke the checkpoint ULID validator with an empty string
    Then the checkpoint validator should raise ValueError "ULID field must not be empty"

  # ───────────────────────────────────────────────────────────
  # Real git rollback verification scenarios (bug #822)
  # ───────────────────────────────────────────────────────────

  Scenario: Rollback reverts modified file content to checkpoint state
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a checkpoint is created from the sandbox HEAD for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a tracked sandbox file is modified and committed after the checkpoint
    When I rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the sandbox file content should match the pre-checkpoint state

  Scenario: Rollback removes files added after the checkpoint
    Given a checkpoint service with sandbox for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a checkpoint is created from the sandbox HEAD for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a new file is added and committed in the sandbox after the checkpoint
    When I rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the file added after the checkpoint should not exist

  Scenario: Rollback emits CheckpointRolledBack domain event
    Given a checkpoint service with sandbox and event bus for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a checkpoint is created from the sandbox HEAD for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And a file is modified in the sandbox after the checkpoint
    When I rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then a CHECKPOINT_RESTORED domain event should have been emitted

  Scenario: Rollback rejects sandbox path that does not exist
    Given a checkpoint service with nonexistent sandbox path for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "abc123"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "sandbox path does not exist"

  Scenario: Rollback rejects sandbox path that is not a git repo
    Given a checkpoint service with non-git sandbox path for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV"
    And I create a checkpoint for plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" with sandbox_ref "abc123"
    When I attempt rollback plan "01ARZ3NDEKTSV4RRFFQ69G5FAV" to the created checkpoint
    Then the rollback should be rejected with "not a git repository"
