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