@plan @executor @coverage
Feature: PlanExecutor coverage gaps
  As a developer
  I want comprehensive tests for PlanExecutor, StrategizeStubActor, and ExecuteStubActor
  So that uncovered branches and lines are fully exercised

  # -- PlanExecutor __init__ validation --

  @init @error
  Scenario: PlanExecutor raises ValidationError when lifecycle_service is None
    When I construct a PlanExecutor with None lifecycle_service
    Then a PE ValidationError should be raised containing "lifecycle_service"

  @init
  Scenario: PlanExecutor initialises successfully with valid lifecycle_service
    Given a mock plan lifecycle service for plan executor
    When I construct a PlanExecutor with the mock lifecycle service
    Then the PlanExecutor should be created without error

  # -- has_runtime property --

  @property
  Scenario: has_runtime is True when execution_context is provided
    Given a mock plan lifecycle service for plan executor
    And a mock execution context
    When I construct a PlanExecutor with the mock lifecycle service and execution context
    Then the executor has_runtime property should be True

  @property
  Scenario: has_runtime is False when no execution_context is provided
    Given a mock plan lifecycle service for plan executor
    When I construct a PlanExecutor with the mock lifecycle service
    Then the executor has_runtime property should be False

  # -- changeset_store property --

  @property
  Scenario: changeset_store returns store from execution_context
    Given a mock plan lifecycle service for plan executor
    And a mock execution context with a changeset store
    When I construct a PlanExecutor with the mock lifecycle service and execution context
    Then the executor changeset_store should be the mock store

  @property
  Scenario: changeset_store returns None when no execution_context
    Given a mock plan lifecycle service for plan executor
    When I construct a PlanExecutor with the mock lifecycle service
    Then the executor changeset_store should be None

  # -- run_strategize guard: wrong phase --

  @strategize @error
  Scenario: run_strategize raises PlanError when plan is not in STRATEGIZE phase
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in EXECUTE phase
    When I attempt to run strategize on the executor
    Then a PE PlanError should be raised containing "not in Strategize phase"

  # -- run_strategize guard: empty plan_id --

  @strategize @error
  Scenario: run_strategize raises ValidationError for empty plan_id
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    When I attempt to run strategize with an empty plan_id
    Then a PE ValidationError should be raised containing "plan_id"

  # -- run_strategize exception handling (except branch) --

  @strategize @error
  Scenario: run_strategize calls fail_strategize when an exception occurs during execution
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in STRATEGIZE phase
    And the lifecycle start_strategize will raise a RuntimeError
    When I attempt to run strategize and expect an exception
    Then fail_strategize should have been called with the plan_id
    And the raised exception should be a RuntimeError

  @strategize @error
  Scenario: run_strategize records error_details on plan when exception occurs
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in STRATEGIZE phase that fails during actor execute
    When I attempt to run strategize and expect an exception
    Then the plan error_details should contain the exception_type
    And fail_strategize should have been called with the plan_id

  # -- _guard_execute: wrong phase --

  @execute @error
  Scenario: run_execute raises PlanError when plan is not in EXECUTE phase
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in STRATEGIZE phase for execute guard
    When I attempt to run execute on the executor
    Then a PE PlanError should be raised containing "not in Execute phase"

  # -- _guard_execute: wrong state --

  @execute @error
  Scenario: run_execute raises PlanError when plan state is not QUEUED
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in EXECUTE phase with PROCESSING state
    When I attempt to run execute on the executor
    Then a PE PlanError should be raised containing "not queued for execution"

  # -- _guard_execute: None decision_root_id --

  @execute @error
  Scenario: run_execute raises PlanError when decision_root_id is None
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in EXECUTE phase with QUEUED state but no decision root
    When I attempt to run execute on the executor
    Then a PE PlanError should be raised containing "no decision tree"

  # -- run_execute routing to stub --

  @execute @stub
  Scenario: run_execute routes to stub actor when no execution_context
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service and no execution context
    And the lifecycle returns a fully valid plan for stub execution
    When I run execute on the executor
    Then the plan executor result should be an ExecuteResult
    And complete_execute should have been called

  # -- run_execute routing to runtime --

  @execute @runtime
  Scenario: run_execute routes to runtime actor when execution_context is set
    Given a mock plan lifecycle service for plan executor
    And a mock execution context
    And a PlanExecutor with the mock lifecycle service and execution context and tool runner
    And the lifecycle returns a fully valid plan for runtime execution
    When I run execute on the executor
    Then the plan executor result should be a RuntimeExecuteResult
    And complete_execute should have been called

  # -- StrategizeStubActor with stream_callback --

  @actor @strategize
  Scenario: StrategizeStubActor emits stream events when callback is provided
    Given a stream event collector
    When I execute the StrategizeStubActor with a plan_id and stream callback
    Then the collector should contain a "strategize_started" event
    And the collector should contain a "strategize_decisions" event
    And the collector should contain a "strategize_complete" event

  @actor @strategize
  Scenario: StrategizeStubActor works without stream_callback
    When I execute the StrategizeStubActor with a plan_id and no callback
    Then the strategize result should have a valid decision_root_id
    And the strategize result should have at least one decision

  # -- ExecuteStubActor with stream_callback --

  @actor @execute
  Scenario: ExecuteStubActor emits stream events when callback is provided
    Given a stream event collector
    And a list of two strategy decisions
    When I execute the ExecuteStubActor with the decisions and stream callback
    Then the collector should contain an "execute_started" event
    And the collector should contain an "execute_step" event
    And the collector should contain an "execute_complete" event

  @actor @execute
  Scenario: ExecuteStubActor works without stream_callback
    Given a list of two strategy decisions
    When I execute the ExecuteStubActor with the decisions and no callback
    Then the execute result should have a valid changeset_id
    And the execute result tool_calls_count should be zero

  # -- run_strategize happy path with stream_callback (lines 138-143) --

  @strategize
  Scenario: run_strategize succeeds with stream_callback on a valid plan
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service
    And the lifecycle returns a plan in STRATEGIZE phase
    And a stream event collector
    When I run strategize with the stream callback
    Then the strategize result should have decisions
    And complete_strategize should have been called

  # -- run_strategize happy path sets execution_context.decision_root_id --

  @strategize
  Scenario: run_strategize sets decision_root_id on execution_context when present
    Given a mock plan lifecycle service for plan executor
    And a mock execution context
    And a PlanExecutor with the mock lifecycle and execution context
    And the lifecycle returns a plan in STRATEGIZE phase
    When I run strategize successfully
    Then the execution context decision_root_id should be set

  # -- run_execute with stub and exception --

  @execute @error
  Scenario: run_execute calls fail_execute when stub execution raises an error
    Given a mock plan lifecycle service for plan executor
    And a PlanExecutor with the mock lifecycle service and no execution context
    And the lifecycle returns a fully valid plan for stub execution
    And the stub execute actor is patched to raise an error
    When I attempt to run execute and expect an exception
    Then fail_execute should have been called with the plan_id
