Feature: Tool lifecycle runtime
  As the tool execution engine
  I need to orchestrate discover/activate/execute/deactivate lifecycle
  With capability enforcement, schema validation, caching, and cancellation

  # ── ToolExecutionContext ──────────────────────────────────────────────

  Scenario: Create a tool execution context with defaults
    Given I create a tool execution context with plan_id "plan-001"
    Then the context plan_id should be "plan-001"
    And the context should not be read-only
    And the context should not require checkpoints
    And the context should have 0 changes
    And the context should have 0 traces
    And the context cancellation token should not be cancelled

  Scenario: Create a read-only execution context
    Given I create a tool execution context with plan_id "plan-002" and read_only True
    Then the context should be read-only

  Scenario: Create a checkpoint-required execution context
    Given I create a tool execution context with plan_id "plan-003" and require_checkpoints True
    Then the context should require checkpoints

  Scenario: Record changes in execution context
    Given I create a tool execution context with plan_id "plan-004"
    When I record a change with operation "create" and resource_id "res-001"
    Then the context should have 1 changes

  Scenario: Add traces to execution context
    Given I create a tool execution context with plan_id "plan-005"
    When I add a trace for tool "test/tool" with success True
    Then the context should have 1 traces

  Scenario: Get resource from context by slot name
    Given I create a tool execution context with plan_id "plan-006"
    And I bind resource "res-001" to slot "repo" with type "git-checkout"
    When I get the resource for slot "repo"
    Then the bound resource_id should be "res-001"

  Scenario: Get resource from context with missing slot raises KeyError
    Given I create a tool execution context with plan_id "plan-007"
    When I try to get the resource for slot "missing"
    Then a KeyError should be raised with message containing "missing"

  Scenario: Context summary includes correct counts
    Given I create a tool execution context with plan_id "plan-008"
    And I bind resource "res-001" to slot "repo" with type "git-checkout"
    When I record a change with operation "modify" and resource_id "res-001"
    And I add a trace for tool "test/tool" with success True
    Then the context summary should show resource_count 1
    And the context summary should show change_count 1
    And the context summary should show trace_count 1

  # ── BoundResource ────────────────────────────────────────────────────

  Scenario: Create a BoundResource with all fields
    Given I create a bound resource with slot "db" and resource_id "res-db-001" and type "postgresql"
    Then the bound resource slot_name should be "db"
    And the bound resource access should be "read_only"

  # ── CancellationToken ────────────────────────────────────────────────

  Scenario: Cancellation token starts not cancelled
    Given I create a cancellation token
    Then the cancellation token should not be cancelled

  Scenario: Cancel sets the cancellation flag
    Given I create a cancellation token
    When I cancel the token
    Then the cancellation token should be cancelled

  Scenario: Check raises ToolCancelledError when cancelled
    Given I create a cancellation token
    When I cancel the token
    Then calling check on the token should raise ToolCancelledError

  # ── Change tracking ──────────────────────────────────────────────────

  Scenario: Create a change with all operations
    Given I create a change with operation "create" and resource_id "res-001"
    Then the change operation should be "create"
    And the change resource_id should be "res-001"

  Scenario: Change has automatic timestamp
    Given I create a change with operation "delete" and resource_id "res-002"
    Then the change timestamp should be a valid ISO-8601 string

  # ── ToolDescriptor ───────────────────────────────────────────────────

  Scenario: Create a tool descriptor
    Given I create a tool descriptor with name "builtin/read-file" and description "Read a file"
    Then the descriptor name should be "builtin/read-file"
    And the descriptor source should be "builtin"

  # ── ToolResult ───────────────────────────────────────────────────────

  Scenario: Create a successful tool result
    Given I create a tool result with success True and data "hello"
    Then the tool result should indicate success
    And the tool result data should be "hello"

  Scenario: Create a failed tool result
    Given I create a tool result with success False and error "something broke"
    Then the tool result should indicate failure
    And the tool result error should be "something broke"

  # ── JSON Schema validation ───────────────────────────────────────────

  Scenario: Validate tool input against valid schema
    Given I have a JSON schema requiring property "path" of type "string"
    When I validate input with path "test.txt" against the schema
    Then the input validation should succeed

  Scenario: Validate tool input against invalid data
    Given I have a JSON schema requiring property "path" of type "string"
    When I validate input with path 42 against the schema
    Then the input validation should fail with ToolSchemaValidationError

  Scenario: Validate tool output against valid schema
    Given I have a JSON schema requiring property "content" of type "string"
    When I validate output with content "file data" against the schema
    Then the output validation should succeed

  Scenario: Validate tool output against invalid data
    Given I have a JSON schema requiring property "content" of type "string"
    When I validate output with content 123 against the schema
    Then the output validation should fail with ToolSchemaValidationError

  Scenario: Schema validation error contains error details
    Given I have a JSON schema requiring property "path" of type "string"
    When I validate input with path 42 against the schema
    Then the schema validation error should have errors list
    And the schema validation error should reference the schema

  # ── ToolLifecycleCache ───────────────────────────────────────────────

  Scenario: Cache starts empty
    Given I create a tool lifecycle cache
    Then the cache should have 0 plans

  Scenario: Put and get an instance from cache
    Given I create a tool lifecycle cache
    And I have a mock tool instance named "builtin/read"
    When I put the instance in cache for plan "plan-001" and tool "builtin/read"
    Then getting the instance for plan "plan-001" and tool "builtin/read" should return it

  Scenario: Get returns None for uncached tool
    Given I create a tool lifecycle cache
    Then getting the instance for plan "plan-001" and tool "builtin/missing" should return None

  Scenario: Increment execution count
    Given I create a tool lifecycle cache
    And I have a mock tool instance named "builtin/read"
    When I put the instance in cache for plan "plan-001" and tool "builtin/read"
    And I increment execution for plan "plan-001" and tool "builtin/read"
    Then the cache stats for plan "plan-001" should show tool "builtin/read" with execution_count 1

  Scenario: Get plan tools lists activated tools
    Given I create a tool lifecycle cache
    And I have a mock tool instance named "builtin/read"
    And I have a mock tool instance named "builtin/write"
    When I put the instance in cache for plan "plan-001" and tool "builtin/read"
    And I put the second instance in cache for plan "plan-001" and tool "builtin/write"
    Then the plan tools for plan "plan-001" should include "builtin/read"
    And the plan tools for plan "plan-001" should include "builtin/write"

  Scenario: Remove a single tool from cache
    Given I create a tool lifecycle cache
    And I have a mock tool instance named "builtin/read"
    When I put the instance in cache for plan "plan-001" and tool "builtin/read"
    And I remove tool "builtin/read" from plan "plan-001"
    Then getting the instance for plan "plan-001" and tool "builtin/read" should return None

  Scenario: Remove plan clears all tools
    Given I create a tool lifecycle cache
    And I have a mock tool instance named "builtin/read"
    And I have a mock tool instance named "builtin/write"
    When I put the instance in cache for plan "plan-001" and tool "builtin/read"
    And I put the second instance in cache for plan "plan-001" and tool "builtin/write"
    And I remove plan "plan-001" from cache
    Then the cache should have 0 plans

  # ── ToolRuntime ──────────────────────────────────────────────────────

  Scenario: Register and list tools
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance
    Then the tool list should contain "builtin/read-file"

  Scenario: Unregister a tool
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance
    When I unregister tool "builtin/read-file"
    Then the tool list should not contain "builtin/read-file"

  Scenario: Get a registered tool
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance
    Then getting tool "builtin/read-file" should return the tool model

  Scenario: Get an unregistered tool returns None
    Given I create a tool runtime
    Then getting tool "builtin/missing" should return None

  Scenario: Discover returns a ToolDescriptor
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance
    When I discover tool "builtin/read-file"
    Then the discovered descriptor name should be "builtin/read-file"

  Scenario: Activate a tool for a plan
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-010"
    When I activate tool "builtin/read-file" in the runtime
    Then the mock instance should have been activated

  Scenario: Activate is idempotent for same plan
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-011"
    When I activate tool "builtin/read-file" in the runtime
    And I activate tool "builtin/read-file" in the runtime again
    Then the mock instance activate count should be 1

  Scenario: Execute a tool successfully
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-012"
    When I execute tool "builtin/read-file" with params path "test.txt"
    Then the execution result should be successful
    And the context should have 1 traces

  Scenario: Execute a tool with input schema validation failure
    Given I create a tool runtime
    And I register a builtin tool with input schema "builtin/typed-tool"
    And I create a tool execution context with plan_id "plan-013"
    When I execute tool "builtin/typed-tool" with invalid params
    Then the execution result should not be successful
    And the execution result error should contain "Input validation failed"

  Scenario: Read-only plan rejects tool with writes
    Given I create a tool runtime
    And I register a builtin tool "builtin/write-file" that writes
    And I create a tool execution context with plan_id "plan-014" and read_only True
    When I try to execute tool "builtin/write-file"
    Then a ToolAccessDeniedError should be raised

  Scenario: Checkpoint-required plan rejects non-checkpointable tool
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-015" and require_checkpoints True
    When I try to activate tool "builtin/read-file" for checkpoint plan
    Then a ToolCheckpointRequiredError should be raised

  Scenario: Execute records changes from tool result
    Given I create a tool runtime
    And I register a builtin tool "builtin/edit-file" that produces changes
    And I create a tool execution context with plan_id "plan-016"
    When I execute tool "builtin/edit-file" with params path "test.txt"
    Then the context should have 1 changes

  Scenario: Execute with cancelled token raises ToolCancelledError
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-017"
    And I cancel the context cancellation token
    When I try to execute tool "builtin/read-file" after cancellation
    Then a ToolCancelledError should be raised from execution

  Scenario: Deactivate a single tool
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-018"
    When I activate tool "builtin/read-file" in the runtime
    And I deactivate tool "builtin/read-file" in the runtime
    Then the mock instance should have been deactivated

  Scenario: Deactivate plan clears all activated tools
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I register a second builtin tool "builtin/list-files" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-019"
    When I activate tool "builtin/read-file" in the runtime
    And I activate tool "builtin/list-files" in the runtime
    And I deactivate plan "plan-019" in the runtime
    Then all mock instances should have been deactivated

  Scenario: Execute unregistered tool raises ToolRuntimeError
    Given I create a tool runtime
    And I create a tool execution context with plan_id "plan-020"
    When I try to execute unregistered tool "builtin/ghost"
    Then a ToolRuntimeError should be raised with message containing "not registered"

  Scenario: Cache stats after execution
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-021"
    When I execute tool "builtin/read-file" with params path "test.txt"
    Then the cache stats for plan "plan-021" should show 1 active tools

  Scenario: Output schema validation failure returns error result
    Given I create a tool runtime
    And I register a builtin tool with output schema "builtin/validated-output"
    And I create a tool execution context with plan_id "plan-022"
    When I execute tool "builtin/validated-output" with params path "test.txt"
    Then the execution result should not be successful
    And the execution result error should contain "Output validation failed"

  Scenario: Execution trace captures duration
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-023"
    When I execute tool "builtin/read-file" with params path "test.txt"
    Then the last trace should have a non-negative duration_ms

  Scenario: ToolExecutionTrace fields
    Given I create a tool execution trace for "builtin/read"
    Then the trace tool_name should be "builtin/read"
    And the trace should have a started_at timestamp

  Scenario: Read-only plan allows read-only tools
    Given I create a tool runtime
    And I register a builtin tool "builtin/search" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-024" and read_only True
    When I execute tool "builtin/search" with params path "*.py"
    Then the execution result should be successful

  Scenario: Activation failure raises ToolActivationError
    Given I create a tool runtime
    And I register a builtin tool "builtin/broken" with a failing activate mock
    And I create a tool execution context with plan_id "plan-025"
    When I try to activate the failing tool "builtin/broken"
    Then a ToolActivationError should be raised

  Scenario: Execution failure raises ToolExecutionError
    Given I create a tool runtime
    And I register a builtin tool "builtin/crashing" with a failing execute mock
    And I create a tool execution context with plan_id "plan-026"
    When I try to execute the crashing tool "builtin/crashing"
    Then a ToolExecutionError should be raised
    And the context should have 1 traces
    And the last trace should show failure

  Scenario: Deactivation failure is non-fatal warning
    Given I create a tool runtime
    And I register a builtin tool "builtin/sticky" with a failing deactivate mock
    And I create a tool execution context with plan_id "plan-027"
    When I activate tool "builtin/sticky" in the runtime
    And I deactivate tool "builtin/sticky" in the runtime
    Then no exception should have been raised from deactivation

  Scenario: Plan deactivation handles failing tools gracefully
    Given I create a tool runtime
    And I register a builtin tool "builtin/sticky" with a failing deactivate mock
    And I create a tool execution context with plan_id "plan-028"
    When I activate tool "builtin/sticky" in the runtime
    And I deactivate plan "plan-028" in the runtime
    Then no exception should have been raised from deactivation

  Scenario: Cache plan_count property tracks plans
    Given I create a tool runtime
    And I register a builtin tool "builtin/read-file" with a mock instance that is read_only
    And I create a tool execution context with plan_id "plan-029"
    When I activate tool "builtin/read-file" in the runtime
    Then the runtime cache plan count should be 1

  Scenario: Discover unregistered tool raises ToolRuntimeError
    Given I create a tool runtime
    When I try to discover unregistered tool "builtin/ghost"
    Then a ToolRuntimeError should be raised with message containing "No implementation registered"

  Scenario: Execution with result error records trace error field
    Given I create a tool runtime
    And I register a builtin tool "builtin/err-tool" with a mock that returns error result
    And I create a tool execution context with plan_id "plan-030"
    When I execute tool "builtin/err-tool" with params path "test.txt"
    Then the last trace should show the error message
