Files
cleveragents-core/features/container_tool_exec.feature
hurui200320 2005b8ef82
CI / push-validation (push) Successful in 18s
CI / build (push) Successful in 19s
CI / helm (push) Successful in 24s
CI / lint (push) Successful in 29s
CI / security (push) Successful in 1m11s
CI / e2e_tests (push) Successful in 2m56s
CI / quality (push) Successful in 3m40s
CI / typecheck (push) Successful in 3m59s
CI / integration_tests (push) Successful in 4m3s
CI / unit_tests (push) Successful in 4m55s
CI / docker (push) Successful in 10s
CI / coverage (push) Successful in 10m44s
CI / status-check (push) Successful in 1s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 1h13m28s
feat(tests): replace all @skip tags with proper @tdd_expected_fail tags or remove them across the entire codebase (#7221)
## Summary

Replaces all 234 bare `@skip` occurrences across 82 Behave feature files with the correct TDD issue-capture tagging system described in CONTRIBUTING.md § Bug Fix Workflow.

Previously, the noxfile ran Behave with `--tags=not @skip`, silently excluding all `@skip`-tagged scenarios from every CI run. These tests never ran, never inverted results via the `@tdd_expected_fail` mechanism, and never contributed to coverage — defeating the purpose of TDD issue-capture testing. Every `@skip` occurrence had a commented-out hint line immediately above it showing the intended proper tags (e.g., `# @tdd_issue @tdd_issue_4272 @tdd_expected_fail @skip`), confirming they were all intended for conversion.

## Changes

### Mechanical conversion (234 replacements across 82 files)
- Extracted the proper TDD tags from the comment hint above each `@skip` line, removed `@skip` from the tag set, and replaced the `@skip` line with those tags.
- Removed the now-redundant comment hint lines alongside each replacement.

### Bug-fixed scenarios — `@tdd_expected_fail` removed (84 scenarios)
- After conversion, ran `nox -s unit_tests` to identify which newly-enabled `@tdd_expected_fail` scenarios now **pass** (their referenced bugs have already been fixed). Removed `@tdd_expected_fail` from those 84 scenarios and their corresponding feature-level tags, leaving only the permanent `@tdd_issue @tdd_issue_<N>` regression-guard tags.
- Affected features include: `tdd_tool_runner_env_precedence`, `tdd_automation_profile_session_leak`, `tls_certificate_check`, `project_create_persist`, `resource_type_bootstrap_*`, and 18 others.

### Noxfile cleanup
- Removed all four `--tags=not @skip` arguments from `noxfile.py` (unit_tests and coverage sessions). With zero `@skip` tags remaining in the codebase, this filter was dead code and its presence would mislead future maintainers into thinking `@skip` is still a supported escape mechanism.

### Regression guard files
- Split the regression guards into two focused files:
  - `tdd_regression_guards_exec_env.feature` for bug #4281 (exec-env precedence)
  - `tdd_regression_guards_session_list.feature` for bug #4271 (session list summary)
- Each file carries only its own `@tdd_issue` tags at the feature level, avoiding cross-contamination via Behave tag inheritance. The `Background` step (`session-list-summary mock`) only appears in the session-list file where it is actually needed.

### Duplicate tag cleanup
- Removed duplicate `@tdd_issue @tdd_issue_4287` tag lines in `tdd_skill_add_regression.feature` (lines 20 and 29).

### Inline comment for retained `@tdd_expected_fail`
- Added inline comment in `ci_workflow_validation.feature:134` explaining why this specific #4227 scenario retains `@tdd_expected_fail` despite #4227 being closed (CI YAML does not encode threshold as a machine-readable value).

### Known edge cases — `@tdd_expected_fail` retained (closed issues, fix on master, scenarios still fail)
The following issues are **closed** and their fixes **are on master**, but the specific test assertions still fail because the fixes address other aspects of the bugs. The `@tdd_expected_fail` tags are functionally correct and must remain until the specific scenario assertions pass:
- `tdd_exec_env_resolution_precedence.feature` — bug #1080 (closed 2026-03-31). The precedence-level-2-vs-4 scenario still fails.
- `session_list_summary_dedup.feature` — bug #3046 (closed 2026-04-05). The dedup-consistency scenarios still fail.
- `actor_add_update_enforcement.feature` — bug #2609 (closed 2026-04-05). The enforcement scenarios still fail.
- `ci_workflow_validation.feature:134` — #4227 (closed 2026-04-08). The CI YAML threshold assertion still fails.

## Verification

- `grep -r "@skip" features/ --include="*.feature"` → **zero results** ✓
- `grep -n "tags=not @skip" noxfile.py` → **zero results** ✓
- `nox -s unit_tests` → **629 features passed, 0 failed** ✓ (up from ~545 before this PR)
- CI all green (coverage ≥ 97%) ✓
- Integration tests (Robot Framework) do not use `@skip` — confirmed no action needed ✓
- E2E tests (Robot Framework) do not use `@skip` — confirmed no action needed ✓
- `CHANGELOG.md` updated with entry for this change ✓
- `CONTRIBUTORS.md` — Rui Hu already listed ✓

## Issues Addressed

Closes #7025

Co-authored-by: CleverThis <hal9000@cleverthis.com>
Reviewed-on: #7221
Reviewed-by: HAL 9000 <HAL9000@cleverthis.com>
Reviewed-by: HAL9001 <hal9001@cleverthis.com>
Co-authored-by: Rui Hu <rui.hu@cleverthis.com>
Co-committed-by: Rui Hu <rui.hu@cleverthis.com>
2026-04-13 04:56:01 +00:00

523 lines
26 KiB
Gherkin

@container-tool-exec
Feature: Container-aware tool execution and I/O forwarding
As a CleverAgents developer
I want tools to execute inside containers when the execution environment is CONTAINER
So that file-based tools operate on the container filesystem transparently
Background:
Given I have the container tool execution module imported
# -- PathMapper ---------------------------------------------------------
Scenario: PathMapper maps host path to container path
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I map host path "/tmp/sandbox/src/main.py" to container
Then the container path should be "/workspace/src/main.py"
Scenario: PathMapper maps container path to host path
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I map container path "/workspace/src/main.py" to host
Then the host path should be "/tmp/sandbox/src/main.py"
Scenario: PathMapper returns unmapped path when outside root
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I map host path "/usr/lib/libc.so" to container
Then the container path should be "/usr/lib/libc.so"
Scenario: PathMapper maps root path exactly
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I map host path "/tmp/sandbox" to container
Then the container path should be "/workspace"
Scenario: PathMapper is_host_path returns true for host paths
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
Then "/tmp/sandbox/file.txt" should be a host path
And "/usr/lib/libc.so" should not be a host path
Scenario: PathMapper is_container_path returns true for container paths
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
Then "/workspace/file.txt" should be a container path
And "/tmp/other/file.txt" should not be a container path
# -- ContainerConfig ----------------------------------------------------
Scenario: ContainerConfig has sensible defaults
When I create a default ContainerConfig
Then the workspace_folder should be "/workspace"
And the container timeout_seconds should be 120
# -- ContainerMetadata --------------------------------------------------
Scenario: ContainerMetadata is frozen and holds execution details
When I create a ContainerMetadata with container_id "abc123" and image "node:20"
Then the container_metadata container_id should be "abc123"
And the container_metadata image should be "node:20"
And the container_metadata timed_out should be false
# -- ContainerToolExecutor basic ----------------------------------------
Scenario: ContainerToolExecutor can be instantiated with config
Given I have a ContainerConfig with workspace "/workspace" and container_id "test-ctr"
When I create a ContainerToolExecutor with that config
Then the executor config container_id should be "test-ctr"
And the executor should have a path mapper
Scenario: ContainerToolExecutor maps input paths before execution
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool inputs with path "/tmp/sandbox/src/file.py"
Then the mapped input path should be "/workspace/src/file.py"
Scenario: ContainerToolExecutor maps output paths after execution
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool output with path "/workspace/build/output.txt"
Then the mapped output path should be "/tmp/sandbox/build/output.txt"
Scenario: ContainerToolExecutor handles nested input path mapping
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool inputs with nested paths under "/tmp/sandbox"
Then all nested paths should be mapped to container paths
# -- Timeout handling ---------------------------------------------------
Scenario: ContainerToolExecutor reports timeout as structured error
Given I have a ContainerToolExecutor with a mock that times out
When I execute tool "file_read" with inputs in the container
Then the container tool result should indicate failure
And the container tool result error should mention "timed out"
And the tool result metadata should contain container info with timed_out true
# -- Error reporting ----------------------------------------------------
Scenario: ContainerToolExecutor reports non-zero exit as structured error
Given I have a ContainerToolExecutor with a mock that returns exit code 1
When I execute tool "file_read" with inputs in the container
Then the container tool result should indicate failure
And the container tool result error should mention "exit code 1"
Scenario: ContainerToolExecutor reports OSError as structured error
Given I have a ContainerToolExecutor with subprocess raising OSError
When I execute tool "compile" with inputs in the container
Then the container tool result should indicate failure
# -- Successful execution -----------------------------------------------
Scenario: ContainerToolExecutor returns parsed JSON output on success
Given I have a ContainerToolExecutor with a mock that returns JSON output
When I execute tool "lint" with inputs in the container
Then the container tool result should indicate success
And the tool result output should contain the parsed JSON
And the tool result metadata should contain container info
Scenario: ContainerToolExecutor returns raw output when not JSON
Given I have a ContainerToolExecutor with a mock that returns plain text
When I execute tool "echo" with inputs in the container
Then the container tool result should indicate success
And the tool result output should contain raw_output
# -- Command building ---------------------------------------------------
Scenario: _build_exec_command places --container-id flag adjacent to its value
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I build an exec command for tool "test_tool" with timeout 30
Then the command should have "--container-id" adjacent to its value
# -- ToolRunner container routing ---------------------------------------
@tdd_issue @tdd_issue_4243 @tdd_expected_fail
Scenario: ToolRunner delegates to ContainerToolExecutor when env is CONTAINER
Given I have a ToolRunner with a ContainerToolExecutor mock
When I execute a tool with container execution environment
Then the ContainerToolExecutor should have been called
And the resolver should have been called with the correct arguments
@tdd_issue @tdd_issue_4243 @tdd_expected_fail
Scenario: ToolRunner returns error when container executor is not configured
Given I have a ToolRunner without a ContainerToolExecutor
When I execute a tool with container execution environment
Then the container tool result error should mention "ContainerToolExecutor must be configured"
# -- ToolInvocation audit trail -----------------------------------------
Scenario: ToolInvocation has container_metadata field
When I create a ToolInvocation with container_metadata
Then the ToolInvocation container_metadata should contain "container_id"
And the ToolInvocation container_metadata should contain "image"
Scenario: ToolInvocation container_metadata defaults to None
When I create a ToolInvocation without container_metadata
Then the ToolInvocation container_metadata should be None
# -- ContainerExecutionError --------------------------------------------
Scenario: ContainerExecutionError carries exit_code and stderr
When I raise a ContainerExecutionError with exit_code 2 and stderr "oops"
Then the error exit_code should be 2
And the error stderr should be "oops"
Scenario: ContainerTimeoutError carries timeout_seconds
When I raise a ContainerTimeoutError with timeout 30
Then the timeout error message should mention "30"
And the timeout error timed_out should be true
# -- Signal exit code preservation ----------------------------------------
Scenario: ContainerToolExecutor preserves negative exit codes from signal kills
Given I have a ContainerToolExecutor with a mock that returns exit code -9
When I execute tool "compile" with inputs in the container
Then the container tool result should indicate failure
And the tool result metadata should have exit_code -9
# -- Path mapping with spaces --------------------------------------------
Scenario: _looks_like_path accepts paths with spaces
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool inputs with path "/tmp/sandbox/My Documents/file.py"
Then the mapped input path should be "/workspace/My Documents/file.py"
# -- Output truncation ---------------------------------------------------
Scenario: ContainerToolExecutor truncates output exceeding max bytes
Given I have a ContainerToolExecutor with a mock that returns oversized output
When I execute tool "big_output" with inputs in the container
Then the container tool result stdout should be truncated
# -- Path traversal protection -------------------------------------------
Scenario: sync_results_to_host rejects path traversal attempts
Given I have a ContainerToolExecutor with a mock that succeeds on sync using a temp dir
When I attempt to sync a path that traverses outside the sandbox
Then a ContainerExecutionError should be raised for path traversal
# -- Sync results -------------------------------------------------------
Scenario: sync_results_to_host maps container path to host
Given I have a ContainerToolExecutor with a mock that succeeds on sync using a temp dir
When I sync container path "/workspace/output.txt" to host
Then the synced host path should be under the host sandbox root
# -- execute_tool input validation (P2-19) --------------------------------
Scenario: execute_tool rejects empty tool_name
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I execute tool with empty tool_name
Then a ValueError should be raised with message "non-empty"
Scenario: execute_tool rejects non-dict inputs
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I execute tool with non-dict inputs
Then a container TypeError should be raised with message "must be a dict"
Scenario: execute_tool rejects negative timeout_seconds
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I execute tool with negative timeout
Then a ValueError should be raised with message "must be > 0"
# -- extract_container_metadata helper (P1-9) -----------------------------
Scenario: extract_container_metadata returns container dict from ToolResult
When I create a ToolResult with container metadata in metadata dict
Then extract_container_metadata should return the container dict
# =========================================================================
# Safe initialization — PathMapper validation guards
# =========================================================================
Scenario: PathMapper rejects null bytes in host_root
When I try to create a PathMapper with null bytes in host_root
Then a PathMapper ValueError should be raised mentioning "null"
Scenario: PathMapper rejects null bytes in container_root
When I try to create a PathMapper with null bytes in container_root
Then a PathMapper ValueError should be raised mentioning "null"
Scenario: PathMapper rejects empty host_root
When I try to create a PathMapper with empty host_root
Then a PathMapper ValueError should be raised mentioning "empty"
Scenario: PathMapper rejects filesystem-root host_root
When I try to create a PathMapper with host_root "/"
Then a PathMapper ValueError should be raised mentioning "/"
Scenario: PathMapper rejects filesystem-root container_root
When I try to create a PathMapper with container_root "/"
Then a PathMapper ValueError should be raised mentioning "/"
Scenario: PathMapper rejects overlapping roots
When I try to create a PathMapper with overlapping roots
Then a PathMapper ValueError should be raised mentioning "overlap"
Scenario: PathMapper is_host_path returns false for null-byte paths
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
Then a path with null bytes should not be a host path
Scenario: PathMapper is_container_path returns false for null-byte paths
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
Then a path with null bytes should not be a container path
# =========================================================================
# Safe initialization — ContainerConfig workspace_folder guards
# =========================================================================
Scenario: ContainerConfig rejects relative workspace_folder
When I try to create a ContainerConfig with workspace_folder "relative/path"
Then a ContainerConfig ValueError should be raised mentioning "absolute"
Scenario: ContainerConfig rejects workspace_folder that normalises to root
When I try to create a ContainerConfig with workspace_folder "/."
Then a ContainerConfig ValueError should be raised mentioning "root"
Scenario: ContainerConfig rejects workspace_folder with dotdot traversal
When I try to create a ContainerConfig with workspace_folder "/workspace/../etc"
Then a ContainerConfig ValueError should be raised mentioning ".."
# =========================================================================
# Safe initialization — ContainerToolExecutor binary resolution
# =========================================================================
Scenario: ContainerToolExecutor without devcontainer binary uses workspace-folder target args
Given I have a ContainerToolExecutor with no container_id configured
When I request the target CLI arguments
Then the target args should use "--workspace-folder"
Scenario: ContainerToolExecutor raises when devcontainer binary is missing at execution
Given I have a ContainerToolExecutor with no devcontainer binary
When I try to require the devcontainer binary
Then a ContainerExecutionError should be raised about missing binary
# =========================================================================
# Cleanup — sync_results_to_host error paths
# =========================================================================
Scenario: sync_results_to_host rejects null bytes in container_path
Given I have a ContainerToolExecutor with a mock that succeeds on sync using a temp dir
When I try to sync a path containing null bytes
Then a sync ValueError should be raised mentioning "null"
Scenario: sync_results_to_host rejects relative container_path
Given I have a ContainerToolExecutor with a mock that succeeds on sync using a temp dir
When I try to sync a relative container path
Then a sync ValueError should be raised mentioning "absolute"
Scenario: sync_results_to_host raises on timeout during file sync
Given I have a ContainerToolExecutor with a mock that times out on sync
When I try to sync container path to host with timeout
Then a ContainerTimeoutError should be raised from sync
Scenario: sync_results_to_host raises on non-zero exit during file sync
Given I have a ContainerToolExecutor with a mock that returns non-zero exit on sync
When I try to sync container path to host with failure
Then a ContainerExecutionError should be raised from sync failure
Scenario: sync_results_to_host raises on OSError during file write
Given I have a ContainerToolExecutor with a mock that triggers OSError on write
When I try to sync container path to host triggering write error
Then a ContainerExecutionError should be raised from write failure
# =========================================================================
# Safe execution — _looks_like_path heuristic boundary checks
# =========================================================================
Scenario: _looks_like_path rejects strings with newlines
When I check whether a string with newlines looks like a path
Then it should not look like a path
Scenario: _looks_like_path rejects strings with null bytes
When I check whether a string with null bytes looks like a path
Then it should not look like a path
Scenario: _looks_like_path rejects protocol-relative URIs
When I check whether a protocol-relative URI looks like a path
Then it should not look like a path
Scenario: _looks_like_path rejects strings with query parameters
When I check whether a path with query string looks like a path
Then it should not look like a path
Scenario: _looks_like_path rejects strings with fragment identifiers
When I check whether a path with fragment identifier looks like a path
Then it should not look like a path
Scenario: _looks_like_path rejects strings with scheme separators
When I check whether a URL-like string looks like a path
Then it should not look like a path
# =========================================================================
# Safe execution — recursion depth guards on path mapping
# =========================================================================
Scenario: _map_input_paths stops at max recursion depth
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map deeply nested input paths exceeding the recursion limit
Then the input mapping should return without error
Scenario: _map_output_paths stops at max recursion depth
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map deeply nested output paths exceeding the recursion limit
Then the output mapping should return without error
# =========================================================================
# Safe execution — _parse_output edge cases
# =========================================================================
Scenario: _parse_output returns result key for non-dict JSON
When I parse stdout containing a JSON array
Then the parsed output should wrap it under "result" key
Scenario: _parse_output returns empty dict for blank stdout
When I parse empty stdout
Then the parsed output should be an empty dict
# =========================================================================
# Safe execution — _map_output_paths skips raw_output key
# =========================================================================
Scenario: _map_output_paths preserves raw_output without path mapping
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map output containing a raw_output key with container path
Then the raw_output value should remain unmapped
# =========================================================================
# Safe execution — container tool non-JSON-serialisable inputs
# =========================================================================
Scenario: execute_tool returns error for non-JSON-serialisable inputs
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I execute a tool with non-serialisable inputs
Then the container tool result should indicate failure
And the container tool result error should mention "JSON-serialisable"
# =========================================================================
# ToolRunner full lifecycle — discover, activate, deactivate
# =========================================================================
Scenario: ToolRunner discover returns tools from registry
Given I have a ToolRunner with a populated registry
When I call discover on the runner
Then the discovered tools list should not be empty
Scenario: ToolRunner activate marks a tool as ready
Given I have a ToolRunner with a populated registry
When I activate a tool on the runner
Then the activated tool spec should be returned
Scenario: ToolRunner activate raises ToolError for unknown tool
Given I have a ToolRunner with a populated registry
When I try to activate an unknown tool
Then a ToolError should be raised for activation
Scenario: ToolRunner deactivate cleans up active tool
Given I have a ToolRunner with a populated registry
When I activate and then deactivate a tool
Then deactivate should return true
And a second deactivate should return false
# =========================================================================
# ToolRunner host execution path
# =========================================================================
Scenario: ToolRunner executes tool on host and returns success
Given I have a ToolRunner with a host-routed tool
When I execute the host tool with valid inputs
Then the host tool result should indicate success
And the host tool result output should contain handler data
Scenario: ToolRunner normalises non-dict handler output
Given I have a ToolRunner with a tool returning non-dict output
When I execute the non-dict-output tool
Then the result output should wrap the value under "result"
Scenario: ToolRunner catches handler exception and returns error
Given I have a ToolRunner with a tool that raises an exception
When I execute the raising tool
Then the tool result should indicate failure with exception info
Scenario: ToolRunner returns error for non-JSON-serialisable host output
Given I have a ToolRunner with a tool returning non-serialisable output
When I execute the non-serialisable-output tool
Then the tool result should indicate failure mentioning "not JSON-serialisable"
Scenario: ToolRunner returns error for non-JSON-serialisable host inputs
Given I have a ToolRunner with a host-routed tool
When I execute the host tool with non-serialisable inputs
Then the tool result should indicate failure mentioning "not JSON-serialisable"
Scenario: ToolRunner raises ToolError for unregistered tool in execute
Given I have a ToolRunner with an empty registry
When I try to execute an unregistered tool
Then a ToolError should be raised for execution
# =========================================================================
# ToolRunner container routing — additional edge cases
# =========================================================================
@tdd_issue @tdd_issue_4243 @tdd_expected_fail
Scenario: ToolRunner returns error when container env resolver raises ContainerUnavailableError
Given I have a ToolRunner with an unavailable-container resolver
When I execute a tool through the unavailable-container runner
Then the tool result should indicate container unavailable
@tdd_issue @tdd_issue_4243 @tdd_expected_fail
Scenario: ToolRunner validates missing required input fields for container tools
Given I have a ToolRunner with a container executor and required-field tool
When I execute the container tool with missing required fields
Then the tool result error should mention "Missing required input fields"
Scenario: ToolRunner returns error for non-JSON-serialisable container inputs
Given I have a ToolRunner with a container executor for JSON validation
When I execute a container tool with non-serialisable inputs
Then the container tool result error should mention "not JSON-serialisable"
@tdd_issue @tdd_issue_4243 @tdd_expected_fail
Scenario: ToolRunner catches container executor exception
Given I have a ToolRunner with a failing container executor
When I execute a tool through the failing container runner
Then the tool result error should mention "Container execution error"
# =========================================================================
# Safe execution — list-type value mapping branches
# =========================================================================
Scenario: _map_input_paths handles list values containing host paths
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool inputs containing a list of host paths
Then the list values should be mapped to container paths
Scenario: _map_output_paths handles list values containing container paths
Given I have a ContainerToolExecutor with host_root "/tmp/sandbox" and container_root "/workspace"
When I map tool output containing a list of container paths
Then the list values should be mapped to host paths
# =========================================================================
# Safe execution — PathMapper null byte rejection in mapping methods
# =========================================================================
Scenario: PathMapper host_to_container rejects null bytes
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I try to map a host path with null bytes to container
Then a mapping ValueError should be raised about null bytes
Scenario: PathMapper container_to_host rejects null bytes
Given I have a PathMapper with host_root "/tmp/sandbox" and container_root "/workspace"
When I try to map a container path with null bytes to host
Then a mapping ValueError should be raised about null bytes
# =========================================================================
# ToolResult validation — success/error consistency
# =========================================================================
Scenario: ToolResult rejects success=True with error message
When I try to create a ToolResult with success true and an error
Then a ToolResult validation error should be raised
Scenario: ToolResult rejects success=False without error message
When I try to create a ToolResult with success false and no error
Then a ToolResult validation error should be raised
# =========================================================================
# ToolError structured attributes
# =========================================================================
Scenario: ToolError carries structured context attributes
When I create a ToolError with known attributes
Then the ToolError should expose tool_name, error_type, and details